How to make corner for Arc - ios

I have made an arc like below. By specifying the Radius, Start angle, End angle
CGContextAddArc(ctx, self.frame.size.width/2 , self.frame.size.height/2,
self.radius, 2*M_PI, 3*M_PI/2-ToRad(angle), 0);
Now i want to make the corner of arch rounded. So need of drawing circles on the both ends. Because I'm using frame dimensions giving constants wont work.

Try to set the Graphics State Parameter CGContextSetLineJoin to round: CGContextSetLineCap(ctx, kCGLineCapRound);
Here is my research based on your question. The method resides in the drawRect: method, I wrote a short method to abstract it, in order to give only the parameter startAngle, endAngle and radius to the method. Of course it ca be refined.
I provided you a picture from the output of this method.
- (void)drawRect:(CGRect)rect {
float startAngle = 0;
float endAngle = 60;
float radius = 50.0;
CGContextRef ctx = [self drawRoundedArcWithStartAngle:startAngle endAngle:endAngle radius:radius];
CGContextStrokePath(ctx);
}
- (CGContextRef)drawRoundedArcWithStartAngle:(float)startAngle endAngle:(float)endAngle radius:(float)radius {
CGContextRef ctx = UIGraphicsGetCurrentContext();
// [CGContextAddArc(ctx, self.frame.size.width/2 , self.frame.size.height/2, radius, 2*M_PI, 3*M_PI/2-(angle * M_PI / 180), 0)];
CGContextAddArc(ctx, self.frame.size.width/ 2, self.frame.size.height/2, radius, (startAngle * M_PI / 180), (endAngle * M_PI / 180), 0);
CGContextSetRGBStrokeColor(ctx, 0.0, 0.0, 0.0, 1.0);
CGContextSetLineWidth(ctx, 20.0f);
CGContextSetLineCap(ctx, kCGLineCapRound);
return ctx;
}
Hope it helps!

You can set it easily by
Objective-C
CGContextSetLineCap(context, kCGLineCapRound);
Swift
context?.setLineCap(.round)
context?.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true/false)

Related

UIBezierpath setLineDash arc thick and thin [duplicate]

I have this line around my shape:
The problem is, it obviously goes from 1px thick, to 2px. Here's the code I'm using:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetFillColorWithColor(context, [BUTTON_COLOR CGColor]);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(context, 1);
int radius = 8;
CGContextMoveToPoint(context, 0, self.frame.size.height / 2);
CGContextAddLineToPoint(context, POINT_WIDTH, self.frame.size.height);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius,
rect.origin.y + rect.size.height);
CGContextAddArc(context, rect.origin.x + rect.size.width - radius,
rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + radius);
CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius,
radius, 0.0f, -M_PI / 2, 1);
CGContextAddLineToPoint(context, POINT_WIDTH, 0);
CGContextAddLineToPoint(context, 0, self.frame.size.height / 2);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
Any ideas?
The reason your top, bottom, and right edges appear thinner than your diagonals is because you are chopping off half of the lines drawn along those edges. As Costique mentioned, Core Graphics draws a stroke so that it is centered on the path. That means half of the stroke lies on one side of the path, and half of the stroke lies on the other side. Since your path runs exactly along the top, right, and bottom edges of your view, half of the stroke falls outside the bounds of your view and isn't drawn.
Costique is also correct that you should move your paths in by .5 points. (Technically you should move by half the stroke width.) However, you're not specifying your coordinates uniformly based on the rect variable, so moving all your paths is difficult.
What you want to do is inset rect by .5 in both dimensions, adjust your graphics context so that its origin is at rect.origin, and use only rect.size to get your coordinates - not self.frame.size. Here's my test:
- (void)drawRect:(CGRect)dirtyRect
{
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSaveGState(gc); {
CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor);
CGContextSetStrokeColorWithColor(gc, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(gc, 1);
static const CGFloat Radius = 8;
static const CGFloat PointWidth = 20;
CGRect rect = CGRectInset(self.bounds, .5, .5);
CGContextTranslateCTM(gc, rect.origin.x, rect.origin.x);
rect.origin = CGPointZero;
CGContextMoveToPoint(gc, 0, rect.size.height / 2);
CGContextAddLineToPoint(gc, PointWidth, rect.size.height);
CGContextAddLineToPoint(gc, rect.origin.x + rect.size.width - Radius,
rect.origin.y + rect.size.height);
CGContextAddArc(gc, rect.origin.x + rect.size.width - Radius,
rect.origin.y + rect.size.height - Radius, Radius, M_PI / 2, 0.0f, 1);
CGContextAddLineToPoint(gc, rect.origin.x + rect.size.width, rect.origin.y + Radius);
CGContextAddArc(gc, rect.origin.x + rect.size.width - Radius, rect.origin.y + Radius,
Radius, 0.0f, -M_PI / 2, 1);
CGContextAddLineToPoint(gc, PointWidth, 0);
CGContextAddLineToPoint(gc, 0, rect.size.height / 2);
CGContextClosePath(gc);
CGContextDrawPath(gc, kCGPathFillStroke);
} CGContextRestoreGState(gc);
}
Here's the result:
Core Graphics draws the stroke on the path edge, that is, half of the stroke width lies inside the path, and the other half, outside. Since you cannot draw outside of the view, half of the stroke is clipped by the context. You have to inset the path by half the stroke width.
The complete example:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetFillColorWithColor(context, [BUTTON_COLOR CGColor]);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(context, 1);
int radius = CORNER_RADIUS;
CGRect pathRect = CGRectInset(self.bounds, 0.5f, 0.5f);
CGContextMoveToPoint(context, CGRectGetMinX(pathRect), CGRectGetMidY(pathRect));
CGContextAddLineToPoint(context, CGRectGetMinX(pathRect) + POINT_WIDTH, CGRectGetMaxY(pathRect));
CGContextAddArc(context, CGRectGetMaxX(pathRect) - radius,
CGRectGetMaxY(pathRect) - radius, radius, M_PI / 2, 0.0f, 1);
CGContextAddArc(context, CGRectGetMaxX(pathRect) - radius, CGRectGetMinY(pathRect) + radius,
radius, 0.0f, -M_PI / 2, 1);
CGContextAddLineToPoint(context, CGRectGetMinX(pathRect) + POINT_WIDTH, CGRectGetMinY(pathRect));
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
Note that several redundant drawing commands are removed without affecting the result. Enjoy.

