How to achieve my drawing using a bezier path? - ios

I would like to get something like this:
I guess one way to do it would be to draw my rectangle with the two rounded angle in a subpath. Then add another subpass to draw the arrow.
My question is: is there a simpler way to draw the arrow than drawing 4 lines and some curves? I tried doing it by using 2 lines and bezierPath.lineJoinStyle = kCGLineJoinRound but when I fill it, I get a triangle.
This is the code I get from PaintCode when I draw the path.
//// Rectangle Drawing
var rectanglePath = UIBezierPath()
rectanglePath.moveToPoint(CGPointMake(26.71, 14))
rectanglePath.addLineToPoint(CGPointMake(85.29, 14))
rectanglePath.addCurveToPoint(CGPointMake(90.18, 14.39), controlPoint1: CGPointMake(87.8, 14), controlPoint2: CGPointMake(89.05, 14))
rectanglePath.addLineToPoint(CGPointMake(90.4, 14.45))
rectanglePath.addCurveToPoint(CGPointMake(93.57, 17.79), controlPoint1: CGPointMake(91.87, 15.01), controlPoint2: CGPointMake(93.04, 16.24))
rectanglePath.addCurveToPoint(CGPointMake(94, 23.17), controlPoint1: CGPointMake(94, 19.21), controlPoint2: CGPointMake(94, 20.53))
rectanglePath.addLineToPoint(CGPointMake(94, 54))
rectanglePath.addLineToPoint(CGPointMake(18, 54))
rectanglePath.addLineToPoint(CGPointMake(18, 23.17))
rectanglePath.addCurveToPoint(CGPointMake(18.37, 18.02), controlPoint1: CGPointMake(18, 20.53), controlPoint2: CGPointMake(18, 19.21))
rectanglePath.addLineToPoint(CGPointMake(18.43, 17.79))
rectanglePath.addCurveToPoint(CGPointMake(21.6, 14.45), controlPoint1: CGPointMake(18.96, 16.24), controlPoint2: CGPointMake(20.13, 15.01))
rectanglePath.addCurveToPoint(CGPointMake(26.71, 14), controlPoint1: CGPointMake(22.95, 14), controlPoint2: CGPointMake(24.2, 14))
rectanglePath.closePath()
UIColor.grayColor().setFill()
rectanglePath.fill()
//// Bezier Drawing
var bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(31.5, 37.5))
bezierPath.addLineToPoint(CGPointMake(56.5, 30.5))
bezierPath.addLineToPoint(CGPointMake(80.5, 37.5))
bezierPath.addLineToPoint(CGPointMake(80.5, 37.5))
bezierPath.addLineToPoint(CGPointMake(56.5, 30.5))
bezierPath.addLineToPoint(CGPointMake(31.5, 37.5))
bezierPath.addLineToPoint(CGPointMake(31.5, 37.5))
bezierPath.closePath()
bezierPath.lineJoinStyle = kCGLineJoinRound;
UIColor.grayColor().setFill()
bezierPath.fill()
UIColor.whiteColor().setStroke()
bezierPath.lineWidth = 5
bezierPath.stroke()
UPDATE:
The arrow is actually some "void" drawing. It's the shape of an arrow but there is nothing inside (we can see through it)

Here's some Core Graphics code that makes a shape like what you're looking for. You would have to translate it into the equivalent BezierPath commands, but that shouldn't be too difficult. You'll of course also have to tweak the coordinates and the colors to your preferences for the size and color. As you can see, it consists of two line shapes with the CGContextSetLineCap command used to round out the ends of each the two line shapes:
CGContextRef ctx = UIGraphicsGetCurrentContext(); // iOS
/* Line Shape 1 */
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathMoveToPoint(pathRef, NULL, 137, 192);
CGPathAddLineToPoint(pathRef, NULL, 300, 145);
CGContextSetLineWidth(ctx, 30);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetRGBStrokeColor(ctx, 0.129, 0.992, 1, 1);
CGContextAddPath(ctx, pathRef);
CGContextStrokePath(ctx);
CGPathRelease(pathRef);
/* Line Shape 2 */
CGMutablePathRef pathRef2 = CGPathCreateMutable();
CGPathMoveToPoint(pathRef2, NULL, 463, 192);
CGPathAddLineToPoint(pathRef2, NULL, 300, 145);
CGContextSetLineWidth(ctx, 30);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetRGBStrokeColor(ctx, 0.129, 0.992, 1, 1);
CGContextAddPath(ctx, pathRef2);
CGContextStrokePath(ctx);
CGPathRelease(pathRef2);

