I'm trying to make a sequence of animations, I've found in CAAnimationGroup
the right class to achieve that object. In practice I'm adding on a view different subviews and I'd like to animate their entry with a bounce effect, the fact is that I want to see their animations happening right after the previous has finished. I know that I can set the delegate, but I thought that the CAAnimationGroup
was the right choice.
Later I discovered that the group animation can belong only to one layer, but I need it on different layers on screen. Of course on the hosting layer doesn't work.
Some suggestions?
- (void) didMoveToSuperview {
[super didMoveToSuperview];
float startTime = 0;
NSMutableArray * animArray = @[].mutableCopy;
for (int i = 1; i<=_score; i++) {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject: self.greenLeaf];
UIImageView * greenLeafImageView = [NSKeyedUnarchiver unarchiveObjectWithData: archivedData];
greenLeafImageView.image = [UIImage imageNamed:@"greenLeaf"];
CGPoint leafCenter = calculatePointCoordinateWithRadiusAndRotation(63, -(M_PI/11 * i) - M_PI_2);
greenLeafImageView.center = CGPointApplyAffineTransform(leafCenter, CGAffineTransformMakeTranslation(self.bounds.size.width/2, self.bounds.size.height));
[self addSubview:greenLeafImageView];
//Animation creation
CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
greenLeafImageView.layer.transform = CATransform3DIdentity;
bounceAnimation.values = @[
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.1],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0]
];
bounceAnimation.duration = 2;
bounceAnimation.beginTime = startTime;
startTime += bounceAnimation.duration;
[animArray addObject:bounceAnimation];
//[greenLeafImageView.layer addAnimation:bounceAnimation forKey:nil];
}
// Rotation animation
[UIView animateWithDuration:1 animations:^{
self.arrow.transform = CGAffineTransformMakeRotation(M_PI/11 * _score);
}];
CAAnimationGroup * group = [CAAnimationGroup animation];
group.animations = animArray;
group.duration = [[ animArray valueForKeyPath:@"@sum.duration"] floatValue];
[self.layer addAnimation:group forKey:nil];
}
CAAnimationGroup
is meant for having multiple CAAnimation
subclasses being stacked together to form an animation, for instance, one animation can perform an scale, the other moves it around, while a third one can rotate it, it's not meant for managing multiple layers, but for having multiple overlaying animations.
That said, I think the easiest way to solve your issue, is to assign each CAAnimation
a beginTime
equivalent to the sum of the durations of all the previous ones, to illustrate:
for i in 0 ..< 20
{
let view : UIView = // Obtain/create the view...;
let bounce = CAKeyframeAnimation(keyPath: "transform.scale")
bounce.duration = 0.5;
bounce.beginTime = CACurrentMediaTime() + bounce.duration * CFTimeInterval(i);
// ...
view.layer.addAnimation(bounce, forKey:"anim.bounce")
}
Notice that everyone gets duration * i
, and the CACurrentMediaTime()
is a necessity when using the beginTime
property (it's basically a high-precision timestamp for "now", used in animations). The whole line could be interpreted as now + duration * i
.
Must be noted, that if a CAAnimation
s is added to a CAAnimationGroup
, then its beginTime
becomes relative to the group's begin time, so a value of 5.0
on an animation, would be 5.0
seconds after the whole group starts. In this case, you don't use the CACurrentMediaTime()
If you review the documentation, you will note that CAAnimationGroup
inherits from CAAnimation
, and that CAAnimation
can only be assigned to one CALayer
. It's intent is really to make it easy to create and manage multiple animations you wish to apply to a CALayer
at the same time, not to manager animations for multiple CALayer
objects.
To handle the sequencing of different animations between different CALayer
or UIView
objects, a technique I use is to create an NSOperation
for each object/animation, then throw them into a NSOperationQueue
to manage the sequencing. This is a bit complicated as you have to use the animation completion callback to tell the NSOperation
it is finished, but if you write a good animation management subclass of NSOperation
, it can be rather convenient and allow you to create sophisticated sequencing paths. The low-rent way of accomplishing the sequencing goal is to simply set the beginTime
property on your CAAnimation
object (which comes from it's adoption of the CAMediaTiming
protocol) as appropriate to get the timing you want.
With that said, I am going to point you to some code that I wrote and open-sourced to solve the exact same use case you describe. You may find it on github here (same code included). I will add the following notes:
- My animation management code allow your to define your animation in a plist by identifying the sequence and timing of image changes, scale changes, position changes, etc. It's actually pretty convenient and cleaner to adjust your animation in a plist file rather than in code (which is why I wrote this).
- If the user is not expected to interact with the subviews you creating, it's actually much better (less overhead) to create layer objects that are added as sub-layers to your hosting view's layer.