xcodeswiftui-navigationlinknavigationbaritems

SwiftUI Navigation does not work as expected with 3 views when navigationLink to third view is embedded in a navigationBar button


I'm a novice Xcode/SwiftUI developer and have been stuck on the following problem related to navigation. In the following minimum, reproducible example, I want the user to be able to navigate backwards through the app view-by-view after reaching the ThirdView as follows: ThirdView -> SecondView -> FirstView (or ContentView, in the example below).

When the NavigationLink in the SecondView to reach the ThirdView is in the body of the SecondView, everything works as expected. (This is the SecondView code I've commented out in the example below.) However, if I move that same NavigationLink up to the navigation bar (as shown), clicking the "Back to 2nd View" bar button in the ThirdView does nothing. The view does not change. Is there a way to navigate from SecondView -> ThirdView via a navigation link in the bar button but still be able to progress backwards from the ThirdView -> SecondView, as well?

I've read some question-and-answers to issues like this online that suggest that this problem is a glitch in the Xcode Simulator, but when I load the app on my device, I have the same problem.

struct ContentView: View {
    @State private var activeLink: Bool = false
    var body: some View {
        NavigationView {
            VStack {
                Spacer()
                NavigationLink("Show Second Screen",
                    destination: SecondView(active: $activeLink), isActive: $activeLink)
                Spacer()
            }.padding()
            .navigationBarTitle("First view")
        }
    }
}

struct SecondView: View {
    @Binding var active: Bool
    @State private var thirdViewLink: Bool = false
    
    var body: some View {
        VStack {
        Spacer()
    // This works as expected but is not ideal for my purposes
     /* NavigationLink("Show Third View",
            destination: ThirdView(thirdViewActive: $thirdViewLink), isActive: $thirdViewLink) */
        Spacer()
        }.padding()
        .navigationBarTitle("Second View")
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: Button("Back to 1st View") {
            self.active = false
        }, trailing: NavigationLink("Show Third View",
                                    destination: ThirdView(thirdViewActive: $thirdViewLink),
                                    isActive: $thirdViewLink))
    }
}

struct ThirdView: View {
    @Binding var thirdViewActive: Bool
    var body: some View {
        VStack(spacing: 15) {
            Text("Third View")
            Spacer()
        }.padding()
        .navigationBarItems(leading: Button("Back to 2nd View") {
            self.thirdViewActive = false
        })
    }
}

Solution

  • The problem is that you have a NavigationLink outside your NavigationView.

    .navigationBarItems(leading: Button("Back to 1st View") {
        self.active = false
    }, trailing: NavigationLink( /// this is not inside your NavigationView!
        "Show Third View",
        destination: ThirdView(thirdViewActive: $thirdViewLink),
        isActive: $thirdViewLink
    )
    

    This will not work and you'll run into weird issues. NavigationLink always needs to be inside NavigationView. You should follow your approach with the "This works as expected but is not ideal for my purposes", and just pass in an EmptyView to hide it.

    struct SecondView: View {
        @Binding var active: Bool
        @State private var thirdViewLink: Bool = false
        
        var body: some View {
            VStack {
                Spacer()
                // This works as expected but is not ideal for my purposes
                NavigationLink(destination: ThirdView(thirdViewActive: $thirdViewLink), isActive: $thirdViewLink) {
                    EmptyView()
                }
                Spacer()
            }.padding()
            .navigationBarTitle("Second View")
            .navigationBarBackButtonHidden(true)
            .navigationBarItems(leading: Button("Back to 1st View") {
                self.active = false
            }, trailing:
                Button("Show Third View") {
                    self.thirdViewLink = true
                }
            )
        }
    }
    

    First view to second view to third view, then reversed