
Programmatic navigation with navigation destination in SwiftUI
Learn how to use the navigation destination modifier for triggering navigation in a SwiftUI app.
SwiftUI’s modern NavigationStack
isolates the logic of how navigation is triggered from how the UI handles it. While NavigationLink(destination:label:)
can directly navigate to a view, the navigation destination modifiers provide a more flexible, data-driven approach. Let's explore the different variations of the modifier and when to use each of them.
Navigating with a Boolean trigger
Use navigationDestination(isPresented:destination:)
when a simple boolean value condition triggers navigation. This modifier associates a destination view with a binding that can be used to push the view onto a NavigationStack
.
struct ContentView: View {
// 1.
@State private var isAddingProduct: Bool = false
var body: some View {
NavigationStack(path: $path) {
List {
// Views for the list here
}
// 3.
.navigationDestination(isPresented: $isAddingProduct) {
// 4.
AddProductView()
}
.toolbar {
ToolbarItem {
Button(action: {
// 2.
isAddingProduct = true
}, label: {
Label("Add", systemImage: "plus")
})
}
}
}
}
}
- A
@State
Boolean variable - A button that will set the variable to true
- Bind this variable in the `isPresented` parameter of the modifier
- The destination view in the closure of the modifier
A common use case is tapping a button that toggles a view to appear without passing custom data.
Navigating with a bound value
The navigationDestination(item:destination:)
modifier is ideal when triggering the navigation relies on selecting a value. SwiftUI will push the destination view to appear whenever the optional binding value becomes non-nil.
struct ContentView: View {
// 1.
@State private var selectedFood: OtherFood? = nil
@State private var otherFoods: [OtherFood] = [
OtherFood(name: "Pizza", symbol: "🍕")
]
var body: some View {
NavigationStack(path: $path) {
List {
Section("Fruit") {
...
}
Section("Vegetables") {
...
}
Section("Other Foods") {
ForEach(otherFoods, id: \.self) { food in
Button(action: {
// 2.
selectedFood = food
}, label: {
HStack {
Text(food.symbol)
Text(food.name)
}
})
}
}
}
// 3.
.navigationDestination(item: $selectedFood) { food in
// 4.
OtherFoodDetailView(food: food)
}
}
}
}
- A
@State
optional value of any Type. Mind that conformance of the Type toHashable
protocol is needed. - A button to select the value
- Bind the optional value in the `item` parameter of the modifier
- The closure of the modifier returns the value, and it is here that you define the destination view.
Since the item is bound, you can navigate to views that can change the value somehow, like for editing or selection purposes.
Navigating based on a value type
If you are trying to develop more complex or dynamic flows, use .navigationDestination(for:destination:)
for one or more Hashable
types. This approach allows SwiftUI to match pushed values in a NavigationPath
to their respective destinations.
struct ContentView: View {
// 1.
@State private var path = NavigationPath()
@State private var fruits = [
Fruit(name: "Apple", symbol: "🍎"),
Fruit(name: "Banana", symbol: "🍌"),
Fruit(name: "Cherry", symbol: "🍒")
]
@State private var vegetables: [Vegetable] = [
Vegetable(name: "Carrot", symbol: "🥕"),
Vegetable(name: "Broccoli", symbol: "🥦")
]
var body: some View {
NavigationStack(path: $path) {
List {
Section("Fruit") {
ForEach(fruits, id: \.self) { fruit in
// 2.
NavigationLink(value: fruit) {
HStack {
Text(fruit.symbol)
Text(fruit.name)
}
}
}
}
Section("Vegetables") {
ForEach(vegetables, id: \.self) { vegetable in
Button(action: {
// 2.
path.append(vegetable)
}, label: {
HStack {
Text(vegetable.symbol)
Text(vegetable.name)
}
})
}
}
}
// 3.
.navigationDestination(for: Fruit.self) { fruit in
FruitDetailView(fruit: fruit)
}
.navigationDestination(for: Vegetable.self) { vegetable in
VegetableDetailView(vegetable: vegetable)
}
}
}
}
- (Optional) A
NavigationPath
- If you don't use it explicitly, SwiftUI will handle it under the hood for you
- You might need it if you want to have full control over navigation
- A value pushed to the
NavigationPath
, which can either be:- The
value
parameter of aNavigationLink
, or - Appending the value to the
NavigationPath
programmatically
- The
- Ensure the type you're using conforms to the
Hashable
protocol and handle every type with the modifier
This is best used when you need to enable automatic navigation based on the type of data when calling path.append(value)
from a button on a NavigationPath
or when you push a value with a NavigationLink(value:label:)
.
This is commonly used to select an item from a list and navigate to its detail view.