How do I get the last point of CGContext in Objective-C?

I'm working on a custom progress bar and I want to get the last point of CGContext because I want to add an image to current state.
This is my code:
-(void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGPoint center = CGPointMake(rect.size.width/2, rect.size.height/2);
float minSize = MIN(rect.size.width, rect.size.height);
float lineWidth = _strokeWidth;
if(lineWidth == -1.0) lineWidth = minSize*_strokeWidthRatio;
float radius = (minSize-lineWidth)/2;
float endAngle = M_PI*(self.value*2);
//what should i do here
//_pont.center = CGPointMake
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, center.x, center.y);
CGContextRotateCTM(ctx, -M_PI*0.5);
CGContextSetLineWidth(ctx, lineWidth);
CGContextSetLineCap(ctx, kCGLineCapRound);
// "Full" Background Circle:
CGContextBeginPath(ctx);
CGContextAddArc(ctx, 0, 0, radius, 0, 2*M_PI, 0);
CGContextSetStrokeColorWithColor(ctx, [_color colorWithAlphaComponent:0.1].CGColor);
CGContextStrokePath(ctx);
// Progress Arc:
CGContextBeginPath(ctx);
CGContextAddArc(ctx, 0, 0, radius, 0, endAngle, 0);
CGContextSetStrokeColorWithColor(ctx, [_color colorWithAlphaComponent:0.9].CGColor);
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);
}
You have to use CGContextGetPathCurrentPoint, but you have to call it before you clear the path by calling stroke or fill. So look at the following playground and see what the value returned by CGContextGetPathCurrentPoint return varies by when you call it.
import CoreGraphics
import UIKit
let lineWidth = CGFloat(10)
let rect = CGRectMake(0, 0, 100, 100)
let size = CGSizeMake(100, 100)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let ctx = UIGraphicsGetCurrentContext()
let center = CGPointMake(rect.size.width/2, rect.size.height/2);
let minSize = fmin(rect.size.width, rect.size.height);
let radius = (minSize-10)/2;
let endAngle = CGFloat(M_PI*(20.0*2))
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, center.x, center.y);
CGContextRotateCTM(ctx, CGFloat(-M_PI*0.5));
CGContextSetLineWidth(ctx, lineWidth);
CGContextSetLineCap(ctx, CGLineCap.Round);
// "Full" Background Circle:
CGContextBeginPath(ctx);
CGContextAddArc(ctx, 0, 0, radius, 0, CGFloat(2*M_PI), 0);
CGContextSetStrokeColorWithColor(ctx, UIColor.greenColor().CGColor);
var endPoint = CGContextGetPathCurrentPoint(ctx)
CGContextStrokePath(ctx);
endPoint = CGContextGetPathCurrentPoint(ctx)
// Progress Arc:
CGContextBeginPath(ctx);
CGContextAddArc(ctx, 0, 0, radius, 0, endAngle, 0);
endPoint = CGContextGetPathCurrentPoint(ctx)
CGContextSetStrokeColorWithColor(ctx, UIColor.redColor().CGColor);
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
It's all simple geometry.
With context transformation matrix from your drawRect: code, you should use:
CGPoint lastPoint = CGPointMake(radius * cos(endAngle), radius * sin(endAngle));
After reset or before setup CTM, you should use:
//Replace "what should i do here" comment with following code:
CGPoint lastPoint = CGPointZero;
lastPoint.x = center.x + radius * cos(endAngle - M_PI * 0.5);
lastPoint.y = center.y + radius * sin(endAngle - M_PI * 0.5);
Also, second solution will work outside of drawRect: implementation.

