Joined UIBezierPath stroke as a whole - ios

Being a beginner in iOS drawing I wanted to draw a small tooltip container which consists of a rectangle with an indicator arrow.
I created 2 UIBezierPaths and joined them with "appendPath".
I thought this was the right way to go about this, but after struggling for 1 day, I had to ask for help.
Here's the screenshot of where I am right now:
As you can see the problem is simply that when I try to stroke the joined path, it doesn't gets stroked as a whole, but as separate pieces.
Maybe someone could point me in the right direction to solve this ?
Here's the code:
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// create indicator path
UIBezierPath *indicator = [[UIBezierPath alloc] init];
// corner radius
CGFloat radius = self.cornerRadius;
// main path
UIBezierPath *path;
// format frame
rect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height - self.indicatorSize.height);
// assign path
path = [UIBezierPath bezierPathWithRoundedRect: rect
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(radius, radius)];
// x point
CGFloat x = (rect.size.width - self.indicatorSize.width) / 2;
[indicator moveToPoint:CGPointMake(x, rect.size.height)];
[indicator addLineToPoint:CGPointMake(x + self.indicatorSize.width / 2, rect.size.height + self.indicatorSize.height)];
[indicator addLineToPoint:CGPointMake(x + self.indicatorSize.width, rect.size.height)];
// close path
[indicator closePath];
// append indicator to main path
[path appendPath:indicator];
[[UIColor redColor] setStroke];
[path setLineWidth:2.0];
[path stroke];
[self.fillColor setFill];
[path fill];
// release indicator
[indicator release];
}
Thanks in advance

You need to draw it all as one path, because at the moment you're telling it you want to draw a whole rectangle, and then a triangle, and that's what it's doing.
From the Apple UIBezierPath documentation:
[appendPath:] adds the commands used to create the path in bezierPath to the end of the receiver’s path. This method does not explicitly try to connect the subpaths in the two objects, although the operations in bezierPath might still cause that effect.
So first it will draw your rectangle, and then it will draw your triangle without attempting to join them

Related

UIBezierPath not drawing arc

I'm trying to draw a very simple circle. Here is my code:
CameraButtonView *buttonView = [[CameraButtonView alloc]initWithFrame:CGRectMake((self.view.frame.size.width / 2) - 45, self.view.frame.size.height * 0.8, 90, 90)];
buttonView.layer.cornerRadius = buttonView.frame.size.width / 2;
buttonView.layer.borderWidth = 2;
buttonView.backgroundColor = [UIColor clearColor];
buttonView.layer.borderColor = [UIColor greenColor].CGColor;
buttonView.clipsToBounds = YES;
[previewView addSubview:buttonView];
then, in drawRect:
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 addArcWithCenter:self.center radius:(20) startAngle:0 endAngle:6.28 clockwise:YES];
[path1 setLineWidth:6];
[[UIColor redColor] setStroke];
[path1 stroke];
I have set the center and radius, specified a line width and stroke color, and called stroke. I have set breakpoints in drawRect to make sure the path is not nil, and it is indeed not. Just nothing is drawn.
The only thing I see on screen is a green circle from the code in the view controller where I set the corner radius. I tried calling [buttonView setNeedsDisplay]; as well, and that did not help. I've tried different initializations of the bezier path as well, such as initializing with the arc instead of creating the path and then calling addArcWithCenter.
what am I doing wrong?
Pay attention that a view center is not the center of the view itself, but is the center position of the view in superview coordinates. Probably you are drawing out of screen.
The center is specified within the coordinate system of its superview
and is measured in points. Setting this property changes the values of
the frame properties accordingly.
If you want to know the center of your view do this:
CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds))

UIBezierPath with multiples arcs

