How to dismiss sheet in nested NavigationView in sheet

Hey,

I wan to make Navigation in presented sheet and in the end of that navigation I just want to dismiss that sheet and I don't know how to make it works.

For illustration I used CaseStudies in TCA.

From 03-Navigation-Sheet-LoadThenPresent.swift I go to Counter. Here is my changes In 01-GettingStarted-Counter:

struct Counter: ReducerProtocol {
    struct State: Equatable {
        var count = 0
        var test: Test.State?
        var isNavigationActive = false
    }
    
    enum Action: Equatable {
        case decrementButtonTapped
        case incrementButtonTapped
        case test(Test.Action)
        case setNavigation(isActive: Bool)
    }
    
    var body: some ReducerProtocol<State, Action>{
        Reduce { state, action in
            switch action {
            case .decrementButtonTapped:
                state.count -= 1
                return .none
            case .incrementButtonTapped:
                state.count += 1
                return .none
            case .setNavigation(isActive: true):
                state.test =  Test.State()
                state.isNavigationActive = true
                return .none
            case .setNavigation(isActive: false):
                state.isNavigationActive = false
                state.test = nil
                return .none
            case .test:
                return .none
            }
        }
        .ifLet(\.test, action: /Action.test){
            Test()
        }
    }
}

// MARK: - Feature view

struct CounterView: View {
    let store: StoreOf<Counter>
    
    var body: some View {
        WithViewStore(self.store, observe: { $0 }) { viewStore in
            NavigationView(){
                VStack {
                    HStack {
                        Button {
                            viewStore.send(.decrementButtonTapped)
                        } label: {
                            Image(systemName: "minus")
                        }
                        
                        Text("\(viewStore.count)")
                            .monospacedDigit()
                        
                        Button {
                            viewStore.send(.incrementButtonTapped)
                        } label: {
                            Image(systemName: "plus")
                        }
                    }
                    NavigationLink(
                        destination: IfLetStore(
                            self.store.scope(
                                state: \.test,
                                action: Counter.Action.test
                            )
                        ) {
                            TestView(store: $0)
                        } else: {
                            ProgressView()
                        },
                        isActive: viewStore.binding(
                            get: \.isNavigationActive,
                            send: Counter.Action.setNavigation(isActive:)
                        )
                    ) {
                        Button {
                            viewStore.send(.setNavigation(isActive: true))
                        } label: {
                            Text("Navigate to Test")
                        }
                    }
                }
            }
        }
    }
}

Here is Test feature:

import Foundation
import ComposableArchitecture
import SwiftUI

struct Test: ReducerProtocol{
    struct State: Equatable {
        var test = "Test"
        var test1: Test1.State?
        var isNavigationActive = false
    }
    
    enum Action: Equatable {
        case setNavigation(isActive: Bool)
        case test1(Test1.Action)
    }
    
    var body: some ReducerProtocol<State, Action>{
        Reduce { state, action in
            switch action {
            case .setNavigation(isActive: true):
                state.test1 =  Test1.State()
                state.isNavigationActive = true
                return .none
            case .setNavigation(isActive: false):
                state.isNavigationActive = false
                state.test1 = nil
                return .none
            case .test1:
                return .none
            }
        }
        .ifLet(\.test1, action: /Action.test1){
            Test1()
        }
    }
}

struct TestView: View {
  let store: StoreOf<Test>

  var body: some View {
    WithViewStore(self.store, observe: { $0 }) { viewStore in
      VStack {
          Text(viewStore.test)
          NavigationLink(
              destination: IfLetStore(
                  self.store.scope(
                      state: \.test1,
                      action: Test.Action.test1
                  )
              ) {
                  Test1View(store: $0)
              } else: {
                  ProgressView()
              },
              isActive: viewStore.binding(
                  get: \.isNavigationActive,
                  send: Test.Action.setNavigation(isActive:)
              )
          ) {
              Button {
                viewStore.send(.setNavigation(isActive: true))
              } label: {
                Text("Navigate to Test1")
              }

          }
      }
    }
  }
}

and finally Test1 Feature:

import Foundation
import ComposableArchitecture
import SwiftUI

struct Test1: ReducerProtocol{
    struct State: Equatable {
        var test = "Test 2"
    }
    
    enum Action: Equatable {
        case dismissSheeet
    }
    
    var body: some ReducerProtocol<State, Action>{
        Reduce { state, action in
            switch action {
            case .dismissSheeet:
                // TODO: dismiss sheet
                return .none
            }
        }
    }
}

struct Test1View: View {
  let store: StoreOf<Test1>

  var body: some View {
    WithViewStore(self.store, observe: { $0 }) { viewStore in
      VStack {
          Text(viewStore.test)
        Button {
          viewStore.send(.dismissSheeet)
        } label: {
          Text("Dismiss sheet")
        }
      }
    }
  }
}

In last Test1View I want to dismiss presented sheet and I don't know how to accomplish that. Is that anti-pattern in iOS, or am I missing something?

Thank you.

Hey! You maybe wanna send an .dismissSheet action to your Test1 feature store, handle the case .test1(.dismissSheet) action on Test feature reducer, and then set state.IsNavigationActive = false, right?

Just be aware of the need to implement the .test1(.dismissSheet) case before the .test1 case in your Test feature reducer.

1 Like

Ooo thanks ! It works although I can see small rendering of "back" action, but it works :slight_smile: !
Thank you very much, I learnt something new how can I access "child" state.

Have a nice day ! :slight_smile:

1 Like