Animation End Callback for CALayer?

IphoneCore Animation

Iphone Problem Overview


I'm wondering where the callbacks are (or if there are anything) for animations in a CALayer. Specifically, for implied animations like altering the frame, position, etc. In a UIView, you could do something like this:

[UIView beginAnimations:@"SlideOut" context:nil];
[UIView setAnimationDuration:.3];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animateOut:finished:context:)];
CGRect frame = self.frame;
frame.origin.y = 480;
self.frame = frame;
[UIView commitAnimations];

Specifically, the setAnimationDidStopSelector is what I want for an animation in a CALayer. Is there anything like that?

TIA.

Iphone Solutions


Solution 1 - Iphone

You could use a CATransaction, it has a completion block handler.

[CATransaction begin];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
[pathAnimation setDuration:1];
[pathAnimation setFromValue:[NSNumber numberWithFloat:0.0f]];    
[pathAnimation setToValue:[NSNumber numberWithFloat:1.0f]];
[CATransaction setCompletionBlock:^{_lastPoint = _currentPoint; _currentPoint = CGPointMake(_lastPoint.x + _wormStepHorizontalValue, _wormStepVerticalValue);}];
[_pathLayer addAnimation:pathAnimation forKey:@"strokeEnd"];
[CATransaction commit];

Solution 2 - Iphone

I answered my own question. You have to add an animation using CABasicAnimation like so:

CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:@"frame"];
anim.fromValue = [NSValue valueWithCGRect:layer.frame];
anim.toValue = [NSValue valueWithCGRect:frame];
anim.delegate = self;
[layer addAnimation:anim forKey:@"frame"];

And implement the delegate method animationDidStop:finished: and you should be good to go. Thank goodness this functionality exists! :D

Solution 3 - Iphone

For 2018 ...

Couldn't be easier.

Don't forget the [weak self] or you'll crash.

func animeExample() {
    
    CATransaction.begin()
    
    let a = CABasicAnimation(keyPath: "fillColor")
    a.fromValue, duration = ... etc etc
    
    CATransaction.setCompletionBlock{ [weak self] in
        self?.animeExample()
        self?.ringBell()
        print("again...")
    }
    
    someLayer.add(a, forKey: nil)
    CATransaction.commit()
}
Critical tip:

You MUST have setCompletionBlock BEFORE the someLayer.add.

The order is critical! It's an iOS quirk.

In the example, it just calls itself again.

Of course, you can call any function.


