可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答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];
回答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
回答3:
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];
回答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()
回答5:
For 2018 ...
Swift 4.
use .setCompletionBlock
In practice you need [weak self]
or you'll typically crash.
func animeExample() {
CATransaction.begin()
let a = CABasicAnimation(keyPath: "fillColor")
a.fromValue, duration = ... etc etc
CATransaction.setCompletionBlock{ [weak self] in
self?.animeExample()
}
someLayer.add(a, forKey: nil)
CATransaction.commit()
}
In the example, it just calls itself again.
Of course, you can call any function.
Note: if you're just getting started. It's worth remembering that
the "key" (as in add#forKey
) is irrelevant and rarely used. Set it to nil. If for some reason you want to set it, set it to "any string" (say, your nickname). On the other hand...
The keyPath
in the CABasicAnimation
call is in fact the actual "thing you are animating", in other words it's literally a property of the layer (but just written as a string).
In short add#forKey
is almost always just nil, it is irrelevant.
You often see code where these two are confused (thanks to the silly naming by apple), which causes all sorts of problems.
Do note that as of recently you can use animationDidStop
with the delegate, see answer by @jack below! In some cases that is easier, sometimes it's easier to just use a completion block. If you have many different animes (which is often the case), just use completion blocks.
回答6:
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.
回答7:
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")
}
回答8:
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.
回答9:
If you don't want to mess around with CATransaction
. I've created an extension that encapsulates the callback in the animation object.
The code can be found in this snippet: https://gitlab.com/snippets/1786298
And you can then use it like this:
let appearAnimation = CASpringAnimation(keyPath: "transform")
appearAnimation.fromValue = contentView.layer.transform
appearAnimation.toValue = CATransform3DIdentity
appearAnimation.mass = 0.65
appearAnimation.duration = appearAnimation.settlingDuration
appearAnimation.isAdditive = true
appearAnimation.onCompletion {
completed()
}