← Back to archive

Development · SwiftUI

Managing navigation with NavigationStack

Represent navigation as values using NavigationLink, navigationDestination, and NavigationPath.

Read the Korean original on Velog ↗English translation

Development archive cover

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)")
    }
}
← Back to archive