Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(iOS) How to animate rounded rectangle with shapeLayer?

I am trying to animate the width of a rounded rectangle, the problem is when going from bigger width to thinner width, the animation does an "aberration ease jump".

Here's the code:

shapeLayer = [CAShapeLayer layer];
shapeRect = CGRectMake(0.0f, 0.0f, 150.0f, 200.0f);
[shapeLayer setBounds:shapeRect];
[shapeLayer setPosition:CGPointMake(iniPosX, 80.0f)];
[shapeLayer setFillColor:[[UIColor blackColor] CGColor]];
[shapeLayer setStrokeColor:[[UIColor clearColor] CGColor]];
[shapeLayer setLineWidth:1.0f];
[shapeLayer setLineJoin:kCALineJoinRound];
[shapeLayer setOpacity:0.2];
path = [UIBezierPath bezierPathWithRoundedRect:shapeRect cornerRadius:15.0];
[shapeLayer setPath:path.CGPath];
[self.layer addSublayer:shapeLayer];

And when I start the animation:

- (void)adjustSelectorToPosAndSize:(float)posX andWidth:(float)width
{
    shapeRect = CGRectMake(0.0f, 0.0f, width, 200.0f);
    [shapeLayer setBounds:shapeRect];
    [shapeLayer setPosition:CGPointMake(posX, 80.0f)];
    path = [UIBezierPath bezierPathWithRoundedRect:shapeRect cornerRadius:15.0];
    [shapeLayer setPath:path.CGPath];
}

What am I doing wrong?

like image 504
murb83 Avatar asked Jun 19 '12 10:06

murb83


2 Answers

Doing this with a CAShapeLayer seems overly complicated to me if you are only using it for a rounded rectangle. Why not simply use a CALayer with the cornerRadius properly set?

Animating the frame with cornerRadius set will work fine.

myLayer = [CALayer layer];
shapeRect = CGRectMake(0.0f, 0.0f, 150.0f, 200.0f);
[myLayer setBounds:shapeRect];
[myLayer setPosition:CGPointMake(iniPosX, 80.0f)];
[myLayer setBackgroundColor:[[UIColor blackColor] CGColor]];
[myLayer setBorderColor:[[UIColor clearColor] CGColor]];
[myLayer setBorderWidth:1.0f];
[myLayer setOpacity:0.2];
[myLayer setCornerRadius:15.0];
[self.layer addSublayer:myLayer];

Animating it is done by simply chaining the frame (not the path)

- (void)adjustSelectorToPosAndSize:(float)posX andWidth:(float)width
{
    shapeRect = CGRectMake(0.0f, 0.0f, width, 200.0f);
    [myLayer setBounds:shapeRect];
    [myLayer setPosition:CGPointMake(posX, 80.0f)];
}

Important:

Some of the properties changed name like fillColor became backgroundColor and the strokeColor and lineWidth became borderColor and borderWidth etc.

like image 163
David Rönnqvist Avatar answered Oct 12 '22 23:10

David Rönnqvist


Although CAShapeLayer’s path property is animatable, it does not do so implicitly like bounds and position, you have to make an explicit animation:

CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath: @"path"];
anim.fromValue = (id) fromPath.CGPath;
anim.toValue = (id) toPath.CGPath;
anim.duration = 1.0;
[layer addAnimation: anim forKey: @“resize”];

Since you probably want all three animations to happen together, you can make them all explicit and grouped together:

CABasicAnimation* anim1 = [CABasicAnimation animationWithKeyPath: @"position"];
anim1.fromValue = [NSValue valueWithCGPoint: fromPosition];
anim1.toValue = [NSValue valueWithCGPoint: toPosition];
anim1.duration = 1.0;

CABasicAnimation* anim2 = [CABasicAnimation animationWithKeyPath: @"bounds"];
anim2.fromValue = [NSValue valueWithCGRect: fromBounds];
anim2.toValue = [NSValue valueWithCGRect: toBounds];
anim2.duration = 1.0;

CABasicAnimation* anim3 = [CABasicAnimation animationWithKeyPath: @"path"];
anim3.fromValue = (id) fromPath.CGPath;
anim3.toValue = (id) toPath.CGPath;
anim3.duration = 1.0;

CAAnimationGroup* animG = [CAAnimationGroup animation];
animG.animations = [NSArray arrayWithObjects: anim1, anim2, anim3, nil];
animG.duration = 1.0;
[layer addAnimation: animG forKey:@"resize"];

If your shape does not have contents or children then you may be able to use a CALayer instead:

CALayer* rectLayer = [CALayer layer];
rectLayer.masksToBounds = YES;
rectLayer.bounds = startBounds;
rectLayer.backgroundColor = [[UIColor blackColor] CGColor];
rectLayer.cornerRadius = 15.0;
[self.layer addSublayer: rectLayer];

// Then later implicitly animate the bounds:
rectLayer.bounds = newBounds;
like image 34
Jeff Argast Avatar answered Oct 13 '22 01:10

Jeff Argast