← すべての記事

Symbol Effects:すべてのアイコンに使えるSwiftUI標準のアニメーション語彙

SF Symbols 5(iOS 17)では、すべてのiOSアプリが使えるアニメーション語彙が登場しました。SF Symbols 6(iOS 18)でさらに拡張され、SF Symbols 7(iOS 26)でも再び拡張されています。それでも、ほとんどのアプリはアイコンを静的な画像として表示したままです。アニメーション語彙はSF Symbols.appの中、そして単一のSwiftUIモディファイアの背後に用意されており、採用コストはゼロ、Appleのアニメーションチームによってネイティブに感じられるよう設計されており、アプリ側で何もせずともアクセシビリティ設定を尊重します。この見過ごしは、現在出荷されているiOSアプリで最もよく見られる品質低下パターンの1つです。

この語彙は、アイコンができることを動詞として名付けています——bounce(弾む)、pulse(脈打つ)、scale(拡縮する)、別のシンボルへのreplace(置き換え)、レイヤーを横断した色のアニメーション、breathe(呼吸する)、rotate(回転する)、wiggle(揺れる)、appear(現れる)、disappear(消える)。それぞれの動詞には固有の意味、固有の視聴覚的キャラクター、そしてアプリの動作の中でその使い所が定められています。システムが開発者に動詞を提供し、開発者の役割は、どの瞬間にどの動詞を使うかを選ぶことです。

TL;DR

  • SwiftUIの.symbolEffect(...)モディファイアは、.bounce.pulse.scale.variableColor.breathe.rotate.wiggle.appear.disappearを含む効果で、任意のSF Symbolをアニメーションさせます1
  • もう1つ別のAPIとして.contentTransition(.symbolEffect(.replace))があり、異なる2つのSF Symbol間で設計済みのトランジションを実行します。replace効果はSymbolEffectではなくContentTransitionに属しています。両者は協調して動作しますが、別のAPIです。
  • 効果は1回だけ実行する(value:による値トリガー)、状態がtrueの間保持する(isActive:による状態トリガー)、または継続的に繰り返す(options: .repeating)といった形で動作します。
  • パフォーマンスは事実上ゼロコストです。アニメーションはSF Symbolアセットを通じてレンダリングされ、GPU上でディスパッチされます。アプリが負担するのは、トリガーのタイミングを判断するために値を読む程度のコストです。
  • アクセシビリティも対応済みで、モーションの強い効果はシステムのReduce Motion設定をアプリ側のコードなしで尊重します2

動詞別の効果一覧

それぞれの効果は、アイコンができることを名付けたものです。その瞬間にユーザーに何を感じてほしいか、で動詞を選びましょう。

.bounce

1回の弾性のあるバウンス。.upまたは.downを指定できます。確認、通知の到着、リフレッシュの完了など、短い肯定的なイベントを示します。視覚的に「はい、起こりました」と伝えるものです。value:パラメータでトリガーします。値が変わるたびにシンボルが1回バウンスします。

@State private var unreadCount = 0

Image(systemName: "bell.badge")
    .symbolEffect(.bounce, value: unreadCount)

値トリガーのパターンでは、変更ごとにバウンスがちょうど1回実行されます。状態機械もアニメーションのタイミング計算もレイアウトへの影響もありません。

.pulse

繰り返しの不透明度パルス。警告にはならない程度に注意を引きたい、継続中の状態を示します。よく使われるのは、着信表示、録画中のドット、「ライブ」バッジなどです。パルスは取り除かれるまで継続して動き続けます。

Image(systemName: "record.circle")
    .symbolEffect(.pulse, options: .repeating)
    .foregroundStyle(.red)

.repeatingオプションでパルスが続き、.repeatingがない場合は1回だけパルスします。

.scale

