[SwiftUI] animation(_:body:) 로 돌발애니메이션 막기
WWDC 23 > Explore SwiftUI animation 를 보다가
iOS 17에서 animation(_:body:) 등장을 알게 되었다.
기존에 animation(_:value:) 잘썼는데 왜 저 인터페이스가 추가되었을까?
WWDC 에 나오는 내용으로 살펴보자.
(참고로 이 글 을 먼저 읽고 오길 추천!)
[1] 문제
이런 코드를 짰는데,
pet 과 상관없는 다른 곳에서도 이 코드를 재사용하고 싶다고 해보자
그래서 이렇게 generic 하게 바꿔줬다.
그럼 어떤 위험이 있을까?
전자처럼 전체 계층구조를 제어할 수 있는 상황이고 leaf component 라면 원하는대로 애니메이션이 잘 작동한다.
하지만 content가 non-leaf component 라면 (== 하위 계층을 가지고 있는 뷰라면)
예상치못한 애니메이션이 발생할 수 있다. // 이걸 돌발 애니메이션 (accidentanal aniamtion) 라고 부름.
돌발애니메이션이 발생하는 예제가 wwdc 에서 안나왔는데.. 내가 이해한대로 대충 만들어보자면..
이렇게 content 가 leaf component 면 괜찮지만
struct Avatar: View {
@Binding var selected: Bool
var content: some View {
Image(systemName: "cloud")
}
var body: some View {
content
.shadow(color: .blue, radius: selected ? 12 : 8)
.animation(.smooth, value: selected)
.scaleEffect(selected ? 5 : 1)
.animation(.bouncy, value: selected)
.onTapGesture {
selected.toggle()
}
}
}
이런 content 가 넘어오게 되면 rotationEffect 도 같이 animation 되어버리게 된다.
var content: some View {
Image(systemName: "cloud")
.rotationEffect(.degrees(selected ? 90 : 0))
}
content 가 어떻게 넘어올지 모르기때문에 이걸 고려해야한다고 말하고 있는 것!
[2] 기존 해결방식
기존에는 content 를 넘겨주는 쪽에서
애니메이션이 가능한 효과 밑에 명시적으로 none 을 써서 돌발애니메이션을 막을 수 있었다.
var content: some View {
Image(systemName: "cloud")
.rotationEffect(.degrees(selected ? 90 : 0))
.animation(.none)
}
[3] 새로운 해결방식
이제는 구현하는 쪽에서 방어를 할 수 있게 된 것이다!
다음과 같이 animation(_:body:) 을 써서
scope 를 두고 scope 내의 코드만 애니메이션 먹이겠다. 하면 된다.
위의 예제도 다시 작성해서 확인해보자.
rotationEffect 는 애니메이션 안먹는 걸 확인할 수 있다.
struct Avatar: View {
@Binding var selected: Bool
var content: some View {
Image(systemName: "cloud")
.rotationEffect(.degrees(selected ? 90 : 0))
}
var body: some View {
content
.animation(.smooth) {
$0.shadow(color: .blue, radius: selected ? 12 : 8)
}
.animation(.bouncy) {
$0.scaleEffect(selected ? 5 : 1)
}
.onTapGesture {
selected.toggle()
}
}
}
좋군! 😎
참고로 저렇게 generic 한 뷰를 안만들더라도 매우 유용하다.
하나의 animation이 여러 effect 들에 다 적용되기 때문에 애니메이션을 원치 않는 effect 에
animation(none) 또는 animation(nil) 을 덕지덕지 적어줘야했었다면 ..
깔끔하게 제거할 수 있을 것!