Unexpected LineJoinStyle behavior when lines in path at 180 degrees - ios

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.

Related

CAShapeLayer mask doesn't show up

I'm making mask using UIBezierPath and CAShapeLayer with this code:
- (void)createPath {
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(100, 100)];
[path moveToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(0, 100)];
[path moveToPoint:CGPointMake(0, 100)];
[path addLineToPoint:CGPointMake(0, 0)];
[path closePath];
CAShapeLayer *layer = [CAShapeLayer new];
layer.frame = self.contentView.bounds;
layer.path = path.CGPath;
self.contentView.layer.mask = layer;
}
But instead of masking my contentView disappears completely. I tried to look at path in debugger and it looks exactly as I want it to look.
When using the layer.mask, the first is to get the right path. You don't need to move to a new point every time. By that way, your path is made of three or four subpaths which can not be closed to form a right path.
The second is trying to use in the View class itself, not to call for other subviews, like contentView. Because you may not know when to call it in subviews. Run the following in a UIView subclass, like in a UITableViewCell (awake from nib). You can get what I mean. If you really want to use contentView, just find the right position to put your layer code. like override setNeedLayout, etc.
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
[self createPath];
}
- (void)createPath {
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(100, 100)];
// [path moveToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(0, 100)];
// [path moveToPoint:CGPointMake(0, 100)];
[path addLineToPoint:CGPointMake(0, 0)];
[path closePath];
CAShapeLayer *layer = [CAShapeLayer new];
layer.frame = self.contentView.bounds;
layer.path = path.CGPath;
self.layer.mask = layer; // not self.contentView.layer.mask;
}

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];
}

How to fill a CGPathRef overlap like this?

I want to draw a single path, which can overlap itself. What I want is like a snap of Moves below:
Can I achieve this just fill a single CGPathRef on iOS?
I created two paths, a gray one and red one below:
There are two overlaps on the gray line, one for itself and one for the red line. The red overlap, you can see it clearly, is above (or below) the gray one. But the gray overlap is just merged.
The Moves trace line is a single line, maybe is not implemented by a single path fill or stroke.
Here's my code:
[[UIColor colorWithWhite:0.0 alpha:0.3] setStroke];
CGFloat lineWidth = 10.0;
UIBezierPath *path = [UIBezierPath bezierPath];
path.lineWidth = lineWidth;
[path moveToPoint:CGPointMake(300, 50)];
[path addLineToPoint:CGPointMake(20, 400)];
[path addLineToPoint:CGPointMake(300, 400)];
[path addLineToPoint:CGPointMake(20, 50)];
[path stroke];
[[UIColor colorWithRed:1.0 green:0.5 blue:0.5 alpha:0.5] setStroke];
UIBezierPath *anotherPath = [UIBezierPath bezierPath];
anotherPath.lineWidth = lineWidth;
[anotherPath moveToPoint:CGPointMake(160, 50)];
[anotherPath addLineToPoint:CGPointMake(300, 200)];
[anotherPath stroke];

How to fill a custom shape using UIBezierPath

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.

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.

Resources