Search code examples
swiftuiswiftui-navigationstack

@Environment(\.dismiss) var dismiss popping to root instead of previous screen


I am working on a SwiftUI project that has a loginView (Root view embedded in NavigationStack). When I push to registration screen (Screen A) using .navigationDestination(...) modifier and then to another screen (Screen B). When I want to pop to the previous screen (Screen A) from (Screen B) it navigates me to the root (login screen) instead of Screen (B) my code:

NOTE* When I am using NavigationLink for pushing then its working fine. but NavigationLink is deprecated and Apple suggest to use NavigationDestination modifier.

// LoginView (Root view)

struct LoginView: View {

    @State var moveToScreenA: Bool = false

    var body: some View {
        NavigationStack {
            VStack(alignment: .leading, spacing: 0, content: {

                Button {
                    self.moveToScreenA = true
                } label: {
                    Text("Registration")
                }
            })
            .navigationDestination(isPresented: $moveToScreenA) {
                ScreenA()
            }
        }
    }
}

// Screen A
struct ScreenA: View {

    @State var moveToScreenB: Bool = false

    var body: some View {
        NavigationStack {
            VStack(alignment: .leading, spacing: 0, content: {

                Button {
                    self.moveToScreenB = true
                } label: {
                    Text("Next")
                }
            })
            .navigationDestination(isPresented: $moveToScreenB) {
                ScreenB()
            }
        }
    }
}

// Screen B
struct ScreenB: View {

    @Environment(\.dismiss) var dismiss

    var body: some View {
        NavigationStack {
            VStack(alignment: .leading, spacing: 0, content: {

                Button {
                    self.dismiss()
                } label: {
                    Image(.BACK_DARK)
                }
            })
        }
    }
}

Solution

  • You just need a single NavigationStack here, on LoginView, then use NavigationPath to navigate to the sub-views. Try this:

    struct LoginView: View {
        @State var path = NavigationPath()
    
        var body: some View {
            NavigationStack(path: $path) {
                VStack(alignment: .leading, spacing: 0, content: {
                    Button {
                        path.append("A")
                    } label: {
                        Text("Registration")
                    }
                })
                .navigationDestination(for: String.self) { string in
                    if string == "A" {
                        ScreenA(path: $path)
                    } else {
                        ScreenB(path: $path)
                    }
                }
                .navigationTitle("Root")
            }
        }
    }
    

    Then remove NavigationStack outer from ScreenA and ScreenB. Use navigationDestination for concrete type to distinguish the correct destination.

    struct ScreenA: View {
    
        @Binding var path: NavigationPath
    
        var body: some View {
            VStack(alignment: .leading, spacing: 0, content: {
                Button {
                    path.append("B")
                } label: {
                    Text("Next")
                }
            })
            .navigationTitle("Screen A")
        }
    }
    
    struct ScreenB: View {
    
        @Environment(\.dismiss) var dismiss
        @Binding var path: NavigationPath
    
        var body: some View {
            VStack(alignment: .leading, spacing: 0, content: {
                Button {
                    path = Navigation() //<- back to the root
                } label: {
                    Image(systemName: "star")
                }
            })
            .navigationTitle("Screen B")
        }
    }
    

    Example for navigation destination with another type in sub view.

    struct ScreenA: View {
    
        @Binding var path: NavigationPath
    
        var body: some View {
            VStack(alignment: .leading, spacing: 0, content: {
                Button {
                    path.append(true)
                } label: {
                    Text("Next")
                }
            })
            .navigationDestination(for: Bool.self) { _ in
                 ScreenB(path: $path)    
            }
            .navigationTitle("Screen A")
        }
    }