
Swift Navigation Skill
- 1 installs
- 1 repo stars
- Updated April 15, 2026
- erikote04/swiftnavigation
Integrate the SwiftNavigation library into SwiftUI apps using its MVVM-C pattern with typed routes, coordinators, deep links, and state restoration.
About
Guides building SwiftUI navigation with SwiftNavigation's intended MVVM-C pattern: typed route enums, one root coordinator, router proxies, injected routing protocols, and a single RoutingView. A developer uses it when adding routes, coordinators, deep links, or navigation state persistence.
- Keeps routes Codable and Hashable for deep links and persisted snapshots
- Progressive reference files per task: routes, coordinators, presentation, deep links, restoration
Swift Navigation Skill by the numbers
- 1 all-time installs (skills.sh)
- Ranked #959 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/erikote04/swiftnavigation --skill swift-navigation-skillAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | April 15, 2026 |
| Repository | erikote04/swiftnavigation ↗ |
What it does
Integrate the SwiftNavigation library into SwiftUI apps using its MVVM-C pattern with typed routes, coordinators, deep links, and state restoration.
Files
SwiftNavigation Skill
Overview
Implement SwiftNavigation using the library's intended MVVM-C pattern: typed route enums and payload structs, one root NavigationCoordinator, feature coordinators backed by NavigationRouterProxy, @Observable view models injected with routing protocols, and a single RoutingView that maps stacks, modals, and alerts.
Start Here
- Load references/workflow.md first for the end-to-end implementation sequence.
- Load only the additional references needed for the current task:
- references/routes-and-state.md
- references/coordinators-and-di.md
- references/presentation.md
- references/deep-links-and-universal-links.md
- references/state-restoration.md
- Prefer the public API and patterns documented by SwiftNavigation over inventing parallel navigation abstractions.
Core Rules
- Keep one root
NavigationCoordinator<StackRoute, ModalRoute, AlertRoute>per app root or scene. - Model routes as
NavigationRoutevalues that stayCodable,Hashable, and easy to decode from persisted snapshots and deep links. - Use structs for payload-heavy routes and enums for the top-level route namespaces.
- Hide navigation behind routing protocols when injecting into view models.
- Keep feature coordinators
@MainActorand back them withNavigationRouterProxy. - Let
RoutingViewown stack, modal, and alert rendering. Do not recreate competing view-local navigation state. - Use
NeverforAlertRouteonly when the app truly does not need coordinator-driven alerts. - Store returned
NavigationEntryIDvalues whenever a flow must revisit or precisely target repeated destinations. - Rebuild full
NavigationStatevalues in deep-link resolvers instead of imperatively replaying navigation from views. - Intercept protected deep links asynchronously and resume them after login with
resumePendingNavigation(). - Persist coordinator snapshots with
exportState()and restore them withrestore(from:).
Recommended Workflow
1. Define AppRoute, AppModalRoute, and AppAlertRoute with the guidance in references/routes-and-state.md. 2. Create the app/root coordinator, shared NavigationRouterProxy, services, and child coordinators with references/coordinators-and-di.md. 3. Inject routing protocols into @Observable view models instead of handing them the full app coordinator. 4. Build the single RoutingView root and map stack, modal, and alert destinations with references/presentation.md. 5. Add NavigationEntryID bookmarks for editable or repeated flow steps. 6. Add URL and notification resolvers, universal-link entry points, and optional login interception with references/deep-links-and-universal-links.md. 7. Persist and restore snapshots with references/state-restoration.md. 8. Verify behavior by asserting coordinator state directly in tests.
Decision Heuristics
- Put browser-like push navigation in
AppRoute. - Put sheets and full screen covers in
AppModalRoute. - Put coordinator-owned confirmations and error messaging in
AppAlertRoute. - Add a child coordinator when a feature needs its own routing contract or reusable flow logic.
- Store
NavigationEntryIDwhen route equality alone is not enough to target the right screen later. - Use a deep-link interceptor when auth, onboarding, or a feature gate must run before the destination applies.
Avoid
- Do not keep long-lived sheet, alert, or navigation booleans in feature views when the coordinator can own them.
- Do not inject the full root coordinator into every view model when a focused routing protocol is enough.
- Do not drive deep links by calling several
pushorpresentmethods fromonOpenURL. - Do not rely only on route equality when the same route can appear more than once in a flow.
- Do not store raw SwiftUI alert state in view models when
AlertDescriptormapping can stay inRoutingView.
Output Expectations
- Produce or update typed routes, coordinators, routing protocols, view-model injection,
RoutingViewmapping, deep-link resolvers, and persistence code together as one coherent integration. - Keep naming explicit and Swift-friendly.
- Follow SwiftUI, Swift Concurrency, and MVVM-C best practices reflected in the SwiftNavigation documentation and maintained examples.
{
"$schema": "https://raw.githubusercontent.com/anthropics/claude-code/refs/heads/main/marketplace/marketplace.schema.json",
"name": "swift-navigation-skill",
"version": "1.0.0",
"owner": {
"name": "Erik Sebastian de Erice Jerez",
"email": "erikote2004@icloud.com"
},
"metadata": {
"description": "SwiftNavigation integration guidance for routes, coordinators, presentation, deep links, universal links, and state restoration."
},
"plugins": [
{
"name": "swift-navigation-skill",
"description": "Implement SwiftNavigation the right way in SwiftUI apps.",
"repository": "https://github.com/Erikote04/SwiftNavigation",
"version": "1.0.0",
"author": {
"name": "Erik Sebastian de Erice Jerez",
"email": "erikote2004@icloud.com"
},
"license": "MIT",
"category": "development",
"keywords": [
"swift",
"swiftui",
"navigation",
"coordinator",
"mvvm-c",
"deeplink",
"universal-links",
"alerts",
"sheets",
"ios"
],
"tags": [
"swift",
"swiftui",
"navigation",
"coordinator",
"mvvm-c",
"deeplink",
"universal-links",
"alerts",
"sheets",
"ios"
],
"source": "./"
}
]
}
{
"name": "swift-navigation-skill",
"version": "1.0.0",
"description": "Implement SwiftNavigation in SwiftUI apps with typed routes, coordinators, deep links, alerts, sheets, and state restoration.",
"author": {
"name": "Erik Sebastian de Erice Jerez",
"email": "erikote2004@icloud.com"
},
"repository": "https://github.com/Erikote04/SwiftNavigation",
"license": "MIT",
"keywords": [
"swift",
"swiftui",
"navigation",
"coordinator",
"mvvm-c",
"deeplink",
"universal-links",
"alerts",
"sheets",
"ios"
],
"skills": [
"./"
]
}
interface:
display_name: "SwiftNavigation Skill"
short_description: "Implement SwiftNavigation the right way"
default_prompt: "Use $swift-navigation-skill to integrate SwiftNavigation into this SwiftUI app with coordinators, routes, deep links, sheets, alerts, and state restoration."
SwiftNavigation Skill
Practical guidance for any AI coding tool that supports the Agent Skills open format and needs to integrate the SwiftNavigation library into a SwiftUI app the right way.
This skill distills the library README, DocC guides, and maintained integration patterns into an implementation-focused workflow for routes, coordinators, view models, sheets, full screen covers, alerts, deep links, universal links, and state restoration.
Who this is for
- Teams integrating
SwiftNavigationinto a production SwiftUI app - Developers migrating an app to typed coordinator-driven navigation
- Anyone who wants a repo-aligned SwiftNavigation setup without digging through all docs and sample code first
How to Use This Skill
Option A: Using skills.sh (recommended)
Install this skill from the SwiftNavigation repository:
npx skills add https://github.com/Erikote04/SwiftNavigation --skill skillThen use the skill in your AI agent, for example:
Use the swift-navigation-skill and integrate SwiftNavigation into this app with app and child coordinators, deep links, alerts, sheets, and state restoration.
Option B: Claude Code Plugin
The .claude-plugin metadata is included in this folder so it can be published or copied as a standalone plugin package.
Personal Usage
If you publish skill/ as its own repository, add the marketplace:
/plugin marketplace add Erikote04/SwiftNavigationThen install the plugin:
/plugin install swift-navigation-skill@swift-navigation-skillProject Configuration
To automatically provide this skill to everyone working in a repository, configure .claude/settings.json:
{
"enabledPlugins": {
"swift-navigation-skill@swift-navigation-skill": true
},
"extraKnownMarketplaces": {
"swift-navigation-skill": {
"source": {
"source": "github",
"repo": "Erikote04/SwiftNavigation"
}
}
}
}Option C: Manual Install
1. Clone this repository. 2. Copy or symlink the `skill/` folder into your AI tool's skills directory. 3. Ask your AI tool to use the swift-navigation-skill skill for SwiftNavigation integration work.
Where to Save Skills
Follow your tool's official documentation:
- Codex: Where to save skills
- Claude: Using Skills
- Cursor: Enabling Skills
How to verify:
- Your agent should reference the implementation workflow in `SKILL.md`
- It should then load the relevant files from `references/` for the current task
What This Skill Offers
This skill helps an AI coding tool implement SwiftNavigation in a library-aligned, project-adaptable way.
Build the Core Navigation Surface
- Define
AppRoute,AppModalRoute, andAppAlertRoute - Model route payloads for persistence, deep links, and reviewability
- Use
NavigationEntryIDfor exact back-navigation in repeated flows
Implement Coordinators and MVVM-C Wiring
- Create a single root
NavigationCoordinator - Add app and child coordinators with
NavigationRouterProxy - Inject routing protocols into
@Observableview models - Keep dependency creation in the composition root
Implement Presentation Correctly
- Build a single
RoutingView - Map stack destinations, sheets, full screen covers, and alerts
- Configure
SheetPresentationOptions - Keep SwiftUI views free from duplicated navigation state
Add External Navigation and Restoration
- Build URL and notification deep-link resolvers
- Add login interception and pending navigation resume
- Wire universal links through the same URL pipeline
- Save, persist, and restore
NavigationState
What Makes This Skill Different
Repo-aligned: It follows the actual SwiftNavigation v2 API surface and documented integration model.
Flow-oriented: It emphasizes entry-backed navigation and exact backtracking for real multi-step flows.
Integration-first: It covers the whole setup, not just route enums or a single coordinator snippet.
Skill Structure
skill/
SKILL.md
README.md
agents/
openai.yaml
references/
workflow.md - End-to-end setup sequence for integrating SwiftNavigation
routes-and-state.md - Route design, NavigationState, and NavigationEntryID guidance
coordinators-and-di.md - App coordinator, child coordinators, routing protocols, and DI patterns
presentation.md - RoutingView, sheets, full screen covers, and alerts
deep-links-and-universal-links.md - URL, notification, universal-link, and interception patterns
state-restoration.md - Snapshot persistence and restoration guidance
.claude-plugin/
plugin.json
marketplace.jsonPublishing Note
This skill currently lives inside the main SwiftNavigation repository under skill/. If you want to distribute it as a standalone plugin repository, keep the contents of this folder together and update repository or license metadata as needed for that package.
License
This repository now uses the MIT License. See ../LICENSE for details.
Coordinators and Dependency Injection
Root Composition Pattern
Create the app coordinator at the composition root and keep dependency construction there.
@MainActor
@Observable
final class AppCoordinator {
let navigationCoordinator: NavigationCoordinator<AppRoute, AppModalRoute, AppAlertRoute>
let homeCoordinator: HomeCoordinator
let accountCoordinator: AccountCoordinator
let homeViewModel: HomeViewModel
let accountViewModel: AccountViewModel
init() {
let navigationCoordinator = NavigationCoordinator<AppRoute, AppModalRoute, AppAlertRoute>(
scope: .application
)
let router = NavigationRouterProxy(coordinator: navigationCoordinator)
let apiClient = APIClient()
let sessionStore = SessionStore()
let homeCoordinator = HomeCoordinator(router: router)
let accountCoordinator = AccountCoordinator(router: router)
self.navigationCoordinator = navigationCoordinator
self.homeCoordinator = homeCoordinator
self.accountCoordinator = accountCoordinator
self.homeViewModel = HomeViewModel(apiClient: apiClient, router: homeCoordinator)
self.accountViewModel = AccountViewModel(sessionStore: sessionStore, router: accountCoordinator)
navigationCoordinator.attachChild(homeCoordinator)
navigationCoordinator.attachChild(accountCoordinator)
}
}Feature Routing Pattern
Define a focused routing protocol per feature:
@MainActor
protocol AccountRouting: AnyObject {
func showProfile(_ route: ProfileRouteData) -> NavigationEntryID
func showSignIn(_ route: SignInRouteData)
func showError(_ message: String)
}Implement it in a feature coordinator:
@MainActor
final class AccountCoordinator: CoordinatorLifecycle, AccountRouting {
let coordinatorID = UUID()
private let router: NavigationRouterProxy<AppRoute, AppModalRoute, AppAlertRoute>
init(router: NavigationRouterProxy<AppRoute, AppModalRoute, AppAlertRoute>) {
self.router = router
}
var isFlowFinished: Bool {
false
}
func showProfile(_ route: ProfileRouteData) -> NavigationEntryID {
router.push(.profile(route))
}
func showSignIn(_ route: SignInRouteData) {
_ = router.present(.signIn(route), style: .sheet)
}
func showError(_ message: String) {
_ = router.presentAlert(.errorMessage(message))
}
}View-Model Injection Pattern
Inject routing protocols, not concrete app coordinators:
@MainActor
@Observable
final class AccountViewModel {
private let router: any AccountRouting
init(router: any AccountRouting) {
self.router = router
}
}Prefer Explicit Dependencies
- Inject repositories, services, clocks, and session stores through initializers.
- Keep
UserDefaultsor persistence adapters at the app/root level unless a feature owns its own persistence boundary. - Avoid hidden globals or singletons when a dependency can be passed explicitly.
Use Child Coordinators for Reusable Flow Logic
Create a child coordinator when:
- multiple views in a feature share route-building logic
- a feature owns reusable sheet or alert presentation rules
- a flow needs exact bookmarks or backtracking logic
- a view model would otherwise collect too much navigation orchestration
Keep child coordinators thin. They should adapt domain/user intent to NavigationRouterProxy, not absorb unrelated business logic.
Deep Links, Notifications, Universal Links, and Interception
Rebuild NavigationState from External Inputs
Implement resolvers instead of replaying navigation imperatively.
URL Resolver Pattern
struct AppURLResolver: URLDeepLinkResolving {
func navigationState(for url: URL) throws -> NavigationState<AppRoute, AppModalRoute, Never> {
// Parse the URL and return the full stack/modal state.
}
}The resolver should:
- validate the scheme or host
- parse path segments and query items
- choose the preferred tab or root context if needed
- return the full
NavigationState
Notification Resolver Pattern
struct AppNotificationResolver: NotificationDeepLinkResolving {
func navigationState(for userInfo: [AnyHashable: Any]) throws -> NavigationState<AppRoute, AppModalRoute, Never> {
// Decode payload fields and return the full state.
}
}Prefer this for notification taps, silent push routing, or payload-driven entry points.
Apply URL and Notification Deep Links
try await coordinator.applyURLDeepLink(url, resolver: AppURLResolver())
try await coordinator.applyNotificationDeepLink(
userInfo: payload,
resolver: AppNotificationResolver()
)Intercept Protected Destinations
Use interception when login or another prerequisite must happen first:
try await coordinator.applyURLDeepLink(url, resolver: AppURLResolver()) { state in
guard sessionStore.isAuthenticated else {
return .redirect(
loginState: NavigationState(
modalStack: [.init(style: .sheet, root: .signIn(signInRoute))]
),
pendingState: state
)
}
return .proceed
}When login finishes:
coordinator.resumePendingNavigation()If the pending deep link should be abandoned:
coordinator.clearPendingNavigation()Only one pending state is stored at a time, so a newer redirect replaces the previous pending destination.
Universal Links
Treat universal links as ordinary https URLs flowing through the same resolver pipeline:
.onOpenURL { url in
Task {
try? await coordinator.applyURLDeepLink(url, resolver: AppURLResolver())
}
}
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
guard let url = activity.webpageURL else { return }
Task {
try? await coordinator.applyURLDeepLink(url, resolver: AppURLResolver())
}
}Keep the app responsible for:
- Associated Domains entitlement
apple-app-site-association- hosted domain ownership and configuration
Keep SwiftNavigation responsible for turning the URL into NavigationState.
Notification Tap Bridging
If a notification delegate exists outside SwiftUI, bridge it into the app coordinator with NotificationCenter or another app-level event handoff, then call the notification resolver from the root coordinator.
Recommended Parsing Strategy
- Centralize URL and payload parsing in one parser namespace.
- Prefer explicit validation errors over silent fallback.
- Return empty
NavigationState()only when an input is valid but intentionally maps to the app root.
Presentation: RoutingView, Sheets, Full Screen Covers, Alerts
Build One Routing Container
Use a single RoutingView at the app root:
RoutingView(
coordinator: appCoordinator.navigationCoordinator,
root: {
HomeView(viewModel: appCoordinator.homeViewModel)
},
stackDestination: { route in
switch route {
case .profile(let route):
ProfileView(route: route, viewModel: appCoordinator.accountViewModel)
}
},
modalDestination: { route in
switch route {
case .signIn(let route):
SignInView(route: route)
case .settings:
SettingsView()
}
},
alertDestination: { route in
switch route {
case .errorMessage(let message):
AlertDescriptor(
title: "Something went wrong",
message: message,
actions: [.dismiss("OK")]
)
}
}
)
.navigationCoordinator(appCoordinator.navigationCoordinator)If alerts are not used, configure the coordinator as NavigationCoordinator<AppRoute, AppModalRoute, Never> and use the no-alert RoutingView initializer.
Present Sheets
Use .sheet plus SheetPresentationOptions:
_ = router.present(
.signIn(signInRoute),
style: .sheet,
sheetPresentation: SheetPresentationOptions(
detents: [.medium, .large],
background: .regularMaterial,
backgroundInteraction: .enabledThrough(.medium),
interactiveDismissDisabled: true
)
)Use sheet options when you need:
presentationDetentspresentationBackgroundpresentationBackgroundInteractioninteractiveDismissDisabled
Present Full Screen Covers
Use .fullScreen when the presented flow should feel separate from the underlying UI:
_ = router.present(.settings, style: .fullScreen)Prefer this for onboarding, locked flows, or experiences that should not visually stack like a sheet.
Present Alerts
Keep alert state in the coordinator, not in feature views:
_ = router.presentAlert(.errorMessage("The request failed."))Map alert routes into AlertDescriptor:
AlertDescriptor(
title: "Discard draft?",
message: "Your edits will be lost.",
actions: [
AlertAction(title: "Discard", role: .destructive),
.dismiss("Keep Editing", role: .cancel)
]
)Keep Views Thin
- Let views render route data and forward user intent to the view model.
- Let the coordinator decide whether that intent becomes a push, a sheet, a full screen cover, or an alert.
- Do not reintroduce
.sheet(isPresented:),.alert(isPresented:), or view-local navigation booleans for flows already owned by SwiftNavigation.
Routes and Navigation State
Design Routes for Persistence and Deep Links
- Make every route conform to
NavigationRoute. - Prefer enums for the route namespaces:
enum AppRoute: NavigationRoute {
case dashboard
case profile(ProfileRouteData)
}- Prefer structs for payload-heavy route data:
struct ProfileRouteData: NavigationRoute {
let userID: UUID
let displayName: String
let source: String
}- Keep payloads stable enough to encode, restore, and reconstruct from URLs or notifications.
Split the Route Surface Intentionally
- Put pushed destinations in
AppRoute. - Put sheets and full screen covers in
AppModalRoute. - Put coordinator-owned alerts in
AppAlertRoute.
enum AppModalRoute: NavigationRoute {
case signIn(SignInRouteData)
case settings
}
enum AppAlertRoute: NavigationRoute {
case errorMessage(String)
case discardChanges(UUID)
}Use Entry IDs for Exact Navigation
Route equality is not enough when the same route can appear multiple times.
Use:
let id = coordinator.push(.profile(route))coordinator.popToEntry(id)coordinator.containsEntry(id)
Store those identifiers in a view model or feature coordinator when the user may later say "take me back to the amount screen I just edited".
Use Modal Path Entry IDs for Nested Modal Flows
For navigation inside a presented modal:
pushModalRoute(_:at:)modalPathEntries(at:)popModalToEntry(_:at:)containsModalEntry(_:at:)
Use these when a modal hosts its own NavigationStack and repeated screens must still be individually addressable.
Build NavigationState Directly When Needed
NavigationState is the canonical snapshot for:
- deep links
- universal links
- notification payloads
- persistence and restoration
- test setup
Examples:
let state = NavigationState<AppRoute, AppModalRoute, AppAlertRoute>(
stack: [.profile(profileRoute)],
modalStack: [.init(style: .sheet, root: .signIn(signInRoute))]
)Or with entry-backed values:
let state = NavigationState<AppRoute, AppModalRoute, AppAlertRoute>(
stackEntries: [
NavigationEntry(route: .profile(profileRoute))
]
)Keep Routes Reviewable
- Prefer descriptive case names such as
.profile,.editor,.receipt. - Avoid route cases that simply mirror view names without domain meaning.
- Keep route payloads free of live service references or non-
CodableUI state. - Store identifiers, values, and lightweight display context, not whole feature objects.
State Restoration
Persist the Coordinator Snapshot
Use exportState() as the canonical serialization source:
let snapshot = coordinator.exportState()
let data = try JSONEncoder().encode(snapshot)
userDefaults.set(data, forKey: "navigationState")Persist this from the app/root coordinator or scene-level composition root.
Restore Early
Restore as early as possible in app startup, after the root coordinator exists and before the user starts navigating again:
if let data = userDefaults.data(forKey: "navigationState") {
let snapshot = try JSONDecoder().decode(
NavigationState<AppRoute, AppModalRoute, AppAlertRoute>.self,
from: data
)
coordinator.restore(from: snapshot)
}Handle Decode Failure Gracefully
If decoding fails:
- remove the invalid snapshot
- log or assert in debug builds
- continue with a clean coordinator state
Know What Gets Stored
exportState() captures:
- entry-backed root stack state
- modal stack state
- modal path entries
- the active alert presentation
Pending intercepted navigation is stored separately on the coordinator. Manage that with:
resumePendingNavigation()clearPendingNavigation()
Use Cases
- app relaunch continuation
- scene restoration
- crash-safe checkpoints
- deterministic test setup for complex flows
Compatibility Note
SwiftNavigation can decode legacy route-only snapshots and synthesize missing entry identifiers during restore. Do not add your own migration layer unless the app wraps route data in an app-specific envelope.
SwiftNavigation Integration Workflow
Purpose
Follow this sequence when integrating SwiftNavigation into a new or existing SwiftUI app.
Sequence
1. Define the route surface. 2. Build the root coordinator and composition root. 3. Add child coordinators and routing protocols. 4. Connect routing to @Observable view models. 5. Build the RoutingView container. 6. Add flow bookmarks for repeated destinations. 7. Add deep links, universal links, and notification entry points. 8. Add persistence and restoration. 9. Verify with direct coordinator-state assertions.
1. Define the Route Surface
- Create
AppRoutefor root stack navigation. - Create
AppModalRoutefor sheets and full screen covers. - Create
AppAlertRoutefor coordinator-owned alerts. UseNeveronly when you do not need global typed alerts. - Keep route payloads small, explicit, and
Codable.
Load routes-and-state.md before implementing this step.
2. Build the Root Coordinator and Composition Root
- Create one
NavigationCoordinator<AppRoute, AppModalRoute, AppAlertRoute>at the app root. - Create one shared
NavigationRouterProxyfrom that coordinator. - Construct app-wide dependencies here: services, repositories, session stores, clocks, UUID generators, and feature coordinators.
- Attach child coordinators with
attachChild(_:)when they conform toCoordinatorLifecycle.
Load coordinators-and-di.md before implementing this step.
3. Add Child Coordinators
- Give each feature a narrow routing protocol such as
PaymentRoutingorProfileRouting. - Implement that protocol in a feature coordinator backed by
NavigationRouterProxy. - Return
NavigationEntryIDfrom routing methods when later exact back-navigation is likely.
4. Connect View Models
- Inject the feature routing protocol into
@Observableview models. - Keep views focused on rendering and wiring button taps to view-model methods.
- Let view models decide when to navigate, but not how SwiftUI presents destinations.
5. Build the Routing Container
- Wrap the root UI in one
RoutingView. - Map
AppRouteinstackDestination. - Map
AppModalRouteinmodalDestination. - Map
AppAlertRouteinalertDestinationif alerts are enabled. - Apply
.navigationCoordinator(coordinator)to the root container.
Load presentation.md before implementing this step.
6. Add Flow Bookmarks
- Capture the result of
push(_:)for steps that may need later editing. - For modal-internal flows, use
pushModalRoute(_:at:). - Use
popToEntry(_:)orpopModalToEntry(_:at:)to jump back to an exact screen instance.
Load routes-and-state.md before implementing this step.
7. Add External Navigation
- Implement a
URLDeepLinkResolvingtype for URL and universal-link inputs. - Implement a
NotificationDeepLinkResolvingtype for notification payloads. - Rebuild full
NavigationStatesnapshots from those inputs. - Add interception when login or another prerequisite must run first.
Load deep-links-and-universal-links.md before implementing this step.
8. Add Persistence
- Save
exportState()to disk when the app backgrounds or when you want checkpoints. - Restore
NavigationStateearly in app startup. - Clear invalid or outdated snapshots if decoding fails.
Load state-restoration.md before implementing this step.
9. Verify
- Assert
stackEntries,modalStack,alertPresentation, andpendingNavigationStatedirectly in tests. - Verify duplicated routes still behave correctly with
NavigationEntryID. - Verify protected deep links redirect to login and resume correctly.
Integration Checklist
- Routes are typed and
Codable. - The app has one root coordinator.
- View models depend on routing protocols, not SwiftUI.
RoutingViewis the single navigation container.- Sheets, alerts, and full screen covers come from the coordinator.
- Deep links rebuild
NavigationState. - Universal links reuse the same URL resolver pipeline.
- State snapshots save and restore cleanly.