Drawing half-circles with CoreGraphics - ios

I know how to draw lines and shapes with CGPaths . But I couldn't figure out how to draw this symbol for a circuit diagram
What code should I write to get that half circle shape?
This is what I have so far:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 5.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextMoveToPoint(context, 60, 80);
CGContextAddLineToPoint(context, 120, 80);
CGContextMoveToPoint(context, 120, 80);
CGContextAddArc(120,80,M_PI,M_PI/2);
CGContextMoveToPoint(context, 200, 80);
CGContextAddLineToPoint(context, 300, 80);
CGContextStrokePath(context);
}

You could create a UIBezierPath, e.g. something like:
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint point = CGPointMake(0, 50);
CGFloat radius = 15.0;
CGFloat lineLength = 25.0;
[path moveToPoint:point];
point.x += lineLength;
[path addLineToPoint:point];
point.x += radius;
[path addArcWithCenter:point radius:radius startAngle:M_PI endAngle:M_PI * 2 clockwise:YES];
point.x += radius * 2;
[path addArcWithCenter:point radius:radius startAngle:M_PI endAngle:M_PI * 2 clockwise:YES];
point.x += radius * 2;
[path addArcWithCenter:point radius:radius startAngle:M_PI endAngle:M_PI * 2 clockwise:YES];
point.x += radius * 2;
[path addArcWithCenter:point radius:radius startAngle:M_PI endAngle:M_PI * 2 clockwise:YES];
point.x += lineLength + radius;
[path addLineToPoint:point];
You could have your view controller just create a CAShapeLayer and add it to your view's layer.
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = [path CGPath];
layer.lineWidth = 2.0;
layer.fillColor = [[UIColor clearColor] CGColor];
layer.strokeColor = [[UIColor blackColor] CGColor];
[self.view.layer addSublayer:layer];
If you wanted to do it in the drawRect of a UIView subclass you could, stroke this path:
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint point = CGPointMake(0, 50);
CGFloat radius = 20.0;
CGFloat lineLength = 45.0;
[path moveToPoint:point];
point.x += lineLength;
[path addLineToPoint:point];
point.x += radius;
[path addArcWithCenter:point radius:radius startAngle:M_PI endAngle:M_PI * 2.0 clockwise:YES];
point.x += radius * 2.0;
[path addArcWithCenter:point radius:radius startAngle:M_PI endAngle:M_PI * 2.0 clockwise:YES];
point.x += radius * 2.0;
[path addArcWithCenter:point radius:radius startAngle:M_PI endAngle:M_PI * 2.0 clockwise:YES];
point.x += radius * 2.0;
[path addArcWithCenter:point radius:radius startAngle:M_PI endAngle:M_PI * 2.0 clockwise:YES];
point.x += lineLength + radius;
[path addLineToPoint:point];
path.lineWidth = 2.0;
[[UIColor blackColor] setStroke];
[[UIColor clearColor] setFill];
[path stroke];
}
Or, as your revised question suggests, if you're more comfortable with CoreGraphics, you could do that, too:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint point = CGPointMake(0, 50);
CGFloat radius = 20.0;
CGFloat lineLength = 45.0;
CGContextMoveToPoint(context, point.x, point.y);
point.x += lineLength;
CGContextAddLineToPoint(context, point.x, point.y);
point.x += radius;
CGContextAddArc(context, point.x, point.y, radius, M_PI, M_PI * 2.0, NO);
point.x += radius * 2.0;
CGContextAddArc(context, point.x, point.y, radius, M_PI, M_PI * 2.0, NO);
point.x += radius * 2.0;
CGContextAddArc(context, point.x, point.y, radius, M_PI, M_PI * 2.0, NO);
point.x += radius * 2.0;
CGContextAddArc(context, point.x, point.y, radius, M_PI, M_PI * 2.0, NO);
point.x += lineLength + radius;
CGContextAddLineToPoint(context, point.x, point.y);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(context, 2.0);
CGContextDrawPath(context, kCGPathStroke);
}

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:

Create UIView with triangle on top

