Reputation: 33644
How can I have one CABasicAnimation to run after the other one has finishes? In other words sequentially. I've added the begin time of the second animation, however it seems that the second animation doesn't gets executed:
CABasicAnimation * appearance =[CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
appearance.duration = 0.5;
appearance.fromValue = [NSNumber numberWithFloat:0];
appearance.toValue = [NSNumber numberWithFloat:340];
appearance.repeatCount = 1;
appearance.fillMode = kCAFillModeForwards;
appearance.removedOnCompletion = NO;
[notif.layer addAnimation:appearance forKey:@"transform.translation.y"];
CABasicAnimation * theAnimation=[CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
theAnimation.duration = 0.5;
theAnimation.fromValue = [NSNumber numberWithFloat:0];
theAnimation.toValue = [NSNumber numberWithFloat:10];
theAnimation.repeatCount = 3;
theAnimation.autoreverses = YES;
theAnimation.fillMode = kCAFillModeForwards;
theAnimation.removedOnCompletion = NO;
theAnimation.beginTime = appearance.beginTime + appearance.duration;
[notif.layer addAnimation:theAnimation forKey:@"transform.translation.y"];
Any idea why?
Upvotes: 10
Views: 18458
Reputation: 12631
let basic_seconds = 2.0
blah.path = .. begin
let a1 = CABasicAnimation(keyPath: "path")
.. set from, to, etc
a1.duration = basic_seconds
let a2 = CABasicAnimation(keyPath: "path")
.. set from, to, etc
a2.beginTime = CACurrentMediaTime() + basic_seconds
a2.duration = 0.5
blah.add(a1, forKey: "a1")
blah.add(a2, forKey: "a2") // KEYS MUST BE DIFFERENT
CATransaction.begin()
blah.path = .. end
CATransaction.commit()
You simply have to run both but set the DELAY on the second one to be the length of the first one.
NOTE THAT in rare complex cases (where you don't know the length of the first one), use -animationDidStop:finished:
as @ChrisParker points out.
Don't forget that to set a delay in CA animations, you just use this silly code:
.beginTime = CACurrentMediaTime() + .. some delay seconds ..
It's bizarre but that's the only mechanism to do sequential CA animations.
Upvotes: 0
Reputation: 8995
iOS 15, Swift 5.x
It is a bit nuts, but it was the only solution I could get to work, everything else I tried wanted to do things in parallel.
Note this doesn't run the second when the first finishes, it waits 4 seconds before running the second since it knows the first takes 4 seconds.
let foo = SCNAction.customAction(duration: 0) { (node,elapsedTime) in
let animation = CABasicAnimation(keyPath:"morpher.weights[0]")
animation.fromValue = 0.0
animation.toValue = 1.0
animation.autoreverses = true
animation.repeatCount = 0
animation.isRemovedOnCompletion = true
animation.fillMode = .removed
animation.duration = 2
node.addAnimation(animation, forKey: nil)
}
let delay = SCNAction.wait(duration: 4)
let foo2 = SCNAction.customAction(duration: 0) { (node,elapsedTime) in
let animation2 = CABasicAnimation(keyPath:"morpher.weights[1]")
animation2.fromValue = 0.0
animation2.toValue = 1.0
animation2.autoreverses = true
animation2.repeatCount = 0
animation2.isRemovedOnCompletion = true
animation2.fillMode = .removed
animation2.duration = 2
node.addAnimation(animation2, forKey: nil)
}
node.runAction(SCNAction.sequence([foo,delay,foo2]))
Upvotes: -1
Reputation: 12444
Updated Code, this will work!
1st animation
CABasicAnimation * appearance =[CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
[appearance setValue:@"animation1" forKey:@"id"];
appearance.delegate = self;
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:)];
appearance.duration = 0.5;
appearance.fromValue = [NSNumber numberWithFloat:0];
appearance.toValue = [NSNumber numberWithFloat:340];
appearance.repeatCount = 1;
appearance.fillMode = kCAFillModeForwards;
appearance.removedOnCompletion = NO;
[notif.layer addAnimation:appearance forKey:@"transform.translation.y"];
2nd Animation:
- (void)animationDidStop:(CAAnimation *)theAnimation2 finished:(BOOL)flag {
if([[theAnimation2 valueForKey:@"id"] isEqual:@"animation1"]) {
CABasicAnimation * theAnimation=[CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
theAnimation.duration = 0.5;
theAnimation.fromValue = [NSNumber numberWithFloat:0];
theAnimation.toValue = [NSNumber numberWithFloat:10];
theAnimation.repeatCount = 3;
theAnimation.autoreverses = YES;
theAnimation.fillMode = kCAFillModeForwards;
theAnimation.removedOnCompletion = NO;
theAnimation.beginTime = appearance.beginTime + appearance.duration;
[notif.layer addAnimation:theAnimation forKey:@"transform.translation.y"];
}
}
This is how the 2nd animation would fire after the 1st one finishes.
Upvotes: 15
Reputation: 73608
The reason your animation in not running according to plan is due to the way you have coded up & used Core Animation. Core animations are all GPU powered. CA infact handles couple of things very well to render animations optimally.
One element of it is the raw parallel processing power of modern graphics cards, and another is the fact that Core Animation can cache the contents of a CALayer on the card so that your code doesn't need to constantly redraw it. This, by the way, is part of the reason the iPhone UI is so incredibly fast on some relatively modest hardware. Core Animation can automatically take advantage of a multi-core Mac because the layer tree is rendered on a separate thread.
The last line is imp. CA's are running in a separate thread ( & not the main thread). So coming back to your problem, you have 2 blocks of CA's each trying to animate same transform.translation.y
Both at the same time! How would this work?
So to solve this, either do -
I just wanted to highlight the theory & reasoning behind the way Core Animation works. Hope this helps...
Upvotes: 1
Reputation: 1252
The simplest thing to do is set up a delegate for the first animation and in that object implement -animationDidStop:finished:
. In that method, touch off the second animation.
Upvotes: 2