SwiftUI 的新变化
What's new in SwiftUI
2024年6月10日
一句话判断
SwiftUI 在 iOS 18 终于有了像样的 TabView 重写(sidebarAdaptable)、Mesh Gradient、Control Widgets、和 zoom transition——框架的完成度到了一个新台阶。
这场 Session 讲了什么
这是一场信息密度极高的 Session,覆盖了 SwiftUI 在 iOS 18 / macOS 15 / visionOS 2 的全面更新。内容分为四个板块。
第一,让 App 焕然一新的功能:TabView 有了全新的 type-safe 语法和 .sidebarAdaptable 样式(可以在 tab bar 和 sidebar 之间切换),Mesh Gradient 让你可以创建漂亮的彩色网格渐变,Swift Charts 支持函数绘图,Table 支持动态列数。第二,平台适配改进:macOS 上可以自定义窗口样式(plain style、floating level、default placement)、visionOS 上有了 pushWindow 和自定义 hover effect。第三,基础构建块的改进:SF Symbols 6 带来了 wiggle/breathe/rotate 三种新动画效果和 MagicReplace。第四,沉浸体验工具:新的 Document Launch Scene、Control Widgets(控制中心和锁屏的自定义控件)。
值得深挖的点
TabView 的 sidebarAdaptable 重写
这是 SwiftUI TabView 自发布以来最大的重构。新的 API 用 type-safe 语法替代了之前的字符串标签方式,编译期能捕获更多错误。核心变化是 .sidebarAdaptable 样式——它让同一个 TabView 在不同场景下自动切换形态:内容少时显示为 floating tab bar,内容多时展开为 sidebar。用户可以通过拖拽来自定义 tab 的顺序和可见性,而且这些自定义是完全可编程控制的。
这个设计解决了一个长期痛点:之前的 TabView 和 NavigationSplitView 是两套独立 API,要做”tab bar 可以展开为 sidebar”的效果需要写大量桥接代码。现在一个 TabView + .sidebarAdaptable 就搞定了。它还在 tvOS 上自动适配为 refreshed sidebar、在 macOS 上可以显示为 toolbar segmented control。这是真正的跨平台自适应。
Control Widgets:控制中心和锁屏的自定义控件
SwiftUI 现在可以创建自定义控件(按钮和开关),这些控件可以出现在控制中心、锁屏、甚至 Action Button 上。Control Widget 是一种新的 Widget 类型,基于 App Intents 构建。
它的价值在于把 App 的核心操作暴露到了系统层面——用户不需要打开你的 App 就能触发操作。比如音乐 App 的”开始播放”按钮、智能家居的”开灯”开关、拍照 App 的”快速自拍”按钮。API 很简洁:ControlWidgetButton 和 ControlWidgetToggle,配合 AppIntent 定义操作逻辑。这延续了 Apple 的趋势:让 App 的功能不再局限在 App 内部,而是渗透到系统的各个触点。
代码片段
新的 TabView 语法
场景:使用 type-safe 的 TabView,支持 sidebar 和 tab bar 切换。
TabView {
Tab("歌曲", systemImage: "music.note") {
SongListView()
}
Tab("播放列表", systemImage: "list.bullet") {
PlaylistView()
}
Tab("搜索", systemImage: "magnifyingglass") {
SearchView()
}
TabSection("更多") {
Tab("设置", systemImage: "gear") {
SettingsView()
}
Tab("关于", systemImage: "info.circle") {
AboutView()
}
}
}
.tabViewStyle(.sidebarAdaptable)
// 自动在 tab bar 和 sidebar 之间切换
// 用户可以拖拽自定义 tab 顺序
坑:TabSection 中的 tab 在 tab bar 模式下可能被折叠到”更多”菜单中。
Mesh Gradient
场景:创建彩色网格渐变效果。
MeshGradient(
width: 3,
height: 3,
points: [
.init(0, 0), .init(0.5, 0), .init(1, 0),
.init(0, 0.5), .init(0.5, 0.5), .init(1, 0.5),
.init(0, 1), .init(0.5, 1), .init(1, 1)
],
colors: [
.purple, .pink, .orange,
.blue, .white, .yellow,
.indigo, .mint, .green
]
)
坑:points 的数量必须等于 width * height,否则会崩溃。渐变效果在低分辨率设备上可能显示不同。
Control Widget 按钮
场景:在控制中心添加一个快速操作按钮。
import WidgetKit
import AppIntents
struct KaraokeStartControl: ControlWidget {
var body: some ControlWidgetConfiguration {
ControlWidgetButton(action: StartKaraokeIntent()) {
Label("开始 K 歌", systemImage: "mic.fill")
}
}
}
// 对应的 AppIntent
struct StartKaraokeIntent: AppIntent {
static var title: LocalizedStringResource = "开始 K 歌"
func perform() async throws -> some IntentResult {
// 触发 App 内的唱歌功能
return .result()
}
}
坑:Control Widget 需要在 Info.plist 中正确配置,否则不会出现在控制中心。每个 Control Widget 的大小有限制,不能放太多内容。
macOS 自定义窗口
场景:创建一个浮动的歌词预览窗口。
Window("歌词预览", id: "lyrics-preview") {
LyricsPreviewView()
}
.windowStyle(.plain) // 移除默认窗口装饰
.windowLevel(.floating) // 浮在最上层
.defaultWindowPlacement { contentSize, context in
// 放在屏幕顶部
let screen = context.screens.first!
return WindowPlacement(
position: .init(screen.bounds.midX - contentSize.width / 2,
screen.bounds.minY),
size: contentSize
)
}
.defaultSize(width: 400, height: 60)
坑:.plain 样式移除了标题栏,用户无法通过标题栏拖动窗口——需要自己加 WindowDragGesture。
最佳实践
已有项目:优先迁移 TabView 到新语法——type-safe API 能在编译期发现问题。如果你的 App 使用 NavigationSplitView 模拟 sidebar/tab 切换,现在是替换成 .sidebarAdaptable TabView 的好时机。Mesh Gradient 和 SF Symbols 新动画可以作为视觉升级的一部分逐步加入。
新项目:直接使用新的 TabView API,从第一天就支持 sidebarAdaptable。考虑哪些功能适合做成 Control Widget——频繁使用的操作(如开始记录、切换状态、快捷拍照)是首选。macOS 项目充分利用新的窗口定制能力(plain style、floating level、自定义 placement)。visionOS 项目使用 pushWindow 来管理焦点窗口。
还有什么值得关注
- Swift Charts 新增函数绘图(
LinePlot),可以直接画数学函数图像,也可以叠加实际数据。 TableColumnForEach让 Table 支持动态列数,再也不用为不确定列数的表格头疼了。- Sheet 的 presentation sizing 统一为
.form(小表单)和.page(全页),不再需要手动指定detents。 - visionOS 上的 hover effect 现在可以自定义闭包来控制视觉反馈,同时保护用户隐私(不暴露精确的眼动数据)。
onModifierKeysChanged让任何 view 都能响应修饰键状态变化,对 macOS 和 iPadOS 的键盘增强体验很有用。