All skills
rshankras avatar

/animation-patterns

@32566aa

SwiftUI animation patterns including springs, transitions, PhaseAnimator, KeyframeAnimator, SF Symbol effects, scroll-driven effects, mesh gradients, text renderers, and shader effects. Use when implementing, reviewing, or fixing animation or visual-effect code on iOS/macOS.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/animation-patterns

This session only. Nothing lands on disk.

symbol-effects.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

SF Symbol Effects

Animate SF Symbols using the .symbolEffect() modifier (iOS 17+) — not withAnimation.

Available Effects

Effect Visual Use Case
.bounce Jumps up once Tap feedback, notification arrival
.pulse Fades in and out Ongoing activity, waiting state
.variableColor Animates multi-layer color Signal strength, download progress
.replace Cross-fades to new symbol Play/pause toggle, state change
.wiggle Shakes side to side Attention, error indication
.breathe Gentle scale pulse Ambient activity, Siri-like
.rotate Rotates the symbol Loading, refresh, processing

Three Usage Patterns

1. Continuous (Repeating)

Starts when the view appears and loops indefinitely. Pass the effect directly to .symbolEffect():

// Pulsing while loading
Image(systemName: "wifi")
    .symbolEffect(.pulse, isActive: isSearching)

// Variable color always running
Image(systemName: "antenna.radiowaves.left.and.right")
    .symbolEffect(.variableColor.iterative)

// Breathing while recording
Image(systemName: "mic.fill")
    .symbolEffect(.breathe, isActive: isRecording)

Use isActive: to start/stop the effect based on state.

2. One-Shot (Event-Based with value:)

Triggers once each time a value changes. Use the .symbolEffect(_:value:) modifier:

// Bounce on tap
@State private var bounceCount = 0

Image(systemName: "heart.fill")
    .symbolEffect(.bounce, value: bounceCount)
    .onTapGesture {
        bounceCount += 1
    }

// Wiggle on error
Image(systemName: "exclamationmark.triangle")
    .symbolEffect(.wiggle, value: errorCount)

// Rotate on refresh
Image(systemName: "arrow.clockwise")
    .symbolEffect(.rotate, value: refreshCount)

3. Content Transition (Symbol Swap)

Animate between different symbols using .contentTransition(.symbolEffect(.replace)):

Image(systemName: isPlaying ? "pause.fill" : "play.fill")
    .contentTransition(.symbolEffect(.replace))
    .animation(.smooth, value: isPlaying)

Image(systemName: isMuted ? "speaker.slash.fill" : "speaker.wave.2.fill")
    .contentTransition(.symbolEffect(.replace.downUp))

Replace directions: .replace, .replace.downUp, .replace.upUp, .replace.offUp

Effect Options

Speed and Repeat

Image(systemName: "bell.fill")
    .symbolEffect(.bounce, options: .speed(2.0), value: trigger)

Image(systemName: "bell.fill")
    .symbolEffect(.bounce, options: .repeat(3), value: trigger)

// Combine options
Image(systemName: "bell.fill")
    .symbolEffect(.bounce, options: .speed(1.5).repeat(2), value: trigger)

Non-Repeating (for continuous effects)

// Pulse exactly once, then stop
Image(systemName: "heart.fill")
    .symbolEffect(.pulse, options: .nonRepeating, value: trigger)

Variable Color Modes

// Iterative — layers animate one by one
.symbolEffect(.variableColor.iterative)

// Cumulative — layers build up
.symbolEffect(.variableColor.cumulative)

// Reversing — forward then backward
.symbolEffect(.variableColor.iterative.reversing)

// Hide inactive layers (instead of dimming)
.symbolEffect(.variableColor.iterative.hideInactiveLayers)

Anti-Patterns

// ❌ WRONG: Using withAnimation for symbol effects
withAnimation(.spring) {
    Image(systemName: "star.fill")
}
// Symbol effects are NOT driven by withAnimation — they have their own timing

// ✅ RIGHT: Use .symbolEffect modifier
Image(systemName: "star.fill")
    .symbolEffect(.bounce, value: trigger)

// ❌ WRONG: Using .animation() modifier for symbol swap
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
    .animation(.spring, value: isPlaying)
// This animates the IMAGE view, not the SYMBOL content

// ✅ RIGHT: Use contentTransition for symbol swap
Image(systemName: isPlaying ? "pause.fill" : "play.fill")
    .contentTransition(.symbolEffect(.replace))

// ❌ WRONG: Applying symbolEffect to non-symbol views
Text("Hello")
    .symbolEffect(.bounce, value: trigger)  // No effect on Text
// symbolEffect only works on Image views with SF Symbols

Accessibility — Reduce Motion

Symbol effects automatically respect the system's Reduce Motion setting:

  • Bounce, wiggle, rotate — suppressed entirely when Reduce Motion is on
  • Pulse, breathe — reduced to opacity-only change
  • Variable color — still plays (it's informational, not decorative)
  • Replace — falls back to cross-fade

No manual handling needed for symbol effects — the system manages this automatically. However, if you use isActive: to show ongoing state, ensure the state is also communicated through non-motion means (label, color, VoiceOver):

Image(systemName: "wifi")
    .symbolEffect(.variableColor.iterative, isActive: isSearching)
    .accessibilityLabel(isSearching ? "Searching for networks" : "Wi-Fi")

Source: SKILL.md on GitHub

No alerts2mo3 checks · Risk SAFE
  • Gen Agent Trust Hub2mo

    The skill is a comprehensive reference guide for SwiftUI animation patterns. It contains only instructional documentation and code snippets for Apple's native frameworks. No security issues were detected.

  • Socket2mo

    No alerts

  • Snyk2mo

    Risk: LOW · No issues

Signed by skilld at 32566aa. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 3 months ago
What it can do
Reads files
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 3 allowed tools
ReadGlobGrep

README badge

README badge for rshankras/claude-code-apple-skills/animation-patterns