拡大または縮小の処理。アイコンの重要度の状態変化を強調します。ボタンが押された、項目が選択された、コントロールがフォーカスされた、といった場面です。scale効果は方向修飾子(.scale.up.scale.down)と双方向のデフォルトをサポートし、保持中はシンボルが拡縮されたままになります。

Image(systemName: "heart")
    .symbolEffect(.scale, isActive: isLiked)

isActive:パラメータは別のトリガーパターンです。booleanがtrueの間は効果が保持され、falseになると効果が解除されます。アイコンのアニメーションが状態を直接追従すべきトグル状態に適したパターンです。

.variableColor

階層を意識した色のアニメーション。シンボルのレイヤーを順に点灯させます(Wi-Fiの信号バーが埋まっていく様子や、バッテリーの充電を思い浮かべてください)。動作オプションで方向を決めます——.iterativeは前進、.cumulativeは埋めて保持、.reversingは前進してから後退——そして.dimInactiveLayersはオフ状態のレイヤーを暗くするか消すかを制御します。

Image(systemName: "wifi")
    .symbolEffect(
        .variableColor.iterative.reversing.dimInactiveLayers,
        options: .repeating
    )

variable-color効果は、iOSのControl Center、設定、そしてほとんどのAppleアプリが「信号強度」や「レベルインジケーター」シンボルに使っているものです。プラットフォーム全体でアニメーションが共有されているため、このパターンは横断的に認識されます。

.replace

シンボルの入れ替え時に、SwiftUIのデフォルトのクロスフェードを破る効果です。.contentTransition(.symbolEffect(.replace))は2つのシンボル間で設計済みのトランジションを実行します。オプションとして.downUp(離れるシンボルが下に動き、現れるシンボルが上に動く)またはデフォルトの拡縮とフェードの動作が選べます。

@State private var isPlaying = false

Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill")
    .contentTransition(.symbolEffect(.replace))
    .onTapGesture { isPlaying.toggle() }

このパターンは、トグルボタン全般(再生/一時停止、ミュート/ミュート解除、展開/折りたたみ、いいね/いいね解除)に最適です。SwiftUIのデフォルトの動作はシンボルを意識しないクロスフェードですが、symbol-effectのreplaceはシンボルの構造を尊重した、設計済みのトランジションです。

.appear.disappear

レイアウトに入る/離れるアイコン用の、条件付き表示/非表示アニメーション。SwiftUIのビュートランジションと組み合わせて、シンボルの登場を唐突ではなく意図的に感じさせます。

Image(systemName: "checkmark.circle.fill")
    .symbolEffect(.appear, isActive: isVisible)

アイコンの存在自体に意味がある場合に使いましょう(確認インジケーター、完了時に現れるステータスバッジなど)。常に表示されているアイコンには、これらの効果は使う価値がありません。

.breathe

ゆっくりとした拡縮と不透明度の呼吸モーション(iOS 18+)。緊急性なくユーザーの視線を引きたい、穏やかでアンビエントな状態を示します。瞑想タイマー、アンビエントオーディオのインジケーター、アイドル状態などに最適です。

.rotate.wiggle

回転と揺れのアニメーション(iOS 18+)。Rotateはローディング状態(更新中の矢印、同期中の歯車)に合います。Wiggleは「これは編集可能、ドラッグして」「注意が必要」といったプロンプトに合います。両方とも方向と速度のオプションを持ちます。

文法:トリガーとオプション

すべての効果は、同じ3つのトリガーパターンをサポートします。その瞬間に合うものを選びましょう。

値トリガーvalue:パラメータ)。バインドされた値が変わるたびに、効果が1回実行されます。カウントの増加、状態遷移など、イベントに有効です。システムが値の同一性を読み、効果を実行し、リセットします。

状態トリガーisActive:パラメータ)。バインドされたbooleanがtrueの間、効果が実行されます。係合中はパルスすべきトグル、録画中はパルスすべき録画インジケーターなど、保持される状態に有効です。