Draw circle with hole by using MKOverlayRenderer does not work well

I'd like to draw circle with hole (like donut) on mapview.
my code is here.
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CG ContextRef)context {
WPCircleOverlay * circleOverlay = self.overlay;
CGPoint centerPoint = [self pointForMapPoint:MKMapPointForCoordinate(circleOverlay.coordinate)];
CGFloat innerRadius = MKMapPointsPerMeterAtLatitude(circleOverlay.coordinate.latitude) * circleOverlay.innerRadius;
CGFloat outerRadius = MKMapPointsPerMeterAtLatitude(circleOverlay.coordinate.latitude) * circleOverlay.outerRadius;
CGMutablePathRef path = CGPathCreateMutable();
//CGPathMoveToPoint(path, ...);
CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, outerRadius, 0, 2 * M_PI, true);
CGPathCloseSubpath(path);
// Add the inner arc to the path (later used to substract the inner area)
CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, innerRadius, 0, 2 * M_PI, true);
CGPathCloseSubpath(path);
// Add the path to the context
CGContextAddPath(context, path);
CGContextSetFillColorWithColor(context, self.fillColor.CGColor);
CGContextEOFillPath(context);
CGPathRelease(path);
It works well in simulator, but on device it doesn't.
On device, outer circle is filled with color, and inner circle wasn't clipped.
How can I modify my code to work well on device?
I fixed it by using CGContextAddEllipseInRect method instead of CGContextAddArc.
WPCircleOverlay * circleOverlay = self.overlay;
CGRect rectForMapRect = [self rectForMapRect:mapRect];
CGPoint centerPoint = [self pointForMapPoint:MKMapPointForCoordinate(circleOverlay.coordinate)];
CGFloat innerRadius = MKMapPointsPerMeterAtLatitude(circleOverlay.coordinate.latitude) * circleOverlay.innerRadius;
CGFloat outerRadius = MKMapPointsPerMeterAtLatitude(circleOverlay.coordinate.latitude) * circleOverlay.outerRadius;
CGRect innerRect = CGRectMake(centerPoint.x - innerRadius, centerPoint.y - innerRadius, innerRadius * 2.0, innerRadius * 2.0);
CGRect outerRect = CGRectMake(centerPoint.x - outerRadius, centerPoint.y - outerRadius, outerRadius * 2.0, outerRadius * 2.0);
if (CGRectIntersectsRect(rectForMapRect, outerRect)) {
CGContextAddRect(context, rectForMapRect);
CGContextSaveGState(context);
CGContextClip(context);
CGContextAddEllipseInRect(context, outerRect);
CGContextAddEllipseInRect(context, innerRect);
CGContextSaveGState(context);
CGContextEOClip(context);
UIColor * color = [self.fillColor copy];
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, outerRect);
CGContextRestoreGState(context);
CGContextRestoreGState(context);
UIGraphicsPopContext();
}

Animated pie chart in iOS

