iOS Navigation (Modular MVVM-C)
Opinionated navigation enforcement for SwiftUI apps using the clinic modular architecture. Focus on coordinator + route shell wiring, feature isolation, and resilient push/sheet/deep-link flows.
Non-Negotiable Constraints (iOS 26 / Swift 6.2)
-
@Equatable macro on every navigation view, AnyView never
-
@Observable everywhere, ObservableObject / @Published never
-
App-target coordinators own NavigationPath ; route shells own .navigationDestination mappings
-
Coordinator-owned modal state, inline @State booleans for sheets never
-
Domain layer defines coordinator protocols; concrete coordinators stay out of feature modules
Clinic Architecture Contract (iOS 26 / Swift 6.2)
All guidance in this skill assumes the clinic modular MVVM-C architecture:
- Feature modules import Domain
- DesignSystem only (never Data , never sibling features)
-
App target is the convergence point and owns DependencyContainer , concrete coordinators, and Route Shell wiring
-
Domain stays pure Swift and defines models plus repository, *Coordinating , ErrorRouting , and AppError contracts
-
Data owns SwiftData/network/sync/retry/background I/O and implements Domain protocols
-
Read/write flow defaults to stale-while-revalidate reads and optimistic queued writes
-
ViewModels call repository protocols directly (no default use-case/interactor layer)
When to Apply
Reference these guidelines when:
-
Designing navigation hierarchies with NavigationStack or NavigationSplitView
-
Choosing between push, sheet, and fullScreenCover
-
Implementing hero animations, zoom transitions, or gesture-driven dismissals
-
Building multi-step flows (onboarding, checkout, registration)
-
Using @Observable with @Environment and @Bindable for shared navigation state
-
Reviewing code for navigation anti-patterns and modular architecture compliance
-
Adding deep linking, state restoration, or tab persistence
-
Ensuring VoiceOver and reduce motion support for navigation
Rule Categories by Priority
Priority Category Impact Prefix
1 Navigation Architecture CRITICAL arch-
2 Navigation Anti-Patterns CRITICAL anti-
| 3 | Transition & Animation | HIGH | anim- | | 4 | Modal Presentation | HIGH | modal- | | 5 | Flow Orchestration | HIGH | flow- | | 6 | Navigation Performance | MEDIUM-HIGH | perf- | | 7 | Navigation Accessibility | MEDIUM | ally- | | 8 | State & Restoration | MEDIUM | state- |
Quick Reference
- Navigation Architecture (CRITICAL)
-
arch-navigation-stack
-
Use NavigationStack over deprecated NavigationView
-
arch-value-based-links
-
Use value-based NavigationLink over destination closures
-
arch-destination-registration
-
Register navigationDestination at stack root
-
arch-destination-item
-
Use navigationDestination(item:) for optional-based navigation (iOS 26 / Swift 6.2)
-
arch-route-enum
-
Define routes as Hashable enums
-
arch-split-view
-
Use NavigationSplitView for multi-column layouts
-
arch-coordinator
-
Extract navigation logic into Observable coordinator
-
arch-observable-environment
-
Use @Environment with @Observable and @Bindable for shared state
-
arch-deep-linking
-
Handle deep links by appending to NavigationPath
-
arch-navigation-path
-
Use NavigationPath for heterogeneous type-erased navigation
-
arch-equatable-views
-
Apply @Equatable macro to every navigation view
-
arch-observable-only
-
Use @Observable only — never ObservableObject or @Published
-
arch-no-anyview
-
Never use AnyView in navigation — use @ViewBuilder or generics
-
arch-coordinator-modals
-
Present all modals via coordinator — never inline @State
- Navigation Anti-Patterns (CRITICAL)
-
anti-mixed-link-styles
-
Avoid mixing NavigationLink(destination:) with NavigationLink(value:)
-
anti-scattered-destinations
-
Avoid scattering navigationDestination across views
-
anti-shared-stack
-
Avoid sharing NavigationStack across tabs
-
anti-hidden-back-button
-
Avoid hiding back button without preserving swipe gesture
-
anti-navigation-in-init
-
Avoid heavy work in view initializers
-
anti-hamburger-menu
-
Avoid hamburger menu navigation
-
anti-programmatic-tab-switch
-
Avoid programmatic tab selection changes
- Transition & Animation (HIGH)
-
anim-zoom-transition
-
Use zoom navigation transition for hero animations (iOS 18+)
-
anim-matched-geometry-same-view
-
Use matchedGeometryEffect only within same view hierarchy
-
anim-spring-config
-
Use modern spring animation syntax (iOS 26 / Swift 6.2)
-
anim-gesture-driven
-
Use interactive spring animations for gesture-driven transitions
-
anim-transition-source-styling
-
Style transition sources with shape and background
-
anim-reduce-motion-transitions
-
Respect reduce motion for all navigation animations
-
anim-scroll-driven
-
Use onScrollGeometryChange for scroll-driven transitions (iOS 18+)
- Modal Presentation (HIGH)
-
modal-sheet-vs-push
-
Use push for drill-down, sheet for supplementary content
-
modal-detents
-
Use presentation detents for contextual sheet sizing
-
modal-fullscreen-cover
-
Use fullScreenCover only for immersive standalone experiences
-
modal-sheet-placement
-
Place .sheet on container view, not on NavigationLink
-
modal-interactive-dismiss
-
Guard unsaved changes with interactiveDismissDisabled
-
modal-nested-navigation
-
Use separate NavigationStack inside modals
- Flow Orchestration (HIGH)
-
flow-tab-independence
-
Give each tab its own NavigationStack
-
flow-multi-step
-
Use NavigationStack with route array for multi-step flows
-
flow-sidebar-navigation
-
Use NavigationSplitView with selection binding for sidebar
-
flow-tab-sidebar-adaptive
-
Use sidebarAdaptable TabView for iPad tab-to-sidebar (iOS 18+)
-
flow-pop-to-root
-
Implement pop-to-root by clearing NavigationPath
-
flow-screen-independence
-
Keep screens independent of parent navigation context
- Navigation Performance (MEDIUM-HIGH)
-
perf-lazy-destinations
-
Use value-based NavigationLink for lazy destination construction
-
perf-task-modifier
-
Use .task for async data loading on navigation
-
perf-state-object-ownership
-
Own @Observable state with @State, pass as plain property
-
perf-avoid-body-side-effects
-
Avoid side effects in view body
- Navigation Accessibility (MEDIUM)
-
ally-rotor-headers
-
Mark navigation section headers for VoiceOver rotor
-
ally-focus-after-navigation
-
Manage focus after programmatic navigation events
-
ally-group-navigation-elements
-
Group related navigation elements to reduce swipe count
-
ally-hide-decorative-navigation
-
Hide decorative navigation elements from VoiceOver
-
ally-keyboard-focus
-
Use @FocusState for keyboard navigation in forms
- State & Restoration (MEDIUM)
-
state-codable-routes
-
Make route enums Codable for navigation persistence
-
state-scene-storage
-
Use SceneStorage for per-scene navigation persistence
-
state-tab-persistence
-
Persist selected tab with SceneStorage
-
state-deep-link-urls
-
Parse deep link URLs into route enums
-
state-avoid-app-level-path
-
Avoid defining NavigationPath at App level
How to Use
Read individual reference files for detailed explanations and code examples:
-
Section definitions - Category structure and impact levels
-
Rule template - Template for adding new rules
Reference Files
File Description
references/_sections.md Category definitions and ordering
assets/templates/_template.md Template for new rules
metadata.json Version and reference information