Related

Drawing a line with half a circle using UIBezierPath

I'm using this piece of code to draw a straight line using UIBezierPath class as follows:
let myPath = UIBezierPath()
myPath.move(to: CGPoint(x:10, y:5))
myPath.addLine(to: CGPoint(x:100, y:5))
myPath.close()
UIColor.blue.set()
myPath.stroke()
myPath.fill()
however, I don't know how to change this basic drawing to include half a circle in the path to be as follows:
From: addArc documentation
myPath.addArc(withCenter: CGPoint(x: 55, y: 5),
radius: 10,
startAngle: 0,
endAngle: CGFloat.pi,
clockwise: false)
This should give you about the circle you want. You will also need to break your line into 2 segments.

Dynamic UIBezierPath for Animations

I want to create an animation where an object animates its position along a path. The trouble I am having is making this path dynamic meaning that it works on all screen sizes.
The spaceship in the gif above follows a "U" shape path initially.
I use this "U" shape path and it works, here is the code:
func animateAlongPath() {
// Create and add image to view
let myImage = UIImage(named: "myImage")!
let myImageView = UIImageView(image: myImage)
myImageView.frame = CGRect(x: 100, y: 100, width: myImage.size.width, height: myImage.size.height)
holderView.addSubview(myImageView)
// Create "U" shape path for animation (path code created in PaintCode)
let path = UIBezierPath()
path.moveToPoint(CGPointMake(59.5, 81.5))
path.addCurveToPoint(CGPointMake(66.5, 91.5), controlPoint1: CGPointMake(63.64, 84.13), controlPoint2: CGPointMake(62.62, 85.9))
path.addCurveToPoint(CGPointMake(81.5, 110.5), controlPoint1: CGPointMake(72.31, 99.88), controlPoint2: CGPointMake(75.91, 103.08))
path.addCurveToPoint(CGPointMake(109.5, 142.5), controlPoint1: CGPointMake(88.44, 119.71), controlPoint2: CGPointMake(102.39, 135.23))
path.addCurveToPoint(CGPointMake(145.5, 156.5), controlPoint1: CGPointMake(122.47, 155.76), controlPoint2: CGPointMake(129.15, 157.54))
path.addCurveToPoint(CGPointMake(237.5, 81.5), controlPoint1: CGPointMake(185.04, 153.99), controlPoint2: CGPointMake(237.5, 81.5))
let pathAnimation = CAKeyframeAnimation(keyPath: "position")
pathAnimation.path = path.CGPath // Use "U" shape path for animation
pathAnimation.calculationMode = kCAAnimationPaced
pathAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
pathAnimation.duration = 1
pathAnimation.removedOnCompletion = false
pathAnimation.fillMode = kCAFillModeForwards
myImageView.layer.addAnimation(pathAnimation, forKey: nil)
myImageView.layer.position = path.currentPoint
}
The problem is that the UIBezierPath is not dynamic. It works great on the iPhone 6 device screen size, but does not work great on any other device size since it is static. I tried using PaintCode to create dynamic paths with variables, but just can't get it to work.
Any ideas?
PaintCode can generate code for resizable UIBezierPaths using Frames.
Put the Bezier inside a Frame object and then set desired autoresizing springs (all flexible). The resulting code will be parametrized with CGRect/NSRect and will use relative coefficients to calculate bezier points.
There’s also a video tutorial about Dynamic Shapes if you want to get more into depth.

Inserting PaintCode code into UIImageView in Swift

