Highlight
SwiftUI 在 2025 年获得 Liquid Glass 设计系统、macOS 列表 6× 加载提速、
@Animatable宏、WebView、Chart3D、TextEditor 富文本绑定,以及把 SwiftUI 场景桥接到 UIKit/AppKit 应用的能力。
核心内容
写过列表的开发者都遇到过这个问题:macOS 上 10 万条目的 List 一刷新就卡,滚动时掉帧;自定义 Shape 想做动画,得写一长段 AnimatableData 把每个属性手动展开;想在原有 UIKit 应用里用 MenuBarExtra 这种 SwiftUI 专属的场景类型,发现根本接不进去。这些痛点在 SwiftUI 2025 都拿到了正面回应。
Anna 和 Peter 用一个徒步规划 app 串起所有更新。重新编译后,应用自动获得 Liquid Glass 外观,侧边栏变成玻璃质感,工具栏项在导航转场时会 morph,搜索栏在 iPhone 上自动移到底部更顺手。框架底层的列表加载快了 6 倍、更新快了 16 倍,scroll 调度被重写以减少高帧率掉帧,新的 SwiftUI Performance Instrument 提供了专门的 lane 来定位 body 更新和平台视图更新的耗时。@Animatable 宏让 SwiftUI 自动合成动画属性。Scene Bridging 让 UIKit/AppKit 应用直接消费 MenuBarExtra、ImmersiveSpace 这些 SwiftUI 专属场景。WebView、Chart3D、TextEditor 的 AttributedString 绑定补齐了多年来一直缺位的视图能力。
详细内容
Liquid Glass 工具栏分区(02:27)。新的 ToolbarSpacer API 让你显式控制工具栏项之间的分组:
NavigationStack {
TripList()
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
UpButton()
DownButton()
}
ToolbarSpacer(.fixed, placement: .primaryAction)
ToolbarItem(placement: .primaryAction) {
SettingsButton()
}
}
}
关键点:
ToolbarItemGroup把 Up/Down 两个按钮聚成一个胶囊。ToolbarSpacer(.fixed, placement: .primaryAction)在它们和 Settings 之间插入固定间隔,让 SettingsButton 单独成为一个 Liquid Glass 胶囊。- 导航转场时,胶囊会沿这个分组结构 morph。
Search 作为独立 Tab(04:12)。把搜索声明成一个 role: .search 的 Tab,系统会把它和普通 Tab 在视觉上分离,并在点击时直接 morph 成搜索框:
TabView {
Tab("Summary", systemImage: "heart") {
NavigationStack { Text("Summary") }
}
Tab("Sharing", systemImage: "person.2") {
NavigationStack { Text("Sharing") }
}
Tab(role: .search) {
NavigationStack { Text("Search") }
}
}
.searchable(text: $text)
关键点:
Tab(role: .search)标记这是搜索目的地。.searchable仍然挂在TabView上,绑定text。- 在 iPhone 上点击该 Tab,搜索框会从 Tab 位置 morph 出来,而不是新弹一层。
@Animatable 宏(11:24)。以前自定义 Shape 想动画几个属性,要手写 AnimatableData 把它们打包成 AnimatablePair 嵌套;现在交给宏:
@Animatable
struct LoadingArc: Shape {
var center: CGPoint
var radius: CGFloat
var startAngle: Angle
var endAngle: Angle
@AnimatableIgnored var drawPathClockwise: Bool
func path(in rect: CGRect) -> Path {
return Path()
}
}
关键点:
@Animatable自动合成animatableData,把所有可动画属性串起来。@AnimatableIgnored标记drawPathClockwise这种布尔属性不参与插值。- 不用再写
AnimatablePair<AnimatablePair<...>, ...>的嵌套样板。
WebView + WebPage(20:44)。WebKit 终于有了原生 SwiftUI 视图。最简版本只要一个 URL,但要做编程式导航就用 WebPage:
struct InAppBrowser: View {
@State private var page = WebPage()
var body: some View {
WebView(page)
.ignoresSafeArea()
.onAppear {
page.load(URLRequest(url: sunshineMountainURL))
}
}
}
关键点:
WebPage是为 Swift 设计的 Observable 模型,不是WKWebView的薄封装。WebView(page)接受WebPage实例,渲染由它驱动的页面。page.load(_:)触发导航;同 API 还支持读取页面属性、调用 JavaScript、自定义 URL scheme。
3D 图表(21:35)。Swift Charts 加入 Chart3D,可以画曲面:
Chart3D {
SurfacePlot(x: "x", y: "y", z: "z") { x, y in
sin(x) * cos(y)
}
.foregroundStyle(Gradient(colors: [.orange, .pink]))
}
.chartXScale(domain: -3 ... 3)
.chartYScale(domain: -3 ... 3)
.chartZScale(domain: -3 ... 3)
关键点:
Chart3D是新的容器,配合SurfacePlot渲染曲面。- 闭包
(x, y) -> z直接给 SwiftUI 一个数学函数,不需要预先采样。 - 三个
chart*Scale限定可视域。
Drag and Drop 多选与删除(22:18)。Mac 上拖拽一直是 UIKit/AppKit 桥接的重灾区,今年补齐了:
ScrollView {
LazyVGrid(columns: gridColumns) {
ForEach(model.photos) { photo in
view(photo: photo)
.draggable(containerItemID: photo.id)
}
}
}
.dragContainer(for: Photo.self, selection: selectedPhotos) { draggedIDs in
photos(ids: draggedIDs)
}
.dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))
.onDragSessionUpdated { session in
let ids = session.draggedItemIDs(for: Photo.ID.self)
if session.phase == .ended(.delete) {
trash(ids)
deletePhotos(ids)
}
}
.dragPreviewsFormation(.stack)
关键点:
draggable(containerItemID:)把每个 cell 标记为容器内的可拖项,只传 ID。dragContainer(for:selection:)在 drop 真正发生时才被调用,懒加载实际Photo数据。DragConfiguration(allowDelete: true)允许拖到废纸篓触发删除。onDragSessionUpdated监听session.phase == .ended(.delete),做真实的清理。.dragPreviewsFormation(.stack)把多张图叠成一摞作为拖拽预览。
TextEditor 富文本(23:55)。TextEditor 接受 AttributedString 绑定后,工具栏自带粗体/斜体/列表等格式控件:
struct CommentEditor: View {
@Binding var commentText: AttributedString
var body: some View {
TextEditor(text: $commentText)
}
}
关键点:
- 绑定类型从
String换成AttributedString,富文本能力即开即用。 - 系统提供格式化控件,开发者也可以自定义段落样式、限制允许的属性集。
核心启发
-
做什么:把
UIRequiresFullscreen从 Info.plist 里删掉,并补做窗口自由调整大小的适配。- 为什么值得做:这个 key 在 iPadOS 26 已经 deprecated,留着会让你的 app 在 iPad 多任务窗口里行为受限。
- 怎么开始:删除该 key 后跑一遍 app,重点检查
NavigationSplitView的列在窄宽度下的折叠行为,以及自定义布局在极端尺寸下是否崩。
-
做什么:用 SwiftUI Performance Instrument 替代 Time Profiler 做首轮性能排查。
- 为什么值得做:通用 Time Profiler 看到的是 CPU 火焰图,而新的 Instrument 直接告诉你哪个 view 的
body被多次调用、哪个平台视图更新最慢。 - 怎么开始:用 Xcode 26 打开 Instruments,选 SwiftUI 模板,跑你最复杂的列表/动画场景,从 long body update lane 开始读。
- 为什么值得做:通用 Time Profiler 看到的是 CPU 火焰图,而新的 Instrument 直接告诉你哪个 view 的
-
做什么:把现有自定义 Shape 的
animatableData替换成@Animatable。- 为什么值得做:嵌套
AnimatablePair是公认的样板代码,宏直接消除它,可读性提升一个量级。 - 怎么开始:在 Shape 上加
@Animatable,把不该动画的属性挂上@AnimatableIgnored,删掉手写的animatableData,再跑一遍现有动画测试。
- 为什么值得做:嵌套
-
做什么:评估能否用 Scene Bridging 把现有 UIKit/AppKit 应用改造成可以打开
MenuBarExtra/ImmersiveSpace/RemoteImmersiveSpace。- 为什么值得做:对于不打算整体迁移到 SwiftUI lifecycle 的老项目,这是把 Vision Pro 立体内容和 Mac 菜单栏功能接入应用的最低成本路径。
- 怎么开始:在 UIKit/AppKit 入口里声明 SwiftUI 场景,先用
MenuBarExtra做一个最小验证,再考虑RemoteImmersiveSpace配合 CompositorServices。
-
做什么:把内嵌 H5 页面的旧封装替换成
WebView+WebPage。- 为什么值得做:旧的
WKWebView桥接通常用 JavaScript bridge 做交互,类型不安全且难调试;WebPage是 Observable 模型,导航和页面属性都可以纯 Swift 访问。 - 怎么开始:先把展示型页面(FAQ、协议页)切到
WebView(url:);交互型页面再迁移到WebView(page)+page.load(_:),并用WebPage监听导航事件。
- 为什么值得做:旧的
关联 Session
- Build a SwiftUI app with the new design — 用新设计系统从零构建 SwiftUI 应用的最佳实践
- Meet WebKit for SwiftUI —
WebView和WebPage的完整 API 与高级用法 - Optimize SwiftUI performance with Instruments — 新的 SwiftUI Instrument 各 lane 的解读方法
- Better together: SwiftUI and RealityKit — Entity 的 Observable、坐标转换与 PresentationComponent
- Meet SwiftUI spatial layout —
Alignment3D、spatialOverlay等空间布局工具
评论
GitHub Issues · utterances