Search code examples
swiftuiswiftui-navigationlinkswiftui-navigationstack

SwiftUI: NavigationPath always empty


The issue I'm facing is that my NavigationPath is always empty. The context is that I have 3 Views.

View A contains the NavigationStack and has two NavigationLinks, to View B and View C respectively.
View B has a NavigationLink to View C and a toolbar button to View A.
View C has a NavigationLink to View B and a toolbar button to View A.

struct ViewA: View {
    @State private var path = NavigationPath()
    var body: some View {
        NavigationStack(path: $path) {
            Text("ViewA")
            NavigationLink {
                ViewB(path: $path)
            } label: {
                Text("Go to ViewB")
            }
            NavigationLink {
                ViewC(path: $path)
            } label: {
                Text("Go to ViewC")
            }
        }
    }
}

struct ViewB: View {
    @Binding var path: NavigationPath
    var body: some View {
        VStack {
            Text("ViewB")
            NavigationLink {
                ViewC(path: $path)
            } label: {
                Text("Go to ViewC")
            }
        }
        .toolbar {
            Button("ViewA") {
                path = NavigationPath()
            }
        }
    }
}

struct ViewC: View {
    @Binding var path: NavigationPath
    var body: some View {
        VStack {
            Text("ViewC")
            NavigationLink {
                ViewB(path: $path)
            } label: {
                Text("Go to ViewB")
            }
        }
        .toolbar {
            Button("ViewA") {
                path = NavigationPath()
            }
        }
    }
}

Variable path is always empty.

I've tried multiple implementations, still not working. I'm expecting to have the toolbar button send me to View A.


Solution

  • You need to use the value parameter of the NavigationLink in conjuction to the navigationDestination modifier for the path State variable to be changed, or you could append objects that conform to the Hashable protocol to navigate (the navigationDestination is needed either way).

    Here's an example:

    struct ViewA: View {
        @State private var path = NavigationPath()
        var body: some View {
            NavigationStack(path: $path) {
                
                VStack(spacing: 8) {
                    Text("ViewA")
                    NavigationLink(value: "B") {
                        Text("Go to B")
                    }
                    
                    NavigationLink(value: "C") {
                        Text("Go to C")
                    }
                }
                /// Here you handle possible destinations for the String type
                /// You can define other destinations for different types as well
                .navigationDestination(for: String.self) { value in
                    switch value {
                        case "B": ViewB(path: $path)
                        case "C": ViewC(path: $path)
                        case "D": Text("View \(value)")
                    default:
                        Text("View not found")
                    }
                    
                }
            }
        }
    }
    

    And here are ViewB and ViewC:

    struct ViewB: View {
        @Binding var path: NavigationPath
        var body: some View {
            VStack {
                NavigationLink(value: "C") {
                    Text("Go to C")
                }
            }
            .toolbar {
                Button("ViewA") {
                    path = NavigationPath()
                }
            }
        }
    }
    
    struct ViewC: View {
        @Binding var path: NavigationPath
        var body: some View {
            VStack {
                Text("ViewC")
                    
                Text("Go To D")
                    .foregroundStyle(.blue)
                    /// You can also append stuff to the path
                    .onTapGesture {
                        path.append("D")
                    }
            }
            .toolbar {
                Button("ViewA") {
                    path = NavigationPath()
                }
            }
        }
    }
    

    When you append values to the path the navigationDestination callback for the type appended gets called. It took me a bit to get it when I first saw it but now it is pretty straightforward. Let me know if you any doubts.