iosswiftswiftuiswiftui-navigationstack

Why does my SwiftUI View not show when it is wrapped in its own NavigationStack?


I have a SwiftUI view that is the landing page for my app with a body like this:

@State private var navigationPath = NavigationPath()

var body: some View {
    NavigationStack(path: $navigationPath) {
        VStack {
             // my UI
        }
        .navigationDestination(for: NavigationDestination.self) { destination in
            switch destination {
            case .auth:
                AuthView(navigationPath: navigationPath)
            }
        }
    }
}

where NavigationDestination is a private Hashable enum.

After appending NavigationDestination.auth to navigationPath, I can correctly see my AuthView when it doesn't include its own NavigationStack:

var body: some View {
    VStack {
        // my UI
    }
    .navigationBarBackButtonHidden(true)
}

However, once I wrap it in its own NavigationStack based on the path I passed into it:

var body: some View {
    NavigationStack(path: $navigationStack) {
        VStack {
            // my UI
        }
        .navigationBarBackButtonHidden(true)
        .navigationDestination(for: NavigationDestination.self) { destination in
            switch destination {
            case .signUp:
                SignUpView(navigationPath: navigationPath)
            }
        }
    }
}

(NavigationDestination here is different from the initial view's, also a private Hashable enum)

AuthView no longer is presented and instead it is a view with a yellow triangle and exclamation point. Why does wrapping my second SwiftUI view in its own NavigationStack prevent navigation to it?

When I remove the NavigationStack from my second view, everything works fine, but I cannot navigate in the same manner I did on the first view. Currently, the only solution I see would be to have a root view that handles all navigation, but I do not like that idea for an app that needs to scale.


Solution

  • You should not nest NavigationStacks within each other. Just put one NavigationStack at the root, and if the views need access to the navigation path, pass it along your view hierarchy as a Binding.

    struct ContentView: View
    {
        @State var path = NavigationPath()
        
        var body: some View {
            NavigationStack(path: $path) {
                Root(path: $path)
            }
        }
    }
    

    You can still achieve decentralised navigation by having each view declare their own enum representing their destinations. Here is Root and AuthView, for example:

    struct Root: View {
        private enum NavigationDestinations {
            case auth
        }
        
        @Binding var path: NavigationPath
        
        var body: some View {
            VStack {
                Text("Root")
                NavigationLink("Go to Auth", value: NavigationDestinations.auth)
            }
            .navigationDestination(for: NavigationDestinations.self) {
                switch $0 {
                case .auth:
                    AuthView(path: $path)
                }
            }
        }
    }
    
    struct AuthView: View {
        @Binding var path: NavigationPath
        
        private enum NavigationDestinations {
            case signUp
        }
        
        var body: some View {
            VStack {
                Text("Auth")
                NavigationLink("Sign Up", value: NavigationDestinations.signUp)
            }
            .navigationDestination(for: NavigationDestinations.self) {
                switch $0 {
                case .signUp:
                    SignUpView(path: $path)
                }
            }
            .navigationBarBackButtonHidden()
        }
    }
    
    struct SignUpView: View {
        @Binding var path: NavigationPath
        
        var body: some View {
            Text("Sign Up")
                .navigationBarBackButtonHidden()
        }
    }