How to fill a custom shape using UIBezierPath - ios

I want to create a custom shape into a UIView by drawing it myself using a UIBezierPath object and render it in drawRect:.
So far I managed to create the shape and stroke it so it is drawn on screen exactly how I want it to be. However I can't fill it properly. Here is the code so far:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.f);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineWidth:2.f];
[path setLineJoinStyle:kCGLineJoinRound];
// Stroke top left arc
//[path moveToPoint:CGPointMake(20.f, 2.f)];
[path addArcWithCenter:CGPointMake(10.f, 10.f) radius:5.f startAngle:3*M_PI/2 endAngle:M_PI clockwise:0];
// Stroke bottom left arc
[path addArcWithCenter:CGPointMake(10.f, 50.f) radius:5.f startAngle:M_PI endAngle:M_PI/2 clockwise:0];
// Stroke bottom line
[path moveToPoint:CGPointMake(10.f, 55.f)];
[path addLineToPoint:CGPointMake(60.f, 55.f)];
// Stroke bottom arc
[path moveToPoint:CGPointMake(80.f, 55.f)];
[path addArcWithCenter:CGPointMake(71.f, 65.f) radius:15.f startAngle:degreesToRadians(315) endAngle:degreesToRadians(215) clockwise:1];
// Stroke bottom right arc
[path moveToPoint:CGPointMake(80.f, 55.f)];
[path addArcWithCenter:CGPointMake(80.f, 50.f) radius:5.f startAngle:M_PI/2 endAngle:0 clockwise:0];
// Stroke top right arc
[path addArcWithCenter:CGPointMake(80.f, 10.f) radius:5.f startAngle:0 endAngle:3*M_PI/2 clockwise:0];
// Close path
[path moveToPoint:CGPointMake(80.f, 5.f)];
[path addLineToPoint:CGPointMake(10.f, 5.f)];
[path stroke];
[path fill];
}
Stroke works as expected, but the fill only fills portions on the path, instead of filling the entire path. Is there something I'm missing?
Thanks in advance!

Remove all your
- (void)moveToPoint:(CGPoint)point
methods except the first one because it is a continuous path you don't need it.
It is highly recommended to close the path by calling the method - (void)closePath because you are having 3 pixel gap.

I figured it out.
Just needed to get rid of moveToPoint: calls since they create subpaths and so the filling will only affect the new subpaths. And finally called closePath.
Now my path is continuous and the fill works for the entire shape.
Edited code:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineWidth:2.f];
[path setLineJoinStyle:kCGLineJoinRound];
// Stroke top left arc
[path addArcWithCenter:CGPointMake(10.f, 10.f) radius:5.f startAngle:3*M_PI/2 endAngle:M_PI clockwise:0];
// Stroke bottom left arc
[path addArcWithCenter:CGPointMake(10.f, 50.f) radius:5.f startAngle:M_PI endAngle:M_PI/2 clockwise:0];
// Stroke bottom arc
[path addArcWithCenter:CGPointMake(71.f, 65.f) radius:15.f startAngle:degreesToRadians(215) endAngle:degreesToRadians(315) clockwise:0];
// Stroke bottom right arc
[path addArcWithCenter:CGPointMake(80.f, 50.f) radius:5.f startAngle:M_PI/2 endAngle:0 clockwise:0];
// Stroke top right arc
[path addArcWithCenter:CGPointMake(80.f, 10.f) radius:5.f startAngle:0 endAngle:3*M_PI/2 clockwise:0];
// Close path
[path closePath];
[path fill];
[path stroke];
}

Simply call the method
- (void)closePath
at the end to close your UIBezierPath.

Related

Draw a triangle in a graph with dynamic values in iOS

I want to draw a triangle with dynamic values with base down and sharp edge up. How can I do that?
This should do it.
- (void)drawRect:(CGRect)rect {
//This set the line color
[[UIColor blueColor] setStroke];
[[UIColor redColor] setFill];
UIBezierPath *path = [UIBezierPath bezierPath];
//Starting point
[path moveToPoint:CGPointMake(0, 0)];
//Vertice one
[path addLineToPoint:CGPointMake(1, 1)];
//Vertice two
[path addLineToPoint:CGPointMake(0, 1)];
[path closePath];
//fill path with the defined color
[path fill];
//draw the stroke with the defined color
[path stroke];
}

iOS : bow ends thinner than the center

I'm drawing a curve between two points with a centerPoint, and I would like to have the bow ends thinner than the center part. I mean, I want different setLineWidth for the ends. Any idea?
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint p=CGPointMake(posXAbsolutaComienza, posYAbsolutaComienza);
[path moveToPoint:p];
CGPoint p2=CGPointMake(posXAbsolutaAcaba, posYAbsolutaAcaba);
CGPoint centerPoint=CGPointMake(posXAbsolutaIntermedio, posYAbsolutaIntermedio);
[path setLineWidth:grosorLinea];
[path addQuadCurveToPoint:p2 controlPoint:centerPoint];
[[UIColor blackColor] setStroke];
[path stroke];

