← All daysDay 17 of 49 · Part 5: Lists and navigation
Day 17

NavigationStack and programmatic navigation

These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.

Lesson 19

NavigationStack and programmatic navigation

NavigationStack manages a stack of screens. You push a screen with NavigationLink(value:), and you say once, with .navigationDestination(for:), which screen each type of value leads to. The contents of the stack can be bound to an array that you own, called the path. Navigation then becomes ordinary data: append a value to push, remove one to pop, empty the array to return to the root, and assign several values at once to open a deep link.

Coming from UIKit, this replaces pushViewController and coordinator objects. Because the path is plain data, you can save and restore it, and you can unit test navigation logic with no user interface at all.

Example
struct TripsList: View {
    let trips: [Trip]
    @State private var path: [Trip] = []       // the navigation stack, as data

    var body: some View {
        NavigationStack(path: $path) {
            List(trips) { trip in
                NavigationLink(trip.name, value: trip)      // push by value
            }
            .navigationDestination(for: Trip.self) { trip in
                Button("Back to start") { path.removeAll() }    // pop to root
                    .navigationTitle(trip.name)
            }
        }
    }
}
Exercise

Build a flow of list → detail → sub-detail. Add a button on the deepest screen that returns to the root by clearing the path, and a button on the root that jumps straight to a sub-detail screen.

After this lesson you should be able to answer
  1. What does .navigationDestination(for:) do, and where do you attach it?
  2. How do you return to the root screen in code?
  3. Why does binding the path to your own state make navigation testable?
Resources