在应用中为 SF Symbols 添加动画
Animate symbols in your app
2023年6月5日
一句话判断
SF Symbols 5 带来的动画体系不是”锦上添花”级别的小功能——它是一套有行为分类、有配置选项、有完整生命周期管理的动画框架,足以改变你对图标动画的使用方式。
这场 Session 讲了什么
iOS 17 和 macOS Sonoma 中,Apple 为 SF Symbols 引入了名为 Symbol Effects 的全新动画系统。这些动画适用于所有内置符号和自定义符号。
七种基础动画效果:Bounce(弹跳)、Pulse(脉冲)、Variable Color(变色)、Scale(缩放)、Appear(出现)、Disappear(消失)、Replace(替换)。
API 设计上,每个效果用点分隔的命名方式表达,比如 .bounce、.variableColor.iterative.reversing。这不是字符串,是真实的 Swift 代码,Xcode 会自动补全,配置错误在编译期就能捕获。Symbols framework 是底层框架,SwiftUI、UIKit、AppKit 都免费包含。
四种行为分类是理解这套 API 的关键:
- Discrete(离散):一次性动画,播放完就结束。Bounce 属于此类。
- Indefinite(持续):改变符号状态并保持,直到显式移除。Scale、Variable Color 的默认用法属于此类。
- Transition(过渡):Appear 和 Disappear,控制符号的显示与隐藏。
- Content Transition(内容过渡):Replace,在两个不同符号之间切换动画。
每种行为对应一个 protocol,效果通过遵循这些 protocol 来声明自己支持哪些行为。比如 Variable Color 同时支持 Indefinite 和 Discrete——当你持续播放时它是 Indefinite,当你只播放一次时它是 Discrete。
值得深挖的点
点分隔命名系统的设计哲学值得琢磨。Apple 把效果类型、方向、配置选项全部用 .propertyName 的方式链式调用,比如 .variableColor.iterative.reversing。这让代码可读性极高,同时编译器能做完整检查。这种 API 设计风格在 Apple 的新框架中越来越常见。
**Appear/Disappear 的两种”平行宇宙”**是个巧妙的设计。一种情况下符号消失但 image view 仍在视图层级中,布局不变;另一种情况下 image view 真正被添加和移除,周围视图会重新布局。两种行为分别对应 Indefinite 和 Transition 两种 protocol。
Replace 作为 Content Transition 的实现方式也很有意思。它不是简单的淡入淡出,而是根据两个符号的形状差异生成动画路径。这意味着从 pause 到 play 的替换动画和从 heart 到 heart.fill 的替换动画看起来会完全不同。
代码片段
SwiftUI 中添加持续动画效果:
// Variable Color 持续动画——适合表示网络连接等状态
Image(systemName: "wifi")
.symbolEffect(.variableColor.iterative.reversing, isActive: isConnecting)
// isConnecting 为 false 时动画自动停止
SwiftUI 中触发离散动画(基于值变化):
@State private var bounceValue = 0
Image(systemName: "bell")
// 每当 bounceValue 变化时触发弹跳
.symbolEffect(.bounce, value: bounceValue)
Button("提醒我") {
bounceValue += 1 // 触发一次弹跳
}
UIKit 中使用 Symbol Effects:
// 添加持续效果
imageView.addSymbolEffect(.variableColor.iterative.reversing)
// 移除持续效果
imageView.removeSymbolEffect(.variableColor)
// 添加离散效果(自动播放一次,无需移除)
imageView.addSymbolEffect(.bounce)
// 指定重复次数
imageView.addSymbolEffect(.bounce, options: .repeat(count: 2))
SwiftUI 中使用 Replace 内容过渡:
// 符号切换时的平滑过渡动画
Button {
isPlaying.toggle()
} label: {
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
.contentTransition(.symbolEffect(.replace))
}
UIKit 中使用符号内容过渡:
// 在两个符号之间动画切换
imageView.setSymbolImage(newImage, contentTransition: .replace)
最佳实践
- 用 SF Symbols app 的 Animation 标签页探索所有效果和配置选项,可以直接复制效果名称到代码里。
- Indefinite 效果一定要设置
isActive或提供移除机制。网络连接符号的 Variable Color 动画在连接成功后应该停止,否则会让用户困惑。 - Discrete 效果配合 SwiftUI 的
value参数使用,避免手动管理触发时机。让 SwiftUI 的声明式范式帮你处理。 - 同一个符号可以叠加多个效果——Variable Color 加 Scale.up 的组合在状态指示器中效果很好。
- Replace 效果选择符号对时,尽量选形状相近的符号,过渡动画会更流畅自然。
- 不要过度使用动画。每个界面中同时播放的 symbol animation 不超过 2-3 个,多了会分散注意力。
还有什么值得关注
- 所有动画效果都适用于自定义符号,前提是你按 Apple 的模板正确设计了自定义符号的图层结构。
- Pulse 效果不仅支持 Indefinite 也支持 Discrete,这比大多数开发者预期的更灵活。
- Symbol Effects 和 Accessibility 有天然配合——动画可以提供视觉反馈给看不到符号标签的用户。
- 搭配 Session 10036(无障碍)的内容,可以用 Bounce 动画替代自定义的按钮反馈动画,同时自动获得无障碍支持。
- Symbols framework 是独立框架,如果需要在非 UI 场景(比如命令行工具)生成符号图像,也可以直接使用。