I've got a project where I'm animating a UIBezierPath based on a set progress. The BezierPath is in the shape of a circle and lies in a UIView and animation is done in drawRect using CADisplayLink right now. Simply put, based on a set progress x
the path should radially extend (if x
is larger than before) or shrink (if x
is smaller).
self.drawProgress = (self.displayLink.timestamp - self.startTime)/DURATION;
CGFloat startAngle = -(float)M_PI_2;
CGFloat stopAngle = ((self.x * 2*(float)M_PI) + startAngle);
CGFloat currentEndAngle = ((self.oldX * 2*(float)M_PI) + startAngle);
CGFloat endAngle = currentEndAngle-((currentEndAngle-stopAngle)*drawProgress);
UIBezierPath *guideCirclePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
This is in the case of x
shrinking since our last update. The issues I'm experiencing are actually a few:
startAngle
to -45º makes no difference really because it always "pops" to 45. Is there anything I can do about this, or do I have to resort to other methods of drawing?CAShapeLayer
but I haven't quite understood the actual difference (in terms of drawbacks and benefits) in using these two methods. If anyone could clarify I would be very much obliged!UPDATE: I migrated the code over to CAShapeLayer instead, but now I'm facing a different issue. It's best described with this image:
What's happening is that when the layer is supposed to shrink, the thin outer line is still there (regardless of direction of movement). And when the bar shrinks, the delta of 1-x
isn't removed unless I explicitly make a new white shape over it. The code for this follows. Any ideas?
UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:stopAngle clockwise:YES];
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [circlePath CGPath];
circle.strokeStart = 0;
circle.strokeEnd = 1.0*self.progress;
// Colour and other customizations here.
if (self.progress > self.oldProgress) {
drawAnimation.fromValue = @(1.0*self.oldProgress);
drawAnimation.toValue = @(circle.strokeEnd);
} else {
drawAnimation.fromValue = @(1.0*self.oldProgress);
drawAnimation.toValue = @(1.0*self.progress);
circle.strokeEnd = 1.0*self.progress;
}
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; //kCAMediaTimingFunctionEaseIn
[circle addAnimation:drawAnimation forKey:@"strokeEnd"];
UPDATE 2: I've ironed out most of the other bugs. Turned out it was just me being rather silly the whole time and overcomplicating the whole animation (not to mention multiplying by 1 everywhere, what?). I've made a gif of the bug I can't solve:
Any ideas?
UPDATE 3: (and closure). I managed to get rid of the bug by calling
[self.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
And now everything works as it should. Thanks for all the help!
Using CAShapeLayer is much easier and cleaner. The reason is that CAShapeLayer includes properties strokeStart and strokeEnd. These values range from 0 (the beginning of the path) to 1 (the end of the path) and are animatable.
By changing them you can easily draw any arc of your circle (or any part of an arbitrary path, for that matter.) The properties are animatable, so you can create an animation of a growing/shrinking pie slice or section of a ring shape. It's much easier and more performant than implementing code in drawRect.