Reputation: 10896
I have been trying to create a sequence of animations that deal three cards from a poker deck sequentially. I just want to animate the position of the three cards -- say the first animation begins immediately on the first card's position and goes for 0.4 seconds, the second begins after 0.4 seconds with the same duration, and the last begins after 0.8 seconds. I can't figure out how to do this! The code below doesn't work. Perhaps I need to use a CAGroupAnimation
, but I don't know how to make a group of sequential
animations on the same property!
CGFloat beginTime = 0.0;
for (Card *c in cards) {
CardLayer *cardLayer = [cardToLayerDictionary objectForKey:c];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];
anim.fromValue = [NSValue valueWithCGPoint:stockLayer.position];
anim.toValue = [NSValue valueWithCGPoint:wasteLayer.position];
anim.duration = 0.4;
anim.beginTime = beginTime;
beginTime += 0.4;
cardLayer.position = wasteLayer.position;
[cardLayer addAnimation:anim forKey:@"position"];
…
}
Upvotes: 1
Views: 3194
Reputation: 10896
The following recursive method is the best I could do to chain animations. I use a CATransaction
to animate the position
property and set up a block that makes a recursive call for the next card.
-(void)animateDeal:(NSMutableArray*)cardLayers {
if ([cardLayers count] > 0) {
CardLayer *cardLayer = [cardLayers objectAtIndex:0];
[cardLayers removeObjectAtIndex:0];
// ...set new cardLayer.zPosition with animations disabled...
[CATransaction begin];
[CATransaction setCompletionBlock:^{[self animateDeal:cardLayers];}];
[CATransaction setAnimationDuration:0.25];
[cardLayer setFaceUp:YES];
cardLayer.position = wasteLayer.position;
[CATransaction commit];
}
}
Of course this doesn't solve the chaining problem with overlapping time intervals.
Upvotes: 0
Reputation: 126137
Like Einstein said, time is relative. All your layers' beginTime
s are relative to the timespace of their superlayer---since it isn't animating, they all end up the same.
It looks like there are two possible solutions:
Get an absolute timebase and set each layer's animation's beginTime
relative to it:
int i = 0; float delay = 0.4;
for (Card *c in cards) {
// ...
float baseTime = [cardLayer convertTime:CACurrentMediaTime() fromLayer:nil];
anim.beginTime = baseTime + (delay * i++);
// ...
}
Wrap each card's animation in a group. For some reason I don't quite get, this puts those animations on a common timescale, even though these groups are separate from one another. It's worked for some, but I'm a bit disinclined to trust it---seems like spooky action at a distance.
Either way, you're probably also going to want the layers to stay where they are at the end of the animation. You could make the animation "stick" like so:
anim.fillMode = kCAFillModeForwards;
anim.removedOnCompletion = NO;
But that might give your trouble later---the layer's model position is still where it was to start, and if you adjust that after the animation (say, for dragging a card around) you might get weird results. So it might be appropriate to wrap your card dealing animation in a CATransaction
, on which you set a completion block that sets the layers' final positions.
Speaking of CATransaction
s with completion blocks, you could use those to make implicit animations happen in sequence (if you're not using explicit animation for some other reason):
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[CATransaction begin];
[CATransaction setCompletionBlock:^{
card3Layer.position = wasteLayer.position;
}];
card2Layer.position = wasteLayer.position;
[CATransaction end];
}];
card1Layer.position = wasteLayer.position;
[CATransaction end];
Upvotes: 4