Custom Circle Progress View IOS - ios

I'm trying to create a circular progress view that updates with a timer. I am trying to get 4 sections with different colours. I've managed to update the circle with one colour but I'm having trouble figuring out how to split it into 4 quarter circles.
#implementation CircularProgressBar
- (void)drawRect:(CGRect)rect
{
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[bezierPath addArcWithCenter:CGPointMake(160, 180)
radius:120
startAngle:0
endAngle: DEGREES_TO_RADIANS(360)
clockwise:YES];
// Set the display for the path, and stroke it
bezierPath.lineWidth = 12;
[[UIColor whiteColor] setStroke];
[bezierPath stroke];
UIBezierPath* progressPath = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[progressPath addArcWithCenter:CGPointMake(160, 180)
radius:120
startAngle:-M_PI_2
endAngle:-M_PI_2 + (2/M_PI) * self.progress
clockwise:YES];
// Set the display for the path, and stroke it
progressPath.lineWidth = 12;
[[UIColor redColor] setStroke];
[progressPath stroke];
UIBezierPath* progressPath1 = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[progressPath1 addArcWithCenter:CGPointMake(160, 180)
radius:120
startAngle:-M_PI_2 + (2/M_PI)
endAngle:M_PI_2* self.progress1/
clockwise:YES];
// Set the display for the path, and stroke it
progressPath1.lineWidth = 12;
[[UIColor grayColor] setStroke];
[progressPath1 stroke];
}
-(void)setProgress:(CGFloat)progress {
//update progress with timer
if (progress != _progress) {
_progress = progress;
if(progress > 1.1)
_progress1=_progress;
[self setNeedsDisplay];
}
}
Timer Class
-(float) progress {
return (float)self.count/450.0;
}
timer is 1800 seconds. Divided by 4 is 450 seconds per section

I made a small library for this purpose, very flexible with tons of options : https://github.com/kirualex/KAProgressLabel
A simple configuration looks like this :
[self.pLabel setTrackWidth: 2.0];
[self.pLabel setProgressWidth: 4];
self.pLabel.fillColor = [[UIColor lightGrayColor] colorWithAlphaComponent:.3];
self.pLabel.trackColor = self.startSlider.tintColor;
self.pLabel.progressColor = [UIColor greenColor];

You can download useful and very easy to embedd types of progressview on the link http://code4app.net/category/progress

Related

Draw path around visible part of UIBezierPath

Is it possible to draw a path around the visible part of a UIBezierPath?
Here's an example of my problem
Here's what I'd like to accomplish
Here's what I got so far:
- (void)drawRect:(CGRect)rect {
CGFloat side = MIN(rect.size.width, rect.size.height);
CGPoint center = CGPointMake(rect.size.width / 2.0f, rect.size.height / 2.0f);
UIColor *yinYangColor = [UIColor whiteColor];
UIBezierPath *yinYangPath = [UIBezierPath bezierPath];
// Draw Yin&Yang part
[yinYangPath addArcWithCenter:CGPointMake(center.x, center.y - side / 4.0f) radius:side / 4.0f startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:YES];
[yinYangPath addArcWithCenter:CGPointMake(center.x, center.y + side / 4.0f) radius:side / 4.0f startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:NO];
[yinYangPath addArcWithCenter:CGPointMake(center.x, center.y) radius:side / 2.0f startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:YES];
[yinYangPath closePath];
[yinYangColor setFill];
[yinYangPath fill];
// Add border
CAShapeLayer *borderLayer = [[CAShapeLayer alloc] init];
borderLayer.path = yinYangPath.CGPath;
borderLayer.fillColor = [UIColor clearColor].CGColor;
borderLayer.strokeColor = [UIColor blackColor].CGColor;
borderLayer.lineWidth = 5.0f;
[self.layer addSublayer:borderLayer];
}
The angle π/2 radians is along the positive y axis.
In standard UIKit geometry, the positive y axis points down toward the bottom of the screen. Therefore the top arc (at center.y - side/4) needs to start at angle -π/2 and end at angle π/2. Since you got these backward, your second arc doesn't start where your first arc ended, so your path contains a straight line connecting those points. Ditto for your second and third arcs. The single straight line visible in your image is actually the combination of those two lines.
Also, incidentally, the rect passed to drawRect: is in theory not necessarily the bounds of the view. It's better not to treat it as such.
Also also, you shouldn't add sublayers in drawRect:. You should do that in init or layoutSubviews and you should make sure you don't duplicate layers. I guess maybe you're using a CAShapeLayer because you don't want the border cut off. I would solve that by insetting the view bounds by the border width:
- (void)drawRect:(CGRect)dirtyRect {
CGFloat lineWidth = 4;
CGRect rect = CGRectInset(self.bounds, lineWidth / 2, lineWidth / 2);
CGFloat side = MIN(rect.size.width, rect.size.height);
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat smallRadius = side / 4;
[path addArcWithCenter:CGPointMake(center.x, center.y - smallRadius) radius:smallRadius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:NO];
[path addArcWithCenter:CGPointMake(center.x, center.y + smallRadius) radius:smallRadius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:YES];
[path addArcWithCenter:center radius:side / 2 startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:NO];
[path closePath];
[path setLineJoinStyle:kCGLineJoinRound];
[path setLineWidth:lineWidth];
[[UIColor whiteColor] setFill];
[path fill];
[[UIColor blackColor] setStroke];
[path stroke];
}
Result:
If you want the bottom tip to be more pointy, I would do that by clipping all drawing to the path, then drawing the border twice as thick. Half the border will be drawn outside the path and clipped away, leaving a sharp point. In this case, you don't have to inset the bounds.
- (void)drawRect:(CGRect)dirtyRect {
CGFloat lineWidth = 4 * 2;
CGRect rect = self.bounds;
CGFloat side = MIN(rect.size.width, rect.size.height);
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat smallRadius = side / 4;
[path addArcWithCenter:CGPointMake(center.x, center.y - smallRadius) radius:smallRadius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:NO];
[path addArcWithCenter:CGPointMake(center.x, center.y + smallRadius) radius:smallRadius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:YES];
[path addArcWithCenter:center radius:side / 2 startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:NO];
[path closePath];
[path setLineJoinStyle:kCGLineJoinRound];
[path addClip];
[path setLineWidth:lineWidth];
[[UIColor whiteColor] setFill];
[path fill];
[[UIColor blackColor] setStroke];
[path stroke];
}
Result:

