Skip to content
intermediate Phase 4 · Navigation & Data Flow

NavigationStack

Build type-safe navigation with NavigationStack, NavigationPath, and programmatic navigation.

50m
3 problems
Topic Progress 0%

Programmatic Navigation

Why Programmatic Navigation?

Sometimes you need to navigate based on business logic — not just user taps. For example:

  • After a successful login, navigate to the main screen
  • When a push notification arrives, open a specific detail view
  • After completing a multi-step form, navigate to the confirmation screen

Programmatic navigation uses the NavigationPath to control the stack from code.

NavigationLink with Value

NavigationLink can trigger navigation by appending a value to the path:

NavigationLink("View Report", value: ReportRoute.dashboard)

Or using the trigger parameter for imperative navigation:

NavigationLink("Log In", value: AuthRoute.login, isActive: $shouldLogin)

Triggering Navigation from Async Operations

A common pattern is navigating after an async task completes:

struct LoginView: View {
    @State private var path = NavigationPath()
    @State private var isLoading = false

    var body: some View {
        NavigationStack(path: $path) {
            Button("Login") {
                isLoading = true
                Task {
                    try await authService.login()
                    isLoading = false
                    path.append(AppRoute.home)
                }
            }
            .navigationDestination(for: AppRoute.self) { route in
                switch route {
                case .home:
                    HomeView()
                case .profile:
                    ProfileView()
                }
            }
        }
    }
}

Replacing the Entire Stack

Sometimes you want to replace the entire navigation stack — for example, after logout:

// Clear the entire path and push a new root
path = NavigationPath()
path.append(AuthRoute.login)

Or pop to root and push a different view:

path.removeLast(path.count)
path.append(AppRoute.settings)

Navigation with Environment

The @Environment(\.navigationPath) property wrapper (available on iOS 17+) lets you access the navigation path from any child view without explicit binding:

struct ChildView: View {
    @Environment(\.navigationPath) private var navigationPath

    func openSettings() {
        navigationPath?.append(AppRoute.settings)
    }
}

Complex Navigation Flows

For multi-step flows, model each step as a route enum:

enum OnboardingStep: Hashable {
    case welcome
    case permissions
    case accountSetup
    case complete
}

Then manage the flow:

@State private var onboardingPath = NavigationPath()

func advanceToNextStep(current: OnboardingStep) {
    switch current {
    case .welcome: onboardingPath.append(.permissions)
    case .permissions: onboardingPath.append(.accountSetup)
    case .accountSetup: onboardingPath.append(.complete)
    case .complete: break
    }
}

This gives you full control over multi-screen flows without manual view management.

Quiz

1. What replaced NavigationView in iOS 16 for push/pop navigation?

Question 1 options

2. How do you programmatically push a view onto a NavigationStack?

Question 2 options

3. Which modifier defines which views correspond to which navigation values?

Question 3 options

4. What protocol does NavigationPath conform to for saving/restoring state?

Question 4 options

Flashcards

Question

What is NavigationPath?

Answer

An observable collection representing the navigation state of a NavigationStack. It holds hashable values that map to views.

Question

How do you pop a view programmatically?

Answer

Use path.removeLast() to pop one view, or path.removeLast(path.count) to pop to root.

Question

What is the difference between NavigationStack and NavigationSplitView?

Answer

NavigationStack is for push/pop hierarchies (iPhone), NavigationSplitView is for column-based layouts (iPad).

Question

How do you dismiss a view inside a NavigationStack?

Answer

Use @Environment(\.dismiss) and call dismiss() to pop the current view.

Revision Notes

Key Takeaways

  • 1. NavigationStack is SwiftUI's modern push/pop navigation API (iOS 16+)
  • 2. NavigationPath provides programmatic control and is Codable for state restoration
  • 3. .navigationDestination(for:) provides type-safe view routing
  • 4. @Environment(\.dismiss) is the recommended way to pop views programmatically
  • 5. Use NavigationStack for iPhone, NavigationSplitView for iPad column layouts

Interview Tips

  • Explain why NavigationStack replaced NavigationView (programmatic control, Codable path)
  • Know how to save/restore navigation state using NavigationPath.CodableRepresentation
  • Be able to implement a multi-step onboarding flow with programmatic navigation
  • Discuss when to use NavigationStack vs NavigationSplitView

Cheat Sheet

NavigationStack (iOS 16+): Push/pop navigation with NavigationPath.

NavigationPath: Observable, Codable collection of hashable values.

Key modifiers:

  • .navigationDestination(for:) — type-safe view routing
  • @Environment(.dismiss) — programmatic pop
  • path.append(value) — push
  • path.removeLast() — pop

NavigationStack replaces NavigationView. NavigationSplitView for column layouts.