實作 SwiftUI 導航模式,包括 NavigationStack、NavigationSplitView、sheet 呈現、分頁導航與深層連結。適用於建構推播導航、程式化路由、多欄佈局、模態 sheet、分頁列、通用連結或自訂 URL scheme 處理。
SwiftUI 導航
適用於 iOS 26+ 與 Swift 6.3 的 SwiftUI 應用程式導航模式。涵蓋推播導航、多欄佈局、sheet 呈現、分頁架構與深層連結。除非特別標註,否則這些模式可向下相容至 iOS 17。
目錄
NavigationStack(推播導航)
使用帶有型別 [Route] 綁定的 NavigationStack 來進行程式化推播導航。將路由定義為 Hashable 列舉,並透過 .navigationDestination(for:) 進行對應;這樣可以讓路徑在編譯時就受到檢查。僅在單一堆疊需要容納異質路由值型別時,才使用 NavigationPath。
enum Route: Hashable {
case item(id: Item.ID)
}
struct ContentView: View {
@State private var path: [Route] = []
let items: [Item]
var body: some View {
NavigationStack(path: $path) {
List(items) { item in
NavigationLink(value: Route.item(id: item.id)) {
ItemRow(item: item)
}
}
.navigationDestination(for: Route.self) { route in
switch route {
case .item(let id):
DetailView(itemID: id)
}
}
.navigationTitle("項目")
}
}
}
程式化導航:
path.append(.item(id: item.id)) // 推入
path.removeLast() // 彈出一個
path = [] // 回到根
路由器模式: 對於具有複雜導航的應用程式,使用一個擁有路徑與 sheet 狀態的路由器物件。每個分頁透過 .environment() 注入自己的路由器實例。使用單一的 .navigationDestination(for:) 區塊或共用的 withAppRouter() 修飾器來集中目的地對應。
完整的路由器範例(包括每個分頁的堆疊、集中目的地對應與通用分頁路由)請參閱 references/navigationstack.md。
NavigationSplitView(多欄)
在 iPad 與 Mac 上使用 NavigationSplitView 來實現側邊欄-詳細內容的佈局。在 iPhone 上則會自動退回堆疊導航。
struct 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("項目")
} detail: {
if let item = selectedItem {
ItemDetailView(item: item)
} else {
ContentUnavailableView("選取一個項目", systemImage: "sidebar.leading")
}
}
}
}
自訂分割欄(手動 HStack)
對於自訂的多欄佈局(例如,獨立於選取狀態的通知欄),使用帶有 horizontalSizeClass 檢查的手動 HStack 分割:
@MainActor
struct 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 { /* 分頁 */ }
}
private var secondaryColumn: some View {
NotificationsTab()
.environment(\.isSecondaryColumn, true)
.frame(maxWidth: .secondaryColumnWidth)
}
}
當你需要完全控制或非標準的次要欄時,使用手動 HStack 分割。當你想要標準系統佈局且只需最小自訂時,使用 NavigationSplitView。
Sheet 呈現
當狀態代表一個選取的模型時,優先使用 .sheet(item:) 而非 .sheet(isPresented:)。Sheet 應擁有自己的動作並在內部呼叫 dismiss()。
@State private var selectedItem: Item?
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
}
呈現尺寸(iOS 18+): 使用 .presentationSizing 控制 sheet 尺寸:
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
.presentationSizing(.form) // .form、.page、.fitted、.automatic
}
PresentationSizing 值:
.automatic-- 平台預設.page-- 約紙張大小,用於資訊內容.form-- 比 page 略窄,用於表單風格 UI.fitted-- 根據內容的理想大小調整
微調:.fitted(horizontal:vertical:) 限制適配軸;.sticky(horizontal:vertical:) 在指定維度上只增長不縮小。
關閉保護: 在 iOS/iPadOS 上,使用 .interactiveDismissDisabled(hasUnsavedChanges) 並在 sheet 內部提供明確的儲存/捨棄動作。在 macOS 15+ 上,使用 .dismissalConfirmationDialog("Discard?", shouldPresent: hasUnsavedChanges) 來進行視窗關閉確認。所有程式化關閉都應通過相同的儲存/驗證/捨棄閘道;interactiveDismissDisabled 僅保護互動式關閉。
列舉驅動的 sheet 路由: 定義一個 Identifiable 的 SheetDestination 列舉,將其儲存在路由器上,並透過共用的視圖修飾器進行對應。這樣任何子視圖都可以呈現 sheet,而無需屬性傳遞。完整的集中式 sheet 路由模式請參閱 references/sheets.md。
分頁導航
使用帶有選取綁定的 Tab API 來實現可擴展的分頁架構。每個分頁應將其內容包裝在獨立的 NavigationStack 中。
struct MainTabView: View {
@State private var selectedTab: AppTab = .home
var body: some View {
TabView(selection: $selectedTab) {
Tab("首頁", systemImage: "house", value: .home) {
NavigationStack { HomeView() }
}
Tab("搜尋", systemImage: "magnifyingglass", value: .search) {
NavigationStack { SearchView() }
}
Tab("個人檔案", systemImage: "person", value: .profile) {
NavigationStack { ProfileView() }
}
}
}
}
帶有副作用的自訂綁定: 透過函數路由選取變更,以攔截特殊分頁(例如,撰寫),這些分頁應觸發動作而非變更選取。
iOS 26 分頁新增功能
Tab(value:role:)搭配.search-- 標記專用搜尋分頁,具有系統預設搜尋標題、圖示與固定行為.tabViewSearchActivation(_:)-- 控制搜尋分頁的啟用與停用行為.tabBarMinimizeBehavior(_:)--.onScrollDown、.onScrollUp、.never(僅限 iPhone).tabViewSidebarHeader/Footer-- 在 iPadOS/macOS 上自訂側邊欄區段.tabViewBottomAccessory { }-- 在分頁列下方附加內容(例如,正在播放列)TabSection-- 將分頁分組為側邊欄區段,搭配.tabPlacement(.sidebarOnly)
完整的 TabView 模式(包括自訂綁定、動態分頁與側邊欄自訂)請參閱 references/tabview.md。
深層連結
使用解析 → 驗證 → 提交的流程。先解析為型別路由而不改變導航;驗證 scheme/host/path、識別碼格式、授權與目的地是否存在;然後原子地更新分頁/路徑。無效連結必須保持當前導航不變。
通用連結
通用連結讓 iOS 可以為標準 HTTPS URL 開啟你的應用程式。它們需要:
- 在
/.well-known/apple-app-site-association放置 Apple App Site Association (AASA) 檔案 - 一個關聯網域授權(
applinks:example.com)
在 SwiftUI 中使用 .onOpenURL 處理通用連結與自訂 URL scheme:
@main
struct MyApp: App {
@State private var router = Router()
var body: some Scene {
WindowGroup {
ContentView()
.environment(router)
.onOpenURL { url in router.handle(url: url) }
}
}
}
自訂 URL Scheme
在 Info.plist 的 CFBundleURLTypes 下註冊 scheme。使用 .onOpenURL 處理。對於公開分享的連結,優先使用通用連結而非自訂 scheme——它們提供網頁備援與網域驗證。
Handoff(NSUserActivity)
使用 .userActivity() 宣傳活動,並使用 .onContinueUserActivity() 接收 Handoff 或其他使用者活動。在 Info.plist 的 NSUserActivityTypes 下宣告活動類型。設定 isEligibleForHandoff = true 並提供 webpageURL 作為備援。
完整的 AASA 配置、路由器 URL 處理、自訂 URL scheme 與 NSUserActivity 接續範例請參閱 references/deeplinks.md。
常見錯誤
- 使用已棄用的
NavigationView-- 應使用NavigationStack或NavigationSplitView - 在所有分頁間共用一個導航路徑或路由器 -- 每個分頁需要自己的路徑
- 當狀態代表模型時使用
.sheet(isPresented:)-- 應改用.sheet(item:) - 在導航路徑中儲存視圖實例 -- 應儲存輕量的
Hashable路由資料 - 將
@Observable路由器物件巢狀在其他@Observable物件內 - 偏好使用
Tab(value:)搭配TabView(selection:)而非舊的.tabItem { }API - 假設
tabBarMinimizeBehavior在 iPad 上有效 -- 它僅限 iPhone - 在多處處理深層連結 -- 應在路由器中集中處理 URL 解析
- 硬編碼 sheet 框架尺寸 -- 應使用
.presentationSizing(.form)替代 - 路由器類別缺少
@MainActor-- Swift 6 並發安全需要此標記
審查清單
- [ ] 使用
NavigationStack(而非NavigationView) - [ ] 每個分頁擁有自己的
NavigationStack與獨立路徑 - [ ] 路由列舉為
Hashable且具有穩定識別碼 - [ ]
.navigationDestination(for:)對應所有路由型別 - [ ] 優先使用
.sheet(item:)而非.sheet(isPresented:) - [ ] Sheet 在內部擁有自己的關閉邏輯
- [ ] 路由器物件為
@MainActor與@Observable - [ ] 深層連結 URL 在導航前經過解析與驗證
- [ ] 通用連結已配置 AASA 與關聯網域
- [ ] 分頁選取使用帶有綁定的
Tab(value:)
參考資料
- NavigationStack 與路由器模式:references/navigationstack.md
- Sheet 呈現與路由:references/sheets.md
- TabView 模式與 iOS 26 API:references/tabview.md
- 深層連結、通用連結與 Handoff:references/deeplinks.md
- 架構與狀態管理:請參閱
swiftui-patterns技能 - 佈局與元件:請參閱
swiftui-layout-components技能






