Reputation: 3681
I'm trying to implement a circular indicator that draws itself with animation as values change.
I use CAShapeLayer
to draw with animation. So far I got it to draw itself from the beginning each time, it works as intended. Now I want to make it more sleek and draw it forward and "erase" depending on if the new value is greater or lower than previous. I'm not sure how to implement the erasing part. There's a background so I cant just draw on top with white colour. Here's an image to better understand how it looks.
Any ideas how erasing part could be achieved? There's some solutions here on SO on similar problems, but those involve no animation.
I use 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"];
}
Upvotes: 3
Views: 1617
Reputation: 104092
I was able to do this in a slightly different way from what you're doing. Instead of drawing an arc of a certain length, I made my path a full circle, but only animate it part way. You will notice that I use the presentation layer's strokeEnd to get the toValue, so that if an animation is in progress when you need to change the value of the final strokeEnd, it will start from where the path is currently drawn. Here is the code in my view controller; outline layer draws the gray circle, similar to what you have in your image. I added 5 buttons to my view (for testing) whose tags are used to change the final stroke value of the animation,
@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 for the subclassed 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;
}
Upvotes: 6