Create UIView with triangle on top - ios

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

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:

Hexagonal image in object

I have used a scrollView in my page in order to display maximum 10 Images. Images are displayed in a hexagonal shape but not in the shape i want. Now it looks like current hex shape
but i need to show like this- hex shape i need
I am posting my code below. Please anyone can guide me.
-(CAShapeLayer*)ChangeShape:(UIView*)view
{
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.frame = view.bounds;
CGFloat width = view.frame.size.width;
CGFloat height = view.frame.size.height;
CGFloat hPadding = width * 1 / 8 / 2;
UIGraphicsBeginImageContext(view.frame.size);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(width/2, 0)];
[path addLineToPoint:CGPointMake(width - hPadding, height / 4)];
[path addLineToPoint:CGPointMake(width - hPadding, height * 3 / 4)];
[path addLineToPoint:CGPointMake(width / 2, height)];
[path addLineToPoint:CGPointMake(hPadding, height * 3 / 4)];
[path addLineToPoint:CGPointMake(hPadding, height / 4)];
[path closePath];
[path closePath];
[path fill];
[path stroke];
maskLayer.path = path.CGPath;
UIGraphicsEndImageContext();
return maskLayer;
}
Change your ChangeShape function like this
-(CAShapeLayer*)ChangeShape:(UIView*)view{
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.frame = view.bounds;
CGFloat width = view.frame.size.width;
CGFloat height = view.frame.size.height;
CGFloat hPadding = width * 1 / 8 / 2;
UIGraphicsBeginImageContext(view.frame.size);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, height / 2)];
[path addLineToPoint:CGPointMake(width / 4 , hPadding)];
[path addLineToPoint:CGPointMake(width * 3 / 4, hPadding)];
[path addLineToPoint:CGPointMake(width, height / 2)];
[path addLineToPoint:CGPointMake(width * 3 / 4, height - hPadding)];
[path addLineToPoint:CGPointMake(width / 4, height - hPadding)];
[path closePath];
[path fill];
[path stroke];
maskLayer.path = path.CGPath;
UIGraphicsEndImageContext();
return maskLayer;
}

How to create a crescent moon using bezier paths in iOS?

