ハイライト
iOS 17 と macOS Sonoma では Symbols framework が導入され、カスタムシンボルを含むすべての SF Symbols に統一されたアニメーション API が提供されます。開発者は
.symbolEffect()修飾子とドット区切りのエフェクト名を使って、SwiftUI、UIKit、AppKit に Bounce、Pulse、Variable Color などのアニメーション効果を追加できます。
主要内容
シンボルアニメーションの 4 つの動作
以前はアイコンを動かすために UIView.animate や withAnimation を手書きし、キーフレームも自分で計算する必要がありました。Symbols framework はこれを簡単にし、必要な効果を宣言するだけで、アニメーションの詳細はシステムが処理します。
すべての効果は 4 種類の動作に分類できます(04:25):
| 動作 | 説明 | 代表的な効果 |
|---|---|---|
| Discrete(離散) | 一度だけ再生され、完了後に自動終了するアニメーション | Bounce |
| Indefinite(継続) | シンボルの状態を変更して維持し、明示的な削除が必要 | Scale、Variable Color |
| Transition(遷移) | シンボルの表示と非表示を制御 | Appear、Disappear |
| Content Transition(コンテンツ遷移) | 2 つのシンボル間を切り替える | Replace |
各動作は Swift protocol に対応しています。効果はこれらの protocol に準拠することで、どの動作をサポートするかを宣言します。たとえば Variable Color は Indefinite と Discrete の両方をサポートし、実際の動作は API の使い方で決まります。
ドット区切りの効果名
Symbols framework の設計上の特徴のひとつは、効果名にドット区切りの構文を使うことです(02:03):
.bounce
.variableColor.iterative.reversing
.scale.up
これらは文字列ではなく実際の Swift コードです。Xcode は各部分を自動補完し、設定ミスはコンパイル時に検出できます。SF Symbols app の Animation タブで効果をプレビューし、その効果名をそのままコードへコピーできます。
SwiftUI で使う
SwiftUI には symbolEffect 修飾子が追加されました(05:11)。もっとも簡単な使い方は、Image に直接効果を追加することです:
Image(systemName: "wifi.router")
.symbolEffect(.variableColor.iterative.reversing)
.symbolEffect(.scale.up)
効果は重ねて適用できます。上のコードでは Variable Color アニメーションと拡大効果を同時に適用しています。
UIKit/AppKit で使う
UIKit 和 AppKit 的 UIImageView/NSImageView 新增了 addSymbolEffect 方法(05:30):
let imageView: UIImageView = ...
imageView.addSymbolEffect(.variableColor.iterative.reversing)
imageView.addSymbolEffect(.scale.up)
詳細
Indefinite 効果:アニメーションの開始と停止を制御する
Indefinite 効果は削除されるまで継続して再生されます。SwiftUI では isActive パラメータで制御します(06:49):
struct ContentView: View {
@State var isConnectingToInternet: Bool = true
var body: some View {
Image(systemName: "wifi.router")
.symbolEffect(
.variableColor.iterative.reversing,
isActive: isConnectingToInternet
)
}
}
キーポイント:
isActiveがtrueのときにアニメーションが開始されるisActiveがfalseになるとアニメーションがなめらかに終了する- ネットワーク接続やファイルアップロードなど、「進行中」の状態表示に適している
UIKit/AppKit では removeSymbolEffect(ofType:) でアニメーションを停止します(07:09):
imageView.addSymbolEffect(.variableColor.iterative.reversing)
// 接続に成功したらアニメーションを停止
imageView.removeSymbolEffect(ofType: .variableColor)
Discrete 効果:イベントに応答する一度だけのアニメーション
Discrete 効果は値が変化したときに一度だけ発火します。Bounce は代表的な Discrete 効果です(08:26):
struct ContentView: View {
@State var bounceValue: Int = 0
var body: some View {
VStack {
Image(systemName: "antenna.radiowaves.left.and.right")
.symbolEffect(
.bounce,
options: .repeat(2),
value: bounceValue
)
Button("Animate") {
bounceValue += 1
}
}
}
}
キーポイント:
valueパラメータが変化したときにアニメーションが発火するoptions: .repeat(2)によりアニメーションを 2 回繰り返す- ボタンをタップした後のフィードバックなど、ユーザー操作への応答に適している
Pulse と Variable Color も Discrete 動作をサポートしています。value で発火させると、一度だけのアニメーションシーケンスとして再生されます。
Replace 効果:シンボル切り替えアニメーション
Replace 効果は、2 つのシンボル間の切り替えにアニメーション遷移を加えます(09:40):
struct ContentView: View {
@State var isPaused: Bool = false
var body: some View {
Button {
isPaused.toggle()
} label: {
Image(systemName: isPaused ? "pause.fill" : "play.fill")
.contentTransition(.symbolEffect(.replace.offUp))
}
}
}
キーポイント:
contentTransition(.symbolEffect(.replace.offUp))は、シンボル切り替え時に上方向へ抜ける置換アニメーションを付ける.offUp、.offDownなどのオプションで置換方向を制御する- 再生/一時停止、お気に入り/解除などの状態切り替えに適している
UIKit では setSymbolImage(_:contentTransition:) で同じ効果を実現します(09:57):
let pauseImage = UIImage(systemName: "pause.fill")!
imageView.setSymbolImage(pauseImage, contentTransition: .replace.offUp)
Appear と Disappear:2 つの使い方
Appear と Disappear は 2 種類の動作モードをサポートします(10:15):
方法 1:Indefinite 動作(レイアウトを変更しない)
シンボルはその場で消えたり現れたりし、周囲のビューのレイアウトは変わりません:
Image(systemName: "moon.stars")
.symbolEffect(.disappear, isActive: isMoonHidden)
方法 2:Transition 動作(レイアウトを変更する)
シンボルがビュー階層に挿入または削除され、周囲のビューが再レイアウトされます:
if !isMoonHidden {
Image(systemName: "moon.stars")
.transition(.symbolEffect(.disappear.down))
}
キーポイント:
- Indefinite 方式はバッジや状態マークの表示/非表示に適している
- Transition 方式は条件付きで描画するビューに適している
.automatic遷移効果は最適なアニメーションを自動選択する
UIKit では completion handler を使い、アニメーション終了後にビューを削除できます(12:59):
imageView.addSymbolEffect(.disappear) { context in
if let imageView = context.sender as? UIImageView, context.isFinished {
imageView.removeFromSuperview()
}
}
効果の伝播と無効化
SwiftUI では symbolEffect 修飾子が下方向に伝播します(14:19):
VStack {
Image(systemName: "figure.walk")
.symbolEffectsRemoved()
Image(systemName: "car")
Image(systemName: "tram")
}
.symbolEffect(.pulse)
キーポイント:
- 親ビューの
.symbolEffect(.pulse)は、すべての子ビュー内のシンボルをパルスさせる .symbolEffectsRemoved()は特定のビューが効果を継承するのを防ぐ- リストやツールバーでアニメーションを一括制御するのに適している
アニメーションなしで効果を適用する
場合によっては、シンボルを初期状態から特定の効果状態にしておき、遷移アニメーションは行いたくないことがあります。SwiftUI では Transaction でアニメーションを無効化します(14:55):
Image(systemName: "iphone.radiowaves.left.and.right")
.symbolEffect(.scale.up, isActive: isScaledUp)
.onAppear {
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
isScaledUp = true
}
}
UIKit/AppKit では animated: false パラメータを直接使います:
imageView.addSymbolEffect(.disappear, animated: false)
Variable Value のなめらかな遷移
iOS 16 で導入された Variable Value は、iOS 17 で自動アニメーションに対応しました(15:44):
struct ContentView: View {
@State var signalLevel: Double = 0.5
var body: some View {
Image(systemName: "wifi", variableValue: signalLevel)
}
}
SwiftUI では variableValue が変化すると、自動でクロスフェードアニメーションが発生します。UIKit では setSymbolImage(_:contentTransition: .automatic) で同じ効果を実現します。
UIKit コントロールの組み込みアニメーション
iOS 17 では一部の UIKit コントロールにシンボルアニメーションが組み込まれています(13:47):
UISliderのつまみは両端に到達するとバウンスするUIBarButtonItemはaddSymbolEffectをサポートするUIControlとUIBarButtonItemには、アニメーション再生を制御するisSymbolAnimationEnabledプロパティが追加された
重要ポイント
ネットワーク状態インジケータに Variable Color アニメーションを追加する
- 何をするか:ナビゲーションバーやツールバーに Wi-Fi またはルーターのアイコンを置き、接続中に Variable Color アニメーションを再生する
- 取り組む価値:Discrete + Indefinite 効果の典型的な用途で、接続中は継続再生し、接続成功後になめらかに停止できる
- 始め方:
.symbolEffect(.variableColor.iterative.reversing, isActive: isConnecting)を使う
Replace 効果で再生コントロールボタンを改善する
- 何をするか:再生/一時停止ボタンのアイコン切り替えに Replace アニメーションを追加する
- 取り組む価値:Replace 効果により 2 つのシンボル間の切り替えに方向感が生まれ、画像を直接差し替えるより質感が高まる
- 始め方:
.contentTransition(.symbolEffect(.replace.offUp))を使う
リスト操作に Bounce フィードバックを追加する
- 何をするか:ユーザーがお気に入りや削除などのボタンをタップしたとき、アイコンを一度バウンスさせる
- 取り組む価値:Bounce は Discrete 効果で、操作フィードバックに自然に使え、静的なハイライトより生き生きと見える
- 始め方:
.symbolEffect(.bounce, value: tapCount)を使い、タップごとにtapCountを増やす
Disappear 遷移で条件付きビューを作る
- 何をするか:条件付きで表示するアイコンに
.transition(.symbolEffect(.disappear))を追加し、表示/非表示のアニメーションを付ける - 取り組む価値:Transition 動作はレイアウトを変更するため、バッジやヒントマークの表示/非表示に適している
- 始め方:
if条件内の Image に.transition(.symbolEffect(.automatic))を追加する
関連セッション
- Create animated symbols — SF Symbols app でアニメーションシンボルを作成・プレビュー
- What’s new in SF Symbols 5 — SF Symbols 5 の新機能概要
- Animate with springs — SwiftUI のスプリングアニメーションの基礎原理
コメント
GitHub Issues · utterances