일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- @sendable
- 360도 이미지
- completion handler
- React-Native
- data driven construct
- 파노라마 뷰
- 앱 성능 개선
- ios
- 뷰 생명주기
- ssot
- 네이티브
- requirenativecomponent
- 명시적 정체성
- 라이브러리 없이
- 뷰 정체성
- 구조적 정체성
- 360도 뷰어
- react-native-fast-image
- 리액트
- launchscreen
- panorama view
- React Native
- 리액트 네이티브
- 스켈레톤 통합
- native
- 360도 이미지 뷰어
- Android
- SwiftUI
- launch screen
- react
- Today
- Total
Neoself의 기술 블로그
SwiftUI 애니메이션 정리(Animatable, Animation, Transaction) 본문
가장 먼저 Animation의 근간이 되는 뷰 업데이트에 대해 톺아보겠습니다.
SwiftUI View는 뷰의 종속항목을 상시 추적합니다. 여기서 종속항목은 @State, @ObservedObject, @Published 등의 프로퍼티 래퍼가 될 수 있는데요.
종속항목이 변경되면서 뷰의 상태가 변경되면, 아래의 절차가 연차적으로 수행됩니다.
1. 다운스트림 속성값이 만료되며 기존 View 무효화
2. Transaction 생성
3. 업스트림 종속성이 변경되며 프레임워크가 View Value을 호출
3. Transaction으로 뷰 계층 구조를 순회
4. 속성값 모두 업데이트된 후, Transaction 폐기
*여기서 Transaction은 딕셔너리 중 하나로 SwiftUI가 현재 업데이트에 대한 컨텍스트를 암시적으로 전파하는데 사용되는 강력한 데이터 흐름 구조를 의미
*업스트림은 상태변화가 발생하는 곳. 일반적으로 @State, @ObservableObject, @EnvironmentObject 등의 데이터 소스
*다운스트림은 업스트림의 변화에 의해 영향을 받는 뷰나 컴포넌트. 데이터 흐름의 종착점입니다
아래는 Apple Developer 유튜브 채널의 "SwiftUI 애니메이션 살펴보기" 영상에 소개된 다이어그램입니다.
탭 상호작용을 통해 업스트림 Dependency인 selected 상태변수가 변경될 시, 프레임워크는 좌측 다이어그램의 새로운 뷰에 표시될 정보들을 담은 View Value를 호출합니다. 이후 열리는 Transaction은 View Value를 활용해 UI 새부항목과 매핑되는 초록색의 속성들을 하나하나씩 순회하며 새로고침을 진행해줍니다. 순회가 완료되면서 뷰 업데이트가 완료되면 Transaction은 종료되고, View Value은 폐기됩니다. 마지막으로 랜더링 업데이트를 위해 Attribute가 드로잉 커맨드를 랜더러에게 보냅니다.
Animatable 속성
struct Avatar: View {
var pet: Pet
@State private var selected: Bool = false
var body: some View {
Image(pet.type)
.scaleEffect(selected ? 1.5 : 1.0)
.onTapGesture {
withAnimation {
selected.toggle()
}
}
}
}
위처럼 애니메이션 적용 방식중 하나인 withAnimation을 통해 트리거 함수를 래핑하게 될경우에는 속성그래프가 어떻게 변경하게 될까요??
onTapGesture 내부 클로저 실행 시, 가장 먼저 트랜잭션 애니메이션이 설정되고 그 이후에 selected가 토글되면서 뷰 상태 업데이트 과정이 진행되게 됩니다. 여기서 주목해야할 속성은 scaleEffect입니다. 이는 Animatable한 특수 속성으로, Transaction이 해당 속성을 순회올때마다 Transaction으로부터 애니메이션이 설정되어있는지 확인하는 과정이 추가되는데요.
위 상황의 경우 Transaction은 transaction 수정자 순회 시, 애니메이션 정보를 담게 되며 Animatable 속성을 순회하면서 담고 있는 애니메이션을 새로운 값으로 값을 보간하는 데에 사용합니다.
scaleEffect와 같은 속성은 내장되어있는 애니메이션 가능(Animatable) 속성으로, 뷰 코드 호출 없이 메인 스레드에서 작업 수행이 가능하기에 적은 리소스로 애니메이션을 동작할 수 있습니다. 하지만 Animatable API를 통해 직접 커스텀 에니메이션 정의하게 될 경우, 애니메이션의 모든 프레임에 대해 본문이 호출되면서, 레이아웃이 재실행되기 때문에 애니메이션 비용이 큰 폭으로 늘어나게 됩니다.
Animation
애플에서는 애니메이션을 애니메이션 "적용 가능한 데이터를 시간에 따라 보간하는 범용 알고리즘"으로 정의하고 있습니다.
애니메이션의 경우 아래 선택지들이 있습니다.
1. Spring
스프링의 질량, 강성, 감쇠와 같은 요소를 조정하여 자연스러운 움직임을 구현
- smooth, snappy, bouncy
- withAnimation의 기본 적용값
2. Timing curve
애니메이션속도를 결정하는 커브와 지속 시간을 가질 수 있으며, 여러 프리셋(Linear, easeIn, easeOut, easeInOut)이 내장되어있음
- linear, ease in, ease out, ease in and out
3. Higher Order
기본 애니메이션에 delay, repeat 부여
4. CustomAnimation 프로토콜
필수 구현 메서드
animate: 애니메이션을 적용할 벡터, 애니메이션 시작 후 경과 시간, 추가 애니메이션 상태를 포함하는 콘텍스트가 들어감
니메이션의 현재 값을 반환하거나 애니메이션이 끝났다면 nil을 반환
shouldMerge: shouldMerge 메서드 구현 시, 이전 애니메이션 상태를 통합함에 따라 두 애니메이션이 함께 실행될 경우, presentation 값을 쉽게 계산할 수 있음.
velocity: 실행 중인 애니메이션이 새 애니메이션과 병합될 때 속도를 유지하 수 있도록 값을 반환하는 메서드
Transaction
특정 UI 업데이트에 수행되는 작업을 의미하며, SwiftUI에서는 애니메이션관련 정보를 전파하는 데 사용
애니메이션 부여는 Transaction을 통해 제어되며, Animatable 속성이 트랜잭션 딕셔너리를 통해 속성 그래프에 전파된다
위에 소개한 withAnimation 외에도, transaction 수정자를 통해 애니메이션 Transaction을 접근해 애니메이션을 설정할 수도 있는데요.이는 이미지 뷰의 모든 파생 애니메이션에 영향을 줄 수 있기 때문에, 애니메이션 적용 범위를 특정 상태변수로만 특정할 수 있는 animation 뷰 수정자를 사용하는 것을 애플에서 권유하고 있습니다.
Image("exampleImage")
.scaleEffect(selected ? 1.5 1.0)
// transaction 수정자 내부에서 transaction 애니메이션 직접 설정해 본문 호출 시 속성이 애니메이션을 재정의하게 할 수 있음.
// 하지만, 뷰가 업데이트될때마다 모든 파생 애니메이션이 재정의되기 때문에, 돌발 애니메이션 발생 가능성 있음.
//.transaction {
// $0.animation = .bouncy
//}
// 특정 상태값에 대한 애니메이션만 지정하는 뷰 수정자로 돌발 애니메이션 발생 방지
.animation(.bouncy,value:selected)
.onTapGesture {
// transaction 애니메이션을 animation 뷰 수정자로 직접 설정하였기 때문에 불필요
//withAnimation(.bouncy) {
selected.toggle()
//}
}
또한 Transaction은 각 속성들을 위에서 아래로 하나씩 순회하면서 업데이트를 진행하기 때문에 아래와 같이 애니메이션이 뷰에 적용되는 시기와 방법을 세밀하게 조절할 수 있습니다.
Image("exampleImage")
.shadow(radius: selected ? 12 : 8)
.animation(.smooth,value: selected)
.scaleEffect(selected ? 1.5 1.0)
.animation(.bouncy,value: selected)
.onTapGesture {
withAnimation(.bouncy) {
selected.toggle()
}
}
위 코드의 경우, 같은 상태변수에 의존함에도 불구하고, shadow와 scaleEffect Animatable 속성에 각기 다른 애니메이션을 적용시킬 수 있게 됩니다.
감사합니다.
Reference
https://www.youtube.com/watch?v=IuSuHJs5-KE&list=LL&index=12
'개발지식 정리 > WWDC 정리' 카테고리의 다른 글
Meet async/await in Swift 정리 (0) | 2025.04.03 |
---|---|
Explore structured concurrency in Swift 정리 (0) | 2025.04.01 |
Understanding Swift Performance 정리 (0) | 2025.01.07 |
SwiftUI 심층정리 (0) | 2024.12.16 |
SwiftUI | PhaseAnimator, Keyframes으로 고급 애니메이션 구현하기 (6) | 2024.10.16 |