CABasicAnimation rotate returns to original position
Asked Answered
S

2

3

i'm rotating a CALayer using CABasicAnimation and works fine. The problem is, when I try to rotate the same layer, it returns back to its original position before it will rotate. My expected output is that, for the next rotation, it should start from where it has ended. Here's my code:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
animation.fromValue         = 0;
animation.toValue           = [NSNumber numberWithFloat:3.0];
animation.duration          = 3.0;
animation.timingFunction    = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.removedOnCompletion = NO;
animation.fillMode          = kCAFillModeForwards;
animation.autoreverses      = NO;
[calayer addAnimation:animation forKey:@"rotate"];

Is there anything missing on my code? thanks

Substitution answered 7/10, 2011 at 5:55 Comment(0)
L
16

What's happening is that you're seeing the animation in the presentation layer. However, that doesn't update the actual position of your layer. So, once the animation finishes, you see the layer as it was because it hasn't changed.

It's really worth reading the "Core Animation Rendering Architecture". Otherwise this can be very confusing.

To fix it, set a delegate to your CABasicAnimation as follows:

[animation setDelegate:self];

Then, create a method to set your target properties that you want when the animation completes. Now, here's the confusing part. You should do this on animationDidStart not animationDidStop. Otherwise, the presentation layer animation will finish, and you'll get a flicker as you see the calayer in the original position then it jumps - without animation - to the target position. Try it with animationDidStop and you'll see what I mean.

I hope that's not too confusing!

- (void)animationDidStart:(CAAnimation *)theAnimation
{
    [calayer setWhateverPropertiesExpected];
}

EDIT:

I later discovered that Apple recommend a much better way to do this.

Oleg Begemann has a nice description of the correct technique in his blog post Prevent Layers from Snapping Back to Original Values When Using Explicit CAAnimations

Basically what you do is before you start the animation, you take a note of the layer's current value, i.e., the original value:

// Save the original value
CGFloat originalY = layer.position.y;

Next, set the toValue on the layer's model. Therefore the layer model has the final value of whatever animation you are about to do:

// Change the model value
layer.position = CGPointMake(layer.position.x, 300.0);

Then, you set the animation up with the animation fromValue being that original value that you noted above:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];

// Now specify the fromValue for the animation because
// the current model value is already the correct toValue
animation.fromValue = @(originalY);
animation.duration = 1.0;

// Use the name of the animated property as key
// to override the implicit animation
[layer addAnimation:animation forKey:@"position"];

Note that code in edit above was copy/pasted from Ole Begemann's blog for clarity

Lactate answered 7/10, 2011 at 17:37 Comment(2)
How would you do this with key paths such as transform.rotation.y ?Phoebephoebus
For me, setting a key for the animation doesn't replace the implicit animation, so I had to use this answer instead.Grandnephew
U
1

If you want the animation to start from where it has ended, then set the fromValue property to the CALayer's current rotation.

Obtaining that value is tricky, but this SO post shows you how: https://mcmap.net/q/430225/-calayer-with-rotation-animation

Uneducated answered 20/9, 2012 at 14:35 Comment(1)
I think this is more appropriate than another answer which suggests using [animationDidStart:], this is what the poster wantVon

© 2022 - 2024 — McMap. All rights reserved.