Notes for any anyone new to iOS animations:

  1. The "key" (as in forKey) is irrelevant and rarely used. Set it to nil. If you want to set it, set it to "any string".

  2. The "keyPath" is in fact the actual "thing you are animating". It is literally a property of the layer such as "opacity", "backgroundColor" etc, but written as a string. (You can't just type in "anything you want" there, it has to be the name of an actual property of the layer, and, it has to be animatable.)

To repeat: the "key" (rarely used - just set it to nil) and the "keyPath" are totally unrelated to each other.

You often see example code where these two are confused (thanks to the silly naming), which causes all sorts of problems.


Note that alternately you can use the delegate, but it's far easier to just use the completion block, since (A) it's self-contained and can be used anywhere and (B) you usually have more than one anime, in which case using the delegate is a bore.

Solution 4 - Iphone

Here is an answer in Swift 3.0 based on bennythemink's solution:

    // Begin the transaction
    CATransaction.begin()
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.duration = duration //duration is the number of seconds
    animation.fromValue = 0
    animation.toValue = 1
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    circleLayer.strokeEnd = 1.0

    // Callback function
    CATransaction.setCompletionBlock { 
        print("end animation")
    }

    // Do the actual animation and commit the transaction
    circleLayer.add(animation, forKey: "animateCircle")
    CATransaction.commit() 

Solution 5 - Iphone

Wasted 4 hours with this garbage, just to do a fade in fade out. Note the comment in the code.

   [CATransaction begin];
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.duration = 0.3;
    animation.fromValue = [NSNumber numberWithFloat:0.0f];
    animation.toValue = [NSNumber numberWithFloat:1.0f];
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeBoth;
  ///  [box addAnimation:animation forKey:@"j"]; Animation will not work if added here. Need to add this only after the completion block.
    
    [CATransaction setCompletionBlock:^{

        CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation2.duration = 0.3;
        animation2.beginTime = CACurrentMediaTime()+1;
        animation2.fromValue = [NSNumber numberWithFloat:1.0f];
        animation2.toValue = [NSNumber numberWithFloat:0.0f];
        animation2.removedOnCompletion = NO;
        animation2.fillMode = kCAFillModeBoth;
        [box addAnimation:animation2 forKey:@"k"];
        
    }];
    
    [box addAnimation:animation forKey:@"j"];
    
    [CATransaction commit];

Solution 6 - Iphone

Just a note for those who find this page on Google: You really can get the job done by setting the "delegate" property of your animation object to the object that will receive the notification and implementing the "animationDidStop" method in that object's .m file. I just tried it, and it works. I don't know why Joe Blow said that's not the correct way.

Solution 7 - Iphone

In Swift 4+ i have just added delegate as

class CircleView: UIView,CAAnimationDelegate {
...

let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.delegate = self//Set delegate

Animation completion callback -

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
     print("Animation END")
  }

Solution 8 - Iphone

Swift 5.0

func blinkShadow(completion: @escaping (() -> Void)) {
	CATransaction.begin()
	let animation = CABasicAnimation(keyPath: "shadowRadius")
	animation.fromValue = layer.shadowRadius
	animation.toValue = 0.0
	animation.duration = 0.1
	animation.autoreverses = true
	CATransaction.setCompletionBlock(completion)
	layer.add(animation, forKey: nil)
	CATransaction.commit()
}

Solution 9 - Iphone

You can set the name of a given animation when setting up the CAAnimation object. In animationDiStop:finished, just compare the name of theAnimation object provided to perform you specific functionality based on the animation.

Solution 10 - Iphone

For 2020 ...

ValueAnimator, update ur custom properties.

https://github.com/Only-IceSoul/ios-jjvalueanimator

 class OnAnimationListener : AnimatorListener {
        
        weak var s : ViewController?

        init(_ ins: ViewController) {
            s = ins
        }
        func onAnimationStart(_ animation: Animator) {}
        func onAnimationEnd(_ animation: Animator) {

           print("end")
           s?.label.text = "end"
            
        }
        func onAnimationCancel(_ animation: Animator) {}
        func onAnimationRepeat(_ animation: Animator) {}
        
    }

Solution 11 - Iphone

I wrote an extension on CAAnimation which gives you a began & a finished closure, as I was getting fed up of implementing the delegate, especially for multiple animations where you'd have to do horrible stuff like using the animation's key to see which animation was calling the delegate - makes this sort of thing really easy.

It's on GitHub - Animation Actions

Hopefully useful to someone!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJeffrey ForbesView Question on Stackoverflow
Solution 1 - IphonebennytheminkView Answer on Stackoverflow
Solution 2 - IphoneJeffrey ForbesView Answer on Stackoverflow
Solution 3 - IphoneFattieView Answer on Stackoverflow
Solution 4 - IphonetdonView Answer on Stackoverflow
Solution 5 - Iphoneuser3077725View Answer on Stackoverflow
Solution 6 - IphoneMontana BurrView Answer on Stackoverflow
Solution 7 - IphoneJackView Answer on Stackoverflow
Solution 8 - IphoneJimmy_mView Answer on Stackoverflow
Solution 9 - IphoneJavaJoeView Answer on Stackoverflow
Solution 10 - IphoneIceSoulView Answer on Stackoverflow
Solution 11 - IphoneSomaManView Answer on Stackoverflow