{"id":"fd7b3d04-80df-4694-8778-506299bf7e18","shortId":"6CgpyB","kind":"skill","title":"Swiftui Navigation","tagline":"Swift Ios Skills skill by Dpearson2699","description":"# SwiftUI Navigation\n\nNavigation 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.\n\n## Contents\n\n- [NavigationStack (Push Navigation)](#navigationstack-push-navigation)\n- [NavigationSplitView (Multi-Column)](#navigationsplitview-multi-column)\n- [Sheet Presentation](#sheet-presentation)\n- [Tab-Based Navigation](#tab-based-navigation)\n- [Deep Links](#deep-links)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n## NavigationStack (Push Navigation)\n\nUse `NavigationStack` with a `NavigationPath` binding for programmatic, type-safe push navigation. Define routes as a `Hashable` enum and map them with `.navigationDestination(for:)`.\n\n```swift\nstruct ContentView: View {\n    @State private var path = NavigationPath()\n\n    var body: some View {\n        NavigationStack(path: $path) {\n            List(items) { item in\n                NavigationLink(value: item) {\n                    ItemRow(item: item)\n                }\n            }\n            .navigationDestination(for: Item.self) { item in\n                DetailView(item: item)\n            }\n            .navigationTitle(\"Items\")\n        }\n    }\n}\n```\n\n**Programmatic navigation:**\n\n```swift\npath.append(item)        // Push\npath.removeLast()        // Pop one\npath = NavigationPath()  // Pop to root\n```\n\n**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.\n\nSee [references/navigationstack.md](references/navigationstack.md) for full router examples including per-tab stacks, centralized destination mapping, and generic tab routing.\n\n## NavigationSplitView (Multi-Column)\n\nUse `NavigationSplitView` for sidebar-detail layouts on iPad and Mac. Falls back to stack navigation on iPhone.\n\n```swift\nstruct MasterDetailView: View {\n    @State private var selectedItem: Item?\n\n    var body: some View {\n        NavigationSplitView {\n            List(items, selection: $selectedItem) { item in\n                NavigationLink(value: item) { ItemRow(item: item) }\n            }\n            .navigationTitle(\"Items\")\n        } detail: {\n            if let item = selectedItem {\n                ItemDetailView(item: item)\n            } else {\n                ContentUnavailableView(\"Select an Item\", systemImage: \"sidebar.leading\")\n            }\n        }\n    }\n}\n```\n\n### Custom Split Column (Manual HStack)\n\nFor custom multi-column layouts (e.g., a dedicated notification column independent of selection), use a manual `HStack` split with `horizontalSizeClass` checks:\n\n```swift\n@MainActor\nstruct AppView: View {\n  @Environment(\\.horizontalSizeClass) private var horizontalSizeClass\n  @AppStorage(\"showSecondaryColumn\") private var showSecondaryColumn = true\n\n  var body: some View {\n    HStack(spacing: 0) {\n      primaryColumn\n      if shouldShowSecondaryColumn {\n        Divider().edgesIgnoringSafeArea(.all)\n        secondaryColumn\n      }\n    }\n  }\n\n  private var shouldShowSecondaryColumn: Bool {\n    horizontalSizeClass == .regular\n      && showSecondaryColumn\n  }\n\n  private var primaryColumn: some View {\n    TabView { /* tabs */ }\n  }\n\n  private var secondaryColumn: some View {\n    NotificationsTab()\n      .environment(\\.isSecondaryColumn, true)\n      .frame(maxWidth: .secondaryColumnWidth)\n  }\n}\n```\n\nUse 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.\n\n## Sheet Presentation\n\nPrefer `.sheet(item:)` over `.sheet(isPresented:)` when state represents a selected model. Sheets should own their actions and call `dismiss()` internally.\n\n```swift\n@State private var selectedItem: Item?\n\n.sheet(item: $selectedItem) { item in\n    EditItemSheet(item: item)\n}\n```\n\n**Presentation sizing (iOS 18+):** Control sheet dimensions with `.presentationSizing`:\n\n```swift\n.sheet(item: $selectedItem) { item in\n    EditItemSheet(item: item)\n        .presentationSizing(.form)  // .form, .page, .fitted, .automatic\n}\n```\n\n`PresentationSizing` values:\n- `.automatic` -- platform default\n- `.page` -- roughly paper size, for informational content\n- `.form` -- slightly narrower than page, for form-style UI\n- `.fitted` -- sized by the content's ideal size\n\nFine-tuning: `.fitted(horizontal:vertical:)` constrains fitting axes; `.sticky(horizontal:vertical:)` grows but does not shrink in specified dimensions.\n\n**Dismissal confirmation (macOS 15+ / iOS 26+):** Use `.dismissalConfirmationDialog(\"Discard?\", shouldPresent: hasUnsavedChanges)` to prevent accidental dismissal of sheets with unsaved changes.\n\n**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.\n\n## Tab-Based Navigation\n\nUse the `Tab` API with a selection binding for scalable tab architecture. Each tab should wrap its content in an independent `NavigationStack`.\n\n```swift\nstruct MainTabView: View {\n    @State private var selectedTab: AppTab = .home\n\n    var body: some View {\n        TabView(selection: $selectedTab) {\n            Tab(\"Home\", systemImage: \"house\", value: .home) {\n                NavigationStack { HomeView() }\n            }\n            Tab(\"Search\", systemImage: \"magnifyingglass\", value: .search) {\n                NavigationStack { SearchView() }\n            }\n            Tab(\"Profile\", systemImage: \"person\", value: .profile) {\n                NavigationStack { ProfileView() }\n            }\n        }\n    }\n}\n```\n\n**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.\n\n### iOS 26 Tab Additions\n\n- **`Tab(role: .search)`** -- replaces the tab bar with a search field when active\n- **`.tabBarMinimizeBehavior(_:)`** -- `.onScrollDown`, `.onScrollUp`, `.never` (iPhone only)\n- **`.tabViewSidebarHeader/Footer`** -- customize sidebar sections on iPadOS/macOS\n- **`.tabViewBottomAccessory { }`** -- attach content below the tab bar (e.g., Now Playing bar)\n- **`TabSection`** -- group tabs into sidebar sections with `.tabPlacement(.sidebarOnly)`\n\nSee [references/tabview.md](references/tabview.md) for full TabView patterns including custom bindings, dynamic tabs, and sidebar customization.\n\n## Deep Links\n\n### Universal Links\n\nUniversal links let iOS open your app for standard HTTPS URLs. They require:\n1. An Apple App Site Association (AASA) file at `/.well-known/apple-app-site-association`\n2. An Associated Domains entitlement (`applinks:example.com`)\n\nHandle in SwiftUI with `.onOpenURL` and `.onContinueUserActivity`:\n\n```swift\n@main\nstruct MyApp: App {\n    @State private var router = Router()\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .environment(router)\n                .onOpenURL { url in router.handle(url: url) }\n                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in\n                    guard let url = activity.webpageURL else { return }\n                    router.handle(url: url)\n                }\n        }\n    }\n}\n```\n\n### Custom URL Schemes\n\nRegister 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.\n\n### Handoff (NSUserActivity)\n\nAdvertise 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.\n\nSee [references/deeplinks.md](references/deeplinks.md) for full examples of AASA configuration, router URL handling, custom URL schemes, and NSUserActivity continuation.\n\n## Common Mistakes\n\n1. Using deprecated `NavigationView` -- use `NavigationStack` or `NavigationSplitView`\n2. Sharing one `NavigationPath` across all tabs -- each tab needs its own path\n3. Using `.sheet(isPresented:)` when state represents a model -- use `.sheet(item:)` instead\n4. Storing view instances in `NavigationPath` -- store lightweight `Hashable` route data\n5. Nesting `@Observable` router objects inside other `@Observable` objects\n6. Prefer `Tab(value:)` with `TabView(selection:)` over the older `.tabItem { }` API\n7. Assuming `tabBarMinimizeBehavior` works on iPad -- it is iPhone only\n8. Handling deep links in multiple places -- centralize URL parsing in the router\n9. Hard-coding sheet frame dimensions -- use `.presentationSizing(.form)` instead\n10. Missing `@MainActor` on router classes -- required for Swift 6 concurrency safety\n\n## Review Checklist\n\n- [ ] `NavigationStack` used (not `NavigationView`)\n- [ ] Each tab has its own `NavigationStack` with independent path\n- [ ] Route enum is `Hashable` with stable identifiers\n- [ ] `.navigationDestination(for:)` maps all route types\n- [ ] `.sheet(item:)` preferred over `.sheet(isPresented:)`\n- [ ] Sheets own their dismiss logic internally\n- [ ] Router object is `@MainActor` and `@Observable`\n- [ ] Deep link URLs parsed and validated before navigation\n- [ ] Universal links have AASA and Associated Domains configured\n- [ ] Tab selection uses `Tab(value:)` with binding\n\n## References\n\n- NavigationStack and router patterns: [references/navigationstack.md](references/navigationstack.md)\n- Sheet presentation and routing: [references/sheets.md](references/sheets.md)\n- TabView patterns and iOS 26 API: [references/tabview.md](references/tabview.md)\n- Deep links, universal links, and Handoff: [references/deeplinks.md](references/deeplinks.md)\n- Architecture and state management: see `swiftui-patterns` skill\n- Layout and components: see `swiftui-layout-components` skill","tags":["swiftui","navigation","swift","ios","skills","dpearson2699"],"capabilities":["skill","source-dpearson2699","category-swift-ios-skills"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-navigation","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"install_from":"skills.sh"}},"qualityScore":"0.300","qualityRationale":"deterministic score 0.30 from registry signals: · indexed on skills.sh · published under dpearson2699/swift-ios-skills","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T05:40:38.382Z","embedding":null,"createdAt":"2026-04-18T20:33:46.794Z","updatedAt":"2026-04-22T05:40:38.382Z","lastSeenAt":"2026-04-22T05:40:38.382Z","tsv":"'/.well-known/apple-app-site-association':768 '0':345 '1':759,896 '10':996 '15':522 '17':43 '18':448 '2':769,904 '26':18,524,679,1094 '3':917 '4':930 '5':941 '6':950,1005 '6.3':21 '7':962 '8':972 '9':985 'aasa':765,883,1065 'accident':532 'across':908 'action':426,673 'activ':694,809,852,861 'activity.webpageurl':814 'addit':681 'advertis':851 'api':592,961,1095 'app':15,173,752,762,787 'appl':761 'applink':774 'appstorag':333 'apptab':619 'appview':326 'architectur':32,600,1106 'associ':764,771,1067 'assum':963 'attach':708 'automat':468,471 'axe':507 'back':247 'backward':39 'backward-compat':38 'bar':688,713,717 'base':69,73,587 'bind':100,596,653,736,1076 'block':206 'bodi':130,263,340,622,794 'bool':356 'call':428 'category-swift-ios-skills' 'central':198,224,581,979 'cfbundleurltyp':828 'chang':538,659,676 'check':322 'checklist':86,89,1009 'child':567 'class':1001 'code':988 'column':27,57,61,234,298,305,311,395 'common':80,83,894 'common-mistak':82 'compat':40 'complex':175 'compon':1117,1122 'compos':668 'concurr':1006 'configur':884,1069 'confirm':520 'constrain':505 'content':46,480,495,606,709 'contentunavailableview':290 'contentview':122,798 'continu':893 'control':388,449 'cover':22 'custom':296,302,407,652,702,735,741,820,836,888 'data':940 'declar':860 'dedic':309 'deep':34,75,78,742,974,1054,1098 'deep-link':77 'default':473 'defin':108,544 'deprec':898 'destin':199,225 'detail':240,281 'detailview':151 'dimens':451,518,991 'discard':527 'dismiss':429,519,533,1045 'dismissalconfirmationdialog':526 'divid':349 'domain':772,847,1068 'dpearson2699':8 'drill':574 'driven':541 'dynam':737 'e.g':307,667,714 'edgesignoringsafearea':350 'edititemsheet':442,460 'effect':656 'els':289,815 'entitl':773 'enum':113,540,547,1024 'enum-driven':539 'environ':197,328,373,799 'exampl':218,881 'example.com':775 'fall':246 'fallback':845,875 'field':692 'file':766 'fine':500 'fine-tun':499 'fit':467,491,502,506 'form':464,465,481,488,994 'form-styl':487 'frame':376,990 'full':216,387,580,731,880 'function':662 'generic':228 'get':190 'group':719 'grow':511 'guard':811 'handl':776,829,887,973 'handoff':849,1103 'hard':987 'hard-cod':986 'hashabl':112,938,1026 'hasunsavedchang':529 'home':620,629,633 'homeview':635 'horizont':503,509 'horizontalsizeclass':321,329,332,357 'hous':631 'hstack':300,318,343,382 'https':755 'ideal':497 'identifi':550,1029 'includ':219,734 'independ':312,609,1021 'info.plist':826,864 'inform':479 'inject':195 'insid':946 'instanc':194,933 'instead':674,929,995 'intercept':664 'intern':430,1047 'io':4,17,42,447,523,678,749,1093 'ipad':243,967 'ipados/macos':706 'iphon':252,699,970 'iseligibleforhandoff':868 'ispres':415,920,1041 'issecondarycolumn':374 'item':137,138,142,144,145,149,152,153,155,160,261,268,271,275,277,278,280,284,287,288,293,412,436,438,440,443,444,456,458,461,462,928,1037 'item.self':148 'itemdetailview':286 'itemrow':143,276 'layout':28,241,306,404,1115,1121 'let':283,565,748,812 'lightweight':937 'link':35,76,79,743,745,747,834,841,975,1055,1063,1099,1101 'list':136,267 'logic':1046 'mac':245 'maco':521 'magnifyingglass':639 'main':784 'mainactor':324,998,1051 'maintabview':613 'manag':1109 'manual':299,317,381 'map':115,200,226,557,1032 'masterdetailview':255 'maxwidth':377 'minim':406 'miss':997 'mistak':81,84,895 'model':421,925 'modifi':211,563 'multi':26,56,60,233,304 'multi-column':25,55,232,303 'multipl':977 'myapp':786 'narrow':483 'navig':2,10,11,24,49,53,70,74,94,107,157,176,250,588,1061 'navigationdestin':118,146,204,1030 'navigationlink':140,273 'navigationpath':99,128,166,907,935 'navigationsplitview':54,59,231,236,266,397,903 'navigationsplitview-multi-column':58 'navigationstack':47,51,92,96,133,610,634,642,650,901,1010,1019,1078 'navigationstack-push-navig':50 'navigationtitl':154,279 'navigationview':899,1013 'need':386,913 'nest':942 'never':698 'non':392 'non-standard':391 'note':45 'notif':310 'notificationstab':372 'nsuseract':850,892 'nsuseractivitytyp':866 'nsuseractivitytypebrowsingweb':808 'object':180,945,949,1049 'observ':943,948,1053 'older':959 'oncontinueuseract':782,807,859 'one':164,906 'onopenurl':780,801,831 'onscrolldown':696 'onscrollup':697 'open':750 'own':182 'page':466,474,485 'paper':476 'pars':981,1057 'path':127,134,135,165,184,916,1022 'path.append':159 'path.removelast':162 'pattern':12,36,171,584,733,1081,1091,1113 'per':221 'per-tab':220 'person':647 'place':978 'platform':472 'play':716 'pop':163,167 'prefer':410,832,951,1038 'present':30,63,66,409,445,569,1085 'presentations':453,463,469,993 'prevent':531 'primarycolumn':346,362 'privat':125,258,330,335,353,360,367,433,616,789 'profil':645,649 'profileview':651 'programmat':102,156 'prop':573 'prop-dril':572 'provid':843,871 'public':839 'push':23,48,52,93,106,161 'receiv':856 'refer':90,91,1077 'references/deeplinks.md':877,878,1104,1105 'references/navigationstack.md':213,214,1082,1083 'references/sheets.md':576,577,1088,1089 'references/tabview.md':728,729,1096,1097 'regist':823 'regular':358 'replac':685 'repres':418,923 'requir':758,1002 'return':816 'review':85,88,1008 'review-checklist':87 'role':683 'root':169 'rough':475 'rout':109,230,543,583,657,939,1023,1034,1087 'router':170,179,193,217,555,791,792,800,885,944,984,1000,1048,1080 'router.handle':804,817 'safe':105 'safeti':1007 'scalabl':598 'scene':796 'scheme':822,824,837,890 'search':637,641,684,691 'searchview':643 'secondari':394 'secondarycolumn':352,369 'secondarycolumnwidth':378 'section':704,723 'see':212,575,727,876,1110,1118 'select':269,291,314,420,595,626,658,677,956,1071 'selecteditem':260,270,285,435,439,457 'selectedtab':618,627 'set':867 'share':209,561,840,905 'sheet':29,62,65,186,408,411,414,422,437,450,455,535,542,570,582,919,927,989,1036,1040,1042,1084 'sheet-present':64 'sheetdestin':546 'shouldpres':528 'shouldshowsecondarycolumn':348,355 'showsecondarycolumn':334,337,359 'shrink':515 'side':655 'sidebar':239,703,722,740 'sidebar-detail':238 'sidebar.leading':295 'sidebaron':726 'singl':203 'site':763 'size':446,477,492,498 'skill':5,6,1114,1123 'slight':482 'source-dpearson2699' 'space':344 'special':665 'specifi':517 'split':297,319,383 'stabl':1028 'stack':223,249 'standard':393,402,754 'state':124,187,257,417,432,615,788,922,1108 'sticki':508 'store':551,931,936 'struct':121,254,325,612,785 'style':489 'swift':3,20,120,158,253,323,431,454,611,783,1004 'swiftui':1,9,14,778,1112,1120 'swiftui-layout-compon':1119 'swiftui-pattern':1111 'system':403 'systemimag':294,630,638,646 'tab':31,68,72,189,222,229,366,586,591,599,602,628,636,644,666,680,682,687,712,720,738,910,912,952,1015,1070,1073 'tab-bas':67,585 'tab-based-navig':71 'tabbarminimizebehavior':695,964 'tabitem':960 'tabplac':725 'tabsect':718 'tabview':365,625,732,955,1090 'tabviewbottomaccessori':707 'tabviewsidebarheader/footer':701 'target':16 'trigger':671 'true':338,375,869 'tune':501 'type':104,862,1035 'type-saf':103 'ui':490 'univers':744,746,833,1062,1100 'unless':44 'unsav':537 'url':756,802,805,806,813,818,819,821,886,889,980,1056 'use':95,177,235,315,379,396,525,589,897,900,918,926,992,1011,1072 'useract':854 'valid':1059 'valu':141,274,470,632,640,648,953,1074 'var':126,129,259,262,331,336,339,354,361,368,434,617,621,790,793 'verif':848 'vertic':504,510 'via':196 'view':123,132,256,265,327,342,364,371,562,568,614,624,932 'want':400 'web':844 'webpageurl':873 'windowgroup':797 'withapprout':210 'without':571 'work':965 'wrap':604","prices":[{"id":"56c51513-6f2e-4481-9681-f69268c2eb3a","listingId":"fd7b3d04-80df-4694-8778-506299bf7e18","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:33:46.794Z"}],"sources":[{"listingId":"fd7b3d04-80df-4694-8778-506299bf7e18","source":"github","sourceId":"dpearson2699/swift-ios-skills/swiftui-navigation","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swiftui-navigation","isPrimary":false,"firstSeenAt":"2026-04-18T22:01:26.061Z","lastSeenAt":"2026-04-22T00:53:45.484Z"},{"listingId":"fd7b3d04-80df-4694-8778-506299bf7e18","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/swiftui-navigation","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-navigation","isPrimary":true,"firstSeenAt":"2026-04-18T20:33:46.794Z","lastSeenAt":"2026-04-22T05:40:38.382Z"}],"details":{"listingId":"fd7b3d04-80df-4694-8778-506299bf7e18","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"swiftui-navigation","source":"skills_sh","category":"swift-ios-skills","skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-navigation"},"updatedAt":"2026-04-22T05:40:38.382Z"}}