How to draw a closed crescent moon using bezier paths, when the stroke and fill are configurable? So car I can get once curve but haven't found a strategy to connect and draw the other curve.
If you want something better approximating an astronomically correct crescent (where the outer arc is 180 degrees), I might suggest something like:
CGFloat angle = M_PI_2 * 0.60; // how much of a crescent do you want (must be less than M_PI_2 and greater than zero)
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:TRUE];
[path addArcWithCenter:CGPointMake(center.x - radius * tan(angle), center.y) radius:radius / cosf(angle) startAngle:M_PI_2 - angle endAngle:angle - M_PI_2 clockwise:FALSE];
[path closePath];
That yields:
As you can see, the path is basically defined by two circular arcs, one clockwise, one counter clockwise. The specifics of the center, radius, and starting and ending angles of the inner arc is a matter of basic trigonometry (but the actual formulae will vary depending upon the precise desired look and feel). For example, in my code snippet, the angle in my code above is the blue angle in the following diagram, and I then calculate red triangle that will be used to determine the correct starting and ending angles and center for the inner arc.
Below, someone asked about rotating this counter clockwise 90 degrees like:
In Swift, that would be:
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: -.pi, endAngle: 0, clockwise: true)
path.addArc(withCenter: CGPoint(x: center.x, y: center.y + radius * tan(angle)), radius: radius / cos(angle), startAngle: -angle, endAngle: -.pi + angle, clockwise: false)
path.close()
The bezier path can be constructed from two arcs. In swift:
var big = UIBezierPath()
var angle: CGFloat = 5.0 // in radians
var rad : CGFloat = 50.0
big.addArcWithCenter(CGPointMake(0.0, 0.0), radius: rad, startAngle:-angle/2.0, endAngle: angle/2.0, clockwise: true)
big.addArcWithCenter(CGPointMake(rad * cos(angle/2.0), 0.0), radius: rad * sin(angle/2.0), startAngle: 3.14/2.0, endAngle: -3.14/2.0, clockwise: false)
big.closePath()
Will create a path that when drawn will look like:
Edit: Objective-C with fill and stroke with upper left corner in x,y = 100,100):
UIBezierPath *big = [UIBezierPath new];
CGFloat angle = 5.0; // in radians
CGFloat rad = 50.0;
CGFloat x= 100.0;
CGFloat y = 100.0;
[big addArcWithCenter:CGPointMake(x, y) radius:rad startAngle:-angle/2.0 endAngle:angle/2.0 clockwise:YES];
[big addArcWithCenter:CGPointMake(x + rad * cos(angle/2.0), y) radius: rad * sin(angle/2.0) startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:NO];
big.lineWidth = 2.0;
[[UIColor lightGrayColor] setFill];
[[UIColor darkGrayColor] setStroke];
[big closePath];
[big fill];
[big stroke];
For complex shape drawing, I could suggest you a great application called PaintCode. With it, you can import vector graphics and get the associated path formatted in form of a bezierPath. There is a trial available. Here is what I got after converting your provided shape into a SVG file:
UIBezierPath* bezierPath = UIBezierPath.bezierPath;
[bezierPath moveToPoint: CGPointMake(47.18, 5.32)];
[bezierPath addCurveToPoint: CGPointMake(67.96, 17.62) controlPoint1: CGPointMake(55.58, 7.5) controlPoint2: CGPointMake(61.06, 10.72)];
[bezierPath addCurveToPoint: CGPointMake(81.46, 49.5) controlPoint1: CGPointMake(77.56, 27.15) controlPoint2: CGPointMake(81.46, 36.45)];
[bezierPath addCurveToPoint: CGPointMake(67.96, 81.37) controlPoint1: CGPointMake(81.46, 62.55) controlPoint2: CGPointMake(77.56, 71.85)];
[bezierPath addCurveToPoint: CGPointMake(47.18, 93.67) controlPoint1: CGPointMake(61.06, 88.27) controlPoint2: CGPointMake(55.58, 91.5)];
[bezierPath addCurveToPoint: CGPointMake(24.98, 93.67) controlPoint1: CGPointMake(41.33, 95.17) controlPoint2: CGPointMake(30.83, 95.17)];
[bezierPath addCurveToPoint: CGPointMake(9.16, 86.32) controlPoint1: CGPointMake(19.81, 92.32) controlPoint2: CGPointMake(12.38, 88.87)];
[bezierPath addLineToPoint: CGPointMake(7.06, 84.52)];
[bezierPath addLineToPoint: CGPointMake(10.13, 80.47)];
[bezierPath addCurveToPoint: CGPointMake(19.13, 61.27) controlPoint1: CGPointMake(13.81, 75.52) controlPoint2: CGPointMake(18.01, 66.67)];
[bezierPath addCurveToPoint: CGPointMake(18.83, 38.25) controlPoint1: CGPointMake(20.33, 55.5) controlPoint2: CGPointMake(20.18, 43.42)];
[bezierPath addCurveToPoint: CGPointMake(9.98, 19.95) controlPoint1: CGPointMake(17.11, 31.57) controlPoint2: CGPointMake(14.03, 25.35)];
[bezierPath addLineToPoint: CGPointMake(6.31, 15.07)];
[bezierPath addLineToPoint: CGPointMake(8.78, 13.05)];
[bezierPath addCurveToPoint: CGPointMake(24.08, 5.55) controlPoint1: CGPointMake(12.16, 10.27) controlPoint2: CGPointMake(18.53, 7.12)];
[bezierPath addCurveToPoint: CGPointMake(47.18, 5.32) controlPoint1: CGPointMake(29.93, 3.82) controlPoint2: CGPointMake(41.11, 3.75)];
[bezierPath closePath];
self.shapeLayer = [CAShapeLayer new];
self.shapeLayer.path = bezierPath.CGPath;
self.shapeLayer.frame = CGRectMake(80, 80, 85, 98);
self.shapeLayer.strokeColor = [UIColor blueColor].CGColor;
self.shapeLayer.lineWidth = 4;
self.shapeLayer.fillColor = [UIColor lightGrayColor].CGColor;
[self.layer addSublayer:self.shapeLayer];
The result:

Drawing half-circles with CoreGraphics

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

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