Install
Terminal · npx$
npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-best-practicesWorks with Paperclip
How Swiftui Navigation fits into a Paperclip company.
Swiftui Navigation drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
S
SaaS FactoryPaired
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
$27$59
Explore packSource file
SKILL.md262 linesExpandCollapse
---name: swiftui-navigationdescription: "Implement SwiftUI navigation patterns including NavigationStack, NavigationSplitView, sheet presentation, tab-based navigation, and deep linking. Use when building push navigation, programmatic routing, multi-column layouts, modal sheets, tab bars, universal links, or custom URL scheme handling."--- # SwiftUI Navigation Navigation patterns for SwiftUI apps targeting iOS 26+ with Swift 6.3. Covers push navigation, multi-column layouts, sheet presentation, tab architecture, and deep linking. Patterns are backward-compatible to iOS 17 unless noted. ## Contents - [NavigationStack (Push Navigation)](#navigationstack-push-navigation)- [NavigationSplitView (Multi-Column)](#navigationsplitview-multi-column)- [Sheet Presentation](#sheet-presentation)- [Tab-Based Navigation](#tab-based-navigation)- [Deep Links](#deep-links)- [Common Mistakes](#common-mistakes)- [Review Checklist](#review-checklist)- [References](#references) ## NavigationStack (Push Navigation) Use `NavigationStack` with a `NavigationPath` binding for programmatic, type-safe push navigation. Define routes as a `Hashable` enum and map them with `.navigationDestination(for:)`. ```swiftstruct ContentView: View { @State private var path = NavigationPath() var body: some View { NavigationStack(path: $path) { List(items) { item in NavigationLink(value: item) { ItemRow(item: item) } } .navigationDestination(for: Item.self) { item in DetailView(item: item) } .navigationTitle("Items") } }}``` **Programmatic navigation:** ```swiftpath.append(item) // Pushpath.removeLast() // Pop onepath = NavigationPath() // Pop to root``` **Router pattern:** For apps with complex navigation, use a router object that owns the path and sheet state. Each tab gets its own router instance injected via `.environment()`. Centralize destination mapping with a single `.navigationDestination(for:)` block or a shared `withAppRouter()` modifier. See [references/navigationstack.md](references/navigationstack.md) for full router examples including per-tab stacks, centralized destination mapping, and generic tab routing. ## NavigationSplitView (Multi-Column) Use `NavigationSplitView` for sidebar-detail layouts on iPad and Mac. Falls back to stack navigation on iPhone. ```swiftstruct MasterDetailView: View { @State private var selectedItem: Item? var body: some View { NavigationSplitView { List(items, selection: $selectedItem) { item in NavigationLink(value: item) { ItemRow(item: item) } } .navigationTitle("Items") } detail: { if let item = selectedItem { ItemDetailView(item: item) } else { ContentUnavailableView("Select an Item", systemImage: "sidebar.leading") } } }}``` ### Custom Split Column (Manual HStack) For custom multi-column layouts (e.g., a dedicated notification column independent of selection), use a manual `HStack` split with `horizontalSizeClass` checks: ```swift@MainActorstruct AppView: View { @Environment(\.horizontalSizeClass) private var horizontalSizeClass @AppStorage("showSecondaryColumn") private var showSecondaryColumn = true var body: some View { HStack(spacing: 0) { primaryColumn if shouldShowSecondaryColumn { Divider().edgesIgnoringSafeArea(.all) secondaryColumn } } } private var shouldShowSecondaryColumn: Bool { horizontalSizeClass == .regular && showSecondaryColumn } private var primaryColumn: some View { TabView { /* tabs */ } } private var secondaryColumn: some View { NotificationsTab() .environment(\.isSecondaryColumn, true) .frame(maxWidth: .secondaryColumnWidth) }}``` Use the manual HStack split when you need full control or a non-standard secondary column. Use `NavigationSplitView` when you want a standard system layout with minimal customization. ## Sheet Presentation Prefer `.sheet(item:)` over `.sheet(isPresented:)` when state represents a selected model. Sheets should own their actions and call `dismiss()` internally. ```swift@State private var selectedItem: Item? .sheet(item: $selectedItem) { item in EditItemSheet(item: item)}``` **Presentation sizing (iOS 18+):** Control sheet dimensions with `.presentationSizing`: ```swift.sheet(item: $selectedItem) { item in EditItemSheet(item: item) .presentationSizing(.form) // .form, .page, .fitted, .automatic}``` `PresentationSizing` values:- `.automatic` -- platform default- `.page` -- roughly paper size, for informational content- `.form` -- slightly narrower than page, for form-style UI- `.fitted` -- sized by the content's ideal size Fine-tuning: `.fitted(horizontal:vertical:)` constrains fitting axes; `.sticky(horizontal:vertical:)` grows but does not shrink in specified dimensions. **Dismissal confirmation (macOS 15+ / iOS 26+):** Use `.dismissalConfirmationDialog("Discard?", shouldPresent: hasUnsavedChanges)` to prevent accidental dismissal of sheets with unsaved changes. **Enum-driven sheet routing:** Define a `SheetDestination` enum that is `Identifiable`, store it on the router, and map it with a shared view modifier. This lets any child view present sheets without prop-drilling. See [references/sheets.md](references/sheets.md) for the full centralized sheet routing pattern. ## Tab-Based Navigation Use the `Tab` API with a selection binding for scalable tab architecture. Each tab should wrap its content in an independent `NavigationStack`. ```swiftstruct MainTabView: View { @State private var selectedTab: AppTab = .home var body: some View { TabView(selection: $selectedTab) { Tab("Home", systemImage: "house", value: .home) { NavigationStack { HomeView() } } Tab("Search", systemImage: "magnifyingglass", value: .search) { NavigationStack { SearchView() } } Tab("Profile", systemImage: "person", value: .profile) { NavigationStack { ProfileView() } } } }}``` **Custom binding with side effects:** Route selection changes through a function to intercept special tabs (e.g., compose) that should trigger an action instead of changing selection. ### iOS 26 Tab Additions - **`Tab(role: .search)`** -- replaces the tab bar with a search field when active- **`.tabBarMinimizeBehavior(_:)`** -- `.onScrollDown`, `.onScrollUp`, `.never` (iPhone only)- **`.tabViewSidebarHeader/Footer`** -- customize sidebar sections on iPadOS/macOS- **`.tabViewBottomAccessory { }`** -- attach content below the tab bar (e.g., Now Playing bar)- **`TabSection`** -- group tabs into sidebar sections with `.tabPlacement(.sidebarOnly)` See [references/tabview.md](references/tabview.md) for full TabView patterns including custom bindings, dynamic tabs, and sidebar customization. ## Deep Links ### Universal Links Universal links let iOS open your app for standard HTTPS URLs. They require:1. An Apple App Site Association (AASA) file at `/.well-known/apple-app-site-association`2. An Associated Domains entitlement (`applinks:example.com`) Handle in SwiftUI with `.onOpenURL` and `.onContinueUserActivity`: ```swift@mainstruct MyApp: App { @State private var router = Router() var body: some Scene { WindowGroup { ContentView() .environment(router) .onOpenURL { url in router.handle(url: url) } .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in guard let url = activity.webpageURL else { return } router.handle(url: url) } } }}``` ### Custom URL Schemes Register schemes in `Info.plist` under `CFBundleURLTypes`. Handle with `.onOpenURL`. Prefer universal links over custom schemes for publicly shared links -- they provide web fallback and domain verification. ### Handoff (NSUserActivity) Advertise activities with `.userActivity()` and receive them with `.onContinueUserActivity()`. Declare activity types in `Info.plist` under `NSUserActivityTypes`. Set `isEligibleForHandoff = true` and provide a `webpageURL` as fallback. See [references/deeplinks.md](references/deeplinks.md) for full examples of AASA configuration, router URL handling, custom URL schemes, and NSUserActivity continuation. ## Common Mistakes 1. Using deprecated `NavigationView` -- use `NavigationStack` or `NavigationSplitView`2. Sharing one `NavigationPath` across all tabs -- each tab needs its own path3. Using `.sheet(isPresented:)` when state represents a model -- use `.sheet(item:)` instead4. Storing view instances in `NavigationPath` -- store lightweight `Hashable` route data5. Nesting `@Observable` router objects inside other `@Observable` objects6. Prefer `Tab(value:)` with `TabView(selection:)` over the older `.tabItem { }` API7. Assuming `tabBarMinimizeBehavior` works on iPad -- it is iPhone only8. Handling deep links in multiple places -- centralize URL parsing in the router9. Hard-coding sheet frame dimensions -- use `.presentationSizing(.form)` instead10. Missing `@MainActor` on router classes -- required for Swift 6 concurrency safety ## Review Checklist - [ ] `NavigationStack` used (not `NavigationView`)- [ ] Each tab has its own `NavigationStack` with independent path- [ ] Route enum is `Hashable` with stable identifiers- [ ] `.navigationDestination(for:)` maps all route types- [ ] `.sheet(item:)` preferred over `.sheet(isPresented:)`- [ ] Sheets own their dismiss logic internally- [ ] Router object is `@MainActor` and `@Observable`- [ ] Deep link URLs parsed and validated before navigation- [ ] Universal links have AASA and Associated Domains configured- [ ] Tab selection uses `Tab(value:)` with binding ## References - NavigationStack and router patterns: [references/navigationstack.md](references/navigationstack.md)- Sheet presentation and routing: [references/sheets.md](references/sheets.md)- TabView patterns and iOS 26 API: [references/tabview.md](references/tabview.md)- Deep links, universal links, and Handoff: [references/deeplinks.md](references/deeplinks.md)- Architecture and state management: see `swiftui-patterns` skill- Layout and components: see `swiftui-layout-components` skill