Manage multiple sheet with swift composable architecture

281 Views Asked by At

I am trying to learn Swift and Composable Architecture, and I am following this tutorial. Now the above code works as expected, I having issue with showing different views in the same sheet. I have created another destination from LoginView like below

struct LoginFeature: Reducer {
    ...
    ...
    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .forgotPasswordButtonTapped:
                state.destination = .forgotPassword(
                    ForgotPasswordFeature.State()
                )
                return .none
            
            case .privacyPolicyButtonTapped:
                state.destination = .privacyPolicy(
                    GeneralWebFeature.State(urlString: "<some link>")
                )
                return .none

    ...
    ...
   
}

and

extension LoginFeature {

    struct Destination: Reducer {
     
        enum State: Equatable {
            case forgotPassword(ForgotPasswordFeature.State)
            case privacyPolicy(GeneralWebFeature.State)
        }
    
        enum Action: Equatable {
            case forgotPassword(ForgotPasswordFeature.Action)
            case privacyPolicy(GeneralWebFeature.Action)
        }
    
        var body: some ReducerOf<Self> {
            Scope(state: /State.forgotPassword, action: /Action.forgotPassword) {
                 ForgotPasswordFeature()
            }
            Scope(state: /State.privacyPolicy, action: /Action.privacyPolicy) {
                GeneralWebFeature()
            }
        }
    }
}

Now the problem is how to modify this sheet to support multiple views since the state and action param does not support multiple actions, or this a wrong approach?

 .sheet(
        store: self.store.scope(
            state: \.$destination,
            action: { .destination($0) }
        ),
        state: /LoginFeature.Destination.State.forgotPassword,
        action: LoginFeature.Destination.Action.forgotPassword
    ) { forgotPasswordStore in
        NavigationStack {
            ForgotPasswordView(store: forgotPasswordStore)
        }
    }

Little help will be highly appreciated, thank you.

2

There are 2 best solutions below

4
On BEST ANSWER

Add another sheet modifier to handle the privacyPolicy destination. See sample below:

import ComposableArchitecture
import SwiftUI

struct ViewFeature: Reducer {
  struct State: Equatable {
    @PresentationState var destination: Destination.State?
  }
  enum Action {
    case showFirstSheetButtonTapped
    case showSecondSheetButtonTapped
    case destination(PresentationAction<Destination.Action>)
  }
  
  struct Destination: Reducer {
    enum State: Equatable {
      case firstSheet(String)
      case secondSheet(String)
    }
    enum Action: Equatable {
      case firstSheet(String)
      case secondSheet(String)
    }
    var body: some ReducerOf<Self> {
      Scope(
        state: /ViewFeature.Destination.State.firstSheet,
        action: /ViewFeature.Destination.Action.firstSheet
      ) {
        EmptyReducer()
      }
      Scope(
        state: /ViewFeature.Destination.State.secondSheet,
        action: /ViewFeature.Destination.Action.secondSheet
      ) {
        EmptyReducer()
      }
    }
  }
  
  var body: some ReducerOf<Self> {
    Reduce { state, action in
      switch action {
      case .showFirstSheetButtonTapped:
        state.destination = .firstSheet("first sheet state")
        return .none
        
      case .showSecondSheetButtonTapped:
        state.destination = .secondSheet("second sheet state")
        return .none
        
      case .destination:
        return .none
      }
    }
    .ifLet(\.$destination, action: /ViewFeature.Action.destination) {
      Destination()
    }

  }
}

struct SampleView: View {
  @State var store = Store(initialState: ViewFeature.State()) {
    ViewFeature()
  }
  
  var body: some View {
    WithViewStore(self.store, observe: { $0 }) { viewStore in
      VStack {
        Button("Show First Sheet") {
          viewStore.send(.showFirstSheetButtonTapped)
        }
        Button("Show Second Sheet") {
          viewStore.send(.showSecondSheetButtonTapped)
        }
      }
      .sheet(
        store: self.store.scope(
          state: \.$destination,
          action: ViewFeature.Action.destination
        ),
        state: /ViewFeature.Destination.State.firstSheet,
        action: { .firstSheet($0) }
      ) { firstSheetStore in
        WithViewStore(firstSheetStore, observe: {$0}) {
          Text($0.state)
        }
      }
      .sheet(
        store: self.store.scope(
          state: \.$destination,
          action: ViewFeature.Action.destination
        ),
        state: /ViewFeature.Destination.State.secondSheet,
        action: { .secondSheet($0) }
      ) { secondSheetStore in
        WithViewStore(secondSheetStore, observe: {$0}) {
          Text($0.state)
        }
      }
    }
  }
}

#Preview("Sample View") {
  SampleView()
}
0
On
 .popover(
        store: self.store.scope(
            state: \.$destination,
            action: { .destination($0) }
        ),
        state: /LoginFeature.Destination.State.privacyPolicy,
        action: LoginFeature.Destination.Action.privacyPolicy
    ) { webViewStore in
        NavigationStack {
            GeneralWebView(store: webViewStore)
        }
    }

I have solved this with popover and it acts same as sheet