dpearson2699/swift-ios-skills4 files

Swiftui Animation

Implement, diagnose, or review SwiftUI motion using explicit and scoped implicit animations, springs, transitions, PhaseAnimator, KeyframeAnimator, matched geometry or navigation zoom, SF Symbol effects, and custom Animation types. Use when views should animate on state changes, insertion, removal, navigation, or multi-step choreography, or when motion must respect Reduce Motion and Swift concurrency.

Specification
Skill ID
dpearson2699/swift-ios-skills/swiftui-animation
Publisher
dpearson2699
Repository
swift-ios-skills
Installs
0
Files
4
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

dpearson2699/swift-ios-skills/swiftui-animationInstalls these files
  • SKILL.md
  • evals/evals.json
  • references/animation-advanced.md
  • references/core-animation-bridge.md

What this skill tells the agent

SwiftUI Animation (iOS 26+)

Review, write, and fix SwiftUI animations. Apply modern animation APIs with correct timing, transitions, and accessibility handling using Swift 6.3 patterns.

Contents

  • Triage Workflow
  • withAnimation (Explicit Animation)
  • Implicit Animation
  • Spring Type (iOS 17+)
  • PhaseAnimator (iOS 17+)
  • KeyframeAnimator (iOS 17+)
  • `@Animatable Macro`
  • matchedGeometryEffect (iOS 14+)
  • Navigation Zoom Transition (iOS 18+)
  • Transitions (iOS 17+)
  • ContentTransition (iOS 16+)
  • Symbol Effects (iOS 17+)
  • Symbol Rendering Modes
  • Common Mistakes
  • Review Checklist
  • References

Triage Workflow

Step 1: Identify the animation category

CategoryAPIWhen to use
State-drivenwithAnimation, .animation(_:body:), .animation(_:value:)Explicit state changes, selective modifier animation, or simple value-bound changes
Multi-phasePhaseAnimatorSequenced multi-step animations
KeyframeKeyframeAnimatorComplex multi-property choreography
Shared elementmatchedGeometryEffectLayout-driven hero transitions
NavigationmatchedTransitionSource + .navigationTransition(.zoom)NavigationStack push/pop zoom
View lifecycle.transition()Insertion and removal
Text content.contentTransition()In-place text/number changes
Symbol.symbolEffect()SF Symbol animations
CustomCustomAnimation protocolNovel timing curves
Core Animation bridgeCALayer, CAAnimation, CADisplayLinkRead references/core-animation-bridge.md before advising

Step 2: Choose the animation curve

.easeInOut(duration: 0.3)       // mechanical timing
.smooth                         // fluid, no bounce
.snappy                         // responsive, small bounce
.bouncy                         // playful, visible bounce
.spring(duration: 0.5, bounce: 0.3)

Use the advanced catalog when presets do not express the intended motion.

Step 3: Apply and verify

  • Confirm animation triggers on the correct state change.
  • Test with Accessibility > Reduce Motion enabled.
  • Verify no expensive work runs inside animation content closures.
  • For CA bridges, use Coordinators for delegates, invalidate display links, treat frame-rate ranges as hints, and adapt work to the actual refresh rate.

withAnimation (Explicit Animation)

withAnimation(.spring) { isExpanded.toggle() }

// With completion (iOS 17+)
withAnimation(.smooth(duration: 0.35), completionCriteria: .logicallyComplete) {
    isExpanded = true
} completion: { loadContent() }

Implicit Animation

Use withAnimation for state-mutation ownership, .animation(_:body:) for selected modifiers, and .animation(_:value:) for simple value-bound changes.

Badge()
    .foregroundStyle(isActive ? .green : .secondary)
    .animation(.snappy) { content in
        content
            .scaleEffect(isActive ? 1.15 : 1.0)
            .opacity(isActive ? 1.0 : 0.7)
    }
Circle()
    .scaleEffect(isActive ? 1.2 : 1.0)
    .opacity(isActive ? 1.0 : 0.6)
    .animation(.bouncy, value: isActive)

Spring Type (iOS 17+)

