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?
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
removedOnCompletion
(by default)delegate
is set to self
so that the animationDidStop
method
will be calledsetValue: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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With