teunteun2
WWDC23 - Explore SwiftUI Animation 본문
해당 세션에서 다루는 것
- SwiftUI가 View를 Rendering 하는 방법 - Anatomy of an update
- Animatable protocol을 사용해서 무엇을 애니메이션할지 정하는 방법
- Animation을 사용해 시간에 따른 값 보간
- Transaction을 사용해 현재 State 업데이트에 대한 context를 전파(dispatch)하는 방법
Anatomy of an update
SwiftUI는 @State 등의 변수를 바탕으로 View의 종속성을 추적합니다.
해당 코드에서 탭 이벤트가 발생하면 selected값이 바뀌며 transaction이 생성됩니다.
transaction이 생성되면 현재 View는 무효화되고,
transaction이 종료되면 뷰를 다시 렌더링하여 View를 업데이트하기 위해 body를 호출합니다.
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 {
selected.toggle()
}
}
}
SwiftUI는 데이터 관리를 하기 위한 종속성 그래프를 가지며,
이 그래프는 UI요소를 하나씩 나눈 Attribute라 불리는 노드로 이뤄져 있습니다.
Transaction이 발생하면 (value가 변경되면) 각 노드를 따라 순서대로 한 레이어씩 새로 고칩니다.
모든 레이어가 업데이트 되면 기존의 View body값은 삭제되고, 종속성그래프가 '그리기 명령'을 내보내 렌더링을 업데이트 합니다.
위 예시 코드에서 animation 기능을 추가하고, isSelected 값이 바뀌면 마찬가지로 transaction이 생성되어
종속성그래프를 통한 뷰 업데이트가 시작됩니다.
다른점으로는, downStream을 통해 각 노드를 타고 내려가다가
scaleEffect와 같은 Animatable프로토콜을 준수하는 속성을 만났을 때입니다.
이 때 애니메이션을 구현하기 위해 기존의 UI 복사본을 만들고 새 UI으로 보간하는 작업이 이루어집니다.
Animatable 은 애니메이션 되는 data를 결정하고,
Animation은 시간에 따라 어떻게 data를 변화시킬지 결정합니다.
Animatable
Animatable 프로토콜은 VectorArithmetic인 animatableData를 요구합니다.
Animatable 의 일종인 ScaleEffect에선 1,2,4차원 벡터의 animatableData를 구현한 것을 볼 수 있습니다.
보통 ScaleEffect와 같이 SwiftUI에서 제공해주는 Animatable 효과를 사용하므로
Animatable을 직접 구현해서 사용하는 경우는 드물지만 원하는 Animatable에 따라 구성해줘야 할 때가 있습니다.
하지만 Custom Animatable은 내장된 효과들보다 훨씬 더 많은 비용이 들기 때문에 정말 필요한 때만 쓰라고 하네요.
기존 Podium에서는 CGPoint에 대한 presentation이 애니메이션으로 이루어져 위치에 대한 animation이 이뤄졌다면,
Animatable 프로토콜을 채택하여 각도에 대한 data를 애니메이션 요소로 썼기 때문에 각도에 대한 animation이 이뤄집니다.
Animation
Animatable 데이터를 보간하는 Animation입니다.
Animation은 크게 세가지 종류로 나눌 수 있습니다.
Animation taxonomy
- Timing curve
- Spring
- Higher order
Timing Curve
Spring
Higher order
속도를 조절하는 speed
애니메이션 시작 딜레이를 걸 수 있는 delay
반복, 반전 재생을 지정하는 repeatCount
이 세가지를 조절하여 기본 애니메이션을 좀 더 커스텀할 수 있습니다.
더 본격적으로 CustomAnimation을 만들수도 있습니다.
Custom Animation
베타기능
https://developer.apple.com/documentation/swiftui/customanimation?language=objc
animate는 필수 구현 요소, velocity와 souldMerge를 통해 CustomAnimation을 만들 수 있다고 합니다.
Transaction
Transaction은 업데이트에 대한 컨텍스트를 전파한다는 정의를 가지고 있습니다.
@Environment 변수와 같이 가지고 있는 값을 암묵적으로 뷰의 계층구조에 전파합니다.
State의 값 변경이 이루어지면 Transaction이 생성되고
해당 State의 withAnimation은 Root Transaction 딕셔너리에 애니메이션을 설정합니다.
그 후 값 업데이트를 UI에도 적용하기 위해 Body가 호출되며, Transaction 딕셔너리는 종속성그래프 전체에 전파됩니다.
순차적으로 종속성그래프를 따라 내려오다보면 Animatable 속성이 있는지 확인합니다.
위에서 언급했듯 scaleEffect는 Animatable속성이므로 애니메이션 가능하고, 애니메이션이 이루어집니다.
이렇게 Transaction 딕셔너리에서 downstream으로 뷰 계층에 따라 애니메이션을 전달하면
애니메이션이 뷰에 적용되는 시기와 방법을 제어하는 여러 기능들을 사용할 수 있습니다.
위에서처럼 .transaction modifier을 사용하면 Transaction 딕셔너리에 애니메이션이 없거나 다른 애니메이션이 있어도\
항상 뷰가 업데이트될 때마다 애니메이션을 재정의하게 됩니다.
하지만 이 코드와 같이 작성하면 원치 않는 애니메이션을 발생시킬 수 있습니다.
이럴 땐 .animation modifier을 사용합니다.
각 Animatable 속성에 애니메이션을 각각 적용할 수도 있습니다.
하지만 위와 같은 .animation modifier은 은 하위 계층구조에도 영향을 끼치기 때문에
leaf components에 적용하는 것이 안전합니다. (하위 뷰를 가지고 있을 경우, 원치 않는 애니메이션 발생 가능)
따라서 애니메이션 적용 범위를 지정해줄 수 있는 새로운 .animation Modifier이 나왔습니다.
또한 Custom TransactionKey를 만들고 사용할 수 있습니다.
첫번째로 TransactionKey Protocol을 준수하는 타입을 만들고, defaultValue를 선언해주면 됩니다.
두번째로 Transaction을 확장해 위에서 만들어준 Key를 통해 연산 프로퍼티를 만들어 활용할 수 있습니다.
Custom TransactionKey를 적용한 코드입니다.
이 코드 또한 원치 않는 효과를 가져올 수 있기에 transaction body 후행클로저로
범위를 좁게 지정해줄 수 있습니다.