A CAAnimationGroup Mystery

Many newcomers to the iPhone SDK have trouble getting started with Core Animation, but one problem that frequently goes unanswered in forums is the “snap back” when using a CAAnimationGroup.

Let’s say you wrote this line of code as part of your CABasicAnimation instance:

myAnimation.removedOnCompletion = NO;

That line will work, but not if you insert the animation into a CAAnimationGroup. When you add an animation group as an animation to a CALayer, it will ignore the removedOnCompletion property of individual animations. Look at the docs for animation groups, and there is a message stating that they ignore the removedOnCompletion and delegate properties of animations in yourAnimationGroup.animations . However, the group itself does respond to these properties. Therefore you can write:

myAnimationGroup.removedOnCompletion = NO;

And your CALayer will no longer “snap back” to its original pre-animation state. Note that with CAAnimationGroup, you can not, presently, combine animations with different removedOnCompletion flags, unless you iterate through the group (ie: for animation in group.animations {}… ) and add each animation individually. If you are doing that, you could just as easily store your animations in an ordinary NSArray or NSMutableArray.

I hope this saves a few of you from pulling out your hair, and possibly teeth, in frustration.

This entry was posted in Uncategorized. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

2 Comments

  1. Christopher Burns
    Posted June 25, 2010 at 5:02 pm | Permalink

    Hey there,

    I happened across this post looking for an answer to this exact issue… however, even setting removedOnCompletion for the group does not seem to prevent the snapping.

    I have a group that sequences 4 animations. The first to work in tandem, and the second two fire off (again, in tandem) right after the first set completes. As soon as the first set completes, the image snaps back and then the second set begins.

    This cannot be that hard, but it is killing me… Here is the code… do you see anything obviously wrong?

    - (CAAnimationGroup *)aniFromColor:(CGColorRef)fromCol toColor:(CGColorRef)toCol {
    FlipLayer3View *v = (FlipLayer3View *)[self view];
    const CGFloat *fromColors = CGColorGetComponents(fromCol);
    const CGFloat *toColors = CGColorGetComponents(toCol);

    ani1 = [CABasicAnimation animationWithKeyPath:@"transform"];
    [ani1 setDelegate:self];
    [ani1 setDuration:dur];
    [ani1 setToValue:[NSValue valueWithCATransform3D:CATransform3DConcat(v.theSquare.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0))]];
    [ani1 setValue:@"shrink" forKey:@"name"];
    [ani1 setRemovedOnCompletion:NO];

    ani2 = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    UIColor *c1 = [UIColor colorWithRed:(fromColors[0]/2.0f) green:(fromColors[1]/2.0f) blue:(fromColors[2]/2.0f) alpha:1.0f];
    [ani2 setDuration:dur];
    [ani2 setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
    [ani2 setToValue:(id)c1.CGColor];
    [ani2 setValue:@"dim" forKey:@"name"];
    [ani2 setRemovedOnCompletion:NO];

    ani3 = [CABasicAnimation animationWithKeyPath:@"transform"];
    [ani3 setDelegate:self];
    [ani3 setDuration:dur];
    [ani3 setToValue:[NSValue valueWithCATransform3D:CATransform3DConcat(v.theSquare.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0))]];
    [ani3 setValue:@"grow" forKey:@"name"];
    [ani3 setBeginTime:dur];
    [ani3 setRemovedOnCompletion:NO];

    ani4 = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    UIColor *c2 = [UIColor colorWithRed:(toColors[0]/2.0f) green:(toColors[1]/2.0f) blue:(toColors[2]/2.0f) alpha:1.0f];
    [ani4 setDuration:dur];
    [ani4 setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
    [ani4 setFromValue:(id)c2.CGColor];
    [ani4 setToValue:(id)toCol];
    [ani4 setValue:@"glow" forKey:@"name"];
    [ani4 setBeginTime:dur];
    [ani4 setRemovedOnCompletion:NO];

    aniGroup = [CAAnimationGroup animation];
    [aniGroup setDuration:dur*2];
    [aniGroup setAnimations:[NSArray arrayWithObjects:ani1, ani2, ani3, ani4, nil]];
    [aniGroup setRemovedOnCompletion:NO];

    return aniGroup;
    }

    Thanks for any insight,

    Chris

  2. Robert Beverly
    Posted June 26, 2010 at 3:08 am | Permalink

    Christopher,
    Here are a few things to try. First, see if adding this helps:

    // in ani1
    [ani1 setFillMode:kCAFillModeForwards];
    //and in ani2
    [ani2 setFillMode:kCAFillModeForwards];

    That is likely to solve your problem. Here’s a description of fillMode from Apple’s documentation:

    kCAFillModeForwards
    The receiver remains visible in its final state when the animation is completed.
    Available in iPhone OS 2.0 and later.
    Declared in CAMediaTiming.h.

    If that does not help, look at where you apply the animations to a layer, and also set the value of the layer to the end value of the animations. In your case, you might have to work around the timing of different animations or separate them into two groups. You can use setAnimationDidStopSelector to fire group two in that case.

    Whatever implementation details you choose, make sure you understand that the animations you add don’t change the actual layer properties outside of the animation. So, the layer has a “transform” of “x” before you animate it. Then you animate it and give the animation a “transform” of f(x) … but that new transform only exists in the animation, and when the animation is removed your layer will be teleported back to its real location.

    Let me know if this works for you. Good luck.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>