Circular, 3 color gradient, animating progress view - ios

I have been searching the web for a library\solution how to achieve this.
I need to make a circular progress bar.
The progress bar should be built from up to 3 colors, depending of the progress value.
I have found several circular progress bars but I'm having trouble modifying them to the required result.
It's supposed to look sort of like the following image, only with a value label in the centre.
Circular gradient
When setting the progress, it should animate to that point.
Each color is up to a certain progress and then it should change with gradient to the next one.
Any ideas on how to achieve this?

I use a progress HUD from github here. It has an annular progress display.
I used the drawing code from this and had a play making a few adjustments. The following code will draw you an annular ring with each part of the ring using a color based on the progress. Code assumes the progress is in self.progress.
This code draws an annular ring in steps of 0.05 progress. So that gives you 20 color changes possible. Reduce this to show more and increase to show less. This code changes the ring color from red at 0.0 progress to green at 1.0 progress as a demonstration. Just change the code which sets the color to an algorithm of your choosing, table lookup etc...
It works when I do the changes inside the HUD linked to above. Just put this function in the view you want to draw the annular ring in.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat lineWidth = 5.f;
CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
CGFloat radius = (self.bounds.size.width - lineWidth)/2;
CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees
CGFloat endAngle = (2 * (float)M_PI) + startAngle;
UIBezierPath *processPath = [UIBezierPath bezierPath];
processPath.lineCapStyle = kCGLineCapRound;
processPath.lineWidth = lineWidth;
endAngle = (self.progress * 2 * (float)M_PI) + startAngle;
CGFloat tmpStartAngle=startAngle;
CGFloat shownProgressStep=0.05; // The size of progress steps to take when rendering progress colors.
for (CGFloat shownProgress=0.0;shownProgress <= self.progress ;shownProgress+=shownProgressStep){
endAngle=(shownProgress * 2 *(float)M_PI) + startAngle;
CGFloat rval=shownProgress;
CGFloat gval=(1.0-shownProgress);
UIColor *progressColor=[UIColor colorWithRed:rval green:gval blue:0.0 alpha:1.0];
[progressColor set];
[processPath addArcWithCenter:center radius:radius startAngle:tmpStartAngle endAngle:endAngle clockwise:YES];
[processPath stroke];
[processPath removeAllPoints];
tmpStartAngle=endAngle;
}
}

Most probably you will have to go all the way down to CoreGraphics. You can find some instructions on how to that here:
Draw segments from a circle or donut

Related

How can we draw Circle progress as "three fourths" - 3/4?

Currently am working on a circular chart,There is no issue on drawing full circle. I also need to draw a 3/4 circle, Is there any specify math to define 3/4 circle path. I tried by reducing missed position values with full circle but cant get accurate result. Could anyone help with this? If any need I
UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2.0f, self.frame.size.height/2.0f)
radius:(self.frame.size.height * 0.5) - ([_lineWidth floatValue]/2.0f)
startAngle:DEGREES_TO_RADIANS(startAngle)
endAngle:DEGREES_TO_RADIANS(endAngle)
clockwise:clockwise];
circle = [CAShapeLayer layer];
circle.path = circlePath.CGPath;
circle.lineCap = kCALineCapRound;
circle.fillColor = [UIColor clearColor].CGColor;
circle.lineWidth = [_lineWidth floatValue];
circle.zPosition = 1; [self.layer addSublayer:circle];
;
Please refer image below,
Note: I have attached the sample image from web but am sure i need the same results.
You already have the code that you need. The function bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise: takes a start angle and an end angle. If you pass in a start angle of 0 and an end angle of 3π/2 you'll get a 3/4 circle. (3π/2 is 270 degrees, or 3/4 of a full circle.)
Note that if your goal is to animate a circle from 0 to 360 degrees then you need to use a different technique. For that you want to create a path of the full circle, install that into a CAShapeLayer, then animate the layer's strokeEnd property from 0 to 1.

Attempting to mask a circle around an image not working

