---
id: ios-swiftui-animation-deep
title: SwiftUI Animation — implicit / explicit / matchedGeometry
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [ios, swiftui, animation, vibe-coding]
tech_stack: { language: "Swift", applicable_to: ["iOS"] }
applied_in: []
aliases: [SwiftUI animation, withAnimation, matchedGeometryEffect, transition, PhaseAnimator, KeyframeAnimator]
---

# SwiftUI Animation

> SwiftUI 의 animation 가 declarative. **`.animation()`, `withAnimation`, transition, matchedGeometry**. iOS 17+ 의 PhaseAnimator / KeyframeAnimator.

## 📖 핵심 개념
- Implicit: state 변경 = 자동.
- Explicit: `withAnimation` block.
- Transition: appear / disappear.
- matchedGeometry: 두 view 간 morph.

## 💻 코드 패턴

### Implicit animation
```swift
struct ContentView: View {
    @State var scale = 1.0
    
    var body: some View {
        Circle()
            .scaleEffect(scale)
            .animation(.spring(), value: scale)
            .onTapGesture {
                scale = scale == 1.0 ? 2.0 : 1.0
            }
    }
}
```

→ `.animation(_, value:)` 가 modern API. Value 변경 = animate.

### Explicit (withAnimation)
```swift
@State var isExpanded = false

Button("Toggle") {
    withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) {
        isExpanded.toggle()
    }
}
```

→ Block 안 변경 가 animate.

### 모든 animation type
```swift
.animation(.linear(duration: 0.3), value: x)
.animation(.easeInOut(duration: 0.5), value: x)
.animation(.spring(response: 0.5, dampingFraction: 0.7), value: x)
.animation(.bouncy, value: x)              // iOS 17
.animation(.smooth, value: x)              // iOS 17
.animation(.snappy, value: x)              // iOS 17
.animation(.interactiveSpring(), value: x)
```

### Transition
```swift
struct Toast: View {
    @State var show = false
    
    var body: some View {
        VStack {
            if show {
                Text("Hello")
                    .transition(.move(edge: .top).combined(with: .opacity))
            }
        }
        .animation(.spring(), value: show)
    }
}
```

→ Appear / disappear 의 animation.

### Built-in transitions
```swift
.transition(.opacity)
.transition(.scale)
.transition(.slide)
.transition(.move(edge: .leading))
.transition(.push(from: .top))           // iOS 17
.transition(.scale.combined(with: .opacity))
```

### Custom transition (iOS 17 movePhase)
```swift
struct BlurTransition: Transition {
    func body(content: Content, phase: TransitionPhase) -> some View {
        content
            .blur(radius: phase.isIdentity ? 0 : 20)
            .opacity(phase.isIdentity ? 1 : 0)
    }
}

// 사용
.transition(BlurTransition())
```

### matchedGeometryEffect (morph)
```swift
@Namespace var namespace
@State var expanded = false

VStack {
    if expanded {
        Image("hero")
            .matchedGeometryEffect(id: "hero", in: namespace)
            .frame(width: 300, height: 300)
    } else {
        Image("hero")
            .matchedGeometryEffect(id: "hero", in: namespace)
            .frame(width: 100, height: 100)
    }
}
.onTapGesture {
    withAnimation(.spring()) { expanded.toggle() }
}
```

→ 같은 ID = morph. Hero animation 의 답.

### PhaseAnimator (iOS 17+)
```swift
struct PulseView: View {
    var body: some View {
        Circle()
            .phaseAnimator([1.0, 1.5, 1.0]) { content, phase in
                content
                    .scaleEffect(phase)
                    .opacity(phase == 1.5 ? 0.5 : 1.0)
            } animation: { phase in
                .easeInOut(duration: 0.5)
            }
    }
}
```

→ 자동 cycle. Loop 식 animation.

### KeyframeAnimator (iOS 17+)
```swift
struct WaveView: View {
    @State var trigger = false
    
    var body: some View {
        Circle()
            .keyframeAnimator(initialValue: AnimationValues(), trigger: trigger) { content, value in
                content
                    .scaleEffect(value.scale)
                    .rotationEffect(value.rotation)
            } keyframes: { _ in
                KeyframeTrack(\.scale) {
                    LinearKeyframe(1.5, duration: 0.5)
                    SpringKeyframe(1.0, duration: 0.5)
                }
                KeyframeTrack(\.rotation) {
                    CubicKeyframe(.degrees(360), duration: 1.0)
                }
            }
            .onTapGesture { trigger.toggle() }
    }
}

struct AnimationValues {
    var scale = 1.0
    var rotation = Angle.zero
}
```

→ 복잡 multi-property animation.

### Animatable protocol (custom)
```swift
struct CounterShape: Shape, Animatable {
    var value: Double
    
    var animatableData: Double {
        get { value }
        set { value = newValue }
    }
    
    func path(in rect: CGRect) -> Path {
        // value 가 변경 시 매 frame draw
    }
}
```

→ Custom shape 도 animate.

