7

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?

Rudolf Adamkovič
  • 31,030
  • 13
  • 103
  • 118
  • The debug output looks correct (from 0 to 1 second animate from 100% to 0% then for 9 more seconds animate from 0% to 0%). Can you describe what you are seeing? Are you changing the opacity somewhere else? You haven't turned on slow animations in the simulator, right? – David Rönnqvist Dec 15 '13 at 19:48
  • Yeah, CAKeyframeAnimation looks totally right. What I see is that the opacity is being changed for 2 - 2.5 seconds, not one. I'm not changing it anywhere and Slow Animations are off. – Rudolf Adamkovič Dec 16 '13 at 13:20
  • 1
    Do you see the same thing if you create the keyframe animation yourself (not via the UIView methods)? – David Rönnqvist Dec 16 '13 at 13:21
  • @DavidRönnqvist I haven't tried that one yet. Good idea. But I use `CAKeyframeAnimation` for other things all over the app and it works like a charm. Just wanted to try this new `UIView` stuff and boom. – Rudolf Adamkovič Dec 16 '13 at 13:23

1 Answers1

8

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];
Nicolas Bachschmidt
  • 6,475
  • 2
  • 26
  • 36
  • I passed two hours wondering why this *$!§ animation wasn't linear even if I was passing `UIViewKeyframeAnimationOptionCalculationModeLinear` (plus its default). You sir saved me, thanks. – Tancrede Chazallet Feb 05 '15 at 13:10
  • I stumbled upon this answer accidentally. I have been doing this myself for years and it sure fixes the problem, although I still haven't been able to find out exactly why I need to "hack" it this way by embedding the standard `UIView` animation around a KeyFrameAnimation. It works, I know, but my curiosity itches me :-) +1 for analysis. – Unheilig Jun 11 '15 at 02:45
  • @Nicolas this doesn't seem to work on iOS 9 anymore. Any alternative? – Xzya Oct 01 '15 at 10:37
  • @Xzya Works for me on iOS 9. – Rudolf Adamkovič Oct 12 '15 at 13:50
  • Good answer. I think you can also add UIViewAnimationOptionCurveLinear in `animateKeyframesWithDuration ` options. – vmeyer Aug 04 '16 at 10:20