lineWidth not rendering Bezier curve width correctly - ios

I am using lineWidth to create a circle with stroke width 4. I am using UIBezierPath for creating the circle. But, due to some reason, lineWidth is always rendering a circle with a thin stroke no matter what value I assign to lineWidth. I have also tried to put path.lineWidth = 100.0, but no change in stroke width.
This is my code:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(progressView.frame.size.width, progressView.frame.size.height), NO, 0.0);
[[UIColor colorWithRed:246.0/255.0 green:80.0/255.0 blue:36.0/255.0 alpha:1.0] setStroke];
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(progressView.frame.size.width/2 ,progressView.frame.size.height/2) radius:progressView.frame.size.width/2 - 5 startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(angle) clockwise:YES];
[path stroke];
path.lineWidth = 4;
UIImage* pathImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[progressView setImage:pathImg];
I googled a lot, but could not find any solution to this problem.

You need to set the stroke width before you actually stroke the path:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(progressView.frame.size.width, progressView.frame.size.height), NO, 0.0);
[[UIColor colorWithRed:246.0/255.0 green:80.0/255.0 blue:36.0/255.0 alpha:1.0] setStroke];
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(progressView.frame.size.width/2 ,progressView.frame.size.height/2) radius:progressView.frame.size.width/2 - 5 startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(angle) clockwise:YES];
path.lineWidth = 4;
[path stroke];
UIImage* pathImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[progressView setImage:pathImg];

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:

Changing a fill color on ellipse using Objective-C

I am overriding drawRect method in order to draw ellipse
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor mainColorYellow].CGColor);
CGRect rectangle = CGRectMake(0.0, 2.0, rect.size.width , rect.size.height - 4.0);
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
How can I change fill color on ellipse? I tried using CGContextSetFillColorWithColor(CGContextRef c, CGColorRef color) but without result.
There is two different operations, fill and stroke. Stroke es the border, fill is the inside. You are only doing the border. You can control the fill and border color independently. You have, however, to render both explicitly. For example:
[[UIColor yellowColor] setFill];
[[UIColor blackColor] setStroke];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0.0, 2.0, rect.size.width , rect.size.height - 4.0)];
path.lineWidth = 2.0;
[path fill];
[path stroke];
Notice I wrote the code in a more modern quartz. The context is implicit. Hope it helps.
Just add below:
CGContextFillEllipseInRect(context, rectangle);

Draw square and bullet arrow with core graphics

Is it possible to draw the following in objective-c? I tried using the image, but It pixelates. So I figured it is best to draw it programatically. Can someone provide me some sample code to achieve this.
Thank you.
Use a CAShapeLayerand assign it a path describing your shape:
- (UIBezierPath *)createBubblePathInFrame:(CGRect) frame{
CGFloat arrowInset = 10;
CGFloat arrowHeight = 10;
CGFloat arrowWidth = 20;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, arrowHeight)];
[path addLineToPoint:CGPointMake(arrowInset, arrowHeight)];
[path addLineToPoint:CGPointMake(arrowInset+arrowWidth/2, 0)];
[path addLineToPoint:CGPointMake(arrowInset+arrowWidth, arrowHeight)];
[path addLineToPoint:CGPointMake(frame.size.width, arrowHeight)];
[path addLineToPoint:CGPointMake(frame.size.width, frame.size.height)];
[path addLineToPoint:CGPointMake(0, frame.size.height)];
[path addLineToPoint:CGPointMake(0, arrowHeight)];
return path;
}
Use it like this:
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.frame = CGRectMake(50, 50, 200, 100);
shapelayer.path = [self createBubblePathInFrame:shapelayer.bounds].CGPath;
shapelayer.fillColor = [UIColor grayColor].CGColor;
shapelayer.strokeColor = [UIColor redColor].CGColor;
shapelayer.lineWidth = 1;
[self.view.layer addSublayer:shapelayer];
Or you can draw it yourself in drawRect:
[[UIColor grayColor] setFill];
[bezierPath fill];
[[UIColor redColor] setStroke];
[bezierPath stroke];
You can use this as a triangle view:
#implementation Triangle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor clearColor];
self.alpha = 0.75;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, CGRectGetMinX(rect), CGRectGetMinY(rect)); // top left
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMinY(rect)); // top right
CGContextAddLineToPoint(ctx, CGRectGetMidX(rect), CGRectGetMaxY(rect)); // bottom mid
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 25.0/255.0, 25.0/255.0, 25.0/255.0, 1.0);
CGContextFillPath(ctx);
}
#end
Then just subclass a UIView and add an instance of Triangle where appropriate (in your case on the top left corner).
For your purpose you also should rotate this triangle 180 degrees as the code above drwas a triangle that points downwards.

UIBezierPath's shadow: how to hide the stroke's shadow?

I'm trying to draw a UIBezierPathShape in iOS7 then apply a shadow. This works great except that when I stroke the path, the stroke shows up behind the shape. How can I rectify this?
Code:
- (void)drawDiamondWithCount:(NSUInteger)count inRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(ctx);
UIEdgeInsets insets = UIEdgeInsetsMake(cardEdgeInsetTop, cardEdgeInsetRight, cardEdgeInsetBottom, cardEdgeInsetLeft);
CGRect insetsRect = UIEdgeInsetsInsetRect(rect, insets);
CGFloat shapeHeight = insetsRect.size.height / (double) count;
CGRect shapeRect;
for (NSUInteger i = 0; i < count; ++i) {
// Get the rect for the single shape
int numRemainingShapes = count - i - 1;
CGFloat remainingBottomSpace = numRemainingShapes * shapeHeight;
insets = UIEdgeInsetsMake(i * shapeHeight + shapeEdgeInsets, 0, remainingBottomSpace + shapeEdgeInsets, 0);
shapeRect = UIEdgeInsetsInsetRect(insetsRect, insets);
UIBezierPath *path = [self getDiamondPath:shapeRect];
[[UIColor redColor] setFill];
[[UIColor blackColor] setStroke];
UIGraphicsPushContext(ctx);
CGContextSetShadow(ctx, CGSizeMake(5, 2), 5);
[path fill];
UIGraphicsPopContext();
//[path stroke];
}
UIGraphicsPopContext();
}
This gives me what I want, minus the stroke
Uncommenting [path stroke] gives me this. I want the stroke, but don't want to see it behind the shape.
I suspect that instead of UIGraphicsPushContext and UIGraphicsPopContext, I think you want CGContextSaveGState and CGContextRestoreGState:
// create context and configure
CGContextRef ctx = UIGraphicsGetCurrentContext();
[[UIColor redColor] setFill];
[[UIColor blackColor] setStroke];
// create path
UIBezierPath *path = ...;
path.lineJoinStyle = kCGLineJoinMiter;
path.lineWidth = 2.0;
// fill the center with shadow
CGContextSaveGState(ctx);
CGContextSetShadow(ctx, CGSizeMake(5, 2), 5);
[path fill];
CGContextRestoreGState(ctx);
// stroke border without shadow
CGContextSetLineWidth(ctx, 2.0);
[path stroke];
With UIGraphicsPushContext and UIGraphicsPopContext you get:
With CGContextSaveGState and CGContextRestoreGState you get:

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

Resources