### AnimatableModifier
```swift
struct ShakeEffect: GeometryEffect {
    var amount: CGFloat = 10
    var animatableData: CGFloat
    
    func effectValue(size: CGSize) -> ProjectionTransform {
        ProjectionTransform(CGAffineTransform(translationX:
            amount * sin(animatableData * .pi * 5), y: 0))
    }
}

// 사용
.modifier(ShakeEffect(animatableData: CGFloat(attempts)))
```

### Spring (커스텀)
```swift
.animation(.spring(
    response: 0.5,         // duration approximation
    dampingFraction: 0.7,  // overshoot (0 = bounce, 1 = no bounce)
    blendDuration: 0
), value: x)
```

→ `response` + `dampingFraction` 가 일반.

### Animation interruption
```swift
// 진행 중 animation 가 다른 변경 시:
withAnimation(.easeInOut) { x = 100 }
// 0.2 sec 후
withAnimation(.spring()) { x = 50 }

// → 2번째 가 1번째 의 현재 값 부터 시작 (smooth).
```

→ SwiftUI 가 자동.

### Animation curve
```swift
extension Animation {
    static var customCurve: Animation {
        .timingCurve(0.2, 0.8, 0.2, 1.0, duration: 0.5)
    }
}
```

→ `.timingCurve(c1x, c1y, c2x, c2y)` = bezier.

### Disable animation (specific)
```swift
.transaction { tx in
    tx.animation = nil
}

// Or
withTransaction(Transaction(animation: nil)) {
    x = 100
}
```

→ 일부 변경 가 animate X.

### List / scroll animation
```swift
List(items, id: \.id) { item in
    Row(item)
}
.animation(.spring(), value: items)

// onDelete + animation
.swipeActions {
    Button("Delete") {
        withAnimation { items.removeAll { $0.id == item.id } }
    }
}
```

### Scroll 의 visual effect (iOS 17)
```swift
ScrollView {
    ForEach(items) { item in
        Card(item)
            .scrollTransition { content, phase in
                content
                    .opacity(phase.isIdentity ? 1 : 0.3)
                    .scaleEffect(phase.isIdentity ? 1 : 0.8)
            }
    }
}
```

→ Scroll 위치 따라 modify.

### Repeat animation
```swift
.animation(.linear(duration: 1).repeatForever(autoreverses: true), value: x)

// Or
withAnimation(.linear(duration: 1).repeatForever()) {
    rotation = 360
}
```

### Performance
```
- 매 frame draw 안 비싸게.
- Complex shape = lazy.
- Background work + animation 가 main thread X.
- Instruments > Animation Hitches.
```

### Geometry effect (transform)
```swift
struct RotationEffect: GeometryEffect {
    var angle: Double
    var animatableData: Double {
        get { angle }
        set { angle = newValue }
    }
    
    func effectValue(size: CGSize) -> ProjectionTransform {
        ProjectionTransform(CGAffineTransform(rotationAngle: angle))
    }
}
```

### 함정
```
- @State 변경 + animation 없음: 즉시.
- ObservableObject 변경: 자동 X (withAnimation 명시).
- async work 후 변경: main thread + withAnimation.
- 큰 list animation: 성능.
- transition 없이 conditional: 즉시 fade.
```

### Reduce motion (a11y)
```swift
@Environment(\.accessibilityReduceMotion) var reduce

withAnimation(reduce ? nil : .spring()) {
    x.toggle()
}
```

→ User 가 motion ↓ 옵션 — 존중.

### iOS 17 추가 (modern)
```
- ContainerRelativeShape
- Shader Library (Metal 통합)
- Symbol effects
- Gradient animatable

→ SwiftUI 매년 발전.
```

### Symbol effect
```swift
Image(systemName: "heart.fill")
    .symbolEffect(.bounce, value: trigger)
    .symbolEffect(.pulse)
    .symbolEffect(.variableColor.iterative)
```

→ SF Symbol 가 자체 animate.

## 🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| State 변경 | Implicit `.animation(_, value:)` |
| 명시 control | `withAnimation` |
| Appear/disappear | `.transition()` |
| Hero / morph | `matchedGeometryEffect` |
| Loop | `phaseAnimator` |
| 복잡 timeline | `keyframeAnimator` |
| Custom geometry | `Animatable` protocol |
| Symbol | `symbolEffect` |

## ❌ 안티패턴
- **animation(:value:) 없는 implicit**: 모든 거 animate.
- **withAnimation 안 의 async work**: race.
- **Complex shape + 매 frame redraw**: 성능.
- **Transition 없이 conditional**: 갑작 사라짐.
- **Reduce motion 무시**: a11y 위반.
- **Spring response 가 너무 작음**: jittery.
- **Stack 의 모든 child 가 animate**: 폭발.

## 🤖 LLM 활용 힌트
- `.animation(_, value:)` 가 modern (iOS 16+).
- matchedGeometryEffect 가 Hero 답.
- iOS 17 의 phaseAnimator / keyframe 가 powerful.
- Reduce motion 항상 존중.

## 🔗 관련 문서
- [[iOS_SwiftUI_State_Property_Wrappers]]
- [[Frontend_Animation_Motion]]
- [[React_Animation_Performance]]
