Animation End Callback for CALayer?

Solution 1:

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:

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:

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:

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()