oulipo
oulipo

Reputation: 375

How to create a delayed CAAnimation that can be cancelled and/or reverted while playing?

I have a layer that I want to show when the user does an action, and hide after he is done. The layer is then shown again if the user does the action again.

In order for the UI to not go berserk with animations, I want:

what is the best way to do this?

I tried this, but this won't work properly (lot of noise with the layer blinking ):

- (void)hideHintLayer:(bool)hide
{
  if(hide)
  {
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.beginTime = CACurrentMediaTime() + 1.0f;
    animation.duration = 1.0f;
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    animation.keyPath = @"opacity";
    animation.fromValue = @(1.0f);
    animation.toValue = @(0.0f);
    [layer addAnimation:animation forKey:nil];
  }
  else
  {
    layer.opacity = 1.0f;
  }
}

Upvotes: 4

Views: 1611

Answers (1)

Rob
Rob

Reputation: 437422

If you want to stop an animation, you can just do

[layer removeAllAnimations];

If you want to know the current alpha during the animated hiding of the view (so that you can reverse the animation, starting from the right place, you can do:

CALayer *presentationLayer = layer.presentationLayer;
CGFloat startingAlpha = presentationLayer.opacity;

You can then set the alpha to go from startingAlpha to 1.0 to animate the unhide without flickering the screen.

You can do the actual animations using block based animation, or I guess you could use CABasicAnimation, though I'm not sure why you would.


So, for example, you could do something like the following (in my example, I have a "show" button). I'm using block animations, but I suspect it would work fine for CABasicAnimation, too:

- (IBAction)onPressShowButton:(id)sender
{
    [self showAndScheduleHide];
}

- (void)showAndScheduleHide
{
    [UIView animateWithDuration:1.0
                     animations:^{
                         self.containerView.alpha = 1.0;
                     }
                     completion:^(BOOL finished) {
                         [self scheduleHide];
                     }];
}

- (void)show
{
    [UIView animateWithDuration:1.0
                     animations:^{
                         self.containerView.alpha = 1.0;
                     }
                     completion:nil];
}

- (void)scheduleHide
{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:5.0
                                                  target:self
                                                selector:@selector(startToHide)
                                                userInfo:nil
                                                 repeats:NO];
}

- (void)startToHide
{
    self.timer = nil;

    self.hiding = YES;

    [UIView animateWithDuration:5.0
                          delay:0.0
                        options:UIViewAnimationOptionAllowUserInteraction
                     animations:^{
                         self.containerView.alpha = 0.0;
                     }
                     completion:^(BOOL finished) {
                         self.hiding = NO;
                     }];
}

You can then have some utility method for reversing it or rescheduling a hide in progress:

- (void)reverseAndPauseHide
{
    // if we have a "hide" scheduled, then cancel that

    if (self.timer)
    {
        [self.timer invalidate];
        self.timer = nil;
    }

    // if we have a hide in progress, then reverse it

    if (self.hiding)
    {
        [self.containerView.layer removeAllAnimations];

        CALayer *layer = self.containerView.layer.presentationLayer;
        CGFloat currentAlpha = layer.opacity;

        self.containerView.alpha = currentAlpha;

        [self show];
    }
}

Then, the question is when you know to call this reverseAndPauseHide and when to scheduleHide again. So, for example, you could handle touches:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];

    [self reverseAndPauseHide];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];

    [self scheduleHide];
}

Upvotes: 3

Related Questions