Managing navigation with NavigationStack
Represent navigation as values using NavigationLink, navigationDestination, and NavigationPath.
Read the Korean original on Velog ↗English translation

Treating navigation as a collection of links makes a deep route difficult to control in code. NavigationStack can represent the route as data that the application can read and change.
NavigationLink
A navigation link pushes a destination onto the stack and lets the user return to the previous screen.
NavigationStack {
List {
// Navigate to each destination
NavigationLink("Red", destination: Text("Red"))
NavigationLink("Green", destination: Text("Green"))
NavigationLink("Blue", destination: Text("Blue"))
}
.navigationTitle("Colors") // Navigation title
}
navigationDestination
This modifier associates a value type with a destination view. A value-based link describes the data to navigate to, while the destination builder defines the corresponding screen.
NavigationStack {
List {
NavigationLink("Red", value: Color.red)
NavigationLink("Green", value: Color.green)
NavigationLink("Blue", value: Color.blue)
}
// Define destinations for values of this type
.navigationDestination(for: Color.self, destination: { color in
ColorDetail(color: color)
})
.navigationTitle("Colors")
}
The value passed to NavigationLink must match a registered destination type for navigation to work.
struct ColorDetail: View {
var color: Color
var body: some View {
Text("\(self.color.description)")
}
}
Register multiple destination types when a stack needs to handle different kinds of values.
NavigationStack {
List {
NavigationLink("Red", value: Color.red)
NavigationLink("Green", value: Color.green)
NavigationLink("Blue", value: "blue")
}
.navigationDestination(for: Color.self, destination: { color in
ColorDetailForColor(color: color) // Color type
})
.navigationDestination(for: String.self, destination: { color in
ColorDetailForString(color: color) // String values
})
.navigationTitle("Colors")
}
Navigation state
Store a NavigationPath in state to track the route. Removing the last value returns to the previous screen; clearing the path returns to the root.
struct NavigationStackStudy: View {
// Track the navigation path
@State var stack: NavigationPath = NavigationPath()
var body: some View {
NavigationStack(path: $stack) {
List {
NavigationLink("Red", value: Color.red)
NavigationLink("Green", value: Color.green)
NavigationLink("Blue", value: Color.blue)
}
.navigationDestination(for: Color.self, destination: { color in
ColorDetail(stack: $stack, color: color)
NavigationLink("Yellow", value: Color.yellow) // A stack of repeated content
})
.navigationTitle("Colors")
}
}
}
struct ColorDetail: View {
@Binding var stack: NavigationPath
var color: Color
var body: some View {
Text("\(self.color.description)")
Button("Back") {
stack.removeLast() // Return to the previous screen
}
Button("First") {
stack = .init() // Return to the root screen
}
}
}
Setting the path programmatically also supports navigating directly to a particular route, including deep links.
struct NavigationStackStudy: View {
// Track the navigation path
@State var stack: [Color] = []
var body: some View {
NavigationStack(path: $stack) {
List {
NavigationLink("Red", value: Color.red)
NavigationLink("Green", value: Color.green)
NavigationLink("Blue", value: Color.blue)
Button("Go to Yellow") {
// Set the path directly
stack = [.red, .green, .blue, .yellow]
}
}
.navigationDestination(for: Color.self, destination: { color in
ColorDetail(color: color)
})
.navigationTitle("Colors")
}
}
}
struct ColorDetail: View {
var color: Color
var body: some View {
Text("\(self.color.description)")
}
}