Rounded Ends on Circular Graph? iOS?

I am trying to reproduce the rounded ends on the circular graph it is not working out for myself.
I tried google and cannot find the exact terminology to recreate it.
Can anyone lead me in the right direction or tell me what I am looking for ?
I want a circular graph that looks like so
https://www.punchkickinteractive.com/content/uploads/2014/10/apple-watch-activity-app.jpeg
If there is an assisting library that I can style that would even be better
Cheers!
Not sure what kind of difficulties do you have any? If just with drawing, so there is one way:
#interface ArcView ()
#property (nonatomic) double arcAngel;
#end
#implementation ArcView
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGPoint center = CGPointMake(CGRectGetWidth(self.bounds) / 2, CGRectGetHeight(self.bounds) / 2);
double lineWidth = 10;
double radius = center.x - lineWidth / 2;
UIBezierPath *backCircle = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:0
endAngle:2 * M_PI
clockwise:YES];
backCircle.lineCapStyle = kCGLineCapRound;
backCircle.lineWidth = lineWidth;
[[UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:0.3f] setStroke];
[backCircle stroke];
UIBezierPath *highlightCircle = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:-M_PI_2
endAngle:self.arcAngel
clockwise:YES];
highlightCircle.lineCapStyle = kCGLineCapRound;
highlightCircle.lineWidth = lineWidth;
[[UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f] setStroke];
[highlightCircle stroke];
}
- (void)updateCircle {
self.arcAngel += M_PI_2;
[self setNeedsDisplay];
}
#end
UPD: set lineCapStyle property to kCGLineCapRound
There is result:

Trying to fill a graph color drawn with a Bezier Path

I'm trying a UIBezierPath for the first time and am trying to fill a graph I've drawn with a color but without success. This is what I've done within a custom UIView:
- (void)calculateGraphSize
{
self.topY = CGRectGetMinY(self.bounds);
self.bottomY = CGRectGetMaxY(self.bounds);
self.minX = CGRectGetMinX(self.bounds);
self.maxX = CGRectGetMaxX(self.bounds);
}
- (void) drawRect: (CGRect) rect
{
CGSize cornerRadius = CGSizeMake(10.0f, 10.0f);
[self calculateGraphSize];
UIBezierPath *graphBorder = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:cornerRadius];
[[UIColor redColor] setFill];
[graphBorder fill];
UIBezierPath *barGraph = [UIBezierPath bezierPath];
barGraph.lineWidth = 2.0f;
CGPoint point = CGPointMake(self.minX, self.bottomY);
[barGraph moveToPoint:point];
point = CGPointMake(self.minX + 50, self.bottomY - 50);
[barGraph addLineToPoint:point];
point = CGPointMake(self.minX + 100, self.bottomY - 75);
[barGraph addLineToPoint:point];
point = CGPointMake(self.minX + 200, self.bottomY);
[barGraph addLineToPoint:point];
[[UIColor blackColor] setStroke];
[[UIColor greenColor] setFill];
[barGraph closePath];
[barGraph stroke];
}
Just add [barGraph fill]:
[[UIColor blackColor] setStroke];
[[UIColor greenColor] setFill];
[barGraph closePath];
[barGraph fill];
[barGraph stroke];
You probably want to fill and then stroke as I can't remember if the fill will cover up the stroke (I always have to check ha!)

