Wait for Swift Animation to Complete Before Executing Code

Wait for Swift animation to complete before executing code

You can use a CATransaction to call a completion block after the animation completes.

func shakeView(iv: UIImageView){

CATransaction.begin()
CATransaction.setCompletionBlock({
iv.hidden = true
})
var shake:CABasicAnimation = CABasicAnimation(keyPath: "position")
shake.duration = 0.1
shake.repeatCount = 21
shake.autoreverses = true

var from_point:CGPoint = CGPointMake(iv.center.x - 5, iv.center.y)
var from_value:NSValue = NSValue(CGPoint: from_point)

var to_point:CGPoint = CGPointMake(iv.center.x + 5, iv.center.y)
var to_value:NSValue = NSValue(CGPoint: to_point)

shake.fromValue = from_value
shake.toValue = to_value
iv.layer.addAnimation(shake, forKey: "position")
CATransaction.commit()
}

Or you can also group both animations together in a CATransaction as follows.

func shakeView(iv: UIImageView){
var shake:CABasicAnimation = CABasicAnimation(keyPath: "position")
shake.duration = 0.1
shake.repeatCount = 21
shake.autoreverses = true

var from_point:CGPoint = CGPointMake(iv.center.x - 5, iv.center.y)
var from_value:NSValue = NSValue(CGPoint: from_point)

var to_point:CGPoint = CGPointMake(iv.center.x + 5, iv.center.y)
var to_value:NSValue = NSValue(CGPoint: to_point)

shake.fromValue = from_value
shake.toValue = to_value
iv.layer.addAnimation(shake, forKey: "position")
}

override func viewDidLoad() {
CATransaction.begin()

CATransaction.setCompletionBlock({
self.image1.hidden = true
self.image2.hidden = true
})
shakeView(image1)
shakeView(image)
CATransaction.commit()
}

Wait until animation finishes before moving on Swift

That is what the completion handlers are for. Place the next animation inside the completion of the previous one.

Drop this in a playground. If you split it up in functions you can keep it readable. But this will be broken next Wednesday.

var view = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 10))
view.backgroundColor = UIColor.blackColor()

func animationOne() {
UIView.animateWithDuration(1.0,
delay: 0.0,
options: nil,
animations: {view.alpha = 1},
completion: {finished in animationTwo()})
}

func animationTwo() {
UIView.animateWithDuration(1.0,
delay: 0.0,
options: nil,
animations: {view.alpha = 0},
completion: {finished in animationThree()})
}

func animationThree() {
UIView.animateWithDuration(1.0,
delay: 0.0,
options: nil,
animations: {view.alpha = 1},
completion: {finished in print("done")})

}

animationOne()

It appears swift2 doesn't like it when the options are nil:

options are now option sets which are arrays. nil has become []

UIView.animateWithDuration(2.0, delay: 0.0, options: [], animations: { () -> Void in

self.view.alpha = 1.0

}, completion: { (finished: Bool) -> Void in
// next animation
})

How to wait until an animation is finished in Swift?

If you want to use the completion block at the end of the animation, you should use constructs with the delay and options arguments as well.

UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: { 
// put here the code you would like to animate
self.IMGVIEWcat.center = CGPointMake(self.IMGVIEWcat.center.x + 2, self.IMGVIEWcat.center.y)

}, completion: {(finished:Bool) in
// the code you put here will be compiled once the animation finishes
QueryInformations()
})

Wait until task completed before running it again

With that for loop, you're essentially setting up all four animations to run at the same time. If you make that animation code a function, you can call it recursively from the completion block for your white animation:

func animateBackground(times: Int) {
if times == 0 { return }

let blackAnimation = { self.view.backgroundColor = UIColor.blackColor() }
let whiteAnimation = { self.view.backgroundColor = UIColor.whiteColor() }
UIView.animateWithDuration(0.33333, delay: 0.333333, options: UIViewAnimationOptions.CurveEaseIn, animations: blackAnimation) {
completedBlack in // completion block 1

UIView.animateWithDuration(0.333333, animations: whiteAnimation) {
completedWhite in // completion block 2
self.animateBackground(times - 1)
}
}
}

And the initial call looks like:

animateBackground(4)

Swift: Running code prevents other code until completed

I am calling a function which creates several arrays of UIImage objects and takes 4-5 seconds to complete

You absolutely cannot run that on the main thread. A refresh frame is 1/120 second on a modern device. You must not cause frames to be lost. You are blocking user interaction while that happens. The screen will be frozen and the Watchdog process will kill your app dead on the spot. (Unfortunately the Simulator doesn't show you that, nor does running from Xcode on the device; but if you run the app on the device, stop it, and run it from the device rather than from Xcode, you'll see what I mean.)

The issue is that the completion block doesn't run 0.8 seconds later - it doesn't run until AFTER the previous code completes.

As you've surmised, there's a re-entrancy problem here. You need to get off the main thread to allow the completion block to happen. I'm actually surprised that the animation even starts (does it?). Nothing at all can happen while you are still running code on the main thread.

You could call async_after with a short delay to hop off the main thread momentarily and then re-enter it, long enough to let a refresh frame happen, so the system can do some work. But that would just be a way of letting the animation start. Once you're back on the main thread, you'll be blocking it again.

As for speed: a QOS of .background is the slowest choice, so you're getting what you asked for. In general, though, don't use the global queues; make your own queue. I can't imagine what you're doing that can take so long, and maybe that is what you should really be asking about, but you absolutely cannot do it on the main thread.

Create a waiting queue for executing an animation

You may try to make your animation queue as like below example

var results = [Int]()
var isAnimating = false

private func subscribeToNewBLock() {
DispatchQueue.global(qos:.userInteractive).async {
watchForNewBlock() {result in
switch result {
case .Failure:
return
case .Success(let result):

//Call your UI operations in main thread
DispatchQueue.main.async {

self.results.append(result)
//Animation function
self.moveBlocksDown()

//Recursive call to keep listening for new blocks
self.subscribeToNewBLock()
}
}
}
}
}

private func moveBlocksDown() {

guard isAnimating == false && results.count > 0 else {

return
}

self.moveBlocksDown(blockNumber: results.first!)

}

private func moveBlocksDown(blockNumber:Int){

isAnimating = true

UIView.animate(withDuration: 2.0, animations: {

//Animation code goes here

}) { (completed) in

if completed{

//Add follwing code in place of animation completed(May be in completion handler)
self.isAnimating = false
self.results = self.results.filter{$0 != blockNumber} //Remove already animated blockNumber
self.moveBlocksDown() //Call moveBlocksDown function to check if anything pending in queue
}
}
}

How do i wait until an animation in a different class is finished before continuing?

You can push after the animation is done

[UIView animateWithDuration:0.8
animations:^{
// Animation
}
completion:^(BOOL finished){

// PushView

}];

for < iOS 4+ take a look at setAnimationDidStopSelector

or

[self performSelector:@selector(metohodToPushView) withObject:nil afterDelay:0.8];


Related Topics



Leave a reply



Submit