Programmatic Navigation & Deep Linking
Drive navigation from code, not just taps. Learn NavigationStack with a path binding, navigationDestination(for:) , pushing and popping in code, type-safe routes with an enum , and deep linking with onOpenURL .
Learn Programmatic Navigation & Deep Linking in our free Swift course — a beginner-friendly interactive lesson with worked examples, a practice exercise and…
Part of the free Swift course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- • Use NavigationStack with a bound path
- • Map values to screens with navigationDestination(for:)
- • Push and pop programmatically by editing the path
- • Model type-safe routes with a Hashable enum
- • Parse and handle deep links with onOpenURL
- • See how this differs from a basic NavigationLink
1️⃣ Basic NavigationLink
A plain NavigationLink pushes a destination when the user taps it. That is fine for simple menus but gives you no control from code.
2️⃣ Programmatic Push and Pop
Bind a path to the NavigationStack . Append values to push, empty it to pop to root — all from your own code, not taps.
3️⃣ Type-Safe Routes with an Enum
A Hashable enum names every valid destination. A switch in navigationDestination(for:) forces you to handle each case.
Your turn. Fill in the path binding and the method that pushes a value.
4️⃣ Deep Linking with onOpenURL
onOpenURL fires when the app opens from a URL. Parse it into a route value and append it to the path to jump straight to the right screen.
Pro Tips
- 💡 Keep one source of truth. Store the path in your state and drive everything from it.
- 💡 Make routes a Hashable enum so the compiler checks every destination.
- 💡 Empty the path with removeAll() to pop straight to root.
- 💡 Reuse routing for deep links — parse the URL into the same route values.
Common Errors (and the fix)
- Nothing happens on append: you forgot a matching navigationDestination(for:) .
- "does not conform to Hashable": make your route type Hashable .
- Still using NavigationView: switch to NavigationStack for path binding.
- Deep link ignored: attach onOpenURL near the root view.
📋 Quick Reference
Task
Code
Effect
Container
NavigationStack(path: $path)
data-driven stack
Map value
navigationDestination(for:)
value to screen
Push
path.append(route)
show that screen
Pop to root
path.removeAll()
back to start
Value link
NavigationLink(value:)
push by value
Deep link
.onOpenURL { url in … }
handle URL
Frequently Asked Questions
Mini-Challenge: Type-Safe Routing
Build a Hashable route enum, bind it to a NavigationStack, and push a profile screen from a button.
Practice quiz
Which container replaced NavigationView for stack navigation in modern SwiftUI?
- NavigationStack
- NavigationView
- TabView
- ScrollView
Answer: NavigationStack. NavigationStack is the modern push/pop container introduced in iOS 16.
What does binding a path to a NavigationStack let you do?
- Add tabs
- Style the bar
- Drive navigation programmatically by editing the path array
- Disable navigation
Answer: Drive navigation programmatically by editing the path array. A path binding holds the navigation stack so you can push and pop by mutating the array.
Which modifier registers what view to show for a given value type?
- onAppear
- navigationDestination(for:)
- sheet(item:)
- navigationTitle
Answer: navigationDestination(for:). navigationDestination(for:) maps a value type to the destination view it should present.
To pop to the root of a NavigationStack programmatically, you...
- Call dismiss()
- Remove the modifier
- Restart the app
- Set the path to an empty collection
Answer: Set the path to an empty collection. Emptying the path collection pops every pushed view back to the root.
How do you push a new screen programmatically with a path binding?
- Append a value to the path
- Toggle a Bool
- Use a segue
- Call push()
Answer: Append a value to the path. Appending a value to the bound path pushes its matching navigationDestination view.
Why use an enum for type-safe routes?
- It avoids imports
- It enumerates all valid destinations the compiler can check
- It is required by SwiftUI
- It is faster
Answer: It enumerates all valid destinations the compiler can check. An enum lists every valid route so the compiler catches typos and missing cases.
Which modifier handles an incoming deep-link URL?
- onTapGesture
- onChange
- onReceive
- onOpenURL
Answer: onOpenURL. onOpenURL(perform:) fires when the app is opened via a URL, letting you parse and route.
After parsing a deep-link URL, how do you navigate to that screen?
- Show an alert
- Ignore it
- Append the parsed route value to the navigation path
- Reload the app
Answer: Append the parsed route value to the navigation path. You translate the URL into a route value and append it to the path to navigate there.
How does NavigationLink(value:) differ from the basic NavigationLink(destination:)?
- It opens a sheet
- It pushes a value resolved by navigationDestination instead of an inline view
- It does nothing
- It is deprecated
Answer: It pushes a value resolved by navigationDestination instead of an inline view. NavigationLink(value:) sends a value that navigationDestination(for:) turns into a screen.
For routes to drive a path and be diffable, the route type should be...
- Hashable
- A class
- Optional
- Sendable only
Answer: Hashable. Path values must be Hashable so the stack can store and compare them.
Continue this course
- Previous: Result Builders
- Next: Swift Macros