I am experimenting with PaintCode. As an example, I've imported an abstract Illustrator drawing file, which has produced the below bezier Swift code.
On my storyboard I have an UIImageView named exampleImageView that I want to display the PaintCode code and have the ability to scale the resulting image like a vector graphic at different sizes, e.g. 1x, 2.5x 5x etc (i.e. smooth non pixellated lines and curves).
Questions:
1 - How do I display the below code as an image in an UIImageView named exampleImageView in Xcode?
2 - How do I display the below code in an UIView named exampleView in Xcode?
3 - Is it possible to scale up or down the below code (i.e. like a vector graphic), and if so, how in Xcode?
Expected image:
PaintCode code:
//// General Declarations
let context = UIGraphicsGetCurrentContext()
//// Color Declarations
let fillColor = UIColor(red: 0.087, green: 0.086, blue: 0.083, alpha: 1.000)
//// Group 2
CGContextSaveGState(context)
CGContextSetAlpha(context, 0.75)
CGContextBeginTransparencyLayer(context, nil)
//// Bezier Drawing
let bezierPath = UIBezierPath()
bezierPath.moveToPoint(CGPointMake(26.08, 23.65))
bezierPath.addCurveToPoint(CGPointMake(61.71, 12.5), controlPoint1: CGPointMake(38.43, 23.65), controlPoint2: CGPointMake(54.42, 18.61))
bezierPath.addCurveToPoint(CGPointMake(52.65, 1.35), controlPoint1: CGPointMake(69, 6.38), controlPoint2: CGPointMake(64.99, 1.35))
bezierPath.addCurveToPoint(CGPointMake(16.91, 12.5), controlPoint1: CGPointMake(40.2, 1.35), controlPoint2: CGPointMake(24.2, 6.38))
bezierPath.addCurveToPoint(CGPointMake(26.08, 23.65), controlPoint1: CGPointMake(9.63, 18.61), controlPoint2: CGPointMake(13.63, 23.65))
bezierPath.closePath()
bezierPath.moveToPoint(CGPointMake(48.14, 25))
bezierPath.addLineToPoint(CGPointMake(0.79, 25))
bezierPath.addCurveToPoint(CGPointMake(0.24, 24.35), controlPoint1: CGPointMake(-0.02, 25), controlPoint2: CGPointMake(-0.21, 24.73))
bezierPath.addCurveToPoint(CGPointMake(2.41, 23.65), controlPoint1: CGPointMake(0.76, 23.92), controlPoint2: CGPointMake(1.59, 23.65))
bezierPath.moveToPoint(CGPointMake(14.36, 12.5))
bezierPath.addCurveToPoint(CGPointMake(54.26, 0), controlPoint1: CGPointMake(22.62, 5.57), controlPoint2: CGPointMake(40.59, 0))
bezierPath.addCurveToPoint(CGPointMake(64.36, 12.5), controlPoint1: CGPointMake(67.93, 0), controlPoint2: CGPointMake(72.62, 5.57))
bezierPath.addCurveToPoint(CGPointMake(37.3, 23.65), controlPoint1: CGPointMake(58.56, 17.37), controlPoint2: CGPointMake(47.81, 21.59))
bezierPath.addLineToPoint(CGPointMake(49.75, 23.65))
fillColor.setFill()
bezierPath.fill()
CGContextEndTransparencyLayer(context)
CGContextRestoreGState(context)
1 - How do I display the below code as an image in an UIImageView named exampleImageView?
Image can be created in PaintCode, but I don't think it is the right fit here. For starters create a drawing method in PaintCode.
2 - Is it possible to scale up or down the below code (i.e. like a vector graphic), and if so, how?
In PaintCode you can make the thing resizable by using frames.
Look at the Dynamic Shapes tutorial that addresses both.

Why Are Core Graphics Lines Not Aligned?

Quick question: This code should produce (among others) 4 lines that are perpendicular to each other. However, when run, the lines are all off by a small amount.
for i in 0..<60 {
CGContextSaveGState(ctx)
CGContextTranslateCTM(ctx, rect.width / 2, rect.height / 2)
CGContextRotateCTM(ctx, CGFloat(6.0 * Double(i) * M_PI / 180))
CGContextSetStrokeColorWithColor(ctx, UIColor.grayColor().CGColor)
CGContextMoveToPoint(ctx, 50, 50)
if (i % 5 == 0) {
CGContextSetLineWidth(ctx, 3.0)
CGContextAddLineToPoint(ctx, 30, 30)
}
else {
CGContextSetLineWidth(ctx, 2.0)
CGContextAddLineToPoint(ctx, 40, 40)
}
CGContextStrokePath(ctx)
CGContextRestoreGState(ctx)
}
Still learnings Core Graphics, so sorry if this is simple.
Regards, Brandon
EDIT 1:
Here is what I am getting:
If I am assuming it correctly, you want to draw ticks that look like ticks on a watch face.
If you let it draw only one iteration, e.g.
for i in 0..<1
You will see that the first tick starts at some angle. This is happening because you are drawing line from (x=50, y=50) to (x=30, y=30)
Try changing it to (x=50, y=0)(x=30, y=0)
for i in 0..<60 {
CGContextSaveGState(ctx)
CGContextTranslateCTM(ctx, rect.width / 2, rect.height / 2)
CGContextRotateCTM(ctx, CGFloat(6.0 * Double(i) * M_PI / 180))
CGContextSetStrokeColorWithColor(ctx, UIColor.grayColor().CGColor)
CGContextMoveToPoint(ctx, 50, 0) // <-- changed here
if (i % 5 == 0) {
CGContextSetLineWidth(ctx, 3.0)
CGContextAddLineToPoint(ctx, 30, 0) // <-- changed here
}
else {
CGContextSetLineWidth(ctx, 2.0)
CGContextAddLineToPoint(ctx, 40, 0) // <-- changed here
}
CGContextStrokePath(ctx)
CGContextRestoreGState(ctx)
}
Result

