반응형

SwiftUI PhaseAnimator와 KeyframeAnimator를 활용한 고급 애니메이션 구현

 

SwiftUI에서 기존의 withAnimation이나 단순 .animation() 모디파이어는 정적 상태 간 전환에는 유용하지만, 다단계 연속 동작이나 속성별 독립적 타이밍을 제어하는 데는 한계가 존재합니다. iOS 17 이상부터 제공되는 PhaseAnimator와 KeyframeAnimator는 복잡한 애니메이션을 선언적이고 직관적인 코드로 구축할 수 있는 API를 제공합니다.

 

1. PhaseAnimator: 단계별 시퀀스 애니메이션

PhaseAnimator는 정해진 단계(Phase)를 순차적으로 순환하며 뷰의 상태와 애니메이션 곡선을 변경하는 방식입니다. @State 변수를 여러 개 생성하여 중첩 관리할 필요 없이, 열거형(Enum)이나 배열로 단계를 정의하여 처리합니다.

 

열거형 기반 단계 정의 및 트리거 연동

상태 연출 화면과 하단 실행 컨트롤 버튼을 분리한 구현 예제입니다.

import SwiftUI

// 1. 애니메이션 단계 정의
enum HeartPhase: CaseIterable {
    case initial
    case enlarged
    case compressed
    
    var scale: CGFloat {
        switch self {
        case .initial: return 1.0
        case .enlarged: return 1.4
        case .compressed: return 0.95
        }
    }
    
    var color: Color {
        switch self {
        case .initial: return .gray
        case .enlarged: return .pink
        case .compressed: return .red
        }
    }
}

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

    var body: some View {
        VStack(spacing: 24) {
            // UI 화면 영역 (Stage)
            VStack {
                Spacer()
                Image(systemName: trigger ? "heart.fill" : "heart")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 80, height: 80)
                    .phaseAnimator(
                        HeartPhase.allCases,
                        trigger: trigger
                    ) { content, phase in
                        content
                            .scaleEffect(trigger ? phase.scale : 1.0)
                            .foregroundStyle(trigger ? phase.color : .gray)
                    } animation: { phase in
                        switch phase {
                        case .initial: .easeOut(duration: 0.1)
                        case .enlarged: .spring(response: 0.3, dampingFraction: 0.5)
                        case .compressed: .easeInOut(duration: 0.15)
                        }
                    }
                Spacer()
            }
            .frame(maxWidth: .infinity, minHeight: 220)
            .background(Color(.secondarySystemBackground))
            .cornerRadius(16)
            
            // 하단 별도 테스트 실행 버튼
            Button(action: {
                trigger.toggle()
            }) {
                Label("PhaseAnimator 트리거 실행", systemImage: "play.fill")
                    .font(.headline)
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 14)
            }
            .buttonStyle(.borderedProminent)
            .tint(.pink)
        }
        .padding()
    }
}
  • trigger: 하단 버튼 클릭 시 값이 동적으로 변경되며 정의된 단계를 1회 순환합니다.
  • animation: 각 phase로 전환될 때 서로 다른 트랜지션 곡선(spring, easeOut 등)을 다르게 지정합니다.

Phaseanimator 사용예

 

2. KeyframeAnimator: 독립적 타임라인 기반 키프레임 제어

KeyframeAnimator는 뷰의 여러 속성(크기, 회전, 위치, 불투명도 등)을 개별 타임라인 트랙(KeyframeTrack)으로 분리하여 고유한 시점과 인터폴레이션 방식으로 정밀하게 제어할 때 사용됩니다.

 

복합 키프레임 애니메이션 구현

연출 화면과 하단 실행 컨트롤 버튼을 분리한 구현 예제입니다.

import SwiftUI

// 1. 애니메이션 대상 속성 집합 정의
struct BellAnimationValues {
    var scale: CGFloat = 1.0
    var verticalOffset: CGFloat = 0.0
    var rotation: Angle = .zero
}

struct KeyframeAnimatorTestView: View {
    @State private var triggerCount = 0

    var body: some View {
        VStack(spacing: 24) {
            // UI 화면 영역 (Stage)
            VStack {
                Spacer()
                KeyframeAnimator(
                    initialValue: BellAnimationValues(),
                    trigger: triggerCount
                ) { values in
                    Image(systemName: triggerCount % 2 == 1 ? "bell.badge.fill" : "bell.fill")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 80, height: 80)
                        .foregroundStyle(triggerCount % 2 == 1 ? .orange : .gray)
                        .offset(y: values.verticalOffset)
                        .rotationEffect(values.rotation)
                        .scaleEffect(values.scale)
                } keyframes: { _ in
                    KeyframeTrack(\.verticalOffset) {
                        CubicKeyframe(-18, duration: 0.15)
                        CubicKeyframe(0, duration: 0.15)
                    }
                    
                    KeyframeTrack(\.rotation) {
                        CubicKeyframe(.degrees(-20), duration: 0.1)
                        CubicKeyframe(.degrees(20), duration: 0.1)
                        CubicKeyframe(.degrees(-10), duration: 0.1)
                        CubicKeyframe(.degrees(0), duration: 0.1)
                    }
                    
                    KeyframeTrack(\.scale) {
                        SpringKeyframe(1.3, duration: 0.2, spring: .bouncy)
                        SpringKeyframe(1.0, duration: 0.2)
                    }
                }
                Spacer()
            }
            .frame(maxWidth: .infinity, minHeight: 220)
            .background(Color(.secondarySystemBackground))
            .cornerRadius(16)
            
            // 하단 별도 테스트 실행 버튼
            Button(action: {
                triggerCount += 1
            }) {
                Label("KeyframeAnimator 트리거 실행", systemImage: "play.fill")
                    .font(.headline)
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 14)
            }
            .buttonStyle(.borderedProminent)
            .tint(.orange)
        }
        .padding()
    }
}

 

KeyframeAnimator 사용예

 

키프레임 보간 종류

 

키프레임 유형 보간 특성
LinearKeyframe 지정된 시간 동안 일정한 속도로 선형 보간 진행
CubicKeyframe 3차 큐빅 베지어 곡선을 이용해 부드러운 시작과 종료 처리
SpringKeyframe 스프링 물리 엔진(반발력, 감쇄비) 기반 동적 보간 적용
MoveKeyframe 보간 없이 특정 시점에 값을 즉시 변경

 

3. PhaseAnimator vs KeyframeAnimator 비교

 

비교 항목 PhaseAnimator KeyframeAnimator
제어 단위 순차적 단계(Phase) 흐름 시간 축 기반 속성별 트랙(Track)
속성 독립성 단계별로 전체 뷰 상태 일괄 변경 각 프로퍼티별(스케일, 회전 등) 독립 타이밍 적용
구현 복잡도 상대적으로 낮음 (Enum/배열 활용) 상대적으로 높음 (Value 구조체 및 Track 정의)
적합한 유즈케이스 하트 버튼, 탭 바 아이콘 토글 등 알림 종 흔들림, 물리적 바운스, 복합 마이크로 인터랙션

 

결론

PhaseAnimator는 단계 중심의 상태 전환 애니메이션 구축에 적합하며, KeyframeAnimator는 시간축 기반의 다중 속성 고정밀 애니메이션 구현 시 뷰의 가독성과 코드 유지보수성을 극대화합니다.

 

반응형
Posted by 까칠코더
,