SwiftUI 的新功能
What’s new in SwiftUI
2026年6月10日
一句话判断
这场 Session 最值得关注的一件事是:@State 属性包装器(Property Wrapper)被重构成了 Macro(宏),实现了对象引用的惰性初始化(Lazy Initialization),并且苹果把这个性能红利直接向下兼容到了 iOS 17 和 macOS 14。
这场 Session 讲了什么
今年 SwiftUI 的更新分成了“面子”和“里子”两部分。面子是全新的 Liquid Glass(液态玻璃)设计语言,你的 App 只要用 Xcode 27 重新编译,就能自动换上这套带点透明和折射效果的新皮肤,连 iPad 上的非活跃窗口变暗效果都给你做好了。里子则是针对大型 App 和生产力工具的底层优化,特别是 Document API(文档接口)终于支持了直接通过 URL 访问磁盘,不用再把整个文件读进内存里做 diff 了。
对日常写业务的人来说,最爽的改变是 Toolbar(工具栏)终于有了聪明的溢出菜单(Overflow Menu)和可见性优先级(Visibility Priority)。以前窗口一缩小,重要的 Undo/Redo 按钮就被系统随便藏起来,现在你可以精确控制哪些按钮必须露脸,哪些乖乖收进菜单里。配合 toolbarMinimizeBehavior 滚动时自动收起导航栏,iPad 和 Mac 上的大屏体验终于像那么回事了。
值得深挖的点
@State Macro 化与向下兼容的性能红利
以前我们在 SwiftUI 里用 @State 包装一个 class 时,只要 View 被初始化,这个 class 就会立刻在内存里分配。如果 View 树很深或者列表里有很多复杂的 Cell,这种 eager allocation(急切分配)会造成明显的卡顿。苹果今年把 @State 从 Property Wrapper 换成了 Macro。这意味着编译器可以在底层把初始化逻辑改成 lazy(惰性),只有当 View 的 body 真正被评估,或者状态被读取时,对象才会创建。
最让人拍大腿叫好的是,这个改动不是 iOS 27 独占。苹果通过 Swift 编译器的向下兼容能力,把这套 lazy 初始化机制 backport(向后移植)到了 iOS 17 和 macOS 14。你不需要改任何业务代码,只要把编译环境切到 Xcode 27,你的旧 App 在老系统上就能自动获得内存和启动速度的提升。这种“改底层不改 API,且惠及老系统”的操作,比加几个花哨的 UI 修饰符有价值得多。
Document API 的直接 URL 访问与创建上下文
做 Mac 或 iPad 生产力 App 的开发者都知道,FileDocument 以前是个让人又爱又恨的协议。它强制你把整个文件读进内存变成 Data,然后再让你解析。对于几百 MB 的文档或者基于包(Package)格式的复杂工程,这简直是灾难。今年苹果终于给 FileDocument 和 ReferenceFileDocument 加上了 first-class(一等公民)的 URL 直接访问支持。你可以直接拿到文件的磁盘路径,用 mmap 或者流式读取来处理大文件,甚至直接对比文件快照(Snapshot diffing)。
配合全新的 DocumentCreationSource API,新建文档的流程也变正常了。以前你想在“新建”菜单里加一个“从照片新建”的选项,得自己魔改 Scene。现在只要声明不同的 source(来源),系统会在创建闭包里把 context(上下文)传给你,你可以直接根据来源弹 PhotoPicker(照片选择器)。这让 SwiftUI 写出来的文档型 App 终于有了媲美 AppKit 的原生体验。
代码片段
1. 工具栏优先级与溢出菜单
场景:窗口缩小时,保证核心编辑按钮始终显示,次要按钮收进溢出菜单。
StickerPageView()
.toolbar {
// 核心操作,设置高优先级,死活都要显示
ToolbarItemGroup {
UndoButton()
RedoButton()
}
.visibilityPriority(.high)
// 次要操作,空间不够时自动收进 "..." 菜单里
ToolbarOverflowMenu {
ChoosePhotoButton()
ExportAsImageButton()
ClearAllStickersButton()
}
// 固定在右侧的分享按钮
ToolbarItem(placement: .topBarPinnedTrailing) {
ShareButton()
}
}
坑:ToolbarOverflowMenu 里的按钮如果带了自定义的复杂 View,在菜单里可能会丢失样式,尽量用标准的 Button 加 Label。
2. 自定义文档创建来源
场景:在新建文档界面提供“空白文档”和“从照片创建”两个入口。
@main
struct StickersApp: App {
var body: some Scene {
// 定义新建文档的启动场景
DocumentGroupLaunchScene("创建贴纸页") {
NewDocumentButton("空白贴纸页", source: .blank)
NewDocumentButton("从照片创建...", source: .photo)
}
DocumentGroup { document in
StickerPageView(document)
} { configuration, context in
// 根据 context 判断来源,如果是 photo 就触发照片选择
StickerPageDocument(configuration: configuration, context: context)
}
}
}
// 扩展系统协议,定义我们自己的来源 ID
extension DocumentCreationSource {
static let blank = Self(id: "blank")
static let photo = Self(id: "photo")
}
坑:context 参数只在用户点击你定义的 NewDocumentButton 时才会携带对应的 source,如果是系统默认的 Cmd+N 触发,记得做好 fallback(降级)处理。
最佳实践
对于现有的中大型项目,第一件事是把 Xcode 升级到 27 并重新编译。不需要急着去改 @State 的写法,编译器会自动帮你把 class 类型的状态初始化变成 lazy,你可以直接用 Instruments 对比一下升级前后的内存峰值和冷启动时间。
如果你在做 iPad/Mac 的多窗口 App,赶紧把硬编码的 Toolbar 替换成 visibilityPriority 和 ToolbarOverflowMenu。以前为了适配不同窗口尺寸,大家写了无数 if horizontalSizeClass == .compact 的分支代码,现在把这些逻辑删掉,把排布控制权交还给系统。
对于文档类 App,如果你现在的 FileDocument 还在用 Data 硬扛大文件,立刻迁移到基于 URL 的新协议。把文件读写逻辑从 FileWrapper 切换到直接操作 URL,配合 NSFileCoordinator,能彻底解决大文档保存时的 UI 冻结问题。
还有什么值得关注
- AsyncImage 默认支持 HTTP 缓存:终于不用为了加个缓存去引第三方库或者自己手写
URLSession拦截了,现在默认走标准 HTTP cache,还能直接传自定义的URLRequest。 - 任意视图支持 Swipe Actions:左滑操作(Swipe Actions)不再被
List独占,现在你可以给LazyVGrid甚至普通的VStack里的卡片加上滑动删除或标记功能。 - Liquid Glass 交互元素:在 Mac 上,把自定义视图标记为 interactive(可交互),鼠标悬停时会有类似玻璃折射的流体反馈,不用自己算高光了。