애니메이션 콜백 메서드가 없는 이유?
“다른 방식으로 애니메이션이 끝난 시점을 알아올 수 있어서가 아닐까?” - 비비
struct ViewTransition: View {
@State var show = false
var body: some View {
ZStack {
if !show {
Text("View transition")
.onDisappear {
//show 는 즉시 토글됨. 하지만
// RoundedRectangle 의 애니메이션이 끝났을 때 호출
// if else 문으로 엮여 있는 View 들의 라이프 사이클이 함께 가져가진다.
}
} else {
RoundedRectangle(cornerRadius: 30)
.fill(.blue)
.padding()
.transition(.scale(scale: 0.1, anchor: .bottomTrailing))
.zIndex(1)
}
}
.onTapGesture {
withAnimation(.spring()) {
show.toggle()
}
}
}
}
Swift
복사
toggle 값 → 즉시
Appear → 즉시
disappear → 애니메이션이 끝난 시점.
1. View extension 적용
extension View {
/// Calls the completion handler whenever an animation on the given value completes.
/// - Parameters:
/// - value: The value to observe for animations.
/// - completion: The completion callback to call once the animation completes.
/// - Returns: A modified `View` instance with the observer attached.
func onAnimationCompleted<Value: VectorArithmetic>(for value: Value, completion: @escaping () -> Void) -> ModifiedContent<Self, AnimationCompletionObserverModifier<Value>> {
return modifier(AnimationCompletionObserverModifier(observedValue: value, completion: completion))
}
}
/// An animatable modifier that is used for observing animations for a given animatable value.
struct AnimationCompletionObserverModifier<Value>: AnimatableModifier where Value: VectorArithmetic {
/// While animating, SwiftUI changes the old input value to the new target value using this property. This value is set to the old value until the animation completes.
var animatableData: Value {
didSet {
notifyCompletionIfFinished()
}
}
/// The target value for which we're observing. This value is directly set once the animation starts. During animation, `animatableData` will hold the oldValue and is only updated to the target value once the animation completes.
private var targetValue: Value
/// The completion callback which is called once the animation completes.
private var completion: () -> Void
init(observedValue: Value, completion: @escaping () -> Void) {
self.completion = completion
self.animatableData = observedValue
targetValue = observedValue
}
/// Verifies whether the current animation is finished and calls the completion callback if true.
private func notifyCompletionIfFinished() {
guard animatableData == targetValue else { return }
/// Dispatching is needed to take the next runloop for the completion callback.
/// This prevents errors like "Modifying state during view update, this will cause undefined behavior."
DispatchQueue.main.async {
self.completion()
}
}
func body(content: Content) -> some View {
/// We're not really modifying the view so we can directly return the original input value.
return content
}
}
Swift
복사
2. 적절한 곳에 사용
struct IntroductionView: View {
@State private var introTextOpacity = 0.0
var body: some View {
VStack {
Text("Welcome to SwiftLee")
.opacity(introTextOpacity)
.onAnimationCompleted(for: introTextOpacity) {
print("Intro text animated in!")
}
}.onAppear(perform: {
withAnimation(.easeIn(duration: 1.0)) {
introTextOpacity = 1.0
}
})
}
}
Swift
복사