UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to make a NavigationStack return to its root view programmatically

Paul Hudson    @twostraws   

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:

  1. If you're using a simple array for your path, like we're doing in the code above, you can call removeAll() on that array to remove everything in your path, going back to the root view.
  2. If you're using 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.

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.6/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.