Search code examples
ioscocoa-touchuiviewios7core-animation

Keyframe animation key time


I've just created a keyframe animation like this:

[UIView animateKeyframesWithDuration:10 delay:0 options:0 animations:^{
    [UIView addKeyframeWithRelativeStartTime:0 relativeDuration:.1 animations:^{
        view.alpha = 0;
    }];
} completion:nil];

And this is a CAKeyframeAnimation that gets created:

(lldb) po [self.layer animationForKey:@"opacity"]
<CAKeyframeAnimation:0x10a6364b0; keyTimes = (
    0,
    "0.1",
    1
); values = (
    1,
    0,
    0
); calculationMode = linear; delegate = <UIViewKeyframeAnimationState: 0x10a6358d0>; fillMode = both; timingFunction = easeInEaseOut; duration = 10; keyPath = opacity>

Question:

The entire animation should take 10 seconds with the opacity animating for 10 * 0.1 = 1 second, right? When I look at the animation, the change is being animated way longer than 1 second.

Why?


Solution

  • The reason is that the animation's timing function is not linear.

    “Time and space are relative concepts.” – Theory of relativity

    Layer and animations use a hierarchical timing system, where each object has its own local time which is dependant of its parents and its own timing parameters.

    For instance, animations have a timing function that defines their pacing. The default timing functions for an animation created by UIKit is the ease-in ease-out timing function which defines a timing that begins slowly, accelerates through the middle of its duration, and then slows again before completing. It makes for smooth animations that don't start or stop abruptly. However it will also temper with the key times of your keyframe animation which is annoying when you need precise timing.

    You can disable the ease-in ease-out timing by passing the UIViewAnimationOptionCurveLinear option to the animateKeyframesWithDuration:delay:options:animations:completion: method. I'm not sure whether it's intended as you need to cast the value from UIViewAnimationOptions to UIViewKeyframeAnimationOptions and the documentation does not say anything about that. Maybe a better (but more verbose) way is to embed the keyframe animation block inside a standard animation block like this:

    [UIView animateWithDuration:10 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
        [UIView animateKeyframesWithDuration:0 /*inherited*/ delay:0 options:0 animations:^{
            [UIView addKeyframeWithRelativeStartTime:0 relativeDuration:.1 animations:^{
                view.alpha = 0;
            }];
        } completion:nil];
    } completion:nil];