I have an image that I am attempting to mask a circle around so the image appears round. This somewhat works but the circle comes to a point on the top and bottom.
profileImageView.layer.cornerRadius = profileImageView.frame.size.width/2;
profileImageView.layer.masksToBounds = YES;
Should this code be drawing a perfect circle? It seems to draw a circle in one place but in two other places, its not working correctly.
I have had the best results masking the image view with a CAShapeLayer:
CGFloat radius = self.profileImageView.frame.size.width / 2.0;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius) radius:radius startAngle:0 endAngle:M_PI * 2.0 clockwise:TRUE];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = path.CGPath;
layer.lineWidth = 0;
self.profileImageView.layer.mask = layer;
Should this code be drawing a perfect circle?
Not necessarily. After all, the width and the height of this layer might not be the same. And even if they are, dividing by 2 might not give you a radius that fits perfectly into an integral number of points as they are mapped to pixels on the screen.
It really would be better, if what you want is a mask that's a circle, to give this layer an actual mask that is an actual circle. Misusing the corner radius as you are doing is just lazy (and, as you've discovered, it's error-prone).

iOS graphics appearing from upper left corner on custom control

I have been making a circular control and i am doing fine, except that the graphics appears from upper left corner when i do the render first time.
The whole control is subclassed UIControl, with custom CALayer which does rendering of a circle.
This is the code that renders the circle:
- (void) drawInContext:(CGContextRef)ctx{
id modelLayer = [self modelLayer];
CGFloat radius = [[modelLayer valueForKey:DXCircularControlLayerPropertyNameRadius] floatValue];
CGFloat lineWidth = [[modelLayer valueForKey:DXCircularControlLayerPropertyNameLineWidth] floatValue];
//NSLog(#"%s, angle: %6.2f, radius: %6.2f, angle_m: %6.2f, radius_m: %6.2f", __func__, self.circleAngle, self.circleRadius, [[modelLayer valueForKey:#"circleAngle"] floatValue], [[modelLayer valueForKey:#"circleRadius"] floatValue]);
// draw circle arc up to target angle
CGRect frame = self.frame;
CGContextRef context = ctx;
CGContextSetShouldAntialias(context, YES);
CGContextSetAllowsAntialiasing(context, YES);
// draw thin circle
//CGContextSetLineWidth(context, <#CGFloat width#>)
// draw selection circle
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
CGContextAddArc(context, frame.size.width / 2.0f, frame.size.height / 2.0f, radius, 0.0f, self.circleAngle, 0);
CGContextStrokePath(context);
CGContextSetShouldAntialias(context, NO);
}
Here is the video of a problem.
If you watch carefully, you'll notice that rendering of circle somehow doesnt start centered. It skews itself from the upper left corner.
This only happens when doing the animation for the first time.
I know this can happen if one mistakes begin and end of animation commit blocks, but i dont use them here.
Just in case here is the link to the bitbucket repo of this control:
There is nothing wrong with the drawing method - you messed up a little bit setting the layer's frame ;-)
You are setting the frame for the circularControlLayer within your - (void) setAngle:(CGFloat)angle method. That means the frame is set for the first time when you animate the angle property - so the frame will be animated too. Set the frame within the - (void) commonInitDXCircularControlView method.
If you are creating custom layers, have a look at the [UIView layerClass] method. Using it will save you from trouble with bounds/frame management.

How to make a curved progress bar around a circular UIView?

