Highlight
iOS 18 的 UIKit 支持用 SwiftUI Animation 驱动 UIView 动画,手势系统跨框架统一了依赖关系,Tab Bar 自动适配 sidebar 形态。
核心内容
每个用 UIKit 写 iPad 应用的开发者都遇到过同一个问题:UITabBarController 的 tab bar 和 sidebar 是两套独立的 UI,切换时需要手动管理状态,代码写起来又长又容易出 bug。iPadOS 18 终于把这两者合为一体——新的 UITab 和 UITabGroup API 让你只描述一次 app 结构,系统自动生成 tab bar 和 sidebar 两种形态,用户还可以通过拖拽来自定义 tab 顺序。
同样让人头疼的还有 trait 变更的处理。以前需要在 registerForTraitChanges 里手动注册每一个关心的 trait,再在回调里调用 setNeedsLayout 或 setNeedsDisplay。iOS 18 引入 automatic trait tracking 后,只要在 layoutSubviews、drawRect 等方法里读取了某个 trait,UIKit 就自动记住这个依赖,下次 trait 值变化时自动触发对应的 invalidation。这意味着大量样板代码可以直接删掉。
跨框架协作的痛点也在被消除。SwiftUI 的 Animation 类型现在可以直接驱动 UIKit view 的动画,包括 CustomAnimation;手势系统也做了统一,UIKit 和 SwiftUI 的手势之间可以设置 failure/requirement 关系,还新增了 UIGestureRecognizerRepresentable 协议把 UIKit 手势嵌入 SwiftUI 层级。
详细内容
用 SwiftUI Animation 驱动 UIView
这是本次更新中最实用的 API 变化。UIView.animate(using:) 接受一个 SwiftUI Animation 类型,在交互式动画场景下尤其好用——手势过程中用 .interactiveSpring,手势结束后用 .spring,速度在两阶段之间无缝衔接(03:35):
switch gesture.state {
case .changed:
UIView.animate(.interactiveSpring) {
bead.center = gesture.translation
}
case .ended:
UIView.animate(.spring) {
bead.center = endOfBracelet
}
}
关键点:
.changed分支用.interactiveSpring,跟随手指移动,回弹幅度适中.ended分支用.spring,动画终点固定,速度从手势结束瞬间自然过渡- 两段动画共享同一套 velocity 传递机制,不需要手动计算初速度
跨框架手势依赖
当 SwiftUI 视图嵌套在 UIKit 视图中时,两个框架的手势可能同时识别,产生冲突。iOS 18 允许通过 name 和 delegate 方法设置跨框架的 failure requirement(04:46):
// Inner SwiftUI double tap gesture
Circle()
.gesture(doubleTap, name: "SwiftUIDoubleTap")
// Outer UIKit single tap gesture
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRequireFailureOf other: UIGestureRecognizer
) -> Bool {
other.name == "SwiftUIDoubleTap"
}
关键点:
- SwiftUI 手势通过
name:参数打标签,UIKit 端通过other.name匹配 - UIKit 的单击手势要求 SwiftUI 双击手势失败后才触发,避免同时识别
- 这是
UIGestureRecognizerDelegate的标准方法,不需要新的协议
Automatic trait tracking
在 layoutSubviews 中读取 trait,UIKit 自动追踪依赖(06:40):
class MyView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
if traitCollection.horizontalSizeClass == .compact {
// apply compact layout
} else {
// apply regular layout
}
}
}
关键点:
- 当
layoutSubviews被调用时,UIKit 记录你访问了horizontalSizeClass - 之后该 trait 变化时,UIKit 自动调用
setNeedsLayout - 以前需要手动
registerForTraitChanges+ 回调中setNeedsLayout,这段代码可以直接删除 - 支持的方法包括
layoutSubviews、drawRect:、traitCollectionDidChange等,完整列表见文档
List environment trait
UICollectionView list section 和 UITableView 中的所有视图现在自动拥有 list environment trait。UIListContentConfiguration 和 UIBackgroundConfiguration 根据这个 trait 自动调整外观(07:43):
func configurations(for location: FileLocation) ->
(UIListContentConfiguration, UIBackgroundConfiguration) {
var contentConfiguration = UIListContentConfiguration.cell()
let backgroundConfiguration = UIBackgroundConfiguration.listCell()
contentConfiguration.text = location.title
contentConfiguration.image = location.thumbnailImage
return (contentConfiguration, backgroundConfiguration)
}
关键点:
UIListContentConfiguration.cell()替代了以前需要手动判断 sidebar/普通样式的逻辑UIBackgroundConfiguration.listCell()是新增的构造器,自动从 list environment trait 读取外观- 新增的构造器还有
.listHeader和.listFooter - 不再需要把 list appearance 作为参数传入,configuration 在被应用到 cell 时自动适配
UIUpdateLink
替代 CADisplayLink 的新 API,增加了自动 view tracking 和低延迟模式(11:18):
let updateLink = UIUpdateLink(
view: view,
actionTarget: self,
selector: #selector(update)
)
updateLink.requiresContinuousUpdates = true
updateLink.isEnabled = true
@objc func update(updateLink: UIUpdateLink,
updateInfo: UIUpdateInfo) {
view.center.y = sin(updateInfo.modelTime)
* 100 + view.bounds.midY
}
关键点:
- 构造时绑定 view,view 加入可见 window 时自动激活,移除时自动停用
updateInfo.modelTime提供与 display 刷新同步的时间,比手动计时更准确requiresContinuousUpdates = true时每帧都触发;设为false则只在有其他更新驱动时触发(如手势、layer 变化)- 支持低延迟模式,适合绘图类应用
其他更新
- Zoom transition:iOS 18 新增 zoom transition,支持 navigation 和 presentation,且全程可交互——可以在转场过程中拖拽中断(02:50)
- SF Symbols 动画:新增
.wiggle、.breathe、.rotate三种预设动画,以及.periodic(指定重复次数和间隔)和.continuous行为。Magic Replace 自动处理 badge 和 slash 的过渡动画(12:47) - UICanvasFeedbackGenerator:新增的反馈生成器,适合大型画布应用,创建时绑定 view,触发反馈时传入位置坐标(15:02)
- Text formatting panel:UITextView 新增格式化面板,包含字体、字号、列表、高亮等控件,通过
textFormattingConfiguration自定义布局,也可设为nil退出(16:14) - Text highlight:通过
textHighlightStyle和textHighlightColorScheme两个属性实现文本高亮,有 5 种预设配色方案(16:49) - Writing Tools:UITextView 默认集成 Writing Tools UI,editable text view 获得内联编辑支持,non-editable 获得叠加面板(17:40)
- Apple Pencil Pro:squeeze 手势、barrel-roll 角度、undo slider,以及 PKToolPicker 支持自定义工具(19:01)
核心启发
-
做什么:用
UIView.animate(.spring)替代UIViewPropertyAnimator编写交互式动画。为什么值得做:SwiftUI spring 的 velocity 传递在交互/非交互阶段之间无缝衔接,代码量减少一半以上。怎么开始:在手势的.changed和.ended分支分别调用UIView.animate(.interactiveSpring)和UIView.animate(.spring),删除旧的 animator 代码。 -
做什么:把
UITabBarItem数组迁移到UITab/UITabGroup结构。为什么值得做:新 API 自动适配 tab bar 和 sidebar 两种形态,Mac Catalyst 和 visionOS 上也能获得原生体验,不再需要为不同平台写两套导航逻辑。怎么开始:在UITabBarController的viewDidLoad中用UITab和UITabGroup构建tabs数组,删除tabBar.items的手动设置。 -
做什么:删除
registerForTraitChanges中的手动 trait 注册代码。为什么值得做:automatic trait tracking 在layoutSubviews等方法中自动追踪 trait 依赖,手动注册是多余的。怎么开始:在 Xcode 中全局搜索registerForTraitChanges,逐一检查对应 trait 是否在layoutSubviews或drawRect:中已被访问,如果是就删掉注册代码。 -
做什么:用
UIListContentConfiguration.cell()和UIBackgroundConfiguration.listCell()替代手动判断 list 样式的代码。为什么值得做:list environment trait 自动适配 sidebar 和 insetGrouped 外观,cell 在旋转屏幕或进入 split view 时不需要手动 reconfigure。怎么开始:找到 cell 配置代码中判断isSidebar的分支,替换为统一的.cell()/.listCell()构造器。
关联 Session
- Evolve your document launch experience — 定制 document-based app 的启动界面,支持模板文档创建
- Elevate your tab and sidebar experience in iPadOS — UITab / UITabGroup 的完整用法和 sidebar 自定义
- Enhance your UI animations and transitions — zoom transition 和 SwiftUI Animation 驱动 UIKit 的详细演示
- What’s new in SF Symbols 6 — 新增的 symbol 动画预设和 Magic Replace 行为
- Squeeze the most out of Apple Pencil — Apple Pencil Pro 的 squeeze、barrel-roll 和 PKToolPicker 自定义工具
评论
GitHub Issues · utterances