How to have a handler to repeat UIView animateWith

2019-01-26 00:21发布

问题:

I'm using UIView class method animateWithDuration for repeating my view animation. How can I have a handler that could be used to stop this animation later? For example, repeated animation starts in one method and I need to stop it later from another method.

回答1:

You could do something like this assuming you have created a canceled property. As noted in the comments the completion block's startAnimation call needs to be wrapped in an async call to avoid a stack overflow. Be sure to replace the "id" with whatever class type you actually have.

- (void)startAnimation {
    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
                     animations:^(void) {
                         //animate
                     }
                     completion:^(BOOL finished) {
                         if(!self.canceled) {
                             __weak id weakSelf = self;
                             [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                                 [weakSelf startAnimation];
                             }];
                         }
                     }
     ];
}


回答2:

The purpose of the animation is to repeatedly animate the bounce of an image. When there is no worry about manually stopping it then you just need to set three properties (UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat) and animation block code for moving the image - self.image.center = CGPointMake(self.image.center.x, self.image.center.y+25); Here is the full code of the animation:

[UIView animateWithDuration:0.5 delay:0 options:( UIViewAnimationOptionCurveEaseIn | 
 UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | 
 UIViewAnimationOptionAllowUserInteraction) animations:^{self.image.center = 
 CGPointMake(self.image.center.x, self.image.center.y+25);} completion:nil];

That's it. But if you need a manual control then some additional code is required. First, according to jaminguy, you need to have a BOOL property for indication loop/stop (self.playAnimationForImage) the animation and clean separate method with animation code that would be called from elsewhere. Here is the method:

-(void)animateImageBounce{
 [UIView animateWithDuration:0.5 delay:0 options:( 
  UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse |  
  UIViewAnimationOptionAllowUserInteraction) animations:^{self.image.center = 
  CGPointMake(self.image.center.x, self.image.center.y+25);} completion:^(BOOL finished){if 
  (finished && self.playAnimationForImage){
    self.image.center = CGPointMake(self.image.center.x, self.image.center.y-25); 
    [self animateImageBounce];
  }}]; 

and here is the start of the animation call from some method

-(void)someMethod{
...
self.playAnimationForFingers = YES;
[self animateImageBounce];

}

The thing that I would like to note is that, in manual control, you need to reset the center.y of the image back right before next recursive call is performed.



回答3:

Actually, the solution with recursive call didn't worked out for me. The animation started to behave weirdly: every 2- 3 animatation repeat cycle I got animation breaks. After the first bouncing part of item (moving item down) the second part (moving up) was performing almost instantly. I thing it has something to do with the recursive call.

Therefore, I refused to use that. The solution would be to start the animation with autoreverse and repeat options and, in complete block, to check if a flag (self.playAnimationForFingers) indicates to stop the animation.

-(void)animateFingersForLevelCompleteScreen{
  //fix: reset the place of bouncing fingers (starting place). 
  //Otherwise the fingers will slowly move to the bottom at every level. 
  //This resetting is not working when placed inside UIView 
  //animate complete event block

  self.image.center = CGPointMake(10 + self.image.frame.size.width/2, 
                                  95 + self.image.frame.size.height/2); 

  [UIView animateWithDuration:0.5 delay:0 
    options:(UIViewAnimationOptionCurveEaseIn | 
             UIViewAnimationOptionAutoreverse | 
             UIViewAnimationOptionRepeat |
             UIViewAnimationOptionAllowUserInteraction) 
    animations:^{
      self.image.center = CGPointMake(self.image.center.x, 
                                      self.image.center.y+25);
    } 
    completion:^(BOOL finished){
      /*finished not approapriate: finished will not be TRUE if animation 
      was interrupted. It is very likely to happen because animation repeats && */
      if (!self.playAnimationForFingers){
         [UIView setAnimationRepeatAutoreverses:NO]; 
      }
  }];
}


回答4:

U can make use of CABasicAnimation instead.

    CABasicAnimation *appDeleteShakeAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
appDeleteShakeAnimation.autoreverses = YES;
appDeleteShakeAnimation.repeatDuration = HUGE_VALF;
appDeleteShakeAnimation.duration = 0.2;
appDeleteShakeAnimation.fromValue = [NSNumber numberWithFloat:-degreeToRadian(5)];
appDeleteShakeAnimation.toValue=[NSNumber numberWithFloat:degreeToRadian(5)];
[self.layer addAnimation:appDeleteShakeAnimation forKey:@"appDeleteShakeAnimation"];

Then when u want to stop it you can just call

[self.layer removeAnimationForKey:@"appDeleteShakeAnimation"];