SwiftUI는 복잡한 애니메이션을 선언형으로 간단히 구현할 수 있는 강력한 프레임워크입니다.
상태 변화만 잘 정의하고 변경하면, 몇 줄의 코드로도 부드럽고 자연스러운 애니메이션을 쉽게 만들 수 있습니다.
그러나 SwiftUI에서 여러 애니메이션이 동시에 실행될 경우, 간섭으로 인해 문제가 발생할 수 있습니다.
특히, 서로 다른 애니메이션이 하나의 애니메이션을 조기에 종료시키는 현상이 나타날 수 있습니다.
왼쪽 끝에서 오른쪽 끝으로 1초마다 왕복하며 색을 변경하는 사각형이 있습니다.
아래 버튼을 눌러 텍스트를 등장시키면, 사각형의 왕복 동작이 멈추는 것을 확인할 수 있습니다.
이때 색상 변화는 정상적으로 계속 진행됩니다.
텍스트 코드의 withAnimation
을 제거하면 애니메이션이 정상적으로 작동합니다.
이것은 하나의 애니메이션이 다른 애니메이션의 동작에 영향을 주어 덮어 씌워진다는 것을 의미합니다.
struct ContentView: View {
@State private var isAnimated: Bool = false
@State private var isShow: Bool = false
var body: some View {
ZStack {
VStack(spacing: 50) {
Rectangle()
.fill(isAnimated ? Color.red : Color.blue)
.frame(width: 50, height: 50)
.offset(x: isAnimated ? 100 : -100)
if isShow {
Text("애니메이션 사라짐")
}
}
VStack {
Spacer()
Button("텍스트 등장") {
withAnimation {
isShow.toggle()
}
}
}
}
.onAppear {
withAnimation(.linear(duration: 1).repeatForever()){
isAnimated.toggle()
}
}
}
}
사각형의 애니메이션 View를 별도의 모듈로 분리하면 문제가 해결될까요?
아쉽게도 그렇지 않습니다.
이 문제는 모듈화 여부와는 무관하며, 애니메이션 컨텍스트의 동작 방식이 문제입니다.
오히려 모듈화된 상태에서는 문제의 원인을 파악하기 어려울 수 있습니다.
struct ContentView: View {
@State private var isShow: Bool = false
var body: some View {
ZStack {
VStack(spacing: 50) {
AnimationView()
if isShow {
Text("애니메이션 여전히 사라짐")
}
}
VStack {
Spacer()
Button("텍스트 등장") {
withAnimation {
isShow.toggle()
}
}
}
}
}
}
struct AnimationView: View {
@State private var isAnimated: Bool = false
var body: some View {
Rectangle()
.fill(isAnimated ? Color.red : Color.blue)
.frame(width: 50, height: 50)
.offset(x: isAnimated ? 100 : -100)
.onAppear {
withAnimation(.linear(duration: 1).repeatForever()){
isAnimated.toggle()
}
}
}
}
문제를 해결하려면, 다른 애니메이션에 영향을 받지 않도록 명시적으로 설정해야 합니다.
animation(_:, value:)
함수의 첫 번째 인자에 nil을 지정하고, 영향을 받지 않기를 원하는 property의 값을 value에 지정하면 해당 상태 변화가 애니메이션되지 않도록 설정할 수 있습니다.
struct ContentView: View {
@State private var isAnimated: Bool = false
@State private var isShow: Bool = false
var body: some View {
ZStack {
VStack(spacing: 50) {
Rectangle()
.fill(isAnimated ? Color.red : Color.blue)
.frame(width: 50, height: 50)
.offset(x: isAnimated ? 100 : -100)
.animation(nil, value: isShow)
if isShow {
Text("애니메이션 정상동작")
}
}
VStack {
Spacer()
Button("텍스트 등장") {
withAnimation {
isShow.toggle()
}
}
}
}
.onAppear {
withAnimation(.linear(duration: 1).repeatForever()){
isAnimated.toggle()
}
}
}
}
이 사례를 통해 SwiftUI 애니메이션이 동일한 컨텍스트에서 상태 변경이 겹칠 경우 문제가 발생할 수 있음을 확인했습니다.
예를 들어, 색상 변경 애니메이션은 정상적으로 유지되었지만, 위치 변경 애니메이션은 텍스트 애니메이션에 의해 종료되었습니다.
따라서 SwiftUI에서 애니메이션을 사용할 때는 컨텍스트가 겹치지 않도록 조절해야 하며, 명시적으로 애니메이션을 분리하거나 특정 상태 변화가 애니메이션에 영향을 주지 않도록 처리해야 합니다.
isAnimated 가 onAppear가 isShow일 떄도 값을 갱신 해줘도 그럴까요..? 뭔가 onAppear가 아닌 주기적으로 감시하는 역할이 있어야할 것 같기도 하네요