Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animation End Callback for CALayer?

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.

like image 331
Jeffrey Forbes Avatar asked Nov 17 '08 21:11

Jeffrey Forbes


4 Answers

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];
like image 140
bennythemink Avatar answered Nov 03 '22 23:11

bennythemink


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

like image 70
Jeffrey Forbes Avatar answered Nov 03 '22 23:11

Jeffrey Forbes


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.

like image 65
Fattie Avatar answered Nov 04 '22 00:11

Fattie


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() 
like image 55
tdon Avatar answered Nov 03 '22 23:11

tdon