Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for an repeating CAAnimation to finish a cycle before removing it

I have a view that I let pulsate using a CAAnimation.

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
animation.values = @[ @0.0f, @1.0f, @0.0f ];
animation.duration = 0.5;
animation.repeatCount = HUGE_VALF;

[view.layer addAnimation:animation forKey:@"pulsate"];

When I remove the animation using [view.layer removeAnimationForKey:@"pulsate"] the opacity snaps back immediately. What I would like to achieve is that the currently executing pulsating animation is finished and then the animation is removed.

I tried by setting repeatCount to 1, but this throws an exception because the animation is immutable.

Also I tried getting the current value from the presentation layer and applying it to the model, then removing the animation and again adding an animation to finish it. But this gives noticeable hiccups when stopping the animation and also the timing is off usually.

Is there a way to let an animation finish running a cycle and remove it afterwards?

like image 597
Alfonso Avatar asked Jun 27 '14 20:06

Alfonso


1 Answers

There's a lot of details to get right, but the general idea is to create a non-repeating animation that is removed on completion, and then use the animationDidStop delegate method to restart the animation.

The first item of business is to declare some properties

@property (weak, nonatomic) IBOutlet UIImageView *orangeView2;
@property (nonatomic) bool pulseActive;
@property (strong, nonatomic) CAKeyframeAnimation *pulseAnimation;

The first property is the view that will be animated, the second keeps track of whether the animation is enabled, and the last is the actual animation (stored in property so that we only have to instantiate it once).

Next, we'll use lazy instantiation to create the animation object

- (CAKeyframeAnimation *)pulseAnimation
{
    if ( !_pulseAnimation )
    {
        _pulseAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
        _pulseAnimation.values = @[ @0.0f, @1.0f, @0.0f ];
        _pulseAnimation.duration = 0.5;

        _pulseAnimation.delegate = self;
        [_pulseAnimation setValue:@"PulseAnimation" forKey:@"AnimationIdentifier"];
    }

    return( _pulseAnimation );
}

The important bits here are

  • the animation does not repeat (by default)
  • the animation is removedOnCompletion (by default)
  • the delegate is set to self so that the animationDidStop method will be called
  • the animation is given an identifier using setValue:forKey:

That last item is only needed if multiple animations are using the same delegate, since in that case, you'll need a way to determine which animation called animationDidStop. The strings passed to forKey and setValue are arbitrary, and are stored in a dictionary in the animation object.

Ok, so now we need to implement animationDidStop. The implementation checks the pulseActive property and restarts the animation if necessary (after checking the identity of the animation).

- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
    NSString *animationIdentifier = [animation valueForKey:@"AnimationIdentifier"];

    if ( [animationIdentifier isEqualToString:@"PulseAnimation"] )
    {
        if ( self.pulseActive )
            [self.orangeView2.layer addAnimation:self.pulseAnimation forKey:@"pulsate"];
    }
}

All that's left is to start and stop the animation. For example, a button that toggles the animation

- (IBAction)pulseButtonPressed
{
    if ( !self.pulseActive )
    {
        self.pulseActive = YES;
        [self.orangeView2.layer addAnimation:[self pulseAnimation] forKey:@"pulsate"];
    }
    else
    {
        self.pulseActive = NO;
    }
}
like image 193
user3386109 Avatar answered Nov 15 '22 04:11

user3386109