Search code examples
iosobjective-cuibezierpathcashapelayercabasicanimation

Circle strokeWidth change animation without changing the circle radius


I have a few rects that I draw a circle in with UIBezierPath and CAShapeLayer. They should act like buttons, in which when they are tapped they are being selected and when a different button is tapped they are being deselected, where each of these states should change button appearance between these two conditions:

deselected: original selected: enter image description here

This is the general code (which is called when selection changes):

CGFloat radius = isSelected ? 9.8 : 13. ;
CGFloat strokeWidth = isSelected ? 10.5 : 2;

UIBezierPath *bezierPath = [UIBezierPath bezierPath];
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
[bezierPath addArcWithCenter:center radius:radius startAngle:0 endAngle:2 * M_PI clockwise:YES];

CAShapeLayer *progressLayer = [[CAShapeLayer alloc] init];
[progressLayer setPath:bezierPath.CGPath];
[progressLayer setStrokeColor:[UIColor redColor].CGColor];
[progressLayer setLineWidth:strokeWidth]];
[self.layer addSublayer:progressLayer];

When this rect is selected, I would like to change the strokeWidth of the circle only inward, and keep the original radius. When it's deselected, the opposite should happen. As you can see I change both the radius and the lineWidth, because when I make strokeWidth bigger, the outer radius is growing as well. And as I said, I want to keep it the same.

The problem is that I want it animated in a way that the width grows inward when selected, and out when deselected, without changing the outer radius at all. What I have so far, is an animation of the change of strokeWidth together with the radius in order to keep the result with the same appeared radius. But it's not what I need. As you can see, only the radius change is animated here, and the result is a growing circle, which I don't want.

enter image description here

This is the code of the animation (in addition to the original code):

  CABasicAnimation *changeCircle = [CABasicAnimation animationWithKeyPath:@"path"];
  changeCircle.duration = 0.6;
  changeCircle.fromValue = (__bridge id)oldPath; // current bezier path
  changeCircle.toValue = (__bridge id)newPath; // new bezier path

  [progressLayer addAnimation:changeCircle forKey:nil];

I had also the opposite code, that animates only the change of strokeWidth. It also doesn't do what I need. Is there a way to the strokeWidth grow or get small without changing the outer radius?

Like this: enter image description hereenter image description hereenter image description here


Solution

  • Thanks to @Chris, I managed to work it out.

    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    pathAnimation.fromValue = (__bridge id) oldPath;
    pathAnimation.toValue = (__bridge id) newPath;
    
    CABasicAnimation *lineWidthAnimation = [CABasicAnimation animationWithKeyPath:@"lineWidth"];
    lineWidthAnimation.fromValue = isSelected ? @2. : @10.5;
    lineWidthAnimation.toValue = isSelected ? @10.5 : @2.;
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.duration = 0.3;
    group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    group.animations = @[pathAnimation, lineWidthAnimation];
    
    CAShapeLayer *progressLayer = [[CAShapeLayer alloc] init];
    [progressLayer setPath:bezierPath.CGPath];
    [progressLayer setStrokeColor:self.color.CGColor];
    [progressLayer setLineWidth:isSelected ? 10.5 : 2];
    [progressLayer setFillColor:[UIColor clearColor].CGColor];
    [progressLayer addAnimation:group forKey:@"selectedAnimations"];
    [self.layer addSublayer:progressLayer];
    

    enter image description here