Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIView animateWithDuration completion has finished = YES despite being cancelled?

I'm running this animation on a user interaction, and sometimes the animation may be run again before the ongoing animation has finished. What I would like it to do is to cancel the previous animation and continue with the new one.

[UIView animateWithDuration:duration
                      delay:0
                    options:UIViewAnimationOptionBeginFromCurrentState
                 animations:^{
                     self.bounds = bounds;
                 }
                 completion:^(BOOL finished) {
                     if (finished) {
                         // Do some cleanup after animating.
                     }
                 }];

Visually, this seems to work, but in my completion block I am told that it finished in both cases, which causes the cleanup code to run prematurely. So the first animation's completion block runs immediately after the second one starts, with finished = YES. I expected it to have a finished value of NO and the second one (once it completes) to have YES.

Is there a way to know if the animation completed or if it was cancelled by another?

Sidenote: I tried doing the same animation with CABasicAnimation and then I get finished = NO the first time and YES the second time, so the behavior I'm getting seems to be specific to animateWithDuration.

Here's a GIF showing the above code in action with a duration of 10 and the completion block updating the label. As you can see, finished is YES every time the animation is restarted with the animateWithDuration call:

like image 360
Blixt Avatar asked Feb 23 '15 05:02

Blixt


1 Answers

So I investigated this further and I think this is an iOS 8 specific thing, due to how animations are now additive by default, which means that the animations will continue and add up to each other rather than being cancelled.

In my searching I found a WWDC session called "Building Interruptible and Responsive Interactions" which talks about this. In the session they claim that the animations will all finish at the same time but this is not the case in my testing (see the GIF in the question). The workaround suggested in the session is to keep a counter going for how many times the animation started vs. completed and perform the post-animation actions when the counter reaches zero. This is a bit hacky, but works. Here's the question code adjusted for this:

// Add this property to your class.
self.runningAnimations++;
[UIView animateWithDuration:duration
                      delay:0
                    options:UIViewAnimationOptionBeginFromCurrentState
                 animations:^{
                     self.bounds = bounds;
                 }
                 completion:^(BOOL finished) {
                     if (--self.runningAnimations == 0) {
                         // Do some cleanup after animating.
                     }
                 }];

Obviously you'll need a separate counter for every type of animation so it can get a bit messy in the end, but I don't think there's a better way unless you start using CAAnimation directly.

like image 199
Blixt Avatar answered Nov 02 '22 10:11

Blixt