Reading time: 1 min

This quick recipe shows how to support nested NavigationLinks and allow the user to move back programatically. As always, SwiftUI throws a curveball even on trivial tasks such as this.

Here's what the end result looks like:

preview_good

OK, so the situation you have is as following:

  1. You have a multi-level push navigation implemented with NavigationLinks that are triggered programatically - either with boolean isActive: or optional-Identifiable using tag:selection:.
  2. You wish to manually navigate back from one of those screens to the previous one without using the built-in back button.

Here's some code that describes the setup:

struct NavPopTestingRoot: View {
  @State private var isShown = false

  var body: some View {
    NavigationView {
      NavigationLink("Next", isActive: $isShown) {
        NavPopTesting(previousIsShown: $isShown)
      }
    }
  }
}

struct NavPopTesting: View {
  @Binding var previousIsShown: Bool
  @State private var isShown = false

  var body: some View {
    VStack {
      Button("Back") {
        previousIsShown = false // dismiss programatically
      }
      NavigationLink("Next", isActive: $isShown) {
        NavPopTesting(previousIsShown: $isShown)
      }
    }
    .navigationBarBackButtonHidden(true)
  }
}

Now, if you try running it, it works fine for the first dismiss - but for all subsequent ones, setting the isActive binding to false or the selection binding to nil doesn't work - the screen isn't dismissed:

preview_bad

To work around this, add an .isDetailLink(false) modifier to each NavigationLink:

// ...
NavigationLink("Next", isActive: $isShown) {
  NavPopTesting(previousIsShown: $isShown)
}
.isDetailLink(false) // HERE
// ...

After that, everything will work as expected:

preview_good

SwiftUISegues framework has this already taken care of when you're using push segues!

Next Post Previous Post