In my app im currently using the following image, however, going forward I would like to create it programatically using a UIView so I can have more control over it.
I have tried the following but could not achieve the desired result: https://stackoverflow.com/a/4444039/2654425
Also is it more optimal (performance wise not time wise) to use .pngs like I am doing or programmatically create said views?
I did a similar thing with chat-style bubbles in UITableViewCells.
Here's the code I used to achieve the effect. You could easily adapt UIBezierPath to your needs:
- (void)drawBubbleForRect:(CGRect)rect {
CGRect bubbleRect = // Get your rect somehow, or just use rect
// The size of the "blip" in the side of the chat bubble (which points up for this bubble)
CGFloat blipWidth = 11.0f;
CGFloat blipHeight = 7.0f;
CGFloat blipLeft = CGRectGetMinX(bubbleRect) + blipWidth;
CGFloat blipMiddle = blipLeft + (blipWidth / 2.0f);
CGFloat blipRight = blipLeft + blipWidth;
CGFloat blipBottom = CGRectGetMinY(bubbleRect);
CGFloat blipTop = blipBottom - blipHeight;
CGFloat cornerRadius = 3.0f;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint: CGPointMake(CGRectGetMinX(bubbleRect) + cornerRadius, blipBottom)];
[path addLineToPoint: CGPointMake(blipLeft, blipBottom)];
[path addLineToPoint: CGPointMake(blipMiddle, blipTop)];
[path addLineToPoint: CGPointMake(blipRight, blipBottom)];
[path addLineToPoint: CGPointMake(CGRectGetMaxX(bubbleRect) - cornerRadius, blipBottom)];
[path addArcWithCenter: CGPointMake(CGRectGetMaxX(bubbleRect) - cornerRadius, blipBottom + cornerRadius) radius:cornerRadius startAngle:(3 * M_PI / 2) endAngle:0 clockwise:YES];
[path addLineToPoint: CGPointMake(CGRectGetMaxX(bubbleRect), CGRectGetMaxY(bubbleRect) - cornerRadius)];
[path addArcWithCenter: CGPointMake(CGRectGetMaxX(bubbleRect) - cornerRadius, CGRectGetMaxY(bubbleRect) - cornerRadius) radius:cornerRadius startAngle:0 endAngle:(M_PI / 2) clockwise:YES];
[path addLineToPoint: CGPointMake(CGRectGetMinX(bubbleRect) + cornerRadius, CGRectGetMaxY(bubbleRect))];
[path addArcWithCenter: CGPointMake(CGRectGetMinX(bubbleRect) + cornerRadius, CGRectGetMaxY(bubbleRect) - cornerRadius) radius:cornerRadius startAngle:(M_PI / 2) endAngle:M_PI clockwise:YES];
[path addLineToPoint: CGPointMake(CGRectGetMinX(bubbleRect), CGRectGetMinY(bubbleRect) + cornerRadius)];
[path addArcWithCenter: CGPointMake(CGRectGetMinX(bubbleRect) + cornerRadius, CGRectGetMinY(bubbleRect) + cornerRadius) radius:cornerRadius startAngle:M_PI endAngle:(3 * M_PI / 2) clockwise:YES];
[someColor setFill];
[path fill];
}

Objective C - Draw multiple arc within circle

I am trying to draw arc within a circle and fill the arc's. Here is the code i am using:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, rect);
CGContextSetFillColor(ctx, CGColorGetComponents([[clrArray objectAtIndex:0] CGColor]));
CGContextFillPath(ctx);
CGFloat radius = 12.5;
CGFloat startAngle1 = DegreesToRadians(0);
CGFloat endAngle1 = DegreesToRadians(135);
CGFloat startAngle2 = DegreesToRadians(135);
CGFloat endAngle2 = DegreesToRadians(270);
//draw arc
CGPoint center = CGPointMake(radius,radius);
//Arc 1
CGContextAddArc(ctx,
center.x,
center.y,
radius,
startAngle1,
endAngle1,
YES);
[(UIColor*)[clrArray objectAtIndex:1] set];
CGContextFillPath(ctx);
//Arc 2
CGContextAddArc(ctx,
center.x,
center.y,
radius,
startAngle2,
endAngle2,
YES);
[(UIColor*)[clrArray objectAtIndex:2] set];
CGContextFillPath(ctx);
It is not drawing right. If i only provide one arc then if its angle is less than 180 then it is also not daring right. What i am doing wrong?
Edit: Here is the image of what is happening
I used start angle 0 and end angle 45 in this image
Got another solution using bezier Path
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat radius = 50.0/2.0;
CGPoint center = CGPointMake(radius,radius);
CGFloat startAngle1 = DegreesToRadians(0);
CGFloat endAngle1 = DegreesToRadians(120);
CGFloat startAngle2 = DegreesToRadians(120);
CGFloat endAngle2 = DegreesToRadians(240);
CGContextSaveGState(ctx);
UIBezierPath* clip1 = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:startAngle1
endAngle:endAngle1
clockwise:YES];
[clip1 addLineToPoint:center];
[clip1 closePath];
[clip1 addClip];
UIBezierPath *arc1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 50, 50)];
[[UIColor blackColor] set];
[arc1 fill];
CGContextRestoreGState(ctx);
CGContextSaveGState(ctx);
UIBezierPath* clip2 = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:startAngle2
endAngle:endAngle2
clockwise:YES];
[clip2 addLineToPoint:center];
[clip2 closePath];
[clip2 addClip];
UIBezierPath *arc2 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 50, 50)];
[[UIColor orangeColor] set];
[arc2 fill];
CGContextRestoreGState(ctx);

