In Core Animation Programming guide, there is one paragraph about How to Animate Layer-Backed Views
, it says:
If you want to use Core Animation classes to initiate animations, you must issue all of your Core Animation calls from inside a view-based animation block. The UIView class disables layer animations by default but reenables them inside animation blocks. So any changes you make outside of an animation block are not animated.
There are also an example:
[UIView animateWithDuration:1.0 animations:^{
// Change the opacity implicitly.
myView.layer.opacity = 0.0;
// Change the position explicitly.
CABasicAnimation* theAnim = [CABasicAnimation animationWithKeyPath:@"position"];
theAnim.fromValue = [NSValue valueWithCGPoint:myView.layer.position];
theAnim.toValue = [NSValue valueWithCGPoint:myNewPosition];
theAnim.duration = 3.0;
[myView.layer addAnimation:theAnim forKey:@"AnimateFrame"];
}];
In my opinion, it tells that if I don't issue Core Animation calls from inside a view-based animation block, there will no animation.
But it seems that if I add the core animation calls directly without view-based animation block, it works the same.
Have I missed something ?
tl;dr: The documentation only refers to implicit animations. Explicit animations work fine outside of animation blocks.
The simplified version of that quote from the docs is something like (me paraphrasing it):
UIView have disabled implicit animations except for within animation blocks. If you want to do implicit layer animations you must do them inside an animation block.
Implicit animations is what happens when an animatable property of a standalone layer changes. For example, if you create a layer and change it's position it's going to animate to the new position. Many, many layer properties have this behaviour by default.
It happens something like this:
Notice that there is no mention of animation above, instead there is the word "action". An action in this context refers to an object which implements the CAAction
protocol. It's most likely going to be some CAAnimation subclass (like CABasicAnimation, CAKeyframeAnimation or CATransition) but is built to work with anything that conforms to that protocol.
Finding the action for that property happens by calling actionForKey:
on the layer. The default implementation of this looks for an action in this order:
This search happens in this order (ref: actionForKey:
documentation)
- If the layer has a delegate and that delegate implements the Accessing the Layer’s Filters method, the layer calls that method. The delegate must do one of the following:
- Return the action object for the given key.
- Return
nil
if it does not handle the action.- Return the
NSNull
object if it does not handle the action and the search should be terminated.- The layer looks in the layer’s
actions
dictionary.- The layer looks in the
style
dictionary for an actions dictionary that contains the key.- The layer calls its
defaultActionForKey:
method to look for any class-defined actions.- The layer looks for any implicit actions defined by Core Animation.
In the case of layers that are backing views, the view can enable or disable the actions by implementing the delegate method actionForLayer:forKey
. For normal cases (outside an animation block) the view disables the implicit animations by returning [NSNull null]
which means:
it does not handle the action and the search should be terminated.
However, inside the animation block, the view returns a real action. This can easily be verified by manually invoking actionForLayer:forKey:
inside and outside the animation block. It could also have returned nil
which would cause the layer to keep looking for an action, eventually ending up with the implicit actions (if any) if it wouldn't find anything before that.
When an action is found and the transaction is committed the action is added to the layer using the regular addAnimation:forKey:
mechanism. This can easily be verified by creating a custom layer subclass and logging inside -actionForKey:
and -addAnimation:forKey:
and then a custom view subclass where you override +layerClass
and return the custom layer class. You will see that the stand alone layer instance logs both methods for a regular property change but the backing layer does not add the animation, except when within a animation block.
Now, why did I give this very long explanation of how implicit animations work? Well, it's to show that they use the same methods that you use yourself with explicit animations. Knowing how they work, we can understand what it means when the documentation say: "The UIView class disables layer animations by default but reenables them inside animation blocks".
The reason why explicit animations aren't disabled by what UIView does, is that you are doing all the work yourself: changing the property value, then calling addAnimation:forKey:
.
Outside of animation block:
myView.backgroundColor = [UIColor redColor]; // will not animate :(
myLayer.backGroundColor = [[UIColor redColor] CGColor]; // animates :)
myView.layer.backGroundColor = [[UIColor redColor] CGColor]; // will not animate :(
[myView.layer addAnimation:myAnimation forKey:@"myKey"]; // animates :)
Inside of animation block:
myView.backgroundColor = [UIColor redColor]; // animates :)
myLayer.backGroundColor = [[UIColor redColor] CGColor]; // animates :)
myView.layer.backGroundColor = [[UIColor redColor] CGColor]; // animates :)
[myView.layer addAnimation:myAnimation forKey:@"myKey"]; // animates :)
You can see above that explicit animations and implicit animations on standalone layers animate both outside and inside of animation blocks but implicit animations of layer-backed views does only animate inside the animation block.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With