I want to make a circular progress bar such as that, how to animate its resizing and properties (shape, colour, width, etc.) as well.
I am trying to make it around a circular transparent view.
Where to start?
Does anyone has a sample code?
There's no substitute for learning, however a little help never goes a miss, so here are snippets of code that will accomplish the task for you.
The concept is to use a CAShapeLayer and a UIBezierPath and progress is simply setting the strokeEnd propertie of the UIBezierPath. You'll need to declare a CAShapeLayer and set its properties. We'll call this our progressLayer. (i'm not going to provide complete code, simply direction and samples for you to put together.)
// setup progress layer
// N.B borderWidth is a float representing a value used as a margin.
// pathwidth is the width of the progress path
// obviously progressBounds is a CGRect specifying the Layer's Bounds
[self.progressLayer setFrame:progressBounds];
UIBezierPath *progressPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)) radius:(bounds.size.height - self.borderWidth - self.pathWidth ) / 2 startAngle: (5* -M_PI / 12) endAngle: (2.0 * M_PI - 7 * M_PI /12) clockwise:YES];
self.progressLayer.strokeColor = [UIColor whiteColor].CGColor;
self.progressLayer.lineWidth = 6.0f ;
self.progressLayer.path = progressPath.CGPath;
self.progressLayer.anchorPoint = CGPointMake(0.5f, 0.5f);
self.progressLayer.fillColor = [UIColor clearColor].CGColor;
self.progressLayer.position = CGPointMake(self.layer.frame.size.width / 2 - self.borderWidth / 2, self.layer.frame.size.height / 2 - self.borderWidth/2);
[self.progressLayer setStrokeEnd:0.0f];
You will obviously need to add progressLayer to your view hierarchie
Then you will need a simple animation to progress the bar;
// updateInterval is a float specifying the duration of the animation.
// progress is a float storing the, well, progress.
// newProgress is a float
[self.progressLayer setStrokeEnd:newProgress];
CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
strokeEndAnimation.duration = updateInterval;
[strokeEndAnimation setFillMode:kCAFillModeForwards];
strokeEndAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
strokeEndAnimation.removedOnCompletion = NO;
strokeEndAnimation.fromValue = [NSNumber numberWithFloat:self.progress];
strokeEndAnimation.toValue = [NSNumber numberWithFloat:newProgress];
self.progress = newProgress;
[self.progressLayer addAnimation:strokeEndAnimation forKey:#"progressStatus"];
in your image above, the un-progressed path is nothing more than a second fully stroked layer behind the progressLayer
oh, and one final point, you'll find that the Circle progresses Clockwise. If you take the time to learn what's happening here, you'll figure out how to set progress Anti Clockwise.
Good Luck ...
You can also check out my lib MBCircularProgressBar, it also shows how to animate with and how to make it customizable from the Interface Builder. You can make it transparent by using transparent colors.
http://raw.github.com/matibot/MBCircularProgressBar/master/Readme/example.jpg
Here's a great, short tutorial on how to make this: http://www.tommymaxhanks.com/circular-progress-view/
You will need to learn a bit of Core Graphics in order to accomplish this in the way you want (the author used a gradient in the implementation, you may want to change that.), but it's worth it if you want to do cool stuff like this on the iPhone!
I think even more helpful is the example code, which is hosted here: https://github.com/mweyamutsvene/THControls/tree/master/CircularProgressView

iOS - Cant change view origin

I am plowing through the Big Nerd Ranch Guide 4th Edition, but in the chapter dealing with Views and View Hierarchy I am experiencing some problems with view origins.
I have added a custom subview to my view controller and overwritten the drawRect to draw a bunch of circles in the middle of the screen, however, the origin of the circles won't change and they all get stuck up in the left corner, and I have no idea why. I have literally just copied the code from the book, where it seems to work just fine.
This is my drawRect:
- (void)drawRect:(CGRect)rect
{
CGRect bounds = self.bounds;
//Figure out the center of the bounds rectangle
CGPoint center;
center.x = bounds.origin.x + bounds.size.width/2.0;
center.y = bounds.origin.y + bounds.size.height/2.0;
//The largest circle will circumscribe the view
float maxRadius = hypotf(bounds.size.width, bounds.size.height)/2.0;
UIBezierPath *path = [[UIBezierPath alloc]init];
//Loops and create multiple circles with different radii
for (float currentRadius = maxRadius; currentRadius > 0 ; currentRadius -= 20) {
[path moveToPoint:CGPointMake(center.x +currentRadius, center.y)];
[path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI*2.0 clockwise:YES];
}
path.lineWidth = 10;
[self.circleColor setStroke];
[path stroke];
}
And here is the outcome. A bunch of circles NOT in the center of the screen...
I would do a few things.
Find out where your view's origin is by creating another view, smaller, bright blue or anything, and say centerView.center = biggerView.center. Of course, hook it up.
Depending on your results, move your frames/views appropriately.
Is autolayout working? Is it defined right? Try turning it off for this one.
Try using self.center.x and y instead of making another center.x/y.
Actually do this first. Log or breakpoint bounds and make sure it's not 0 for width. You can print it with NSStringFromCGRect()

Resources