継続実行options: .repeating)。モディファイアが取り除かれるまで、効果が継続して実行されます。ローディングインジケーター、点滅するライブバッジ、呼吸する瞑想アイコンなど、アンビエントなシグナルに有効です。

各効果のオプションは動作を細かく調整します——.speed(_)はアニメーションのテンポを調整し、.nonRepeatingは繰り返しを好む効果のデフォルトを上書きし、方向修飾子(.up/.down/.iterative/.cumulative/.reversing)が動きを形作ります。各オプションは小さなものですが、組み合わせることで完全な語彙を生み出します。

コツ:状態を横断するシンボルバリアント

もう少し繊細なパターンとして、状態駆動の名前で解決されるImage(systemName:)にsymbol effectsを組み合わせる方法があります。状態駆動のシンボル選択と.contentTransition(.symbolEffect(.replace))を組み合わせれば、1つのImageビューで多くの状態の間を、手作業のアニメーションコードなしでアニメーションできます。

@State private var connectionState: ConnectionState = .disconnected

var symbol: String {
    switch connectionState {
    case .disconnected: "wifi.slash"
    case .connecting:   "wifi.exclamationmark"
    case .weak:         "wifi.low"
    case .medium:       "wifi.medium"
    case .strong:       "wifi"
    }
}

Image(systemName: symbol)
    .contentTransition(.symbolEffect(.replace))
    .symbolEffect(.variableColor.iterative, options: .repeating, value: connectionState == .connecting)

5つのシンボル状態、2層に重ねた効果(シンボル間のreplaceトランジションと、接続中のvariable color)、1つのImageビュー、カスタムアニメーションコードなし。SF Symbolsは一貫性のあるファミリーとして設計されているため、このパターンが成立します。複数の強度を持つ同じ接続アイコンは、複数の解像度で表現された1つの視覚的アイデアなのです。

パフォーマンス:コストが事実上ゼロである理由

Symbol effectsはGPU上でアニメーションし、SF Symbolsが静的レンダリングですでに使っているのと同じレンダリングパスでディスパッチされます。アニメーションはシンボルアセット自体にエンコードされており、アプリは値を読み、システムがアニメーションをスケジュールし、GPUが実行します。フレームごとのレイアウト処理も、ビュー階層の混乱も、objectWillChange.send()の連鎖もありません。

開発者が支払うコストは、トリガーを駆動するバインディングのコスト——@State@Bindable@Observableプロパティ——だけです。このコストはアニメーションの有無に関係なく存在します。アニメーション自体は、静的レンダリングに対する事実上無料のアップグレードなのです。

このコストの低さは、ライブカメラのUI、多数のアイコンを含むリストセル、60 fpsが譲れないあらゆるビュー階層において重要です。Symbol effectsはカスタムwithAnimationブロックのパフォーマンスコストなしで自由に適用できます。基盤エンジンが処理を引き受けるからです。

アクセシビリティ:Reduce Motionは既に尊重されている

Symbol effectsはシステムのReduce Motion設定を自動的に尊重します。動きの大きい効果(.bounce.scale.rotate.wiggle)は、Reduce Motionが有効な場合に減衰またはスキップされます。主に不透明度ベースの効果(.pulse.breathe)は、モーションへの感受性の問題を引き起こさないため、そのまま残る傾向があります。

この動作はSwiftUIのモディファイアに組み込まれており、各効果に対して開発者がif accessibilityReduceMotion { ... } else { ... }と書く必要はありません。Apple Human Interface Guidelinesは、これらの効果がシステム設定を尊重することを明記しており、SwiftUIの実装はそのドキュメントに合致しています。

アクセシビリティを最優先するアプリ(前庭障害のあるユーザー、ロービジョンのユーザー、モーションに敏感なユーザー向けのアプリ)を作る開発者にとって、symbol effectsは正しい選択です。アプリ側でのアクセシビリティ作業がゼロだからです。

Symbol Effectsを使うべきでない場面

挙げておくべき失敗パターンが3つあります。

