views:

306

answers:

3

I'm trying to animate part of UI in an iPad app when the user taps a button. I have this code in my action method. It works in the sense that the UI changes how I expect but it does not animate the changes. It simply immediately changes. I must be missing something:

- (IBAction)someAction:(id)sender {
    UIViewController *aViewController = <# Get an existing UIViewController  #>;
    UIView *viewToAnimate = aViewController.view;
    CALayer *layerToAnimate = viewToAnimate.layer;

    [CATransaction begin];
    [CATransaction setAnimationDuration:1.0f];

    CATransform3D rotateTransform = CATransform3DMakeRotation(0.3, 0, 0, 1);
    CATransform3D scaleTransform = CATransform3DMakeScale(0.10, 0.10, 0.10);
    CATransform3D positionTransform = CATransform3DMakeTranslation(24, 423, 0);
    CATransform3D combinedTransform = CATransform3DConcat(rotateTransform, scaleTransform);
    combinedTransform = CATransform3DConcat(combinedTransform, positionTransform);
    layerToAnimate.transform = combinedTransform;

    [CATransaction commit];

    // rest of method...
}

I've tried simplifying the animation to just change the opacity (for example) and it still will not animate. The opacity just changes instantly. That leads me to believe something is not setup properly.

Any clues would be helpful!

+2  A: 

Animations on the root layer of a view are disabled by default. Try applying a transform to the view instead, e.g. [view setTransform:CGTransform3D...]. If you must do it at the layer level, add a layer to the root layer and perform your transforms on it instead. Also the view animation block [UIView beginAnimations...] only has an effect when animating view properties--as opposed to layer properties.

Update:

So here is what your code would look like with explicit animation (CATransaction is not required)

CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform"];
CATransform3D rotateTransform = CATransform3DMakeRotation(0.3, 0, 0, 1);
CATransform3D scaleTransform = CATransform3DMakeScale(0.10, 0.10, 0.10);
CATransform3D positionTransform = CATransform3DMakeTranslation(24, 423, 0);
CATransform3D combinedTransform = CATransform3DConcat(rotateTransform, scaleTransform);
combinedTransform = CATransform3DConcat(combinedTransform, positionTransform);
[anim setFromValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
[anim setToValue:[NSValue valueWithCATransform3D:combinedTransform]];
[anim setDuration:1.0f];

[layerToAnimate addAnimation:anim forKey:nil];

Keep in mind that this only performs the animation. You actually have to set the transform property of the layer with a call to:

[layerToAnimate setTransform:combinedTransform];

as well. Otherwise it will just snap back to its starting position.

Layer properties are animated implicitly whenever you set them except in the case where you are animating the root layer of a view. In that case animations are turned off by default and I've found that what I always have to do instead is animate the view rather than the layer when I am interested in animating the root. So what this means is that a call to any layer within your layer tree that makes a property change will be animated implicitly. For example:

CALayer *root = [view layer];

CALayer *sublayer = [[root sublayers] objectAtIndex:0];

[sublayer setTransform:combinedTransform];

This is the only way I know (knew) you can actually use implicit animation on a layer. However, what your code has pointed out is that you can turn the layer animation on for the root layer simply by placing the changes to the layer within a UIView animation block. That's pretty interesting and handy. This is quite a helpful discovery.

Maybe this is buried in the docs somewhere, but I have yet to come across it.

Matt Long
That would make sense as to why my code is not working. I read through the Core Animation Programming guide and a lot of the CA docs, but I never read that anywhere. Do you know if Apple states anywhere in their docs that animation on the root CALayer is disabled?
macinjosh
I changed the animation block to use the UIView style (as opposed to CATransaction) and everything started working.The transform that was set to the layer.transform property and the layer.opacity animated as expected. According to your answer this shouldn't work. Or am I misreading?
macinjosh
Huh. Interesting. So you mean that wrapped in [UIView beginAnimations...] and [UIView commitAnimations] you animated the layer properties instead of the view properties and it worked correctly? Man, just when I think I've got this stuff nailed down, some other nuance pops up. Let me do a little research on that. Thanks.
Matt Long
See the update to my answer. What you've discovered here is *very* interesting.
Matt Long
+1  A: 

It turns out you can turn the layer animation on for the root layer by setting its delegate property to nil. I don't know if it's good practice.

przemek
A: 

Setting your layerToAnimate delegate to your rootView.layer works:

viewToAnimate.layer.delegate = rootView.layer;

Assuming you add the view/layer to the root view/layer as well. Cannot find where this changed, implicit animations use to just work.

Mark