CABasicAnimation with CALayer path doesn't animate
Asked Answered
T

3

20

I'm trying to animation the transition of a CALayer from normal corners to rounded corners. I only want to round the top corners, so I am using a UIBezierPath. Here's the code:

UIRectCorner corners = UIRectCornerTopLeft | UIRectCornerTopRight;

UIBezierPath* newPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(8.0f, 8.0f)];
UIBezierPath* oldPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(0.0f, 0.0f)];

CAShapeLayer* layer = [CAShapeLayer layer];
layer.frame = self.bounds;
layer.path = oldPath.CGPath;

self.layer.mask = layer;

CABasicAnimation* a = [CABasicAnimation animationWithKeyPath:@"path"];
[a setDuration:0.4f];
[a setFromValue:(id)layer.path];
[a setToValue:(id)newPath.CGPath];

layer.path = newPath.CGPath;
[layer addAnimation:a forKey:@"path"];

But when I run this, the corners are rounded, but there is no animation- it happens instantly. I have seen some similar questions here on SO, but they didn't solve my problem.

iPhone CALayer animation

Animating CALayer's shadowPath property

I'm sure that I'm just doing one little thing wrong which is causing the problem, but for the life of me I can't figure it out.

Solution:

- (void)roundCornersAnimated:(BOOL)animated {
    UIRectCorner corners = UIRectCornerTopLeft | UIRectCornerTopRight;

    UIBezierPath* newPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(8.0f, 8.0f)];
    UIBezierPath* oldPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(0.0001f, 0.0001f)];

    CAShapeLayer* layer = [CAShapeLayer layer];
    layer.frame = self.bounds;

    self.layer.mask = layer;

    if(animated) {
        CABasicAnimation* a = [CABasicAnimation animationWithKeyPath:@"path"];
        [a setDuration:0.4f];
        [a setFromValue:(id)oldPath.CGPath];
        [a setToValue:(id)newPath.CGPath];

        layer.path = newPath.CGPath;
        [layer addAnimation:a forKey:@"path"];
    } else {
        layer.path = newPath.CGPath;
    }
}

Really all I needed to do was set a proper 'from' value for the animation.

Thwart answered 13/7, 2012 at 2:40 Comment(1)
Solution would be nice as an answer instead of part of the question, but this is helpful anyway so +1 :)Envoy
P
11

It looks to me like you are creating a shape layer that does not have a path, then trying to animate adding a path to it. Shape layers are special case, and don't work that way.

You have to have a path in the shape layer before and after the animation, and the path needs to have the same number of points in it both before and after.

You would need to create a rounded rectangle path with a corner radius of 0 (manually, using a series of sides and corner arcs), and then build a new path with non zero radii for the corners you want to round and install that path as your animation.

Even that might not work, though, because the path needs to have exactly the same number of points in it, and I suspect that the arcs would simplify to points when the radius is zero.

I just found a UIBezierPath call that will create a rectangle where you can select which corners to round. The call is bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:

That will create a rectangle with as many rounded corners as you like. However, I'm not sure if it will work for path animation. You might need to request all corners be rounded with the 2 non-rounded corners set to zero radius. That way you MIGHT get a path with the same number of points in it as a rounded rectangle path so the path animation works correctly. You'll have to try it.

Pareira answered 13/7, 2012 at 13:16 Comment(1)
Thanks. You were right. Setting a correct 'from' value for the animation did it.Thwart
M
5

Try to put:

layer.path = newPath.CGPath;

after:

[layer addAnimation:a forKey:@"path"];
Minta answered 10/4, 2014 at 9:29 Comment(0)
C
0

Found a really elegant solution from Andrew Wagner here.

Esstentially the system will handle all path change animations, all you need is to:

  1. Implement a CAShapeLayer subclass
  2. Override and implement actionForKey
  3. Update the path as per usual and you'll get your animations!
Ciro answered 13/4, 2018 at 7:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.