Highlight
iOS 18 新增的 Zoom Transition 和 SwiftUI-UIKit 动画打通,让跨框架的连续交互变得更简单。
核心内容
开发一个流畅的转场动画通常需要大量代码。在 UIKit 中要写 CAAnimation,在 SwiftUI 中要写 withAnimation,如果视图层级是混合的,还要手动同步两边的动画状态。
iOS 18 解决了这个问题。首先是 Zoom Transition:点击列表中的 cell,它会放大成全屏详情页。这不是一个简单的视觉特效——转场全程可交互,你可以随时抓取拖拽,甚至转场进行到一半时反向操作。它不只是 Navigation Push 可用,fullScreenCover 和 sheet 也支持。
第二个更底层的改变是 SwiftUI Animation 现在可以直接驱动 UIView 和 NSView。不用再写 UIView.animate,直接传一个 SwiftUI 动画类型即可。这意味着在混合视图层级里,可以用同一套动画 API 统一管理所有视图。
Session 还深入讲解了 Zoom Transition 在 UIKit 中的生命周期回调时序。当你”在 Push 进行中按返回键”这种复杂场景发生时,系统不会取消 Push,而是将它立即完成并转换为 Pop。从视图控制器的角度看,它总是会到达 Appeared 状态,所有回调(viewWillAppear/willAppear/didAppear)都会按预期顺序执行。
详细内容
Zoom Transition 基础用法
SwiftUI 中的 Zoom Transition 需要两个步骤:
NavigationLink {
BraceletEditor(bracelet)
.navigationTransitionStyle(
.zoom(
sourceID: bracelet.id,
in: braceletList
)
)
} label: {
BraceletPreview(bracelet)
}
.matchedTransitionSource(
id: bracelet.id,
in: braceletList
)
关键点:
navigationTransitionStyle(.zoom(...))声明目标视图使用 zoom 转场(2:10).matchedTransitionSource标记源视图,两者用相同的id和namespace配对- 转场时,源视图会 morph 成目标视图,保持同一 UI 元素在屏幕上的连续性
UIKit 中用法类似,在 Push 前设置 preferredTransition:
func showEditor(for bracelet: Bracelet) {
let braceletEditor = BraceletEditor(bracelet)
braceletEditor.preferredTransition = .zoom { context in
let editor = context.zoomedViewController
as! BraceletEditor
return cell(for: editor.bracelet)
}
navigationController?.pushViewController(braceletEditor, animated: true)
}
关键点:
preferredTransition设置在目标视图控制器上(3:02)- closure 会在 zoom in 和 zoom out 时各调用一次
- 应该返回一个稳定的标识符(如 model 对象)而非直接捕获视图,避免 UICollectionView 复用导致的问题
- 从
context取当前数据,支持详情页内左右滑动后返回正确源视图
转场生命周期和兼容性
UIKit 的可中断转场有一个重要设计:中断的 Push 不会被取消,而是立即完成并转为 Pop。
这意味着:
- Push 的
viewWillAppear→viewDidAppear一定会被调用 - 接着在同一 runloop 循环中,Pop 开始,触发
viewWillDisappear→viewDidDisappear - 从代码角度看,就像 Push 正常完成后立即开始 Pop
这是为了兼容现有代码。如果取消 Push,视图控制器从未”真正出现过”,很多假设”只在 viewWillAppear 做一次”的代码会出问题。
SwiftUI Animation 驱动 UIKit 视图
iOS 18 最大的打通是 SwiftUI Animation 类型可以直接用于 UIView 动画:
UIView.animate(.spring(duration: 0.5)) {
bead.center = endOfBracelet
}
关键点:
- 参数不再是传统的
UIView.AnimationOptions,而是 SwiftUI 的Animation类型(8:39) - 支持所有 SwiftUI 动画类型,包括 CustomAnimations
- 内部实现不创建 CAAnimation,直接操作 layer 的 presentation values
- presentation layer 仍会正确反映动画状态
在 UIViewRepresentable 中,用新的 context.animate 桥接动画:
struct BeadBoxWrapper: UIViewRepresentable {
@Binding var isOpen: Bool
func updateUIView(_ box: BeadBox, context: Context) {
context.animate {
box.lid.center.y = isOpen ? -100 : 100
}
}
}
struct BraceletEditor: View {
@State private var isBeadBoxOpen = false
var body: some View {
BeadBoxWrapper($isBeadBoxOpen.animated())
.onTapGesture {
isBeadBoxOpen.toggle()
}
}
}
关键点:
.animated()modifier 让 binding 变化携带动画信息(9:56)context.animate捕获当前 Transaction 上的动画,应用到 UIView 变更- 如果 Transaction 没有动画,代码会立即执行,无需手动判断
- SwiftUI 和 UIView 的动画完美同步
手势驱动的连续动画
拖拽手势结束后保持速度,传统做法需要计算初始 velocity:
switch gesture.state {
case .changed:
UIView.animate(.interactiveSpring) {
bead.center = gesture.translation
}
case .ended:
UIView.animate(.spring) {
bead.center = endOfBracelet
}
}
关键点:
- 手势进行中用
.interactiveSpring,每次变化创建新动画,彼此 retarget(11:39) - 手势结束后用普通
.spring,自动继承之前的 velocity - 无需手动计算 unit velocity,SwiftUI 动画会自动处理
- 这是 SwiftUI 动画的优势:连续事件触发的动画会自动合并并保持速度
核心启发
-
用 Zoom Transition 替代默认 Push 动画:如果你的 App 有”从列表大图进入详情页”的场景,直接启用 Zoom Transition。这是 iOS 18 系统级设计语言,Photos、Messages 都在用。只需添加 modifier 和 source 标记,视觉连续性显著提升。
-
在混合视图层级用 SwiftUI Animation 统一管理动效:不要在 UIKit 部分手写 UIView.animate,在 SwiftUI 部分用 withAnimation。新 API 让你用一套动画类型驱动所有视图。特别是 UIViewRepresentable 里的 UIKit 视图,用
context.animate桥接,外部动画配置会自动传递进去。 -
拖拽交互用 interactiveSpring 简化速度计算:凡是”拖拽后弹回”的场景(列表拖动刷新、卡片拖拽),用
.interactiveSpring配合手势变化,手势结束时的普通 spring 动画会自动继承速度。无需手动计算 velocity/distance 比值,代码更简洁、手感更自然。
关联 Session
- What’s new in SwiftUI — SwiftUI 框架年度更新概览,涵盖 TabView、窗口管理、zoom transition 等新特性
- What’s new in UIKit — UIKit 框架的新特性,包括转场、文本输入和导航栏的更新
- Create custom visual effects with SwiftUI — 学习如何在 SwiftUI 中构建自定义视觉效果,包括滚动特效和颜色处理
- Elevate your tab and sidebar experience in iPadOS — iPadOS 18 新导航系统,支持 Tab Bar 和 Sidebar 之间的灵活切换
- Create custom hover effects in visionOS — 学习如何开发视线触发的自定义悬停效果
评论
GitHub Issues · utterances