5
votes

I'm trying to figure out how to detect that an animation has completed in SwiftUI, to be specific: a Spring() animation. My first thought was to use a GeometryReader to detect when the Circle in the example below reaches the point of origin (offset = .zero), however there is one caveat to this approach: the Spring() animation goes a little bit beyond the point where it should end and then bounces back. So the "end of the animation" would be triggered before the animation has finished.

I did some research and found another approach : SwiftUI withAnimation completion callback. However, in this solution the offset of the animated object is compared to the point of origin so it's the same problem as described above.

I could use a timer but that wouldn't be an elegant solution since the duration of the Spring() animation dynamically changes depending from where it started, so that's not the way.

In the example below, I would like that the circle gets green after the animation has finished.

Is there a way to solve this issue? Thanks for helping!

enter image description here

struct ContentView: View {
    
    @State var offset: CGSize = .zero
    @State var animationRunning = false
    
    var body: some View {
        
        VStack {
            Circle()
                .foregroundColor(self.animationRunning ? .red : .green)
                .frame(width: 200, height: 200)
                .offset(self.offset)
                .gesture(
                    DragGesture()
                        .onChanged{ gesture in
                            self.offset = gesture.translation
                    }
                    .onEnded{_ in
                        self.animationRunning = true
                        
                        withAnimation(.spring()){
                            self.offset = .zero
                        }
                })
            Spacer()
        }
    }
}

1

1 Answers

4
votes

Default animation duration (for those animations which do not have explicit duration parameter) is usually 0.25-0.35 (independently of where it is started & platform), so in your case it is completely safe (tested with Xcode 11.4 / iOS 13.4) to use the following approach:

withAnimation(.spring()){
    self.offset = .zero
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.animationRunning = false
    }
}

Note: you can tune that 0.5 delay, but the difference is not remarkable for human eye.