How to draw a segmented circle in iOS?

I expected to find the answer to this question easily, but, unfortunately, my search produced no results. How to draw such a circle in iOS?
I ended up using UIBezierPath. This is how i draw the given circle:
CGFloat DegreesToRadians(CGFloat degrees)
{
return degrees * M_PI / 180;
};
- (void)drawRect:(CGRect)rect
{
CGFloat radius = 70;
CGPoint center = CGPointMake(90 + radius, 170 + radius);
CGFloat start = DegreesToRadians(-90), end;
NSArray *angles = #[#"0", #"60", #"-90"];
NSArray *colors = #[[UIColor yellowColor], [UIColor blueColor], [UIColor redColor]];
int col_counter = 0;
for (NSString *angle in angles)
{
CGFloat value = [angle floatValue];
end = DegreesToRadians(value);
CGPoint next;
next.x = center.x + radius * cos(start);
next.y = center.y + radius * sin(start);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:center];
[path addLineToPoint:next];
[path addArcWithCenter:center radius:radius startAngle:start endAngle:end clockwise:YES];
[path addLineToPoint:center];
UIColor *color = colors[col_counter];
[color set];
[path fill];
col_counter++;
start = end;
}
}
Using Andrey's answer I was able to add a short piece of code after the loop to add a white circle in the middle to create what looks like a white circle with a thick, multi-coloured border.
end = DegreesToRadians(360);
start = DegreesToRadians(-90);
int width = 10; // thickness of the circumference
radius -= width;
CGPoint next;
next.x = center.x + radius * cos(start);
next.y = center.y + radius * sin(start);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:center];
[path addLineToPoint:next];
[path addArcWithCenter:center radius:radius startAngle:start endAngle:end clockwise:YES];
[path addLineToPoint:center];
UIColor *color = [UIColor whiteColor];
[color set];
[path fill];

How can I make crisper images using quartz?

I'm trying to make high-resolution graphics dynamically. Unfortunately when I make arcs they look really fuzzy.
Here's my code
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];
CGPoint center = CGPointMake(bounds.size.width / 2.0, bounds.size.height / 2.0);
CGFloat lineWidth = 30.0;
CGFloat innerRadius = (bounds.size.width / 2.0) - lineWidth;
CGFloat outerRadius = innerRadius + lineWidth;
CGFloat startAngle = -((float)M_PI / 2.0);
CGFloat endAngle = ((self.percent / 100.0) * 2 * (float)M_PI) + startAngle;
UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
processBackgroundPath.lineWidth = lineWidth;
CGFloat radius = (self.bounds.size.width - lineWidth) / 2.0;
CGFloat fullAngle = (2.0 * (float)M_PI) + startAngle;
[processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:fullAngle clockwise:YES];
[[UIColor whiteColor] set];
[processBackgroundPath stroke];
CGMutablePathRef progressPath = CGPathCreateMutable();
CGPathMoveToPoint(progressPath, NULL, center.x, center.y - innerRadius);
CGPathAddArc(progressPath, NULL, center.x, center.y, innerRadius, startAngle, endAngle, YES);
CGPathAddArc(progressPath, NULL, center.x, center.y, outerRadius, endAngle, startAngle, NO);
CGPathCloseSubpath(progressPath);
UIColor *aColor = [UIColor colorWithRed:0.941 green:0.776 blue:0.216 alpha:1.0];
[aColor setFill];
CGContextAddPath(context, progressPath);
CGContextFillPath(context);
CGPathRelease(progressPath);
}
What do I need to do to make the donut more crisp?

Resources