개발하면서 애니메이션이 필요할 때마다 AI의 도움을 받아서 적용했었는데 이러다보니 항상 개발할 때마다 각 애니메이션 관련 문법을 언제, 어디에, 어떻게 써야 하는지 모른채 사용하는 난관에 빠지게 되었다.
그래서 오늘은 각 애니메이션 관련 문법이 어떤 역할을 하는지 비교하며 알아보려고 한다.
.animation(_:value:)
첫번째로 .animation(_:value:) 이 친구는 뷰 모디파이어로 붙여서 사용할 수 있다.
Image(systemName: "heart.fill")
.scaleEffect(isLiked ? 1.5 : 1.0)
.animation(.easeInOut, value: isLiked)
이런식으로 사용하는데 파라미터로 animation과 value를 받는다.
animation : 어떤 애니메이션을 적용할지 고른다. (예: .easeInOut, .spring())value : 변화를 모니터링할 값이다.원리로는 value에 전달되는 값은 반드시 Equatable 프로토콜을 준수해야해서, 이전 렌더링 시점의 value와 현재의 value를 비교하여 값이 달라졌을 때를 감지하여 애니메이션을 구현해준다.
*상태 변화에 따라 모든 뷰에 애니메이션을 적용하는 대신 특정 뷰에만 애니메이션을 적용할 때 유용하다 !
import SwiftUI
struct SimpleAnimation: View {
@State private var isLiked = false
var body: some View {
Image(systemName: "heart.fill")
.font(.largeTitle)
.foregroundColor(.red)
.scaleEffect(isLiked ? 1.5 : 1.0)
.animation(.default, value: isLiked) //value가 바뀔때 애니메이션이 적용된다.
.onTapGesture { isLiked.toggle() }
}
}
하트를 터치하여 isLiked의 상태가 바뀌었으므로 하트 크기가 커지고 작아지면서 애니메이션이 적용된다.
withAnimation(_:_:)
두번째로 withAnimation(_:_:) 이 친구는 명령형으로 사용할 수 있다.
Button("좋아요") {
withAnimation(.easeInOut) {
isLiked.toggle()
}
}
이런식으로 코드를 감싸는 것만으로 애니메이션이 발동된다.
파라미터로 animation과 body를 받는다.
animation : 어떤 애니메이션을 적용할지 고른다. (예: .easeInOut, .spring())body : body는 클로저로, 애니메이션으로 만들고 싶은 '상태 변화'를 집어넣는 공간이다.원리로는 withAnimation 블록이 실행되면 SwiftUI는 즉시 Transaction이라는 객체를 하나 생성한다. 이 객체 안에는 우리가 설정한 애니메이션 정보(곡선, 시간 등)가 담겨 있다.
동작 원리
1. withAnimation 호출 시 새로운 트랜잭션 컨텍스트가 열린다.
2. 블록 { } 내의 코드가 실행되며 상태(@State) 값이 변경된다.
3. 이 상태 변화로 인해 영향을 받는 모든 뷰 업데이트에 방금 만든 트랜잭션이 강제로 주입된다.
4. 결과적으로, 이 상태와 연결된 모든 뷰는 동일한 애니메이션 정보를 공유하며 한꺼번에 움직인다.
*어쨌든 withAnimation 은 상태변화에 해당하는 모든 뷰를 애니메이션 하는데 유용하다.
struct SimpleWithAnimation: View {
@State private var isChange = false
var body: some View {
VStack(spacing: 30) {
HStack(spacing: 30) {
Circle()
.frame(width: isChange ? 100 : 50) // 크기 변경
Circle()
.frame(width: 100)
.foregroundStyle(isChange ? .blue: .red) //색깔 변경
}
Button("모두 변경") {
withAnimation { isChange.toggle() }
}
}
}
}
'모두 변경' 버튼을 누르면 withAnimation안에 isChange가 토글되면서 상태가 변하고 isChange를 사용하고 있는 뷰들은 애니메이션 영향을 받는다. 그래서 크기 변경, 색깔 변경을 하는 각각의 원이 애니메이션이 적용된 모습이다.
transition(_:)
세번째로 transition(_:) 이 친구는 뷰 모디파이어로 붙여서 사용할 수 있다.
if isActive {
MyView()
.transition(.slide)
}
Button("Toggle") {
withAnimation {
isActive.toggle()
}
}
이런식으로 사용하는데 파라미터로 AnyTransition타입을 받는다.
AnyTransition : 뷰가 나타나고 사라지는 '방법'을 정의하는 객체다. (.slide, .opacity 같은 것들이 모두 이 타입에 해당)transition(_:)을 뷰에 붙이면 이 뷰가 나타나거나 사라질 때 전환 효과가 적용되어 애니메이션 효과로 부드럽게 나타나거나 사라지는 것이다.
동작 원리
1. 상태가 변경되어 뷰가 삭제되어야 하는 시점에, SwiftUI는 뷰에 transition이 있는지 확인한다.
2. transition이 있다면, 뷰를 즉시 삭제하지 않고 '제거 중(Removal)'이라는 특수한 상태로 유지한다.
3. 트랜잭션(withAnimation 등)에 설정된 시간 동안 뷰를 화면에 남겨두며, 설정된 경로(예: 점점 투명해짐, 아래로 이동)로 값을 보간(Interpolate)한다.
4. 애니메이션이 100% 완료되는 순간, 비로소 뷰 계층에서 완전히 들어낸다.
추가적으로, Asymmetric(비대칭)은 insertion(등장), removal(퇴장)을 분리하여 아래와 같이 다르게 적용할 수 있다.
.transition(.asymmetric(
insertion: .move(edge: .leading), // 들어올 땐 왼쪽에서 슈욱
removal: .opacity.combined(with: .scale) // 나갈 땐 투명해지며 작아짐
))
*transition은 '방법'만 정의하기 때문에 실제 애니메이션 실행하는 withAnimation이 있어야 적용된다. (transition 단독 사용 불가)
struct SimpleTransitionAnimation: View {
@State private var show = false
var body: some View {
VStack {
Button("Toggle") { withAnimation { show.toggle() } } //withAnimation이 존재해야 transition이 작동함
if show {
Text("나타났다!")
.transition(.slide) // 나타나고 사라질 때 옆으로 슥!
}
}
}
}
버튼을 누르면 텍스트가 옆에서 슥하고 나타난다. 그리고 다시 누르면 또 옆으로 슥하면서 사라진다. 아까도 말했던 것 처럼 등장과 퇴장이 둘 다 옆으로 슬라이드 되는 모습이 싫다면, Asymmetric(비대칭)을 이용하여 insertion(등장), removal(퇴장)을 분리하면된다.
마무리
.animation(_:value:) 은 value의 변화를 감지하여 이 뷰를 애니메이션 해라 !
withAnimation(_:_:) 은 이 안에 있는 상태 변화에 따라 상태 변화가 일어나는 모든 뷰에 애니메이션 해라 !
transition(_:)은 이 뷰가 나타나고 사라질 때 이 방법으로 전환해라 !
이런식으로 정리할 수 있었고, 애니메이션을 그냥 남용했던 나였지만 이제 각각의 의미를 알게되었고 필요에 따라 적재적소에 배치할 수 있도록 노력해야겠다.
🍎참고
https://developer.apple.com/documentation/swiftui/animations
animation()
withAnimation()
transition()