Drawing Round corners For Custom Shape by Core Graphics

i am drawing Custom shape using Core Graphics and i want to make Rounded Corners for this shape
this is my code of Drawing my custom Shape
CGPoint p1=[self getPointFromAngleQuarter:start_angle2 andRaduis:card.small_Raduis andCenter:center];
CGContextMoveToPoint(context, p1.x, p1.y);
CGPoint p2=[self getPointFromAngleQuarter:start_angle2 andCenter:center andRaduis:self.large_Raduis];
CGContextAddLineToPoint(context, p2.x, p2.y);
CGContextAddArc(context,center.x, center.y, selectedLargeRaduis, start, end,0);
CGPoint p5=[self getPointFromAngle:end_Angle andCenter:center andRaduis:self.small_Raduis];
CGContextAddLineToPoint(context, p5.x, p5.y);
CGContextAddArc(context,center.x, center.y,selectedSmallRaduis, end, start,1);
CGContextDrawPath(context, kCGPathFill);
and here is the final Result of my custom Shape
Custom Shape:
If this shape is a solid color, the easy solution is to use a very wide line width, plus a round line cap and round line join. I presume, though, that you want this rounded shape to lay entirely inside the shape you included in your picture. Then the trick is to offset the arcs you draw by an amount equal to corner radius of the path (and stroke the line with twice the width of the corner radius).
For example, considering this diagram (which is not the desired shape, but shows us how to get there):
The black shape in the background is your original shape. The white path is the path I'm going to draw to achieve the rounded corners. The light gray is that path stroked with a large line width, a rounded line join, and a rounded line cap. The dark gray is that path filled in with another color.
So hopefully this illustrates the idea. Create a new path, offset by the corner radius, and drawn with a line width twice the corner radius. If you simply draw the new path with a solid back stroke (replacing the light gray in the above image) and solid black fill (replacing the dark gray in the above image), you get your desired shape:
Here is routine to get the path (the white line in my first image) in Objective-C:
- (UIBezierPath *)arcWithRoundedCornerAt:(CGPoint)center
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
innerRadius:(CGFloat)innerRadius
outerRadius:(CGFloat)outerRadius
cornerRadius:(CGFloat)cornerRadius {
CGFloat innerTheta = asin(cornerRadius / 2.0 / (innerRadius + cornerRadius)) * 2.0;
CGFloat outerTheta = asin(cornerRadius / 2.0 / (outerRadius - cornerRadius)) * 2.0;
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:center
radius:innerRadius + cornerRadius
startAngle:endAngle - innerTheta
endAngle:startAngle + innerTheta
clockwise:false];
[path addArcWithCenter:center
radius:outerRadius - cornerRadius
startAngle:startAngle + outerTheta
endAngle:endAngle - outerTheta
clockwise:true];
[path closePath];
return path;
}
Or in Swift 3:
private func arcWithRoundedCorners(at center: CGPoint, startAngle: CGFloat, endAngle: CGFloat, innerRadius: CGFloat, outerRadius: CGFloat, cornerRadius: CGFloat) -> UIBezierPath {
let innerTheta = asin(cornerRadius / 2 / (innerRadius + cornerRadius)) * 2
let outerTheta = asin(cornerRadius / 2 / (outerRadius - cornerRadius)) * 2
let path = UIBezierPath()
path.addArc(withCenter: center, radius: innerRadius + cornerRadius, startAngle: endAngle - innerTheta, endAngle: startAngle + innerTheta, clockwise: false)
path.addArc(withCenter: center, radius: outerRadius - cornerRadius, startAngle: startAngle + outerTheta, endAngle: endAngle - outerTheta, clockwise: true)
path.close()
return path
}
(You can do the above with Core Graphics calls if you want, but I generally use UIBezierPath.)
If, though, you needed the fill to be a different color than the stroke, then the process is more complicated, because you can't just use this technique. Instead, you actually have to define a path that is an outline of the above shape, but consists of drawing not only the two big arcs, but four little arcs for each of the corners. It's tedious, but simple, trigonometry to construct that path, but I wouldn't go through that effort unless you had to.

Resources