Displaying a UIBeizerPath using a CAShapeLayer vs Quartz 2D doesn't look as nice

I'm writing an app with a circular progress bar, and have recently changed it to be drawn with a couple of CAShapeLayers (one for the white background, one for the purple progress) so I can animate the purple line, rather than making new UIBeizerPaths in the drawRect of a UIView.
Having made this change, I've had some issues with how it looks now. Below is a screenshot of each way, and a diff (using “Diff” an image using ImageMagick)
My main issue with it is that it looks slightly blurry when using the CAShapeLayers - and I can't for the life of me figure out how to make it look sharper. The other issue is some of the white background is showing through the purple, but I can get around that by changing the width of the white line to be slightly less wide.
The drawRect code as it was originally written is as follows:
- (void)drawRect:(CGRect)rect {
UIBezierPath *backCircle = [UIBezierPath bezierPath];
[backCircle addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:(rect.size.width/2.0f) - 4.0f
startAngle:(self.endAngle - self.startAngle) * _percent + self.startAngle
endAngle:self.endAngle
clockwise:YES];
backCircle.lineWidth = 5;
[[UIColor whiteColor] setStroke];
[backCircle stroke];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:(rect.size.width/2.0f) - 4.0f
startAngle:self.startAngle
endAngle:(self.endAngle - self.startAngle) * _percent + self.startAngle
clockwise:YES];
bezierPath.lineWidth = 5;
[[UIColor purpleColor] setStroke];
[bezierPath stroke];
}
with the init code:
self.startAngle = M_PI * 1.5;
self.endAngle = self.startAngle + (M_PI * 2);
And then with the 2 layers:
- (void)setUpView
{
CGFloat startAngle = M_PI * 1.5;
CGFloat endAngle = startAngle + (M_PI * 2);
UIBezierPath *processPath = [UIBezierPath bezierPath];
[processPath addArcWithCenter:self.boundsCenter
radius:self.radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
self.backgroundShapeLayer.path = [processPath CGPath];
self.progressShapeLayer.path = [processPath CGPath];
}
- (CGPoint)boundsCenter
{
return CGPointMake((self.bounds.size.width ) / 2.0, (self.bounds.size.height ) / 2.0);
}
- (CGFloat)radius
{
return (self.bounds.size.width / 2.0) - 4.0f;
}
- (CAShapeLayer *)progressShapeLayer
{
if (_progressShapeLayer == nil) {
_progressShapeLayer = [CAShapeLayer layer];
_progressShapeLayer.fillColor = [[UIColor clearColor] CGColor];
_progressShapeLayer.lineWidth = 5.0;
_progressShapeLayer.strokeStart = 0.0;
_progressShapeLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:_progressShapeLayer];
}
return _progressShapeLayer;
}
- (CAShapeLayer *)backgroundShapeLayer
{
if (_backgroundShapeLayer == nil) {
_backgroundShapeLayer = [CAShapeLayer layer];
_backgroundShapeLayer.fillColor = [[UIColor clearColor] CGColor];
_backgroundShapeLayer.strokeColor = [[UIColor whiteColor] CGColor];
_backgroundShapeLayer.lineWidth = 5.0;
_backgroundShapeLayer.strokeStart = 0.0;
_backgroundShapeLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:_backgroundShapeLayer];
}
return _backgroundShapeLayer;
}
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
{
if(isnan(progress) || progress < 0) return;
_progress = progress;
if(!animated) {
[CATransaction setDisableActions:YES];
}
self.progressShapeLayer.strokeColor = [self.progressColour CGColor];
self.progressShapeLayer.strokeEnd = _progress;
}
I really feel like I'm going a bit crazy staring at the zoomed in screen, but it just doesn't look quite right...