すべてのアイコンに常時効果を付ける。 パルス、breathe、bounceするアイコンで埋め尽くされたビューは、静的なビューよりも読みづらくなります。各効果は特定の瞬間を示すべきであり、効果がそこら中にあるとノイズになります。「この効果はどの瞬間を示すのか?」と問う規律が必要です。答えが「アイコンが存在すること」なら、その効果は削りましょう。

コンテンツと喧嘩する効果。 wiggleするアイコン付きの項目が並ぶリストは「編集して」とは言いません。「すべてが壊れている」と言ってしまいます。効果はユーザーフローの瞬間と整合していなければなりません。Wiggleは編集モードの編集可能なグリッドに合う動詞であり、デフォルト状態のコンテンツリストには合いません。

テストせずにLiquid Glassの上に効果を載せる。 Liquid Glass(Liquid Glass SwiftUI Patternsで扱っています)は背後にあるものを屈折させます。ガラス越しにバウンスや回転するアイコンは、動く屈折を生み、その下のコンテンツと競合する可能性があります。コミットする前に、実機ハードウェアで組み合わせをテストしましょう。

デフォルトの規律——各効果はオプトインで、特定のユーザーにとって意味のある瞬間に紐付き、アクセシビリティとパフォーマンスについてテスト済み。語彙は豊富にあります。それを機能させるのは編集の目です。

SF Symbols 7(iOS 26)の新機能

AppleのSF Symbolsの年次リリースでは、通常、数千の新しいシンボルが追加され、既存のシンボルが洗練されます。iOS 26のsymbol-effect APIについて、控えめにまとめると次の通りです。

variable-colorの階層が拡張。 階層を意識した既存のシンボルのうち、より細かいvariable-colorアニメーションが入ったものが増えました。以前は3階層だったネットワークや信号強度のシンボルが、今では5階層を経るようになっています。

wiggleとrotateの扱いが向上。 iOS 18で追加されたモーション効果に改良が入り、低性能デバイスでのパフォーマンスが向上し、3D空間内のモーションが異なる手がかりを必要とするvisionOSでも動作が改善されました。

複合シンボル向けのreplaceトランジション。 複数のコンポーネントを持つシンボル(heart-with-pulse、cloud-with-rain、person-with-clock)が以前よりもクリーンに置き換わるようになり、関連する複合シンボル間のトランジション時の視覚的な乱れが減りました。

中心となる機能(前述の10種の効果)は、SF Symbols 5(iOS 17)と6(iOS 18)以来すでに成熟しています。iOS 26の追加は、語彙を作り直すのではなく拡張するものです。正しい採用方針は、次のリリースを待つのではなく、既存の動詞を深く学ぶことです。

このパターンがiOS 26+アプリにとって意味すること

要点は3つです。

  1. 動詞はオプションの装飾ではなく、プラットフォームが話す語彙そのものです。 ユーザーはすべてのAppleアプリで同じ.bounceの確認を目にします。同じ動詞を採用することで、サードパーティのアプリもネイティブに感じられます。一致しないカスタムアニメーションを選べば、アプリはプラットフォーム外のものに感じられてしまいます。動詞はアクセシビリティ、パフォーマンス、プラットフォームの一貫性を一度に得られる勝ちの選択です。

  2. 1つの瞬間に1つの効果。 確認には.bounce、状態のトグルには.replace、ライブ信号には.variableColorを使うビューは、語彙を正しく使っています。視界に入るアイコンすべてを脈動させるビューは、悪い使い方です。規律は編集的なものです——どの瞬間がその効果に値するか?

  3. プラットフォームのアクセシビリティとパフォーマンスのデフォルトを信頼する。 Symbol effectsはReduce Motionを自動的に尊重し、ほぼゼロのコストでGPU上で動作します。本来開発者がやらなければならない作業(reduce-motionの条件分岐を書く、60 fpsに合わせてアニメーションタイミングを調整する)は、フレームワーク側で既に済んでいます。

