SwiftUI Study – 애니메이션 엔진 내부 구조: Transaction, Phase, animatableData, Rendering Pipeline
Dev Study/SwiftUI 2025. 12. 15. 09:49SwiftUI Study – 애니메이션 엔진 내부 구조: Transaction, Phase, animatableData, Rendering Pipeline
1. 왜 이 주제가 중요한가 (문제 배경)
SwiftUI의 애니메이션은 간단해 보이지만, 실제 동작 방식은 다음 요소들이 정교하게 결합된 구조이다.
- Transaction
- Animation Phase
- animatableData
- Timeline & Renderer
- Layout → Display 변환 과정
이 구조를 이해하면 다음 문제가 해결된다.
- .animation(.easeInOut) 을 붙였는데 일부만 애니메이트 되는 이유
- withAnimation 블록 안에서도 애니메이션이 안 먹는 현상
- Layout 변화는 애니메이션되는데, 특정 Modifier는 왜 즉시 반영되는지
- GeometryReader 기반 뷰가 애니메이션 시 튀는 이유
- animatableData를 커스텀해 정교한 애니메이션을 만드는 법
즉, 이번 문서는 SwiftUI 애니메이션의 엔진 내부 동작을 기준으로 한 고급 분석이다.
2. Transaction – 애니메이션의 운전석
SwiftUI에서 모든 애니메이션은 Transaction을 통해 전달된다.
withAnimation(.spring()) {
isOn.toggle()
}
이 호출은 다음 순서로 처리된다:
- Animation을 포함한 새로운 Transaction 생성
- “State 변화”에 Transaction이 붙어서 전달
- SwiftUI는 이 Transaction을 기반으로 “무엇을 어떻게 애니메이트할지” 결정
Transaction이 하는 일
- 애니메이션 여부 결정 (animated? true / false)
- 사용하는 Animation 종류 저장
- Animation Phase 결정
- Layout 및 Rendering 모듈로 전달
결론:
Animation은 State 변화에 붙는 메타데이터이고,
Transaction은 그 메타데이터를 운반하는 컨테이너이다.
3. Animation Phase – SwiftUI가 계산하는 중간 프레임의 정체
SwiftUI는 애니메이션 동안 다음을 자동계산한다.
- 현재 시간(t)
- 전체 duration
- easing function 적용
- 보간(interpolation)된 중간 값
즉,
.opacity(isOn ? 1 : 0)
이 경우 SwiftUI는 내부적으로:
0.0 → 0.1 → 0.25 → 0.5 → 0.75 → 1.0 …
같은 값을 매 프레임 계산해 적용한다.
이를 위해 SwiftUI는 interpolatable value를 필요로 한다.
4. animatableData – SwiftUI가 “값 사이를 보간하는 방법”
SwiftUI는 애니메이션을 하기 위해 “값을 점진적으로 변화시킬 수 있어야” 한다.
예: Rectangle의 cornerRadius
struct RoundedBox: View {
var radius: CGFloat
var body: some View {
RoundedRectangle(cornerRadius: radius)
}
}
cornerRadius는 CGFloat이므로 SwiftUI는 쉽게 보간할 수 있다.
하지만 개발자가 만든 커스텀 타입은?
커스텀 애니메이션 예시
struct MyShape: Shape {
var progress: CGFloat
var animatableData: CGFloat {
get { progress }
set { progress = newValue }
}
func path(in rect: CGRect) -> Path {
// progress를 기반으로 형상 변환
}
}
animatableData를 정의하는 순간:
- SwiftUI는 progress 사이 값을 보간
- Shape는 보간된 상태로 다시 렌더링
- 자연스러운 Shape 변형 효과를 구현 가능
다중 값 애니메이션
var animatableData: AnimatablePair<CGFloat, CGFloat>
또는 더 깊은 중첩도 가능:
AnimatablePair(AnimatablePair(a, b), AnimatablePair(c, d))
➡ 커스텀 Path, Transition, Morphing 애니메이션에서 필수 요소
5. Modifier는 왜 어떤 건 애니메이션되고, 어떤 건 즉시 적용되는가?
SwiftUI Modifier는 크게 두 종류로 나뉜다.
1) Animatable Modifier
ex) opacity, frame, offset, scaleEffect, rotationEffect …
- 값이 Animatable 프로토콜을 만족
- SwiftUI가 값 보간을 처리할 수 있음
2) Non‑Animatable Modifier
ex) background(Color.red), overlay, shadow(일부), mask 등
- “애니메이션 가능한 값”이 아님
- Layout만 바뀌거나, 변경이 렌더 단계에서 즉시 반영됨
- 실제로는 프레임(workload) 규칙이 다름
이 차이를 이해하면:
- 애니메이션이 “반응하지 않는 것처럼 보이는 버그”
- ZStack 이동이 튀는 문제
- Color opacity는 애니메이션되는데 Color 변경은 안 되는 문제
모두 정확히 설명 가능하다.
6. Layout → Render 단계에서 애니메이션이 섞이는 방식
SwiftUI는 다음 단계를 거친다:
- State 변경
- Transaction 전달
- Layout 단계
- Animatable 값 보간
- Render Tree 업데이트
특히 Layout은 애니메이션의 영향을 받는 경우와 받지 않는 경우가 있다.
Layout에 영향 있는 애니메이션
- frame(width:, height:)
- offset
- scaleEffect
Layout이 변경되면 전체 하위 트리에 영향이 생길 수 있다.
Layout에 영향 없는 애니메이션
- opacity
- blur
- brightness
Render 단계에서 처리되므로 비용이 더 낮다고 볼 수 있다.
7. withAnimation vs .animation의 근본적 차이
.animation(_:) (Deprecated 계열)
.opacity(isOn ? 1 : 0)
.animation(.easeInOut, value: isOn)
- View 변경 시 자동으로 애니메이션 적용
- 트래킹 범위가 넓어 예측 어려움
- SwiftUI 4~5에서 사실상 withAnimation 기반으로 이동
withAnimation { }
- State 변화에 직접 Animation을 태워 보냄
- 범위가 명확하고 예측 가능
- 런타임에서 제어하기 편함
(ex: 특정 조건에서만 애니메이션 적용)
8. Animation 성능 – 과도한 애니메이션의 비용
다음 경우 성능 문제가 발생할 수 있다.
- 리스트 셀에서 매번 애니메이션
- GeometryReader 안에서 반복적인 레이아웃 애니메이션
- Transition이 과도하게 중첩된 경우
- Path 기반 애니메이션을 고해상도에서 반복하는 경우
성능 팁:
- Layout 변화를 최소화
- Render-only 효과(opacity, scale) 활용
- animatableData 최소화
- TimelineView와 결합 시 프레임 수 줄이기 (60fps 강제 X)
9. 실전 팁 요약
✔ 팁 1 – 애니메이션이 적용되지 않는 이유는 대부분 “값이 animatable이 아니기 때문”
✔ 팁 2 – Layout 애니메이션은 비싸다
가능하면 Render-only 애니메이션 사용
✔ 팁 3 – Transaction을 명확히 제어하라
특히 withAnimation을 기본 패턴으로 사용할 것
✔ 팁 4 – 커스텀 애니메이션은 animatableData로 구현
Morphing, progress-driven animation은 이게 핵심
✔ 팁 5 – Transition은 실제로 뷰 삽입/제거 시에만 적용됨
상태변경만으로는 transition이 먹히지 않음 → .animation과 개념 다름
10. 정리
SwiftUI의 애니메이션은 단순히 “값이 바뀌면 부드럽게 움직인다”가 아니다.
실제로는:
- Transaction이 상태 변화에 애니메이션 정보를 태워 전달하고
- SwiftUI가 Animation Phase를 계산하며
- animatableData를 기반으로 중간 값을 보간하고
- Layout 단계와 Render 단계가 조합되어 최종 애니메이션이 구현된다.
이 내부 구조를 이해하면:
- 애니메이션이 안 먹는 이유를 정확히 진단하고
- 예측 가능한 부드러운 UI를 만들며
- Shape/Path 기반의 고급 애니메이션까지 설계할 수 있다.
SwiftUI 애니메이션을 “마법”이 아닌 “엔진” 관점으로 다루는 고급 개발자용 문서이다.


