CABasicAnimation with CALayer path doesn't animate

12,577

Solution 1

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.

Solution 2

Try to put:

layer.path = newPath.CGPath;

after:

[layer addAnimation:a forKey:@"path"];
Share:
12,577
eric.mitchell
Author by

eric.mitchell

One should hear a little music, read a little poetry, and see a fine picture each day, in order that worldly cares may not obliterate the sense of the beautiful which is implanted in the human soul. -- Unknown

Updated on June 02, 2022

Comments

  • eric.mitchell
    eric.mitchell almost 2 years

    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.

  • eric.mitchell
    eric.mitchell almost 12 years
    Thanks. You were right. Setting a correct 'from' value for the animation did it.