how to stop/cancel withAnimation in SwiftUI

178 views Asked by At

I need to do following. Start a function that will wait 5 seconds and then execute withAnimation block that will last 3 seconds. In this 3 second period value of a variable opacity should get to 0. I should be able to cancel this function anywhere during the 8 seconds. My code is like this:

@State var opacity: Double = 1.0

    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
        withAnimation(.easeInOut(duration: 3)) {
            opacity = 0.0
        }
    }

How can I achieve that this function can be cancellable anytime during the 8 second period. So if the function gets cancelled, opacity gets set to 1.

2

There are 2 answers

0
MatBuompy On BEST ANSWER

The solution is to use a DispatchWorkItem like so:

struct ContentView: View {
    @State var opacity: CGFloat = 1
    @State private var workItem: DispatchWorkItem?
    
    @State var text = 0
    var body: some View {
        VStack {
            
            Text("Opacity")
                .opacity(opacity)
            
            Button("Start animation") {
                workItem = DispatchWorkItem {
                    withAnimation(.easeInOut(duration: 3)) {
                        opacity = 0.0
                    }
                }
                
                DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: workItem!)
            }
            
            Button("Cancel Animation") {
                workItem?.cancel()
                opacity = 1
            }
            .padding(.top, 20)
            
        }
        .padding()
    }
}

You can cancel that operation whenever you want but, It's not even needed to accomplish what you want. You could just reset the opacity back to one. It works even without the DispatchWorkItem in my testings, but if you needed a cancellable task, there you have it!

2
devPau On

You can encapsulate the animation inside a DispatchWorkItem, as it helps you encapsulate the work and attach a completion handle or execution dependencies (in your case, a cancellable action).

Start by encapsulating your animation inside a workItem:

let workItem = DispatchWorkItem {
        withAnimation(.easeInOut(duration: 3).delay(5)) {
            self.opacity = 0.0
        }
 }

Then to manage the execution:

workItem.perform()

And finally the execution dependency (in your case a cancellable action):

cancellable = AnyCancellable {
        workItem.cancel()
        self.opacity = 1.0
}

Full code below:

   struct ContentView: View {
    
    @State private var opacity: Double = 1.0
    @State private var cancellable: AnyCancellable?

    var body: some View {
        Circle()
            .foregroundStyle(.red)
            .frame(width: 200, height: 200)
            .opacity(opacity)
        
            .onAppear {
                self.startAnimation()
            }
        
        Button("Cancel") {
            cancelAnimation()
        }
    }
    

    func startAnimation() {
        let workItem = DispatchWorkItem {
            withAnimation(.easeInOut(duration: 3).delay(5)) {
                self.opacity = 0.0
            }
        }

        workItem.perform()

        cancellable = AnyCancellable {
            workItem.cancel()
            self.opacity = 1.0
        }
    }

    func cancelAnimation() {
        cancellable?.cancel()
        opacity = 1.0
    }
}

This way, when the view has appeared, the animation will start after 5 seconds and if the cancel button is tapped, the code inside your workItem will stop its execution and the opacity will set be to 1.

Also, don't forget to import Combine