Animate symbols in your app
System & UI 进阶 20m

在应用中为 SF Symbols 添加动画

Animate symbols in your app

2023年6月5日

在 Apple 官方观看视频

一句话判断

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 的关键:

  1. Discrete(离散):一次性动画,播放完就结束。Bounce 属于此类。
  2. Indefinite(持续):改变符号状态并保持,直到显式移除。Scale、Variable Color 的默认用法属于此类。
  3. Transition(过渡):Appear 和 Disappear,控制符号的显示与隐藏。
  4. 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 场景(比如命令行工具)生成符号图像,也可以直接使用。
WWDC 2023