티스토리 뷰

반응형

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) 을 덕지덕지 적어줘야했었다면 .. 

깔끔하게 제거할 수 있을 것! 

 

 

반응형
댓글