7

Assuming a chain of block based animations like the following:

UIView * view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];

//animation 1
[UIView animateWithDuration:2 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
     view.frame = CGRectMake(0, 100, 200, 200);
} completion:^(BOOL finished){

     //animation 2
     [UIView animateWithDuration:2 delay:0 options: UIViewAnimationOptionRepeat |UIViewAnimationOptionAutoreverse animations:^{
          [UIView setAnimationRepeatCount:1.5];
          view.frame = CGRectMake(50, 100, 200, 200);   
     } completion:^(BOOL finished){

          //animation 3
          [UIView animateWithDuration:2 delay:0 options:0 animations:^{
               view.frame = CGRectMake(50, 0, 200, 200);
          } completion:nil];
     }];
}];

What would be the best way to stop this kind of animation? Just calling

[view.layer removeAllAnimations];

is not enough, because that stops only the currently executing animation block and the rest will execute in sequence.

  • Does the compiler allow that? I ran into trouble with nesting blocks about that deep. (Can't remember now if it was gcc or clang — more likely, clang allowed more depth but still not a lot.) – David Dunham Nov 18 '11 at 23:04
  • Block based animation is supported in iOS 4 and higher. The nesting works perfectly. –  Nov 18 '11 at 23:11
  • Then either the compiler bug got fixed, you're using clang, or you haven't hit the limit. – David Dunham Nov 18 '11 at 23:15

2 Answers2

11

You can consult the finished BOOL passed in to your completion blocks. It will be NO in the case that you've called removeAllAnimations.

Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
  • The finished BOOL is YES after I call removeAllAnimations. –  Nov 18 '11 at 23:25
  • It should be NO like you say, but it is YES... "This block has no return value and takes a single Boolean argument that indicates whether or not the animations actually finished before the completion handler was called" –  Nov 18 '11 at 23:32
  • In all of your completion blocks? It should be NO if the animation doesn't complete. Otherwise, I think your other solution is the right thing to do. – Jesse Rusak Nov 18 '11 at 23:33
  • If you used CAAnimations directly, I suspect it would work. Perhaps the UIView is doing something else to trigger the callback which doesn't play nicely with the lower-level removeAllAnimations on the CALayer. – Jesse Rusak Nov 18 '11 at 23:34
  • 6
    Jesse what you suggested works when the UIViewAnimationOptionAllowUserInteraction option is set. –  Nov 19 '11 at 09:18
  • 2
    Wow, that's a pretty random discovery. I wonder why that matters. – Jesse Rusak Nov 19 '11 at 15:04
8

I use the following approach:

UIView * view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];

//set the animating flag
animating = YES;

//animation 1
[UIView animateWithDuration:2 delay:0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction animations:^{
     view.frame = CGRectMake(0, 100, 200, 200);
} completion:^(BOOL finished){
     //stops the chain
     if(! finished) return;

     //animation 2
     [UIView animateWithDuration:2 delay:0 options: UIViewAnimationOptionRepeat |UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction  animations:^{
          [UIView setAnimationRepeatCount:1.5];
          view.frame = CGRectMake(50, 100, 200, 200);   
     } completion:^(BOOL finished){
          //stops the chain
          if(! finished) return;

          //animation 3
          [UIView animateWithDuration:2 delay:0 options:0 animations:^{
               view.frame = CGRectMake(50, 0, 200, 200);
          } completion:nil];
    }];
}];

- (void)stop {
     animating = NO;
     [view.layer removeAllAnimations];
}

The removeAllAnimations message stops the animating block immediately and its completion block is called. The animating flag is checked there and the chain is stopped.

Is there a better way to do it?

  • 1
    In some cases you might want to remove the animations from all views. for(UIView * view in [self.view subviews]) { [view.layer removeAllAnimations]; } –  Nov 18 '11 at 23:49
  • 3
    You did not set `UIViewAnimationOptionAllowUserInteraction` in options. How `stop` is invoked? User taps/pans? If so, then `stop` is not called according to your code. Have a look... Anyhow, `finished` should be used, as explained above. – debleek63 Nov 19 '11 at 01:19
  • You are right the animating flag is unnecessary, adding the UIViewAnimationOptionAllowUserInteraction option and replacing if(! animating) return; with if (! finished) return; is the way to do it. –  Nov 19 '11 at 09:17