Hiiii
I'm trying to draw in a view an arc with dynamic width.
I have built an ARRAY with several UIBezierPath with arcs then I draw one after the other. this is the code:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGFloat radious;
if (self.bounds.size.width < self.bounds.size.height) {
radious = self.bounds.size.width / 2;
}
else{
radious = self.bounds.size.height / 2;
}
float oneGradeInRadians = (float)(2 * M_PI) / 360.f;
float radiandToDraw = (float)self.finalAngleRadians - self.initialAngleRadians;
float splitMeasure = oneGradeInRadians;
int numberOfArcs = radiandToDraw / splitMeasure;
NSMutableArray *arrayOfBeziersPaths = [NSMutableArray arrayWithCapacity:numberOfArcs];
for (int i = 0; i < numberOfArcs; i++) {
float startAngle = self.initialAngleRadians + (i * splitMeasure);
float endAngle = self.initialAngleRadians +((i + 1) * splitMeasure);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2) radius:radious - self.widthLine.floatValue startAngle:startAngle endAngle:endAngle clockwise:YES];
bezierPath.lineWidth = self.widthLine.floatValue + i/20;
float hue = (float)(i / 3.f);
UIColor *color = [UIColor colorWithHue:hue/360.0 saturation:1 brightness:1 alpha:1];
[color setStroke];
[arrayOfBeziersPaths addObject:bezierPath];
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineJoin(context, kCGLineJoinMiter);
//We saved the context
CGContextSaveGState(context);
for (int i = 0; i < numberOfArcs; i++) {
[(UIBezierPath *)[arrayOfBeziersPaths objectAtIndex:i] stroke];
[(UIBezierPath *)[arrayOfBeziersPaths objectAtIndex:i] fill];
}
//Restore the contex
CGContextRestoreGState(context);
}
In this way I get the right shape but, also I get an annoying lines between the arcs:
I think the problem may be a property to change between arcs, but I'm totally lost, or maybe there is another better way to build this.
I tried creating several UIBezierPath paths and I added them to an unique UIBezierPath, then I stoke and fill that path. I didn't get the annoying lines, but the problem is I can not modify the line width, so I can not get the same effect.
Any idea? thanks
dont create many such paths. just create two arcs (inner circle, exterior circle) and two straight lines joining the ends. then fill the path.
Add below code in a viewDidLoad of an empty viewController and check.
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat k =0.5522847498;
UIBezierPath *path =[UIBezierPath bezierPath];
CGFloat radius=130;
[path addArcWithCenter:CGPointMake(0, 0) radius:radius startAngle:M_PI_2 endAngle:M_PI clockwise:NO];
CGFloat start=10;
CGFloat increment=5;
[path addLineToPoint:CGPointMake(-radius-start, 0)];
[path addCurveToPoint:CGPointMake(0, -radius-start-increment) controlPoint1:CGPointMake(-radius-start, -(radius +start)*k) controlPoint2:CGPointMake(-(radius+start)*k, -radius-start-increment)];
[path addCurveToPoint:CGPointMake(radius+start+2*increment, 0) controlPoint1:CGPointMake((radius+start+increment)*k, -radius-start-increment) controlPoint2:CGPointMake(radius+start+2*increment, (-radius-start-increment)*k)];
[path addCurveToPoint:CGPointMake(0, radius+start+3*increment) controlPoint1:CGPointMake(radius+start+2*increment, (radius+start+2*increment)*k) controlPoint2:CGPointMake((radius+start+2*increment)*k,radius+start+3*increment)];
[path addLineToPoint:CGPointMake(0,radius)];
CAShapeLayer *layer =[CAShapeLayer layer];
[layer setFrame:CGRectMake(150, 200, 300, 300)];
[layer setFillColor:[UIColor greenColor].CGColor];
[layer setStrokeColor:[UIColor blackColor].CGColor];
[layer setPath:path.CGPath];
[self.view.layer addSublayer:layer];
}
It produced result like,
The problem here is that you’ve chosen a crazy approach to drawing the shape you’re after. If you take a look at it, you’ll see that it’s actually a filled region bounded on the outside by a circular arc, and on the inner edge by a spiral.
What you should do, therefore, is create a single NSBezierPath, add the outer circular arc to it, then add a line to the start of your spiral, append a spiral (you’ll want to approximate it with Bézier segments) and finally call -closePath. After that, you can -fill the path and you’ll get a good looking result rather than the mess you have above.

UIBezierPaths Not Showing in CALayer drawLayer inContext

