-
Notifications
You must be signed in to change notification settings - Fork 3
[정주] WWDC23 ‐ Explore SwiftUI Animation 정리
유정주 JeongJu Yu edited this page Aug 4, 2024
·
1 revision
SwiftUI 애니메이션 살펴보기
- SwiftUI를 개발하게 된 핵심 동기는 앱에 애니메이션을 쉽게 추가할 수 있도록 하는 것
- Anatomy of an update (뷰의 렌더링을 업데이트)
- Animatable로 애니메이션 적용 대상을 결정
- Animation으로 시간에 따른 값을 보간, Transaction으로 현재 업데이트의 Context 전파 방법 살펴보기
- SwiftUI는 화면에 보이는 종속 상태를 추적함
- 종속 항목이 하나라도 변경되면 View가 무효화
- 각각의 노드를 Attribute라고 말함. Attribute는 UI의 세부 항목에 매핑됨
![스크린샷 2024-08-04 13 16 55](https://private-user-images.githubusercontent.com/89075274/354877824-57defe42-2ca4-40c3-ae28-35c3c0161739.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzMTY5MjYsIm5iZiI6MTczOTMxNjYyNiwicGF0aCI6Ii84OTA3NTI3NC8zNTQ4Nzc4MjQtNTdkZWZlNDItMmNhNC00MGMzLWFlMjgtMzVjM2MwMTYxNzM5LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjExVDIzMzAyNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTFjMTM4OGRlYmFiMmVkZjVjMmFkYWRlNDY2MjA1Mzc0Y2M2NzIzYmZlZWZjN2QzYmU5ZTJiZDJkMmE2MTdhZGMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.01kkY9CiuQyjfxNsI8JnZsX7pFE-XZO6fIDFOzowiVw)
- 트랜잭션이 종료될 때 프레임워크는 body를 호출해서 새 값을 생성 후 렌더링을 새로 고침함
- Tap이벤트 발생 → 새로운 트랜잭션 생성 → @State 값 변경 → View 무효화 → body 호출 → 값이 업데이트 → 트랜잭션 닫힘
-
-
withAnimation {}
을 사용하면 트랜잭션 애니메이션이 설정됨
![스크린샷 2024-08-04 13 24 03](https://private-user-images.githubusercontent.com/89075274/354877865-aa1700ac-49fb-4d68-abd9-e8b3df9aa989.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzMTY5MjYsIm5iZiI6MTczOTMxNjYyNiwicGF0aCI6Ii84OTA3NTI3NC8zNTQ4Nzc4NjUtYWExNzAwYWMtNDlmYi00ZDY4LWFiZDktZThiM2RmOWFhOTg5LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjExVDIzMzAyNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI4YWI5ZTA3YjliYTBmMjUzNGZkMzUyOTYzYzc2YmI3ZWQyNjBmYWNjOGFhOWViMDE0NmRmZTA3Y2M1ZDIwMWQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.NAIMrMmeNbLhRtF1Pvg9hF3dG6yJRgwQvQgLK5wPHj8)
- 애니메이션이 가능한 속성(e.g. scaleEffect)의 값이 변경되면 트랜잭션에 애니메이션이 설정되어 있는지 확인함
- 설정되어 있다면 copy하고 애니메이션을 사용해 시간에 따라 이전 값에서 새 값으로 변화함
- 애니메이션이 가능한 속성은 modal과 presentation 값을 모두 가지고 있음
- SwiftUI는 애니메이션이 속성 그래프에 포함되는 시점을 파악 후 적절한 애니메이션이 가능한 속성을 호출, 다음 프레임 생성
- scaleEffect처럼 자동으로 애니메이션이 가능한 속성이라면 SwiftUI가 효율적임
-
- Animatable은 애니메이션을 적용할 데이터를 결정
- Animation은 시간에 따른 데이터의 변화를 결정함
![스크린샷 2024-08-04 13 39 58](https://private-user-images.githubusercontent.com/89075274/354877926-6c0574af-16a2-4865-bf3c-20616d44d136.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzkzMTY5MjYsIm5iZiI6MTczOTMxNjYyNiwicGF0aCI6Ii84OTA3NTI3NC8zNTQ4Nzc5MjYtNmMwNTc0YWYtMTZhMi00ODY1LWJmM2MtMjA2MTZkNDRkMTM2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjExVDIzMzAyNlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWU5Njk4NjQxYTlmMmM2NmIwY2E5NTcyNDI3MTM3NGNkYjZiZDE1ZTk5ZjU0YTk5ZDhmN2Y2MWM4MTUyZjUwMGEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.FXMUkNNOukcKDxASB4yre-7sJTpoMS05iqWIufzHZt0)
-
Animatable을 채택하면 animatableData 속성을 준수해야 함
- animatableData: 애니메이션이 가능한 속성
public protocol Animatable { associatedtype AnimatableData: VectorArithmetic var animatableData: AnimatableData { get set } }
-
Animatable은 View에 맞춰 애니메이션을 커스텀하고 싶을 때 사용함
- RadialLayout은 기본적으로 최단 거리로 애니메이션됨
- 세 개의 Avatar가 원 중앙을 거쳐서 최단 거리 직선으로 이동
- Podium의 Angle값을 이용해서 정의해서 호를 따라 이동하도록 구현할 수 있음
-
- 딱 한 번만 계산하던 흐름에서 SwiftUI가 매번 각도를 계산해서 레이아웃을 다시 실행하도록 하기 때문
- 커스텀한 애니메이션은 매 애니메이션 프레임마다 body를 실행하기 때문에 애니메이션 비용이 훨씬 많이 든다.
- 따라서 내장된 애니메이션으로 원하는 결과를 얻을 수 없을 때만 사용해야 함 → 가벼운 마음으로 사용하면 안 됨
- RadialLayout은 기본적으로 최단 거리로 애니메이션됨
- SwiftUI에는 여러 강력한 애니메이션이 내장되어 있음
- Timing curve
- Spring
- Higher order (기본 애니메이션을 수정한 고차원 애니메이션)
- Timing curve는 애니메이션 속도를 정의하는 curve와 duration을 가짐
- curve는 Bezier 곡성의 제어점을 사용해 만듬
- duration을 커스텀할 수 있음
- Spring 애니메이션은 스프링 시뮬레이션을 실행해 주어진 시점의 값을 결정함
- mass, stiffeness, damping을 조절하는 방식이었는데, 이걸 조절하는건 직관적이지 않음
- 그래서 bounce를 조절하는 방식으로 변경함
- duration과 extraBounce(탄력도)를 조절할 수 있음
- Spring 애니메이션은 UI에 유기적인 느낌을 주기 때문에 강추
- iOS 17에서는 withAnimation의 기본값이 Spring임
- Higher order 애니메이션은 기본 애니메이션을 커스텀할 수 있음
- 반복, 속도변경, 역재생 등
-
Custom Animation 프로토콜이 추가됨
-
animate, shouldMerge, velocity 메서드를 구현해야 함
- animate는 필수, 나머지는 선택
public protocol CustomAnimation: Hashable { func animate<V: VectorArithmetic>( value: V, time: TimeInterval, context: inout AnimationContext<V> ) -> V? }
-
shouldMerge는 애니메이션 도중에 새로운 애니메이션이 생성됐을 때 애니메이션 상태를 통합할 수 있음
-
velocity는 애니메이션이 통합될 때 속도를 유지할 수 있음
-
Dictionary를 사용하여 애니메이션 상태를 기록한다.
-
animation
모디파이어를 사용해 특정 값이 바뀌었을 때 애니메이션을 트랜잭션에 기록할 수 있다.-
transaction
을 사용해서 여러 애니메이션을 정의하면 예상치 못한 돌발 애니메이션이 발생할 수 있음 - 이를 방지하기 위해
animation
모디파이어가 생김
-
-
animation 모디파이어의 순서를 조정하면 효과에 따라 적용할 애니메이션을 다르게 기록할 수 있음
struct Avatar: View { var pet: Pet @Binding var selected: Bool var body: some View { Image(pet.type) .shadow(radius: selected ? 12 : 8) .animation(.smooth, value: selected) .scaleEffect(selected ? 1.5 : 1.0) .animation(.bouncy, value: selected) .onTapGesture { selected.toggle() } } }
- shadow에는 smooth, scaleEffect에는 bouncy가 적용됨
- 아래에서 위로 적용되는 게 포인트
- bouncy로 기록 → scaleEffect에 적용 → smooth로 기록 → shadow에 적용
- Dictionary로 관리되기 때문에 가능한 원리
- shadow에는 smooth, scaleEffect에는 bouncy가 적용됨
-
Leaf 컴포너트가 아니면 돌발 애니메이션이 발생할 확률이 훨씬 높아짐
- 이전에 적용된 트랜잭션 기록을 이어 받을 수 있기 때문
- 이 상황을 방지하기 위해 Scoped Animation 모디파이어가 생김
struct Avatar<Content: View>: View { var content: Content @Binding var selected: Bool var body: some View { content .animation(.smooth) { $0.shadow(radius: selected ? 12 : 8) } .animation(.bouncy) { $0.scaleEffect(selected ? 1.5 : 1.0) } .onTapGesture { selected.toggle() } } }
- 특정 범위에만 애니메이션을 적용함
- 애니메이션 트랜잭션을 복사한 뒤 사본에만 애니메이션을 기록하여 적용하는 원리
-
- 애니메이션 작업이 완료되면 사본은 제거됨
- 원본은 그대로 애니메이션이 nil이므로 하위 View에는 애니메이션이 적용되지 않음
-
트랜잭션 Key를 적용하여 트랜잭션 Dictionary 값을 설정할 수 있음
-
모든 트랜잭션 값을 구조체 생명주기에서 고유함. 트랜잭션은 View 업데이트가 끝날 때마다 버려지기 때문
- 매 업데이트마다 명시적으로 트랜잭션을 정의해야 함
- 그렇지 않으면 기본값으로 설정됨
-
Transaction 모디파이어도 돌발 애니메이션을 방지하기 위해 값을 설정하거나 body로 범위를 지정할 수 있음
고병학 | 권승용 | 김대황 | 김인환 | 유정주 | 윤동주 | 이준복 | 이창준 | 정종인 | 홍승현 |
---|---|---|---|---|---|---|---|---|---|
bengHak | ericKwon95 | qwerty3345 | loinsir | jeongju9216 | yoondj98 | junbok97 | SwiftyJunnos | chongin12 | WhiteHyun |