Error when drawing a UIBezierPath. No current point

I am creating a UIBezierPath like this:
UIBezierPath *path = [UIBezierPath bezierPath];
[path addLineToPoint:CGPointMake(200.0, 40.0)];
[path addLineToPoint:CGPointMake(160, 140)];
[path addLineToPoint:CGPointMake(40.0, 140)];
[path addLineToPoint:CGPointMake(0.0, 40.0)];
[path closePath];
The problem is that when I try to draw my UIBezierPath, it does not appear and I get an error:
<Error>: void CGPathCloseSubpath(CGMutablePathRef): no current point.
My drawing code is very basic:
UIGraphicsBeginImageContext(self.view.bounds.size);
[[UIColor whiteColor] set];
[myPath stroke];
UIGraphicsEndImageContext();
What am I doing wrong?
The error message is clear. One line of code was missing when I was creating my UIBezierPath:
[path moveToPoint:CGPointMake(0, 0)];
I added it and everything worked like a charm.
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(160, 140)];
[path addLineToPoint:CGPointMake(40.0, 140)];
[path addLineToPoint:CGPointMake(0.0, 40.0)];
[path closePath]
It is interesting that the error happened when I was trying to draw the line and not before - when I created it.

Quartz and clipping area

I have a question about Quartz and clipping area:
I would like to have a rectangle A
inside this rectangle I would like to have a rectangle B
The filling of B dveve also cut in A: I would like that A was pierced by B. What is the best way to do this in quartz? I did not really understand how the clipping
If I understand you correctly, you want to draw a smaller rectangle inside a larger rectangle so that the inner rectangle is transparent. You can achieve this by drawing a CAShapeLayer with a path that contains both rectangles as subpaths. Don't forget to set the layer's fill rule to kCAFillRuleEvenOdd.
Try something like this:
CGRect rectA = CGRectMake(100, 100, 200, 200);
CGRect rectB = CGRectMake(150, 150, 100, 100);
UIBezierPath *path=[[UIBezierPath alloc] init];
// Add sub-path for rectA
[path moveToPoint:CGPointMake(rectA.origin.x, rectA.origin.y)];
[path addLineToPoint:CGPointMake(rectA.origin.x+rectA.size.width, rectA.origin.y)];
[path addLineToPoint:CGPointMake(rectA.origin.x+rectA.size.width, rectA.origin.y+rectA.size.height)];
[path addLineToPoint:CGPointMake(rectA.origin.x, rectA.origin.y+rectA.size.height)];
[path closePath];
// Add sub-path for rectB
[path moveToPoint:CGPointMake(rectB.origin.x, rectB.origin.y)];
[path addLineToPoint:CGPointMake(rectB.origin.x+rectB.size.width, rectB.origin.y)];
[path addLineToPoint:CGPointMake(rectB.origin.x+rectB.size.width, rectB.origin.y+rectB.size.height)];
[path addLineToPoint:CGPointMake(rectB.origin.x, rectB.origin.y+rectB.size.height)];
[path closePath];
// Create CAShapeLayer with this path
CAShapeLayer *pathLayer = [CAShapeLayer layer];
[pathLayer setFillRule:kCAFillRuleEvenOdd]; /* <- IMPORTANT! */
[pathLayer setPath:path.CGPath];
[pathLayer setFillColor:[UIColor blackColor].CGColor];
// Add the CAShapeLayer to a view
[someView.layer addSublayer:pathLayer];
I solved with this simple way:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0);
CGContextFillRect(context,self.bounds);
CGContextAddRect(context, self.bounds);
//Add cropped rectangle:
CGContextAddRect(context, _croppedRegion);
//Clip:
CGContextEOClip(context);
CGContextSetRGBFillColor(context, 255.0, 255.0, 255.0, 0.5);
CGContextFillRect(context, self.bounds);
}

Unexpected LineJoinStyle behavior when lines in path at 180 degrees

I'm getting a clipped LineJoin in a UIBezierPath when one line comes back exactly over the previous line. If I adjust the second line by one pixel, the LineJoin behaves as expected. Here's the code:
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineWidth:10.0f];
[path setLineCapStyle:kCGLineCapRound];
[path setLineJoinStyle:kCGLineJoinRound];
[path moveToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(200, 100)];
[path addLineToPoint:CGPointMake(150, 100)];
[path moveToPoint:CGPointMake(100, 120)];
[path addLineToPoint:CGPointMake(200, 120)];
[path addLineToPoint:CGPointMake(150, 121)];
[[UIColor redColor] setStroke];
[path stroke];
Here's what's displayed:
Is this a bug?
If not, is there some way to get the top path LineJoin to be rounded? (without fudging the points)
This came up when I made a UIBezierPath from 'touch-input', and while scribbling around sometimes this happened.
This is fixed in iOS 7.
Open Radar also updated.

Resources