There are a couple of different ways to achieve the result you are after but before looking at those, I should explain the root cause of the problem.
What is happening
What you see on screen during an animation does not necessary match the values of properties on those layers. In fact, adding an animation to a layer does not change the animated property of the layer. The animation and the values that you see on screen happens in the render server which runs in another process than your application. You can't get to those exact values but you can get to an approximation, called the presentation values. Since we can't get to the values of the render server we often only talk about the model values (the actual values on your layer object) and the presentation values (what appears on screen (or at least a very close approximation of it)).
Only specifying a toValue
for a CABasicAnimation means the that it animates from the current model value and the specified to value. Note that the documentation says that it's the current presentation value but that is actually incorrect. Since the model value never changes, this means that when the second animation is added, it animates from the unchanged, unrotated model value to the toValue
.
(As a side note: since the two animations use the same key the new one replaces the old one. This doesn't really matter since the animations aren't additive so even if the animation wasn't replaced, the new animation would write its value over the old animations value).
Different ways of fixing it
There a many ways to get the behaviour that you want, starting with the simplest version.
Adding an explicit fromValue
As mention above, with only an non-nil toValue
, the animation is going to start from the current model value. You can however, add a non-nil fromValue
that is the current presentation value.
You can get to the presentation value from the layer's presentationLayer
. From it, you can get the current rotation angle using the same key path that you are animating and a little KVC (key-value coding):
NSNumber *currentAngle = [switch.layer.presentationLayer valueForKeyPath:@"transform.rotation"];
If you would only set that as the fromValue
of the animation you would get the right start value but the rotation may not be 360 degrees anymore. While you could figure out the angle and create a new toValue
, there is another property called byValue
which produces a relative change. Specifying byValue
and fromValue
(but not toValue) means:
Interpolates between fromValue
and (fromValue + byValue)
.
So, you can change your animation code to:
CABasicAnimation *fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
fullRotation.fromValue = currentAngle; // the value read from the presentation layer
fullRotation.byValue = @(2.0*M_PI);
fullRotation.duration = 6.0;
At this point your code should continue from the right value but the overall speed of the animation may look a bit odd (it should have looked odd even before this code change). For a view that rotates continuously you would probably want it to always rotate with the same speed (as opposed to accelerating and decelerating for each rotation). You can get this by configuring the animation to have a linear timing function:
fullRotation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
This should give you the behaviour that you are after. Some stylistic remarks would be to specify either INFINITY
or HUGE_VALF
for the repeat count unless you want to it rotate exactly 1000 times, and to use a more descriptive key when adding the layer (unless you are using the key for something else (like removing the animation from the layer):
fullRotation.repeatCount = INFINITY;
[stick.layer addAnimation:fullRotation forKey:@"rotate continuously"];
Altering the speed of the layer
I would think twice before using this as a solution in production code but it can be fun as a learning exercise. Since what you are mostly doing is slowing the animation down by changing the duration from 4 seconds to 6 seconds, one thing you can do to create the same effect is to slow down the layer. Note that this will have side effects (all animation on this layer and all of its sublayers will also be affected).
You can't modify the animation once it has been added to a layer but the layer itself also conforms to the CAMediaTiming
protocol which means that it has the speed
property. Setting the speed to 4.0/6.0
and keeping the old animation on the layer will slow it down making each rotation take 6 seconds instead of 4.
Once again, a bit hacky but fun as a learning exercise.