WWDC Quick Look 💓 By SwiftGGTeam
What’s new in SwiftUI

What’s new in SwiftUI

观看原视频

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 应用直接消费 MenuBarExtraImmersiveSpace 这些 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 作为独立 Tab04: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 出来,而不是新弹一层。

@Animatable11: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 + WebPage20: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 开始读。
  • 做什么:把现有自定义 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

评论

GitHub Issues · utterances