3
votes

I have a CALayer subclass that is constrained to the width of the parent layer, but a fixed height. I want the implicit animation disabled for resizing the window but enabled whenever I set the height of the layer.

To disable the animation when the window is resized I set the layers actions to

NSMutableDictionary *actions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"bounds", [NSNull null], @"position", nil];

and this seems to work. Then I've overridden the actionForKey: method in my layer subclass

- (id<CAAction>)actionForKey:(NSString *)event
{
    if ([event isEqualToString:@"frame.size.height"]) {
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"frame.size.height"];
        [animation setDuration:0.5f];
        return animation;
    }
    return nil;
}

but then when I change the height of my layer with

[layer setValue:[NSNumber numberForFloat:50.0] forKey:@"frame.size.height"];

the height changes but is not animated. I see the actionForKey: method being called with frame.size.height but no matter what I return it is never animated. The height animates correctly if I don't set the actions, or have the actionForKey: method, but then the window resizes are also animated, which I don't want.

Also, maybe I'm misunderstanding, but I thought returning nil from actionForKey: would make the default animations run, but it appears that even the presence of the method stops all implicit animations (whether the actions dictionary is set or not).

What is it am I missing here, I'll be grateful for any pointers.

2

2 Answers

3
votes

Your subclass is going to get actionForKey: called on it for every key, so if you return nil, there won't be an action for that key. If you want the default animations, you should return either [super actionForKey:event] or [CALayer defaultActionForKey:event].

A layer's frame is calculated from its bounds and position, so if there's no animation for those two, there can't be an animation for frame.*

What you want to do is:

[CATransaction setValue:kCFBooleanTrue
                 forKey:kCATransactionDisableActions]

when your layer resizes as a result of the window resizing. This will disable animation just this time (for this transaction); when you change the layer's size directly, it will still animate. I'm not sure what the correct spot to do that call is; I just tried doing it in a callback from notification that the window was resizing, but that didn't work. I'm sure it won't be too hard for you to figure out where to put it in your code.


*: In fact, the docs say:

Note: The frame property is not directly animatable. Instead you should animate the appropriate combination of the bounds, anchorPoint and position properties to achieve the desired result.

2
votes

There are, as they say.. several ways to skin this cat... I find one of the easiest to be... by setting some entries on the layer's "Actions" dictionary..

CALayer *stopAnimatingLikeThat = CALayer.new;
stopAnimatingLikeThat.actions  =
  @{ @"position":NSNull.null, @"bounds":NSNull.null };

This will stop the annoying "stuck in honey" "implicit animations" on bounds' changes, etc.