Apple Ecosystemクラスター全体——型付きApp IntentsMCPサーバールーティングの問題Foundation Modelsランタイム vs ツーリングのLLMの区別3つのサーフェス単一の信頼できる情報源パターン2つのMCPサーバーApple開発のためのhooksLive ActivitieswatchOSランタイムSwiftUIの内部RealityKitの空間メンタルモデルSwiftDataのスキーマ規律Liquid Glassのパターンマルチプラットフォーム出荷プラットフォームマトリクスVision framework書かないと決めたこと。ハブはApple Ecosystem Seriesにあります。AIエージェントを伴うiOSの広い文脈については、iOS Agent Development guideを参照してください。

FAQ

.symbolEffect.contentTransition(.symbolEffect(.replace))の違いは何ですか?

.symbolEffect(...)は、同一性が変わらない単一のシンボル上でアニメーションを実行します(ベルは「ベル」のままバウンスする)。.contentTransition(.symbolEffect(.replace))は、異なる2つのシンボル間で設計済みのトランジションを実行します(ベルがスラッシュ付きベルに変わる)。前者は状態の強調用、後者はシンボルの同一性を入れ替える用です。

Symbol effectsはvisionOSで動きますか?

動きます。Symbol effectsはvisionOSでも同じようにレンダリングされ、システムのモーション調整は空間環境を尊重します。visionOS上のwiggleとrotateの効果は、空間距離で適切に感じられるよう調整されており、開発者がプラットフォーム固有のコードを書く必要はありません。

カスタムのsymbol effectを書けますか?

フレームワークの効果セットは閉じており、開発者が新しい効果型を定義することはできません。セットは十分に豊富なので、カスタム効果が必要になることは稀です。シンボル語彙を超えるアニメーションには、SwiftUIのネイティブなアニメーションプリミティブ(.animationwithAnimationTransaction、カスタムAnimatableビュー)が正しいツールですが、フレームごとのコスト管理は開発者の責任になります。

Symbol effectsを使うとApp Storeの審査に影響しますか?

しません。Symbol effectsは公開されたSwiftUIのAPIであり、他のSwiftUI使用と同様に審査されます。Human Interface Guidelinesは積極的に使用を推奨しており、ガイドラインに従うアプリは、同じ目的でカスタムアニメーションシステムを構築するアプリに比べて、審査での驚きが少なくなります。

値を変えても.bounce効果が動かないのはなぜですか?

よくある原因が3つあります。第一に、値が実際に同一性を変える必要があります(@StateのIntが0から1へ変わる、ではなく、同じInt0を再代入するのは違います)。第二に、モディファイアの順序が重要です——.symbolEffect(.bounce, value: foo)は、ラップするButtonHStackではなくImageに適用されなければなりません。第三に、効果は同一性の変化ごとに1回だけ実行されます。急速な変化はまとまってしまいます。繰り返しや保持される効果には、value:の代わりに.repeatingまたはisActive:を使ってください。

参考文献


  1. Apple Developer Documentation: SwiftUIのSymbolEffectおよび.symbolEffect(_:options:value:)。 

  2. Apple Human Interface Guidelines: Motion。システムのモーション設定(Reduce Motion)はSF Symbol effectsによって自動的に尊重されます。 

関連記事

What SwiftUI Is Made Of

SwiftUI is a result-builder DSL on top of a value-typed View tree. Once the substrate is visible, AnyView, Group, and Vi…

17 分で読める

Liquid Glass in SwiftUI: Three Patterns From Shipping Return on iOS 26

Apple's Liquid Glass is a one-line SwiftUI API. Three patterns from Return go beyond .glassEffect(): glass on text via C…

19 分で読める

The Cleanup Layer Is the Real AI Agent Market

Charlie Labs pivoted from building agents to cleaning up after them. The AI agent market is moving from generation to pr…

15 分で読める