swiftswiftuiswift-composable-architecture

Manage multiple sheet with swift composable architecture


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.


Solution

  • 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()
    }