swiftui-navigation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Navigation
SwiftUI 导航
Navigation patterns for SwiftUI apps targeting iOS 26+ with Swift 6.2. Covers push navigation, multi-column layouts, sheet presentation, tab architecture, and deep linking. Patterns are backward-compatible to iOS 17 unless noted.
本文针对适配iOS 26+、使用Swift 6.2的SwiftUI应用,介绍各类导航模式,包括推送导航、多列布局、弹窗展示、标签页架构以及深度链接。除非另有说明,这些模式向后兼容至iOS 17。
Contents
目录
NavigationStack (Push Navigation)
NavigationStack(推送导航)
Use with a binding for programmatic, type-safe push navigation. Define routes as a enum and map them with .
NavigationStackNavigationPathHashable.navigationDestination(for:)swift
struct 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:
swift
path.append(item) // Push
path.removeLast() // Pop one
path = NavigationPath() // Pop to rootRouter 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 . Centralize destination mapping with a single block or a shared modifier.
.environment().navigationDestination(for:)withAppRouter()See for full router examples including per-tab stacks, centralized destination mapping, and generic tab routing.
references/navigationstack.md结合与绑定,实现程序化、类型安全的推送导航。将路由定义为枚举,通过进行映射。
NavigationStackNavigationPathHashable.navigationDestination(for:)swift
struct 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")
}
}
}程序化导航:
swift
path.append(item) // 推送页面
path.removeLast() // 返回上一页
path = NavigationPath() // 返回根页面路由模式: 对于导航逻辑复杂的应用,可使用路由对象管理路径和弹窗状态。通过为每个标签页注入独立的路由实例。通过单个代码块或共享的修饰符集中管理目标页面映射。
.environment().navigationDestination(for:)withAppRouter()完整的路由示例(包括多标签页独立栈、集中式目标映射、通用标签页路由)可参考。
references/navigationstack.mdNavigationSplitView (Multi-Column)
NavigationSplitView(多列布局)
Use for sidebar-detail layouts on iPad and Mac. Falls back to stack navigation on iPhone.
NavigationSplitViewswift
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("Items")
} detail: {
if let item = selectedItem {
ItemDetailView(item: item)
} else {
ContentUnavailableView("Select an Item", systemImage: "sidebar.leading")
}
}
}
}在iPad和Mac上使用实现侧边栏-详情页布局,在iPhone上会自动回退为栈式导航。
NavigationSplitViewswift
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("Items")
} detail: {
if let item = selectedItem {
ItemDetailView(item: item)
} else {
ContentUnavailableView("Select an Item", systemImage: "sidebar.leading")
}
}
}
}Custom Split Column (Manual HStack)
自定义分栏布局(手动HStack实现)
For custom multi-column layouts (e.g., a dedicated notification column independent of selection), use a manual split with checks:
HStackhorizontalSizeClassswift
@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 { /* 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 when you want a standard system layout with minimal customization.
NavigationSplitView如果需要自定义多列布局(例如,独立于选中项的通知栏),可结合判断,通过手动实现分栏:
horizontalSizeClassHStackswift
@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分栏;如果需要标准系统布局且自定义需求较少,使用即可。
NavigationSplitViewSheet Presentation
弹窗展示
Prefer over when state represents a selected model. Sheets should own their actions and call internally.
.sheet(item:).sheet(isPresented:)dismiss()swift
@State private var selectedItem: Item?
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
}Presentation sizing (iOS 18+): Control sheet dimensions with :
.presentationSizingswift
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
.presentationSizing(.form) // .form, .page, .fitted, .automatic
}PresentationSizing- -- platform default
.automatic - -- roughly paper size, for informational content
.page - -- slightly narrower than page, for form-style UI
.form - -- sized by the content's ideal size
.fitted
Fine-tuning: constrains fitting axes; grows but does not shrink in specified dimensions.
.fitted(horizontal:vertical:).sticky(horizontal:vertical:)Dismissal confirmation (macOS 15+ / iOS 26+): Use to prevent accidental dismissal of sheets with unsaved changes.
.dismissalConfirmationDialog("Discard?", shouldPresent: hasUnsavedChanges)Enum-driven sheet routing: Define a enum that is , store it on the router, and map it with a shared view modifier. This lets any child view present sheets without prop-drilling. See for the full centralized sheet routing pattern.
SheetDestinationIdentifiablereferences/sheets.md当状态对应一个选中模型时,优先使用而非。弹窗应自行管理关闭逻辑,在内部调用。
.sheet(item:).sheet(isPresented:)dismiss()swift
@State private var selectedItem: Item?
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
}弹窗尺寸控制(iOS 18+): 通过控制弹窗尺寸:
.presentationSizingswift
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
.presentationSizing(.form) // 可选值:.form, .page, .fitted, .automatic
}PresentationSizing- -- 平台默认尺寸
.automatic - -- 近似纸张尺寸,适用于信息类内容
.page - -- 比page略窄,适用于表单类UI
.form - -- 根据内容理想尺寸自适应
.fitted
精细调整:可约束自适应的轴方向;可让弹窗在指定轴方向上仅放大不缩小。
.fitted(horizontal:vertical:).sticky(horizontal:vertical:)关闭确认(macOS 15+ / iOS 26+): 使用防止用户意外关闭存在未保存修改的弹窗。
.dismissalConfirmationDialog("Discard?", shouldPresent: hasUnsavedChanges)枚举驱动的弹窗路由: 定义遵循协议的枚举,将其存储在路由对象中,通过共享视图修饰符进行映射。这样子视图无需层层传递状态即可触发弹窗展示。完整的集中式弹窗路由模式可参考。
IdentifiableSheetDestinationreferences/sheets.mdTab-Based Navigation
基于标签页的导航
Use the API with a selection binding for scalable tab architecture. Each tab should wrap its content in an independent .
TabNavigationStackswift
struct 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.
结合API与选中状态绑定,实现可扩展的标签页架构。每个标签页应将内容包裹在独立的中。
TabNavigationStackswift
struct 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() }
}
}
}
}带副作用的自定义绑定: 通过函数处理标签页选中状态变化,可拦截特殊标签页(例如,新建内容的标签页),触发对应操作而非切换选中状态。
iOS 26 Tab Additions
iOS 26 标签页新增特性
- -- replaces the tab bar with a search field when active
Tab(role: .search) - --
.tabBarMinimizeBehavior(_:),.onScrollDown,.onScrollUp(iPhone only).never - -- customize sidebar sections on iPadOS/macOS
.tabViewSidebarHeader/Footer - -- attach content below the tab bar (e.g., Now Playing bar)
.tabViewBottomAccessory { } - -- group tabs into sidebar sections with
TabSection.tabPlacement(.sidebarOnly)
See for full TabView patterns including custom bindings, dynamic tabs, and sidebar customization.
references/tabview.md- -- 选中时将标签栏替换为搜索框
Tab(role: .search) - -- 可选值:
.tabBarMinimizeBehavior(_:),.onScrollDown,.onScrollUp(仅iPhone支持).never - -- 在iPadOS/macOS上自定义侧边栏的头部/尾部
.tabViewSidebarHeader/Footer - -- 在标签栏下方附加内容(例如,正在播放栏)
.tabViewBottomAccessory { } - -- 通过
TabSection将标签页分组为侧边栏中的不同板块.tabPlacement(.sidebarOnly)
完整的TabView模式(包括自定义绑定、动态标签页、侧边栏自定义)可参考。
references/tabview.mdDeep Links
深度链接
Universal Links
通用链接
Universal links let iOS open your app for standard HTTPS URLs. They require:
- An Apple App Site Association (AASA) file at
/.well-known/apple-app-site-association - An Associated Domains entitlement ()
applinks:example.com
Handle in SwiftUI with and :
.onOpenURL.onContinueUserActivityswift
@main
struct 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)
}
}
}
}通用链接可让iOS通过标准HTTPS URL直接打开你的应用,配置要求:
- 在路径下放置Apple App Site Association(AASA)文件
/.well-known/apple-app-site-association - 配置Associated Domains权限()
applinks:example.com
在SwiftUI中通过和处理:
.onOpenURL.onContinueUserActivityswift
@main
struct 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
自定义URL协议
Register schemes in under . Handle with . Prefer universal links over custom schemes for publicly shared links -- they provide web fallback and domain verification.
Info.plistCFBundleURLTypes.onOpenURL在的中注册自定义协议,通过处理。对于公开分享的链接,优先使用通用链接而非自定义协议——通用链接支持网页降级和域名验证。
Info.plistCFBundleURLTypes.onOpenURLHandoff (NSUserActivity)
接力功能(NSUserActivity)
Advertise activities with and receive them with . Declare activity types in under . Set and provide a as fallback.
.userActivity().onContinueUserActivity()Info.plistNSUserActivityTypesisEligibleForHandoff = truewebpageURLSee for full examples of AASA configuration, router URL handling, custom URL schemes, and NSUserActivity continuation.
references/deeplinks.md通过发布活动,通过接收活动。在的中声明活动类型。设置并提供作为降级方案。
.userActivity().onContinueUserActivity()Info.plistNSUserActivityTypesisEligibleForHandoff = truewebpageURL完整的示例(包括AASA配置、路由URL处理、自定义URL协议、NSUserActivity接力)可参考。
references/deeplinks.mdCommon Mistakes
常见错误
- Using deprecated -- use
NavigationVieworNavigationStackNavigationSplitView - Sharing one across all tabs -- each tab needs its own path
NavigationPath - Using when state represents a model -- use
.sheet(isPresented:)instead.sheet(item:) - Storing view instances in -- store lightweight
NavigationPathroute dataHashable - Nesting router objects inside other
@Observableobjects@Observable - Using deprecated API -- use
.tabItem { }withTab(value:)TabView(selection:) - Assuming works on iPad -- it is iPhone only
tabBarMinimizeBehavior - Handling deep links in multiple places -- centralize URL parsing in the router
- Hard-coding sheet frame dimensions -- use instead
.presentationSizing(.form) - Missing on router classes -- required for Swift 6 concurrency safety
@MainActor
- 使用已废弃的——应使用
NavigationView或NavigationStackNavigationSplitView - 所有标签页共享同一个——每个标签页需要独立的路径
NavigationPath - 当状态对应模型时使用——应改用
.sheet(isPresented:).sheet(item:) - 在中存储视图实例——应存储轻量的
NavigationPath路由数据Hashable - 在其他对象中嵌套
@Observable路由对象@Observable - 使用已废弃的API——应结合绑定使用
.tabItem { }与Tab(value:)TabView(selection:) - 认为在iPad上可用——该特性仅支持iPhone
tabBarMinimizeBehavior - 在多个位置处理深度链接——应在路由中集中解析URL
- 硬编码弹窗尺寸——应使用替代
.presentationSizing(.form) - 路由类未添加——Swift 6并发安全要求必须添加
@MainActor
Review Checklist
审核检查清单
- used (not
NavigationStack)NavigationView - Each tab has its own with independent path
NavigationStack - Route enum is with stable identifiers
Hashable - maps all route types
.navigationDestination(for:) - preferred over
.sheet(item:).sheet(isPresented:) - Sheets own their dismiss logic internally
- Router object is and
@MainActor@Observable - Deep link URLs parsed and validated before navigation
- Universal links have AASA and Associated Domains configured
- Tab selection uses with binding
Tab(value:)
- 使用了(而非
NavigationStack)NavigationView - 每个标签页拥有独立的和路径
NavigationStack - 路由枚举遵循协议且具备稳定的标识符
Hashable - 映射了所有路由类型
.navigationDestination(for:) - 优先使用而非
.sheet(item:).sheet(isPresented:) - 弹窗在内部管理关闭逻辑
- 路由对象添加了和
@MainActor@Observable - 深度链接URL在导航前经过解析和验证
- 通用链接已配置AASA文件和Associated Domains权限
- 标签页选中逻辑使用了带绑定的
Tab(value:)
References
参考资料
- NavigationStack and router patterns:
references/navigationstack.md - Sheet presentation and routing:
references/sheets.md - TabView patterns and iOS 26 API:
references/tabview.md - Deep links, universal links, and Handoff:
references/deeplinks.md - Architecture and state management: see skill
swiftui-patterns - Layout and components: see skill
swiftui-layout-components
- NavigationStack与路由模式:
references/navigationstack.md - 弹窗展示与路由:
references/sheets.md - TabView模式与iOS 26 API:
references/tabview.md - 深度链接、通用链接与接力功能:
references/deeplinks.md - 架构与状态管理:参考技能文档
swiftui-patterns - 布局与组件:参考技能文档
swiftui-layout-components