Core Animation 101: From and To

Core Animation is cool, beautiful, and easy to use; but not easy to use correctly.

First of all, you should RTFM(Read The Fabulous Manual). Secondly, of course, you should watch the nice Session Video 424 of WWDC 2010 and play with the sample code Animation101.

Now, I assume you’ve done all the prerequisite work so you must be familiar with that bouncing ball which is our protagonist.

Bouncing is one of the simplest form of animation, just moving from a position to another position and back. But as you are going to see below, with some extra flexibility requirements, such a straight moving is not so straightforward in code.

Here is bouncing I want:

  1. First tap triggers the fall of the ball.
  2. Second tap triggers the rise of the ball.
  3. Second tap can happen half-way along the fall and the bouncing should start immediately at that time and position.

Method 1 — Property animation

This is the most straightforward and recommended method. Nothing could go wrong.

if (viewState == 0) { // First tap.
    [CATransaction setAnimationDuration:2];
    ball.position = p2;
} else { // Second tap.
    ball.position = p1;
}

Method 2 — Explicit animation with manual cancellation

Because the second animation may happen half-way during the first animation, I need to cancel the first one. However, before I cancel it I must capture the presentation value of the property and use it as the fromValue of the second animation.

if (viewState == 0) { // First tap.
    CABasicAnimation *drop = [CABasicAnimation animationWithKeyPath:@"position"];
    drop.fromValue = [NSValue valueWithCGPoint:((CALayer *)ball.presentationLayer).position];
    drop.duration = 1;
    [ball addAnimation:drop forKey:@"drop"];

    // We are doing explicit animation so don't let the implicit animation interfere.
    [CATransaction setDisableActions:YES];
    ball.position = p2;
    [CATransaction setDisableActions:NO];
} else { // Second tap.
    CABasicAnimation *bounce = [CABasicAnimation animationWithKeyPath:@"position"];
    [bounce setValue:@"bounce" forKey:@"tag"];
    bounce.fromValue = [NSValue valueWithCGPoint:((CALayer *)ball.presentationLayer).position];
    [ball addAnimation:bounce forKey:@"bounce"];

    // Remove drop animation.
    [ball removeAnimationForKey:@"drop"];

    // We are doing explicit animation so don't let the implicit animation interfere.
    [CATransaction setDisableActions:YES];
    ball.position = p1;
    [CATransaction setDisableActions:NO];
}

Method 3 — Explicit animation with overwriting

There is a nice feature I can use to cancel an animation with another by overwriting — simply assigning the same key as the first one to the second one when adding the second one to the layer.

if (viewState == 0) { // First tap.
    CABasicAnimation *drop = [CABasicAnimation animationWithKeyPath:@"position"];
    drop.fromValue = [NSValue valueWithCGPoint:((CALayer *)ball.presentationLayer).position];
    drop.duration = 1;
    [ball addAnimation:drop forKey:@"move"];

    // We are doing explicit animation so don't let the implicit animation interfere.
    [CATransaction setDisableActions:YES];
    ball.position = p2;
    [CATransaction setDisableActions:NO];
} else { // Second tap.
    CABasicAnimation *bounce = [CABasicAnimation animationWithKeyPath:@"position"];
    bounce.fromValue = [NSValue valueWithCGPoint:((CALayer *)ball.presentationLayer).position];
    [ball addAnimation:bounce forKey:@"move"];

    // We are doing explicit animation so don't let the implicit animation interfere.
    [CATransaction setDisableActions:YES];
    ball.position = p1;
    [CATransaction setDisableActions:NO];
}

Method 4 — Fall with property animation and rise with explicit animation

Though I don’t know why you would want to use this manner, I want to show you how to properly implement it.

The fall part is exactly the same as that of Method 1, while the rise part is almost the same as that of Method 3. Reasonable enough. However, there are two things you must pay attention to:

  1. Property animations carry implicit keys named after their key paths. So the key “position” is used for the second animation to overwrite the first one which is a position property animation.
  2. Even though I’m using explicit animation for the rise part, I still need to reset the model value because presentation changes do not update modal layers while modal changes do update presentation layers.
if (viewState == 0) { // First tap.
    [CATransaction setAnimationDuration:1];
    ball.position = p2;
} else { // Second tap.
    CABasicAnimation *bounce = [CABasicAnimation animationWithKeyPath:@"position"];
    bounce.fromValue = [NSValue valueWithCGPoint:((CALayer *)ball.presentationLayer).position];
    [ball addAnimation:bounce forKey:@"position"];

    // We are doing explicit animation so don't let the implicit animation interfere.
    [CATransaction setDisableActions:YES];
    ball.position = p1;
    [CATransaction setDisableActions:NO];
}

Method 5 — Fall with explicit animation and rise with property animation

Just to be complete.

if (viewState == 0) { // First tap.
    CABasicAnimation *drop = [CABasicAnimation animationWithKeyPath:@"position"];
    drop.fromValue = [NSValue valueWithCGPoint:((CALayer *)ball.presentationLayer).position];
    drop.duration = 1;
    [ball addAnimation:drop forKey:@"position"];

    // We are doing explicit animation so don't let the implicit animation interfere.
    [CATransaction setDisableActions:YES];
    ball.position = p2;
    [CATransaction setDisableActions:NO];
} else { // Second tap.
    ball.position = p1;
}

You can get the sample code on github.