I am using the code below to draw an arc into the drawLayer method of a custom CALayer class but nothing is displayed:
(void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
float r = self.bounds.size.width/2;
CGContextClearRect(ctx, self.bounds); // clear layer
CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor);
//CGContextFillRect(ctx, layer.bounds);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(0, 0) radius:r startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:NO];
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
}
Note that if I uncomment the CGContextFillRect(ctx, layer.bounds) line, a rectangle is properly rendered.
The problem I can see is that you don't set a stroke color (but a fill color)
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
When you configure a fill color it is used to fill the path. Same for a stroke color.
If you want to only want to either stroke or fill the path then you should use either CGContextStrokePath or CGContextFillPath but if you want to do both then you should use CGContextDrawPath with kCGPathFillStroke as the "drawing mode".
Thanks to both of you. I finally decided to draw the arc outside the drawLayer method using the following code:
- (id) initWithLayer:(id)layer withRadius:(float)radius
{
self = [super initWithLayer:layer];
// ...
self.strokeColor = [UIColor whiteColor].CGColor;
self.lineWidth = 10.0;
return self;
}
- (void) update:(float)progress
{
// ....
UIBezierPath *arcPath = [UIBezierPath bezierPath];
[arcPath addArcWithCenter:CGPointMake(r, r) radius:r - self.lineWidth/2 startAngle:startAngle endAngle:nextAngle clockwise:YES];
self.path = arcPath.CGPath;
}
It looks like all the arc drawing methods for both CGPath and UIBezierPath have a bug on 64 bit devices where they only work if the clockwise parameter is set to YES. I notice that your non-working code shows clockwise:NO and the working code has clockwise:YES.

UIBezierPaths are not kept on screen

I have a custom UIView and in the drawRect method I create a UIBezierPath and render it to the screen. The issue I am having is that the previous UIBezierPath is removed from the view on the next drawRect call?
How can I keep all of these UIBezierPaths on screen?
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
// Move to centre and draw an arc.
[path moveToPoint:self.center];
[path addArcWithCenter:self.center
radius:self.radius
startAngle:self.startAngle
endAngle:self.endAngle
clockwise:YES];
[path closePath];
path.usesEvenOddFillRule = YES;
[self.colorToRender setFill];
[path fill];
}
Whenever drawRect: is called you have to draw the whole screen of content. You can't append new drawings to what's already there. So you need to store the whole list of beziers and draw all of them.
Store them in an array and then loop over the array and draw each.
Also, you shouldn't really be creating new beziers in drawRect:...
I am not sure if this is the most convenient way but you have to keep reference to old paths and then combine the new one with them.
CAShapeLayer * shapeLayer; //Sublayer of your view
CGMutablePathRef combinedPath = CGPathCreateMutableCopy(shapeLayer.path);
CGMutablePathRef linePath = CGPathCreateMutable();
//Create your own custom linePath here
//No paths drawn before
if(combinedPath == NULL)
{
combinedPath = linePath;
}
else
{
CGPathAddPath(combinedPath, NULL, linePath);
}
shapeLayer.path = combinedPath;
CGPathRelease(linePath);

UIBezierPath and applytransform

I am trying to implement a custom UIView which is basically a pie menu (something like a cake divided into slices).
For that I am trying to draw a circle and a series of lines from the center, like the rays in a chart's wheel.
I have successfully drawn the circle and and I would now like to draw the lines dividing the circle in slices.
This is what I have so far:
-(void)drawRect:(CGRect)rect{
[[UIColor blackColor] setStroke];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat minDim = (rect.size.width < rect.size.height) ? rect.size.width : rect.size.height;
CGRect circleRect = CGRectMake(0, rect.size.height/2-minDim/2, minDim, minDim);
CGContextAddEllipseInRect(ctx, circleRect);
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor yellowColor] CGColor]));
CGContextFillPath(ctx);
CGPoint start = CGPointMake(0, rect.size.height/2);
CGPoint end = CGPointMake(rect.size.width, rect.size.height/2);
for (int i = 0; i < MaxSlices(6); i++){
CGFloat degrees = 1.0*i*(180/MaxSlices(6));
CGAffineTransform rot = CGAffineTransformMakeRotation(degreesToRadians(degrees));
UIBezierPath *path = [self pathFrom:start to:end];
[path applyTransform:rot];
}
}
- (UIBezierPath *) pathFrom:(CGPoint) start to:(CGPoint) end{
UIBezierPath* aPath = [UIBezierPath bezierPath];
aPath.lineWidth = 5;
[aPath moveToPoint:start];
[aPath addLineToPoint:end];
[aPath closePath];
[aPath stroke];
return aPath;
}
The problem is that applyTransform on the path doesn't seem to do anything. The first path gest drawn correctly the and the following ones are unaffected by the rotation. Basically what I see is just one path. Check the screen shot here http://img837.imageshack.us/img837/9757/iossimulatorscreenshotf.png
Thank you for your help!
You're drawing the path (with stroke) before you transform it. A path is just a mathematical representation. It isn't the line "on the screen." You can't move what you've already drawn by modifying the data about it.
Just move the [aPath stroke] out of pathFrom:to: and put it after the applyTransform:.

Resources