SwiftUI Navigation: Typed Routes Beat Old Hacks

Use iOS 16+ NavigationStack with Hashable Route enums as path data for clean, scalable navigation—programmatic pushes, deep links, and tabs without state hacks or spaghetti code.

Typed Routes Simplify Navigation State

NavigationStack treats history as a stack of typed, Hashable Route enum values—empty array at root, append to push (e.g., path.append(.details(id: 42))), popLast() to pop, removeAll() for root. Centralize mapping in root's .navigationDestination(for: Route.self) { switch route { case .details(let id): DetailsView(id: id); case .settings: SettingsView() } }. Use NavigationLink(value: Route.details(id: 42)) instead of destination: closures. This centralizes routes, enables compiler-checked refactors, and avoids scattered state. For basic apps (<5 screens), bind @State path: Route directly to NavigationStack(path: $path).

Router Enables Programmatic Control at Scale

For apps with multiple flows (auth, onboarding, tabs), extract path to @MainActor ObservableObject Router with @Published path: Route, methods push(:), pop(), popToRoot(), setPath(:). Inject via @StateObject in root, pass @ObservedObject to children: router.push(.details(id: 7)). Keeps views UI-focused, supports post-login pushes without nested conditionals. Beats pre-iOS 16 NavigationView/isActive fragility, preventing double-pushes or reasoning issues in complex apps.

Parse URLs to Route via DeepLinkParser (guard scheme == "myapp"; if host == "details", id = Int(pathComponents.first): return .details(id: id)). Apply with .onOpenURL { if let newPath = parse(url) { router.setPath(newPath) } }—sets stack directly, no intermediates. For TabView, use separate NavigationStack per tab with own path/router to isolate histories; share Route enum or specialize (HomeRoute, SettingsRoute). Avoids cross-tab stack leakage.

Pitfalls Fixed by This Model

Define destinations only at stack root, not children, to prevent duplication. Stick to NavigationLink(value:) + typed paths over mixed destination: for consistency. Ensure Route conforms to Hashable (hashable payloads only). Persist serialized path manually for app restarts if needed—doesn't auto-save. Scales to 20+ screens, deep links, programmatic nav without hacks.

Summarized by x-ai/grok-4.1-fast via openrouter

9408 input / 2142 output tokens in 9709ms

© 2026 Edge