What's new in UIKit
Privacy & Security 进阶 0m

UIKit 新特性

What's new in UIKit

2025年6月9日

在 Apple 官方观看视频

一句话判断

UIKit 终于原生支持 Swift Observation 了——在 layoutSubviews 里引用 @Observable 对象,UIKit 会自动追踪依赖并在属性变化时精确失效视图,不再需要手动调 setNeedsLayout

这场 Session 讲了什么

这场 Session 的信息量极大,涵盖了 UIKit 在 iOS 26 中的十余项更新。

新设计系统方面,Liquid Glass 材质刷新了所有系统控件,导航转场支持手势中断。新增 Background Extension View、Glass Material、Scroll Edge Effect 等工具。容器方面,UISplitViewController 新增 inspector 支持和可拖拽分隔条。菜单栏从 macOS 来到 iPadOS,支持完整的 UIMenuBuilder API 扩展,新增 focus-based deferred menu element。

架构改进是重头戏:UIKit 核心集成了 Swift Observation,在 layoutSubviews/updateProperties 等方法中自动追踪 @Observable 依赖并精确失效视图。新增 updateProperties 方法在 layoutSubviews 之前执行,用于内容填充和样式配置,和布局逻辑分离。动画新增 flushUpdates 选项,让 UIKit 在动画开始前自动应用待处理的更新,不再需要手动调 layoutIfNeeded

其他改进包括:SwiftUI scene 可在 UIKit app 中使用(UIHostingSceneDelegate)、UIColor 支持 HDR 色彩、强类型化的 NotificationCenter.Message、SF Symbols 7 的 draw 动画和 variable draw 模式、强制迁移到 UIScene 生命周期。

值得深挖的点

updateProperties 与 layoutSubviews 的职责分离

iOS 18 及之前,视图的内容配置和布局逻辑都写在 layoutSubviews 里。问题是:当一个无关事件(如窗口 resize)触发布局时,内容配置代码也被重复执行。新增的 updateProperties 方法解决了这个问题——它在 layoutSubviews 之前执行,但独立于布局。你可以用 setNeedsUpdateProperties 单独触发属性更新而不触发布局 pass,反之亦然。

这个设计还自然地与 automatic observation tracking 集成:在 updateProperties 中读取 @Observable 对象的属性时,UIKit 自动记录依赖关系。当这些属性变化时,只有 updateProperties 被重新执行——如果变化不涉及布局(如更新 badge 数字),整个 layout pass 被跳过。

更新流程变为:更新 traits → 运行 updateProperties → 运行 layoutSubviews → 显示 pass。因为 trait 更新在 updateProperties 之前,所以在 updateProperties 中可以安全读取 trait collection。如果 updateProperties 中需要触发布局,layout pass 会紧接着执行——这是一个”要么两个都跑,要么只跑属性更新”的粒度控制。

flushUpdates 动画选项的原理

iOS 18 及之前,在 UIView.animate 闭包中做 @Observable 驱动的动画需要两个步骤:修改 Observable 对象的属性(触发失效)、然后手动调 layoutIfNeeded(触发更新并创建动画)。这种手动维护依赖的方式容易出错——忘了调 layoutIfNeeded 就没有动画,调错了视图就动画错位。

flushUpdates 选项让 UIKit 在动画开始前自动应用所有待处理的更新。你只需要在动画闭包中修改状态,UIKit 负责找到受影响的视图并执行更新。关键是:闭包内只做状态修改(invalidating changes),不做读取或手动 layoutIfNeeded

更令人兴奋的是,flushUpdates 不限于 @Observable 驱动的动画。你也可以用它来动画化 Auto Layout 约束变化——在闭包中修改 constraint 的 constant 或 activate/deactivate 约束,UIKit 自动对依赖视图执行动画。这把动画代码从”先改值再调 layoutIfNeeded”简化为”改值就行”。

代码片段

使用 updateProperties 更新 badge,避免不必要的布局 pass:

class BadgeButtonViewController: UIViewController {
    let badgeModel = BadgeModel(count: 5)

    override func updateProperties() {
        super.updateProperties()
        // 读取 Observable,UIKit 自动追踪依赖
        barButtonItem?.setBadgeValue("\(badgeModel.count)")
        // 当 badgeModel.count 变化时,只有 updateProperties 被重新执行
    }
}

用 flushUpdates 选项动画化 Auto Layout 变更:

// 不需要手动调 layoutIfNeeded
UIView.animate(withDuration: 0.3, options: [.flushUpdates]) {
    self.collapsedConstraint.isActive = false
    self.expandedConstraint.isActive = true
    // UIKit 自动找到依赖视图并执行动画
}

UIKit 中使用 SwiftUI immersive space:

class ImmersiveSceneDelegate: UIHostingSceneDelegate {
    static var rootScene: some Scene {
        ImmersiveSpace(id: "zen-garden") {
            ZenGardenView()
        }
    }
}

// 通过 scene configuration 激活
let config = UISceneConfiguration(name: "Immersive", sessionRole: .windowApplication)
config.delegateClass = ImmersiveSceneDelegate.self

最佳实践

建议立即开始迁移到 UIScene 生命周期。iOS 26 之后的版本将要求所有使用最新 SDK 构建的 app 必须使用 UIScene,否则无法启动。即使是单窗口应用也需要迁移。

优先在 updateProperties 中做内容和样式配置,在 layoutSubviews 中只做布局计算。这不仅性能更好(内容更新不触发布局 pass),也让代码职责更清晰。

避免在 storyboard 中定义菜单栏。iOS 26 开始,storyboard 中的菜单不再被加载。必须通过代码用 UIMenuBuilder 构建菜单。

还有什么值得关注

  • UIKeyCommand 新增 repeatBehavior 属性,可以控制按键重复行为——对删除等破坏性操作尤其重要,避免按住 Delete 键时连续删除。
  • Focus-based deferred menu element 让菜单栏能根据当前焦点动态填充内容(如 Safari 的历史菜单根据当前 profile 显示),不需要重建整个菜单。
  • UIColor 支持 HDR 色彩,UIColorPickerViewController 和 UIColorWell 支持 HDR 颜色选择,通过设置最大 exposure 值控制。
  • SF Symbols 7 新增 Draw On/Off 动画、variable draw 模式(沿路径绘制任意值)、以及 Gradient 渲染模式。
  • NotificationCenter 新增强类型 Message 类型,键盘通知等可以直接从 message 读取 animation duration 和 frame,不需要 userInfo 解析。
隐私与安全 SwiftUI