macOS 原生应用的窗口与导航管理
SwiftUI 提供了 NavigationSplitView、NavigationStack、TabView 等多种导航容器,它们在 macOS、iPadOS 和 iOS 上的行为各有不同,组合方式也不止一种。
这篇文章整理了我在实际开发中对 macOS 原生应用导航管理的观察与总结,希望能帮助你在项目初期做出更清晰的架构选择。
创建三列应用
NavigationSplitView 是天然首选
如果你的应用始终需要三列结构——比如 Notes 的「文件夹 → 笔记列表 → 编辑器」,或 Mail 的「邮箱 → 邮件列表 → 邮件正文」——那么 NavigationSplitView 的三列形态就是最自然的方案:
NavigationSplitView {
// sidebar
} content: {
// 中间列
} detail: {
// 详情列
}
在这种场景下,系统的原生体验非常稳定,toolbar、标题栏与分栏折叠行为都与系统预期一致,几乎不需要额外干预。


混合列数场景:谨慎切换容器形态
实际项目中更常见的情况是:应用的某些模块需要两列,某些模块需要三列。如果你尝试在模块切换时通过条件渲染来直接切换 NavigationSplitView 的列数结构(比如从二列变三列),可能会遇到明显的重布局感——界面跳动,过渡不够丝滑。
更稳妥的做法是分层处理:在顶层维护「模块导航」的逻辑(决定当前进入哪个功能模块),在模块内部各自管理自己的分栏结构。这样全局容器的形态不会频繁变化,用户感知到的是模块内部的内容切换,而非窗口骨架的重组。
创建两列应用
对于两列结构的 macOS 应用,常见的实现方式有两种,各有适用场景。
方案一:TabView + .sidebarAdaptable + NavigationStack
TabView {
Tab("Library", systemImage: "books.vertical") {
NavigationStack { ... }
}
Tab("Settings", systemImage: "gear") {
NavigationStack { ... }
}
}
.tabViewStyle(.sidebarAdaptable)
左侧呈现为系统托管的侧边栏,右侧通常用 NavigationStack 处理页面层级。Music 就是采用这个方式:

但是需要注意,Podcast 应用和 Journey 应用都不是 Tabview,原因是
- Tabview 的分组 header 不支持折叠,但是 Podcast 的侧边栏有侧叠功能 —— 这是使用
NavigationSplitView并自定义实现的侧边栏 UI。 - Journel 应用侧边栏元素更多,明显不是 Tabview 的效果。

值得注意的一点是: 无论是 NavigationStack 还是 NavigationSplitView,在 macOS 上默认都没有 iOS 风格的推进动画。在 Finder、系统设置、备忘录中看到的内容切换,都是近乎即时的替换,而非动画过渡。
只有 App Store 是例外 —— 那种有过渡感的体验更像是定制转场,不是默认行为。
方案二:两列 NavigationSplitView
NavigationSplitView {
// 自定义 sidebar
} detail: {
// 主内容区
}
固定的 sidebar + detail 两列结构,适合「列表 + 主内容」型的经典布局。备忘录、系统设置等应用是这一模式的典型代表。这两种方案在 macOS 上都能获得原生的侧边栏折叠按钮和一致的系统外观,视觉上的差异并不大。
在两列 NavigationSplitView 中,当 detail 内需要「再深入一层」的页面层级时(比如从文章列表点进文章详情,再点进评论页),仅靠 NavigationSplitView 本身是不够的。
这时需要在 detail 列内部建立明确的 NavigationStack 上下文:
NavigationSplitView {
// sidebar
} detail: {
NavigationStack {
ArticleListView()
.navigationDestination(for: Article.self) { article in
ArticleDetailView(article: article)
}
}
}
Tabview 与 NavigationSplitView 的区别
差异一:在 iPad 和 iOS 平台外观差异
虽然 TabView + .sidebarAdaptable 和两列 NavigationSplitView 在 macOS 上外观接近,但在 iPadOS 和 iOS 上,它们的行为差异会明显放大。
TabView 的跨平台行为:
- iPhone 上默认表现为底部 Tab Bar
- iPad 和 macOS 上可通过
.sidebarAdaptable适配为侧边栏
NavigationSplitView 的跨平台行为:
- iPad 宽度足够时呈现侧边栏或双栏/三栏
- iPhone(紧凑宽度)上会折叠为单列导航流
- 在 iOS 上不会产生底部导航——底部导航的语义始终来自
TabView
差异二:侧边栏选项外观自定义能力 ⭐️⭐️⭐️
TabView(.sidebarAdaptable) 的侧边栏由系统完全托管,无法自定义图标颜色、行间距、Section 分组、badge 样式等。
NavigationSplitView 的 sidebar 列则是一个完全可定制的 SwiftUI 视图。你可以自由使用 List、ScrollView、自定义行组件、彩色图标、任意 Section 分组,甚至在底部放置用户头像行——一切由你掌控。
Tabview 增强
从 iOS 18 开始,Tabview 组件新增了多个修饰器,用于实现不同的增强功能。

关键区别:Footer 是内容的一部分(tab 多了会被滚走),BottomBar 是固定的 UI 层(始终可见)。用户行放在 tabViewSidebarBottomBar 是正确的选择。
tabViewSidebarBottomBar(常驻)

tabViewSidebarBottomBar 会自动添加一条分割线,没找到隐藏的办法。
音乐应用明显也是使用 Tabview 组件:
- 左下角添加用户信息显示
- 分组标题不支持折叠

tabViewSidebarHeader(常驻)
将内容显示在顶部

tabViewSidebarFooter(常驻)
tabViewSidebarFooter 是跟随 tab 列表内容排列的,不固定在底部,而是默认显示在最后一行下面:

Comments ()