< Navigating to different data types using NavigationPath | How to save NavigationStack paths using Codable > |
It's common to be several levels deep in a NavigationStack
, then to decide you want to return to the beginning. For example, perhaps your user is placing an order, and has worked their way through screens showing their basket, asking for shipping details, asking for payment details, then confirming the order, but when they are done you want to go back to the very start – back to the root view of your NavigationStack
.
To demonstrate this, we can create a little sandbox that can push to new views endlessly by generating new random numbers each time.
First, here's our DetailView
that shows its current number as its title, and has a button that pushes to a new random number whenever it's pressed:
struct DetailView: View {
var number: Int
var body: some View {
NavigationLink("Go to Random Number", value: Int.random(in: 1...1000))
.navigationTitle("Number: \(number)")
}
}
And now we can present that from our ContentView
, starting with an initial value of 0 but navigating to a new DetailView
every time a new integer is shown:
struct ContentView: View {
@State private var path = [Int]()
var body: some View {
NavigationStack(path: $path) {
DetailView(number: 0)
.navigationDestination(for: Int.self) { i in
DetailView(number: i)
}
}
}
}
When you run that back you'll see you can keep pushing your way through views endlessly – you can go 20, 50, or even 500 views deep if you like.
Now, if you're 10 views deep in there and decide you want to return home you have two options:
removeAll()
on that array to remove everything in your path, going back to the root view.NavigationPath
for your path, you can set it to a new, empty instance of NavigationPath
, like this: path = NavigationPath()
.However, there's a bigger problem: how you can do that from the subview, when you don't have access to the original path
property?
Here you have two options: either store your path in an external class that uses @Observable
, or bring in a new property wrapper called @Binding
. I've already demonstrated @Observable
previously, so let's focus on @Binding
here.
You've seen how @State
lets us create some storage inside our view, so we can modify values while our app is running. The @Binding
property wrapper lets us pass an @State
property into another view and modify it from there – we can share an @State
property in several places, and changing it in one place will change it everywhere.
In our current code, that means adding a new property to DetailView
to get access to the navigation path array:
@Binding var path: [Int]
And now we need to pass that in from both places in ContentView
where DetailView
is used, like this:
DetailView(number: 0, path: $path)
.navigationDestination(for: Int.self) { i in
DetailView(number: i, path: $path)
}
As you can see, we're passing in $path
because we want to pass the binding in – we want DetailView
to be able to read and write the path.
And now we can add a toolbar to DetailView
to manipulate the path array:
.toolbar {
Button("Home") {
path.removeAll()
}
}
And if you're using NavigationPath
you'd use this:
.toolbar {
Button("Home") {
path = NavigationPath()
}
}
Sharing a binding like this is common – it's exactly how TextField
, Stepper
, and other controls work. In fact, we'll look at @Binding
again in project 11 when we create our own custom component, because it's really helpful.
SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.