I'm trying to create an animated pie chart in iOS that acts basically like this:
In a nutshell, it starts as a grey circle, and as the animation progresses the arrow moves around the circle until it gets to the percentage I specify.
I've used the sample code that Zachary Waldowski posted in this SO question:
Animated CAShapeLayer Pie
That's gotten me to the point where I can create the basic animation. The maroon pie piece grows to hit the correct size. What I'm struggling with is how to map the arrow to the animation so that it gets dragged along as the pie piece grows.
Any thoughts on how I can accomplish this?
Okay, I've found the solution.
Building on the work Zachary Waldowski created (see my original post), I was able to do the entire thing in a CALayer.
In a nutshell, I draw one outer circle in maroon, a smaller circle in light gray, a stroked path in white, then draw the triangle for the tip of the arrow by hand.
Here's the relevant section of code that does the magic:
- (void)drawInContext:(CGContextRef)context {
CGRect circleRect = CGRectInset(self.bounds, 1, 1);
CGFloat startAngle = -M_PI / 2;
CGFloat endAngle = self.progress * 2 * M_PI + startAngle;
CGColorRef outerPieColor = [[UIColor colorWithRed: 137.0 / 255.0 green: 12.0 / 255.0 blue: 88.0 / 255.0 alpha: 1.0] CGColor];
CGColorRef innerPieColor = [[UIColor colorWithRed: 235.0 / 255.0 green: 214.0 / 255.0 blue: 227.0 / 255.0 alpha: 1.0] CGColor];
CGColorRef arrowColor = [[UIColor whiteColor] CGColor];
// Draw outer pie
CGFloat outerRadius = CGRectGetMidX(circleRect);
CGPoint center = CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect));
CGContextSetFillColorWithColor(context, outerPieColor);
CGContextMoveToPoint(context, center.x, center.y);
CGContextAddArc(context, center.x, center.y, outerRadius, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextFillPath(context);
// Draw inner pie
CGFloat innerRadius = CGRectGetMidX(circleRect) * 0.45;
CGContextSetFillColorWithColor(context, innerPieColor);
CGContextMoveToPoint(context, center.x, center.y);
CGContextAddArc(context, center.x, center.y, innerRadius, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextFillPath(context);
// Draw the White Line
CGFloat lineRadius = CGRectGetMidX(circleRect) * 0.72;
CGFloat arrowWidth = 0.35;
CGContextSetStrokeColorWithColor(context, arrowColor);
CGContextSetFillColorWithColor(context, arrowColor);
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, 16);
CGFloat lineEndAngle = ((endAngle - startAngle) >= arrowWidth) ? endAngle - arrowWidth : endAngle;
CGPathAddArc(path, NULL, center.x, center.y, lineRadius, startAngle, lineEndAngle, 0);
CGContextAddPath(context, path);
CGContextStrokePath(context);
// Draw the Triangle pointer
CGFloat arrowStartAngle = lineEndAngle - 0.01;
CGFloat arrowOuterRadius = CGRectGetMidX(circleRect) * 0.90;
CGFloat arrowInnerRadius = CGRectGetMidX(circleRect) * 0.54;
CGFloat arrowX = center.x + (arrowOuterRadius * cosf(arrowStartAngle));
CGFloat arrowY = center.y + (arrowOuterRadius * sinf(arrowStartAngle));
CGContextMoveToPoint (context, arrowX, arrowY); // top corner
arrowX = center.x + (arrowInnerRadius * cosf(arrowStartAngle));
arrowY = center.y + (arrowInnerRadius * sinf(arrowStartAngle));
CGContextAddLineToPoint(context, arrowX, arrowY); // bottom corner
arrowX = center.x + (lineRadius * cosf(endAngle));
arrowY = center.y + (lineRadius * sinf(endAngle));
CGContextAddLineToPoint(context, arrowX, arrowY); // point
CGContextClosePath(context);
CGContextFillPath(context);
[super drawInContext: context];
}
Have the pie with the arrow as an image.
In every step, draw this image, rotating it a little. Draw the gray pie over the image, choosing correct angles for the image rotation.

UITableViewCell left side color

I would like to set a color to the most left of my UITableViewCell, BUT I want it to fall withing the bounds of the cell.
Because I use a UITableViewStyleGrouped UITableView I would like the color to have a round corner for the first and last cell. How can I do this?
I currently have the color in the table, but thats it. This is my code:
UIView *theView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, cell.frame.size.height)];
[theView setBackgroundColor:[UIColor purpleColor]];
[cell.contentView addSubview:theView];
[[cell textLabel] setText:#"Some row"];
This is the result:
Best regards,
Paul Peelen
I solved it in the end using "CoreGraphics" as NR4TR suggested.
I created a new UIView and rewrote the drawRect function.
This is the result code:
- (void)drawRect:(CGRect)rect {
float radius = 10.0f;
CGContextRef context = UIGraphicsGetCurrentContext();
rect = CGRectInset(rect, 0.0f, 0.0f);
CGContextBeginPath(context);
CGContextMoveToPoint(context, CGRectGetMinX(rect) + radius, CGRectGetMinY(rect));
CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMinY(rect));
if (bottom)
{
CGContextAddArc(context, CGRectGetMinX(rect) + radius, CGRectGetMaxY(rect) - radius, radius, M_PI / 2, M_PI, 0);
}
else {
CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMaxY(rect));
}
CGContextAddLineToPoint(context, CGRectGetMinX(rect), CGRectGetMaxY(rect));
if (top)
{
CGContextAddArc(context, CGRectGetMinX(rect) + radius, CGRectGetMinY(rect) + radius, radius, M_PI, 3 * M_PI / 2, 0);
}
else {
CGContextAddLineToPoint(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
}
CGContextSetFillColorWithColor(context, theColor.CGColor);
CGContextClosePath(context);
CGContextFillPath(context);
}
The fastest and easiest way is to use images for the first and last cells.
There is a bit more sophisticated way - drawing with a help of Core Graphics
EDIT: your case is just a rectangle plus a quoter of ellipse (or just a pie slice).

Resources