All skills
dpearson2699 avatar

/swiftui-navigation

@cf3fe87

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.

Use this Skill: https://skilld.dev/gh/dpearson2699/swift-ios-skills/swiftui-navigation

This session only. Nothing lands on disk.

referencestabview.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

TabView

Contents

Intent

Use this pattern for a scalable, multi-platform tab architecture with:

  • a single source of truth for tab identity and content,
  • platform-specific tab sets and sidebar sections,
  • dynamic tabs sourced from data,
  • an interception hook for special tabs (e.g., compose).

Core architecture

  • AppTab enum defines identity, labels, icons, and content builder.
  • SidebarSections enum groups tabs for sidebar sections.
  • AppView owns the TabView and selection binding, and routes tab changes through updateTab.

Example: custom binding with side effects

Use this when tab selection needs side effects, like intercepting a special tab to perform an action instead of changing selection.

@MainActor
struct AppView: View {
  @Binding var selectedTab: AppTab

  var body: some View {
    TabView(selection: .init(
      get: { selectedTab },
      set: { updateTab(with: $0) }
    )) {
      ForEach(availableSections) { section in
        TabSection(section.title) {
          ForEach(section.tabs) { tab in
            Tab(value: tab) {
              tab.makeContentView(
                homeTimeline: $timeline,
                selectedTab: $selectedTab,
                pinnedFilters: $pinnedFilters
              )
            } label: {
              tab.label
            }
            .tabPlacement(tab.tabPlacement)
          }
        }
        .tabPlacement(.sidebarOnly)
      }
    }
  }

  private func updateTab(with newTab: AppTab) {
    if newTab == .post {
      // Intercept special tabs (compose) instead of changing selection.
      presentComposer()
      return
    }
    selectedTab = newTab
  }
}

Example: direct binding without side effects

Use this when selection is purely state-driven.

@MainActor
struct AppView: View {
  @Binding var selectedTab: AppTab

  var body: some View {
    TabView(selection: $selectedTab) {
      ForEach(availableSections) { section in
        TabSection(section.title) {
          ForEach(section.tabs) { tab in
            Tab(value: tab) {
              tab.makeContentView(
                homeTimeline: $timeline,
                selectedTab: $selectedTab,
                pinnedFilters: $pinnedFilters
              )
            } label: {
              tab.label
            }
            .tabPlacement(tab.tabPlacement)
          }
        }
        .tabPlacement(.sidebarOnly)
      }
    }
  }
}

Design choices to keep

  • Centralize tab identity and content in AppTab with makeContentView(...).
  • Use Tab(value:) with selection binding for state-driven tab selection.
  • Route selection changes through updateTab to handle special tabs and scroll-to-top behavior.
  • Use TabSection + .tabPlacement(.sidebarOnly) for sidebar structure.
  • Use .tabPlacement(.pinned) in AppTab.tabPlacement for a single pinned tab; this is commonly used for iOS 26 .searchable tab content, but can be used for any tab.

Dynamic tabs pattern

  • SidebarSections handles dynamic data tabs.
  • AppTab.anyTimelineFilter(filter:) wraps dynamic tabs in a single enum case.
  • The enum provides label/icon/title for dynamic tabs via the filter type.

iOS 26 Tab API

iOS 26 expands the Tab API with minimize behavior, roles, and accessory placements.

Tab Bar Minimization

TabView(selection: $selectedTab) {
    // tabs
}
.tabBarMinimizeBehavior(.onScrollDown) // iPhone only

TabBarMinimizeBehavior values:

  • .automatic -- determine behavior from context
  • .onScrollDown -- minimize when user scrolls down (iPhone only)
  • .onScrollUp -- minimize when user scrolls up (iPhone only)
  • .never -- never minimize the tab bar

Tab Search Role

Mark a dedicated search tab so the system can apply default search title, icon, and pinning behavior. Use tabViewSearchActivation(_:) when selecting the search tab should also activate search:

TabView(selection: $selectedTab) {
    Tab(value: AppTab.search, role: .search) {
        NavigationStack {
            SearchView()
                .searchable(text: $query)
        }
    }
}
.tabViewSearchActivation(.searchTabSelection)

Sidebar Customization

TabView {
    // tabs
}
.tabViewSidebarHeader { SidebarHeaderView() }
.tabViewSidebarFooter { SidebarFooterView() }
.tabViewSidebarBottomBar { BottomBarView() }

Bottom Accessory

Use TabViewBottomAccessoryPlacement for content below the tab bar:

TabView {
    // tabs
}
.tabViewBottomAccessory { NowPlayingBar() }

Pitfalls

  • Avoid adding ViewModels for tabs; keep state local or in @Observable services.
  • Do not nest @Observable objects inside other @Observable objects.
  • Ensure AppTab.id values are stable; dynamic cases should hash on stable IDs.
  • Special tabs (compose) should not change selection.
  • Prefer Tab(value:) with TabView(selection:) over the older .tabItem { } API for typed tab selection.
  • tabBarMinimizeBehavior only works on iPhone; it has no effect on iPad or Mac.

Source: SKILL.md on GitHub

No alerts17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill provides comprehensive documentation and development patterns for SwiftUI navigation targeting iOS 26 and Swift 6.3. It includes best practices for NavigationStack, sheets, and deep links without any security violations.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at cf3fe87. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Steadyupdated 3 months ago
  • swiftui
  • ios
  • navigation
  • routing
  • deep-linking
  • tab-navigation
  • swift
  • sheet-presentation

README badge

README badge for dpearson2699/swift-ios-skills/swiftui-navigation

Implement SwiftUI navigation patterns for iOS 17+ including NavigationStack for push navigation, NavigationSplitView for multi-column layouts, sheet presentation, tab-based navigation with the Tab API, and deep linking via universal links and custom URL schemes. Includes router patterns for programmatic routing and centralized sheet/destination mapping.

Generated from the current SKILL.md.

Does this skill work with iOS versions before iOS 26?
Most patterns are backward-compatible to iOS 17. NavigationStack, NavigationSplitView, sheet presentation, and tab-based navigation work on iOS 17+. iOS 26-specific features like Tab(role: .search), .presentationSizing, and .dismissalConfirmationDialog are noted separately in the skill.
How do I handle deep linking with custom URL schemes?
Register schemes in Info.plist under CFBundleURLTypes, then parse and route URLs in a centralized router object using .onOpenURL. The skill recommends universal links over custom schemes for public links because they provide web fallback and domain verification.
Should each tab have its own NavigationStack?
Yes. Each tab must have an independent NavigationStack with its own NavigationPath to avoid sharing navigation state across tabs, which is a common mistake noted in the skill.
What's the difference between .sheet(item:) and .sheet(isPresented:)?
Use .sheet(item:) when state represents a selected model, and .sheet(isPresented:) for simple boolean flags. The skill prefers .sheet(item:) because it binds to actual data rather than a presentation flag.

Generated from the current SKILL.md. These answers refresh after source changes.