zakdances
zakdances

Reputation: 23685

Can't get a CALayer to update its drawLayer: DURING a bounds animation

I'm trying to animate a custom UIView's bounds while also keeping its layer the same size as its parent view. To do that, I'm trying to animate the layers bounds alongside its parent view. I need the layer to call drawLayer:withContext AS its animating so my custom drawing will change size correctly along with the bounds.

drawLayer is called correctly and draws correctly before I start the animation. But I can't get the layer to call its drawLayer method on EACH step of the bounds animation. Instead, it just calls it ONCE, jumping immediately to the "end bounds" at the final frame of the animation.

// self.bg is a property pointing to my custom UIView
self.bg.layer.needsDisplayOnBoundsChange = YES;
self.bg.layer.mask.needsDisplayOnBoundsChange = YES;

[UIView animateWithDuration:2 delay:0 options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{

    [CATransaction begin];
    self.bg.layer.bounds = bounds;
    self.bg.layer.mask.bounds = bounds;
    [CATransaction commit];

    self.bg.bounds = bounds;
} completion:nil];

Why doesn't the bounds report a change AS its animating (not just the final frame)? What am I doing wrong?

Upvotes: 5

Views: 3317

Answers (3)

matt
matt

Reputation: 535192

This might or might not help...

Many people are unaware that Core Animation has a supercool feature that allows you to define your own layer properties in such a way that they can be animated. An example I use is to give a CALayer subclass a thickness property. When I animate it with Core Animation...

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"thickness"];
ba.toValue = @10.0f;
ba.autoreverses = YES;
[lay addAnimation:ba forKey:nil];

...the effect is that drawInContext: or drawLayer:... is called repeatedly throughout the animation, allowing me to change repeatedly the way the layer is drawn in accordance with its current thickness property value at each moment (an intermediate value in the course of the animation).

It seems to me that that might be the sort of thing you're after. If so, you can find a working downloadable example here:

https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/ch17p498customAnimatableProperty

Discussion (from my book) here:

http://www.apeth.com/iOSBook/ch17.html#_making_a_property_animatable

Upvotes: 3

aLevelOfIndirection
aLevelOfIndirection

Reputation: 3522

Firstly, the layer of a layer backed (or hosting) view is always resized to fit the bounds of its parent view. If you set the view to be the layers delegate then the view will receive drawLayer:inContext: at each frame. Of course you must ensure that If your layer has needsDisplayOnBoundsChange == YES.

Here is an example (on the Mac) of resizing a window, which then changes the path of the underlying layer.

// My Nib contains one view and one button. 
// The view has a MPView class and the button action is resizeWindow:

@interface MPView() {
    CAShapeLayer     *_hostLayer;
    CALayer          *_outerLayer;
    CAShapeLayer     *_innerLayer;
}
@end

@implementation MPView

- (void)awakeFromNib
{
    [CATransaction begin];
    [CATransaction setDisableActions:YES];

    _hostLayer = [CAShapeLayer layer];
    _hostLayer.backgroundColor = [NSColor blackColor].CGColor;
    _hostLayer.borderColor = [NSColor redColor].CGColor;
    _hostLayer.borderWidth = 2;
    _hostLayer.needsDisplayOnBoundsChange = YES;
    _hostLayer.delegate = self;
    _hostLayer.lineWidth = 4;
    _hostLayer.strokeColor = [NSColor greenColor].CGColor;
    _hostLayer.needsDisplayOnBoundsChange = YES;

    self.layer = _hostLayer;
    self.wantsLayer = YES;

    [CATransaction commit];

    [self.window setFrame:CGRectMake(100, 100, 200, 200) display:YES animate:NO];
}

- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    if (layer == _hostLayer) {

        CGSize size = layer.bounds.size;
        CGMutablePathRef path = CGPathCreateMutable();

        CGPathMoveToPoint(path, NULL, 0, 0);
        CGPathAddLineToPoint(path, NULL, size.width, size.height);

        _hostLayer.path = path;

        CGPathRelease(path);
    }
}

- (IBAction)resizeWindow:(id)sender
{
    [self.window setFrame:CGRectMake(100, 100, 1200, 800) display:YES animate:YES];
}

@end

Upvotes: 1

Hjalmar
Hjalmar

Reputation: 989

This is because the layer you are drawing to is not the same layer as the one displayed on the screen.

When you animate a layer property it will immediately be set to its final value in the model layer, (as you have noticed), and the actual animation is done in the presentation layer.

You can access the presentation layer and see the actual values of the animated properties:

CALayer *presentationLayer = (CALayer *)[self.bg.layer presentationLayer];
...

Since you haven't provided your drawLayer:withContext method, it's unclear what you want to draw during the animation, but if you want to animate custom properties, here is a good tutorial for doing that.

Upvotes: 1

Related Questions