So far we have relied on the SwiftUI navigation APIs to handle navigation between our screens in both our NavigationStacks and TabViews in response to user input.
However, when facing complex navigation hierarchies and requirements, it can become necessary for us to take on the responsibility of managing our navigation programmatically. In this post we will learn the ways SwiftUI allows us to perform programmatic navigation in both NavigationStack and TabView.
Contents:
Programmatically controlling NavigationStack
Programmatic navigation using single data type path
Programmatic navigation for different view types
Programmatically controlling TabView
Pop to root using TabView
I highly recommend you check out our previous posts in the series:
Programmatically controlling NavigationStack
Starting with iOS 16, when we declare our NavigationStack, we have two initializers available to us, each taking a binding to a path variable. The first initializer accepts a collection of a single data type, and the second accepts a type-erased collection type called NavigationPath.
Both of these collections represent the current state of our navigation stack, allowing us to both observe and modify it programmatically. To see how the path variable corresponds to our stack, let’s take a look at a depiction:
Notice how the root screen is not added to our path collection, instead, only the screens added on top of the root screen in the navigation stack are appended. If our path variable is empty, that means we are only displaying the root screen. Alternatively, if it is not empty, any screens contained in the collection are screens added to our stack hierarchy, with the last element representing the top of stack (our currently visible screen).
Programmatic navigation using single data type path
When we only have a single data type associated with a navigation destination, we can make use of the first initializer provided for NavigationStack.
Here we use a State property called presentedArticles to manage the state of our navigation stack.
Pass Binding to presentedArticles as the path argument for our NavigationStack's initializer.
Now, we can use the reference to our presentedArticles variable to control navigation as needed. For example, we can use it to quickly pop back to our root view:
We could even use it to add multiple views at once, allowing us to rapidly navigate deep into our view hierarchy:
As stated earlier, the path variable matches our NavigationStack's current state. Any changes made programmatically to it will be reflected by our view hierarchy. The opposite is also true, any changes made to our view hierarchy as a result of user input, will be reflected in our path variable, allowing us to observe when screens are added or removed.
By observing our navigation stack through the path binding, we can take any necessary action required when views are added/removed in our stack. Imagine we have a document management app and we want to keep a record of the path of folders a user navigates through.
We can use the onChange(of:) view modifier to observe changes to our path and log them:
Now every time a user navigates between different folders, we can keep an accurate record.
Programmatic navigation for different view types
When we have multiple data types corresponding to different navigation destinations, we can take advantage of SwiftUI’s NavigationPath type. With NavigationPath we can achieve all the same functionality we’ve already covered above with only minor tweaks in some places.