UIBezierPath with multiples arcs

Hiiii
I'm trying to draw in a view an arc with dynamic width.
I have built an ARRAY with several UIBezierPath with arcs then I draw one after the other. this is the code:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGFloat radious;
if (self.bounds.size.width < self.bounds.size.height) {
radious = self.bounds.size.width / 2;
}
else{
radious = self.bounds.size.height / 2;
}
float oneGradeInRadians = (float)(2 * M_PI) / 360.f;
float radiandToDraw = (float)self.finalAngleRadians - self.initialAngleRadians;
float splitMeasure = oneGradeInRadians;
int numberOfArcs = radiandToDraw / splitMeasure;
NSMutableArray *arrayOfBeziersPaths = [NSMutableArray arrayWithCapacity:numberOfArcs];
for (int i = 0; i < numberOfArcs; i++) {
float startAngle = self.initialAngleRadians + (i * splitMeasure);
float endAngle = self.initialAngleRadians +((i + 1) * splitMeasure);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2) radius:radious - self.widthLine.floatValue startAngle:startAngle endAngle:endAngle clockwise:YES];
bezierPath.lineWidth = self.widthLine.floatValue + i/20;
float hue = (float)(i / 3.f);
UIColor *color = [UIColor colorWithHue:hue/360.0 saturation:1 brightness:1 alpha:1];
[color setStroke];
[arrayOfBeziersPaths addObject:bezierPath];
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineJoin(context, kCGLineJoinMiter);
//We saved the context
CGContextSaveGState(context);
for (int i = 0; i < numberOfArcs; i++) {
[(UIBezierPath *)[arrayOfBeziersPaths objectAtIndex:i] stroke];
[(UIBezierPath *)[arrayOfBeziersPaths objectAtIndex:i] fill];
}
//Restore the contex
CGContextRestoreGState(context);
}
In this way I get the right shape but, also I get an annoying lines between the arcs:
I think the problem may be a property to change between arcs, but I'm totally lost, or maybe there is another better way to build this.
I tried creating several UIBezierPath paths and I added them to an unique UIBezierPath, then I stoke and fill that path. I didn't get the annoying lines, but the problem is I can not modify the line width, so I can not get the same effect.
Any idea? thanks
dont create many such paths. just create two arcs (inner circle, exterior circle) and two straight lines joining the ends. then fill the path.
Add below code in a viewDidLoad of an empty viewController and check.
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat k =0.5522847498;
UIBezierPath *path =[UIBezierPath bezierPath];
CGFloat radius=130;
[path addArcWithCenter:CGPointMake(0, 0) radius:radius startAngle:M_PI_2 endAngle:M_PI clockwise:NO];
CGFloat start=10;
CGFloat increment=5;
[path addLineToPoint:CGPointMake(-radius-start, 0)];
[path addCurveToPoint:CGPointMake(0, -radius-start-increment) controlPoint1:CGPointMake(-radius-start, -(radius +start)*k) controlPoint2:CGPointMake(-(radius+start)*k, -radius-start-increment)];
[path addCurveToPoint:CGPointMake(radius+start+2*increment, 0) controlPoint1:CGPointMake((radius+start+increment)*k, -radius-start-increment) controlPoint2:CGPointMake(radius+start+2*increment, (-radius-start-increment)*k)];
[path addCurveToPoint:CGPointMake(0, radius+start+3*increment) controlPoint1:CGPointMake(radius+start+2*increment, (radius+start+2*increment)*k) controlPoint2:CGPointMake((radius+start+2*increment)*k,radius+start+3*increment)];
[path addLineToPoint:CGPointMake(0,radius)];
CAShapeLayer *layer =[CAShapeLayer layer];
[layer setFrame:CGRectMake(150, 200, 300, 300)];
[layer setFillColor:[UIColor greenColor].CGColor];
[layer setStrokeColor:[UIColor blackColor].CGColor];
[layer setPath:path.CGPath];
[self.view.layer addSublayer:layer];
}
It produced result like,
The problem here is that you’ve chosen a crazy approach to drawing the shape you’re after. If you take a look at it, you’ll see that it’s actually a filled region bounded on the outside by a circular arc, and on the inner edge by a spiral.
What you should do, therefore, is create a single NSBezierPath, add the outer circular arc to it, then add a line to the start of your spiral, append a spiral (you’ll want to approximate it with Bézier segments) and finally call -closePath. After that, you can -fill the path and you’ll get a good looking result rather than the mess you have above.

Resources