tl;dr: They probably look different because they do different things and result in different animations being added to the two views. Unfortunately I don't really know how to fix it.
What's happening behind the first version
As you have already said the first version doesn't use the API correctly. It doesn't add keyframes using addKeyframeWithRelativeStartTime:relativeDuration:animations:
but instead changes the properties directly inside the animation block. Then it creates a new "key frame animation" (you will soon see why I used scare quotes there) in the completion block.
Behind the scenes this doesn't actually result in CAKeyframeAnimations
(though I don't think you should rely on that fact). This is some logging that I did of the four animation objects that are added to the layer behind view1
in your project. (I cheated a bit and only logged the .m41
part of the transform (which corresponds to translation along x)).
BASIC
keyPath: transform
duration: 0.125
from: 300.0
model value: -10.0
timing: easeInEaseOut
BASIC
keyPath: transform
duration: 0.125
from: -10.0
model value: 5.0
timing: easeInEaseOut
BASIC
keyPath: transform
duration: 0.125
from: 5.0
model value: -2.0
timing: easeInEaseOut
BASIC
keyPath: transform
duration: 0.125
from: -2.0
model value: 0.0
timing: easeInEaseOut
As you can see each animation has a fromValue
but neither toValue
or byValue
. As you can read in the documentation, this means:
fromValue
is non-nil
. Interpolates betweenfromValue
and the current presentation value of the property.
What's happening behind the second version
On the other hand, the second version that correctly adds keyframes to the animation results in this single CAKeyframeAnimation to be added to the layer behind view2
. (I'm still only logging the .m41
part of the transform)
KEYFRAME
keyPath: transform
duration: 0.500
values: (
300,
-10,
5,
-2,
0
)
keyTimes: (
0.0,
0.25,
0.5,
0.75,
1.0
)
timing: easeInEaseOut
timings: (nil)
calculationMode: linear
As you can see it animates between the same values with the same times but in a single key frame animation. It also has the same total duration and uses the same timing function.
There is one thing that I'm not understanding. If I modify the real key frame animation (inside of my overridden addAnimation:forKey:
before calling super) to explicitly set the the timing function in the array of timing functions, then they do look exactly the same. That is, I do this to the keyframe animation before it is added to the layer.
CAMediaTimingFunction *function = keyFrame.timingFunction;
keyFrame.timingFunction = nil;
keyFrame.timingFunctions = @[function, function, function, function];
This trick is super ugly and you should never use it for things other than pure debugging!
So why do they look different?
Well, I guess the answer has to do with the timing functions array of the keyframe animation that UIKit created in the second case. That's pretty much the only conclusion I got.
That said, I don't know if this is a bug or how to fix it. I just know what that code in your project actually does on the Core Animation level.