Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use core animation on an Layer-Backed view

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 ?

like image 595
KudoCC Avatar asked Dec 01 '22 17:12

KudoCC


1 Answers

tl;dr: The documentation only refers to implicit animations. Explicit animations work fine outside of animation blocks.


My paraphrasing of the documentation

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.

What is implicit animations and how do they work?

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:

  1. a transaction is started by the system (without us doing anything)
  2. the value of a property is changed
  3. the layer looks for the action for that property
  4. at some point the transaction is committed (without us doing anything)
  5. the action that was found is applied

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.

How does it know what "action" to take?

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)

  1. 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.
  2. The layer looks in the layer’s actions dictionary.
  3. The layer looks in the style dictionary for an actions dictionary that contains the key.
  4. The layer calls its defaultActionForKey: method to look for any class-defined actions.
  5. The layer looks for any implicit actions defined by Core Animation.

What is UIView doing?

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.

Why this long explanation of implicit animations?

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:.

The results in code:

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.

like image 158
David Rönnqvist Avatar answered Dec 09 '22 15:12

David Rönnqvist