Overview
iOS 16 introduced new ways to manage screen navigation.
See the WWDC 2022 video on the new navigation API titled The SwiftUI cookbook for navigation.
Also see the excellent YouTube videos from Stewart Lynch:
Basic Example
In this example the initial screen contains three navigation links and a Button
. Tapping any of these navigates to a detail view for a selected kind of fruit.
struct ContentView: View {
@State private var showingBanana = false
private let fruits = ["Apple", "Banana", "Cherry"]
var body: some View {
NavigationStack {
VStack {
ForEach(fruits, id: \.self) { fruit in
NavigationLink(fruit, value: fruit)
}
Button("Go Ape!") { showingBanana.toggle() }
.buttonStyle(.borderedProminent)
}
.navigationTitle("Fruits")
.navigationDestination(for: String.self) { item in
switch item {
case "Apple":
// During development destinations can be simple
// `Text` views until real views are developed.
AppleView()
case "Banana":
BananaView()
case "Cherry":
CherryView()
default:
Text("unsupported fruit")
}
}
.navigationDestination(isPresented: $showingBanana) {
BananaView()
}
}
}
}
struct AppleView: View {
private let name = "Apple"
var body: some View {
Image(name)
.navigationTitle(name)
.navigationBarTitleDisplayMode(.automatic)
}
}
struct BananaView: View {
private let name = "Banana"
var body: some View {
Image(name).navigationTitle(name)
.navigationBarTitleDisplayMode(.inline)
}
}
struct CherryView: View {
private let name = "Cherry"
var body: some View {
Image(name).navigationTitle(name)
.navigationBarTitleDisplayMode(.large)
}
}
Example App
See NavigationStackDemo which demonstrates everything shared in the Steward Lynch videos linked above.
The app has four tabs.
Fruits tab
The first tab "Fruits" is implemented in the file FruitListView.swift
. It uses a NavigationStack
to allow the user to select a fruit from a List
of NavigationLink
views.
Nested inside the NavigationStack
are instances of NavigationLink
. This struct supports many initializers, some of which take a value
argument. When these links are tapped, their value is placed on the stack.
When a fruit is selected, a view that displays "You chose {fruit-emoji}." is pushed onto the navigation stack.
Click "< Fruits" in the upper-left to return to the list of fruits. The "Show Favorite" button at the bottom demonstrates a NavigationList
that is outside of the List
. The "Smile" button at the bottom demonstrates a NavigationList
that takes advantage of the fact that the only registered navigationDestination
handles any String
value.
Authors tab
The second tab "Authors" is implemented in the file AuthorListView
. It uses a NavigationStack
to allow the user to select an Author
from a List
of NavigationLink
views.
When an author is selected, a view that displays specific information about the author is pushed onto the navigation stack.
There are four navigationDestination
registrations that handle the types:
String
for author nameInt
for number of books by an authorColor
for a color associated with an authorAuthor
for an entireAuthor
object
The "Random" button selects a random author and displays information about that author.
Unlike in the Fruits tab, a path
is passed to NavigationStack
. This can be an instance of NavigationPath
or any array of any type whose values conform to the Codable
and Hashable
protocols. Many built-in types such as String
already conform to both of these protocols.
It seems the NavigationStack
is only populated when the NavigationLink
instances supply the value
argument. This enables modifying the stack to navigate to another screen. For example, the following code navigates to the "root" screen:
path.removeLast(path.count)
If the path passed to NavigationStack
is an Array
of any type instead of a NavigationPath
instance and it is held in state, navigate to the "root" screen by setting the that state variable to an empty array.
Countries Stack tab
The third tab "Countries Stack" uses a NavigationStack
to allow the user to select a Country
from a List
of NavigationLink
views.
When a country is selected, a view that displays a list of cities in the country is pushed onto the navigation stack.
Each city is represented by a NavigationLink
. Tapping one of these pushes a view onto the stack that displays detailed information about that city. A star icon is displayed behind the information if the city is the capital of its country. A bar chart showing the population of each supported city in the same country is displayed below the city data. The "Back to Countries" button demonstrates popping back to the initial view in this tab.
Countries Split tab
The fourth tab "Countries Split" is similar to the third tab, but uses a NavigationSplitView
instead of a NavigationStack
. A NavigationSplitView
can have two of three sections arranged horizontally. These are named "sidebar", "content" (optional), and "detail". This app uses a three-column view where the first column contains a list of countries, the second column contains a list of cities in the selected country, and the third column contains detail about the selected city. Having a "Back to Countries" button doesn't apply in this scenario.
Deep Linking
This app supports navigation via a URL.
To create a URL scheme:
- Select the top item in Navigator.
- Select the main target.
- Select the Info tab.
- Expand "URL Types".
- Click the "+" button.
- For "Identifier", enter the app bundle id found in the target General tab.
- For "URL Schemas", enter a name that is unique within the app such as "NavStack".
- "Icon" is not required.
- "Role" can keep the default value of "Editor".
To configure use of URLs:
- In the App subclass, add the
onOpenURL
view modifier to the initial view (ContentView
here).
To test use of URLS:
Launch the Simulator. The app does not need to be running in the Simulator.
In a terminal window, enter a command like these:
xcrun simctl openurl booted NavStack://Canada
xcrun simctl openurl booted NavStack://Canada/Vancouver
The URLs above can also be links in an email or reminder. Clicking these links will launch the app if it is not already running and will navigate to the appropriate navigation path.
The app will ask for permission to receive a URL the first time a request is received.