--- 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]]