Prefer the perceptual form or a preset. Load the advanced reference only when physical, response-based, or settling parameters are required.

Spring(duration: 0.5, bounce: 0.3)
Spring.smooth
Spring.snappy
Spring.bouncy

PhaseAnimator (iOS 17+)

Cycle through discrete phases with per-phase animation curves.

enum PulsePhase: CaseIterable {
    case idle, grow, shrink
}

struct PulsingDot: View {
    var body: some View {
        PhaseAnimator(PulsePhase.allCases) { phase in
            Circle()
                .frame(width: 40, height: 40)
                .scaleEffect(phase == .grow ? 1.4 : 1.0)
                .opacity(phase == .shrink ? 0.5 : 1.0)
        } animation: { phase in
            switch phase {
            case .idle: .easeIn(duration: 0.2)
            case .grow: .spring(duration: 0.4, bounce: 0.3)
            case .shrink: .easeOut(duration: 0.3)
            }
        }
    }
}

Trigger-based variant advances to the next phase on each trigger change:

PhaseAnimator(PulsePhase.allCases, trigger: tapCount) { phase in
    // ...
} animation: { _ in .spring(duration: 0.4) }

KeyframeAnimator (iOS 17+)

Animate multiple properties along independent timelines.

struct AnimValues {
    var scale: Double = 1.0
    var yOffset: Double = 0.0
    var opacity: Double = 1.0
}

struct BounceView: View {
    @State private var trigger = false

    var body: some View {
        Button { trigger.toggle() } label: {
            Image(systemName: "star.fill")
                .font(.largeTitle)
                .keyframeAnimator(
                    initialValue: AnimValues(),
                    trigger: trigger
                ) { content, value in
                    content
                        .scaleEffect(value.scale)
                        .offset(y: value.yOffset)
                        .opacity(value.opacity)
                } keyframes: { _ in
                    KeyframeTrack(\.scale) {
                        SpringKeyframe(1.5, duration: 0.3)
                        CubicKeyframe(1.0, duration: 0.4)
                    }
                    KeyframeTrack(\.yOffset) {
                        CubicKeyframe(-30, duration: 0.2)
                        CubicKeyframe(0, duration: 0.4)
                    }
                    KeyframeTrack(\.opacity) {
                        LinearKeyframe(0.6, duration: 0.15)
                        LinearKeyframe(1.0, duration: 0.25)
                    }
                }
        }
        .buttonStyle(.plain)
    }
}

Keyframe types: LinearKeyframe (linear), CubicKeyframe (smooth curve), SpringKeyframe (spring physics), MoveKeyframe (instant jump).

Use repeating: true for looping keyframe animations. Swift 6: keyframe closures are @Sendable; capture state/env values before the modifier.

@Animatable Macro

Replaces manual AnimatableData boilerplate. Attach to any type with animatable stored properties.

@Animatable
struct WaveShape: Shape {
    var frequency: Double
    var amplitude: Double
    var phase: Double
    @AnimatableIgnored var lineWidth: CGFloat

    func path(in rect: CGRect) -> Path {
        // draw wave using frequency, amplitude, phase
    }
}

Rules:

  • Stored properties must conform to VectorArithmetic.
  • Use @AnimatableIgnored to exclude non-animatable properties.
  • Computed properties are never included.

matchedGeometryEffect (iOS 14+)

Synchronize geometry between views for shared-element animations.

struct HeroView: View {
    @Namespace private var heroSpace
    @State private var isExpanded = false

    var body: some View {
        Group {
            if isExpanded {
                Button {
                    withAnimation(.spring(duration: 0.4, bounce: 0.2)) {
                        isExpanded = false
                    }
                } label: {
                    DetailCard()
                        .matchedGeometryEffect(id: "card", in: heroSpace)
                }
            } else {
                Button {
                    withAnimation(.spring(duration: 0.4, bounce: 0.2)) {
                        isExpanded = true
                    }
                } label: {
                    ThumbnailCard()

Use this skill in a project

Open any RiverX project, search the Skills panel for dpearson2699/swift-ios-skills/swiftui-animation, and install it in one click.

Use this skill