본문 바로가기
Apple/SwiftUI

[iOS] SwiftUI Text Fade 애니메이션 구현하기

by 어멘드 2022. 6. 28.
반응형

아래 이미지와 같이 Text의 값이 바뀔 때 기존 값은 Fade Out 되고, 새로운 값은 Fade In 되는 애니메이션을 구현하고 싶었다.

 

페이드 인/아웃이 opcaity 변화이므로 .transition(.opacity)를 적용하고,
count 값을 변경할 때 withAnimation을 사용하면 원하는 결과가 나올 것이라고 생각했는데, 결과는..

Text("\(count)")
	.transition(.opacity)
withAnimation(.easeInOut(duration: 0.3)) {
    count += 1
}

 

opacity는 그대로고 이상하게 움찔거리는 애니메이션만 적용되었다ㅠㅠ

 

opacity transition이 제대로 작동하지 않은 이유는 transition은 뷰가 삭제/생성될 때만 적용되기 때문이었다.
따라서 Text의 컨텐츠 값이 바뀌어도 뷰 인스턴스가 재생성되지는 않기 때문에 transition이 적용되지 않는다.
이 문제를 해결하는 간단한 편법을 찾았는데, Text 뷰의 id값을 Text 콘텐츠 값과 연관시켜서 콘텐츠 값이 바뀔 때마다 뷰가 강제로 재생성되도록 만드는 것이다.

 

아래와 같이 id modifier를 사용하여 뷰 id를 커스텀해준다.
이때 count값을 포함시켜서 count값이 바뀌면 뷰의 id도 바뀌게 하자.
SwiftUI는 id를 통해서 뷰를 식별하므로, id가 달라지면 다른 뷰라고 인식하여 기존 뷰를 제거하고 새 뷰를 생성하게 된다.

Text("\(count)")
	.transition(.opacity)
	.id("MyText\(count)")

 

따라서 이제 transition이 제대로 작동하게 된다!

 

하지만 이 방법은 id로 뷰 재생성을 강제한다는 점에서 좋지 못한 방법이라고 생각되었다.
그래서 직접 애니메이션을 구현해보았다.
애니메이션 작동 방식은 아래 FlipClock 예시를 참고하였다.

 

GitHub - elpassion/FlipClock-SwiftUI: Flip clock in SwiftUI

Flip clock in SwiftUI. Contribute to elpassion/FlipClock-SwiftUI development by creating an account on GitHub.

github.com

 

기존 값과 새 값이 겹쳐보여야 하므로 ZStack을 사용해준다.
그리고 기존 값이 점점 흐려질 때, 새 값은 진해져야 하므로 opacity를 서로 반대가 되도록 처리해준다.
isNewValueVisible = true인 경우 newValue만 보이고, 반대로 false인 경우에는 oldValue만 보이게 된다.
(처음에는 isNewValueVisible = true인 상태로 시작)

struct FadeView: View {
    @Binding var newValue: Int
    @Binding var isNewValueVisible: Bool
    
    var oldValue: Int {
        self.newValue - 1
    }
    
    var body: some View {
        ZStack {
            Text("\(oldValue)")
                .opacity(isNewValueVisible ? 0 : 1)
            
            Text("\(newValue)")
                .opacity(isNewValueVisible ? 1 : 0)
        }
    }
}

 

이제 count를 업데이트 할 때, withAnimation을 사용하여 불투명도가 서서히 줄어들고 늘어나게 해 주면 된다.

count += 1
isNewValueVisible = false

withAnimation(.easeInOut(duration: 0.3)) {
    self.isNewValueVisible = true
}
  1. 애니메이션 전에, 값을 미리 업데이트 한다.
    : (oldValue, newValue) 쌍이 (1,2)였다면, (2,3)으로 업데이트해준다.
  2. isNewValueVisible = false로 바꾼다.
    : 값을 미리 업데이트했지만, 아직 업데이트하지 않은 것처럼 보이도록 해야 한다. 바로 업데이트한 것이 보이면 애니메이션 없이 한순가에 뿅 하고 바뀐 것처럼 보일 것이다. isNewValueVisible = false로 바꾸어 주면 (oldValue, newValue) = (2, 3) 중 oldValue가 보이고 newValue는 숨겨진 상태이므로 실제 뷰에는 계속 2가 보이는 상태 그대로가 유지되게 된다.
  3. 애니메이션을 사용하여 isNewValueVisible = true로 바꾼다.
    : 이제 (2, 3)에서 2는 점점 흐려지고, 3은 점점 진해지도록 해준다.

 

 

동작 과정을 그림으로 표현하면 아래와 같다.

 

오른쪽 핑크색이 위의 방법으로 직접 구현한 애니메이션이다.
왼쪽 노란색은 .transition(.opacity) + .id()로 구현한 것.

 

 

 

** 틀린 내용이 있거나, 더 좋은 방법이 있다면 댓글로 알려주세요:)

 

> 레퍼런스

https://stackoverflow.com/questions/59157595/how-to-animate-transition-text-value-change-in-swiftui

https://github.com/elpassion/FlipClock-SwiftUI

반응형

댓글