IOS "erase" drawing with animated CAShapeLayer

I am trying to implement a circular indicator that draws an animation when values ​​change.

I am using CAShapeLayer

drawing with animation. Until now, I had to start from the very beginning every time, it works as intended. Now I want to make it smoother and draw it forward and "erase" depending on whether the new value is greater or less than the previous one. I'm not sure how to implement the erasing part. There is a background, so I can't just paint on top with white. Here's a picture to get a better understanding of how it looks.

Circular indicator

Any ideas on how the erasing part can be achieved? There's some solutions here on SO for similar problems, but no animation in them.

I am using this code to draw:

- (void)drawCircleAnimatedWithStartAngle:(CGFloat)startAngle
                            endAngle:(CGFloat)endAngle
                               color:(UIColor *)color
                              radius:(int)radius
                           lineWidth:(int)lineWidth {


CAShapeLayer *circle = [CAShapeLayer layer];
circle.name = @"circleLayer";

    circle.path = ([UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius) radius:radius-1 startAngle:startAngle endAngle:endAngle clockwise:YES].CGPath);

// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor colorWithRed:19.0/255 green:167.0/255 blue:191.0/255 alpha:1].CGColor;
circle.lineWidth = lineWidth;

// Add to parent layer
for (CALayer *layer in self.circleView.layer.sublayers) {
    if ([layer.name isEqualToString:@"circleLayer"]) {
        [layer removeFromSuperlayer];
        break;
    }
}

[self.circleView.layer addSublayer:circle];
circle.zPosition = 1;


// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
drawAnimation.duration            = 0.5; 
drawAnimation.repeatCount         = 1.0;  // Animate only once..

// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue   = [NSNumber numberWithFloat:1.0f];

drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:@"drawCircleAnimation"];

}

      

+3


source to share


1 answer


I was able to do it in a slightly different way than you do. Instead of drawing an arc of a certain length, I made my path a full circle, but only partially animated it. You will notice that I am using the strokeEnd description layer to get the toValue, so if the animation is running when you need to change the final strokeEnd, it will start where the path is currently going. Here is the code in my controller; The Layer Sketch draws a gray circle, similar to what you have in your image. I have added 5 buttons to my view (for testing), whose tags are used to change the final value of the animation stroke,

@implementation ViewController {
    UIView *circleView;
    RDShapeLayer *circle;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    circleView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
    CALayer *outlineLayer = [CALayer new];
    outlineLayer.frame = circleView.bounds;
    outlineLayer.borderWidth = 2;
    outlineLayer.borderColor = [UIColor lightGrayColor].CGColor;
    outlineLayer.cornerRadius = circleView.frame.size.width/2.0;
    [circleView.layer addSublayer:outlineLayer];
    [self.view addSubview:circleView];

    circle = [[RDShapeLayer alloc] initWithFrame:circleView.bounds color:[UIColor blueColor].CGColor lineWidth:4];
    [circleView.layer addSublayer:circle];
}


-(void)animateToPercentWayAround:(CGFloat) percent {

    circle.strokeEnd = percent/100.0;
    CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    drawAnimation.duration = 1.0;
    drawAnimation.fromValue = @([circle.presentationLayer strokeEnd]);
    drawAnimation.toValue   = @(percent/100.0);

    drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [circle addAnimation:drawAnimation forKey:@"drawCircleAnimation"];
}


- (IBAction)changeStroke:(UIButton *)sender {
    [self animateToPercentWayAround:sender.tag];
}

      



This is the code to subclass CAShapeLayer,

-(instancetype)initWithFrame:(CGRect) frame color:(CGColorRef) color lineWidth:(CGFloat) lineWidth {
    if (self = [super init]) {

        self.path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(frame, 1, 1)].CGPath;
        self.fillColor = [UIColor clearColor].CGColor;
        self.strokeColor = color;
        self.lineWidth = lineWidth;
        self.strokeStart = 0;
        self.strokeEnd = 0;
    }
    return self;
}

      

+6


source







All Articles