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
Related
What is replace of the CGContextAddLineToPoint this is what I'm trying to do
for i in aerr {
//0.5f offset lines up line with pixel boundary
CGContextMoveToPoint(context, self.bounds.origin.x, self.font!.lineHeight * CGFloat(x) + baselineOffset)
CGContextAddLineToPoint(context, CGFloat(self.bounds.size.width), self.font!.lineHeight * CGFloat(x) + baselineOffset)
}
and I'm getting the following error. I know they clearly mention using move(to:) but how to use that?
'CGContextMoveToPoint' is unavailable: Use move(to:) instead
In Swift, you can simply use directly from your context because it methods is part of CGContext.
So CGContextMoveToPoint(context, x, y) will be context.move(to:CGPoint(x, y))
As same as, CGContextAddLineToPoint(context, x, y) will be context.addLine(to: CGPoint(x, y))
So your code will be
guard let context = UIGraphicsGetCurrentContext() else { return }
for i in aerr {
//0.5f offset lines up line with pixel boundary
context.move(to: CGPoint(x: self.bounds.origin.x, y: self.font!.lineHeight * CGFloat(x) + baselineOffset))
context.addLine(to: CGPoint(x: CGFloat(self.bounds.size.width), y: self.font!.lineHeight * CGFloat(x) + baselineOffset))
}
you can do it like this
context.move(to: .init(x: self.bounds.origin.x, y: self.font!.lineHeight * CGFloat(x) + baselineOffset))
How to scale a CGontext without affecting its origin i.e only the width and height should get scaled? If I use scale like the below one directly it scales the origin also.
context.scaledBy(x: 2.0, y: 2.0)
Is there a way to construct an AffineTransform that manipulates the width and height, leaving the origin untouched?
I want an AffineTransform that can be used to both CGContext and CGRect.
For example a CGRect rect = {x, y, w, h}
var t = CGAffineTransform.identity
t = t.scaledBy(x: sx, y: sy)
let tRect = rect.applying(t)
tRect will be {x * sx, y * sy, w * sx, h * sy}
But I want {x, y, w * sx, h * sy}. Though it can be achieved by calculation, I need CGAffineTransform to do this.
You need to translate the origin, then scale, then undo the translation:
import Foundation
import CoreGraphics
let rect = CGRect(x: 1, y: 2, width: 3, height: 4) // Whatever
// Translation to move rect's origin to <0,0>
let t0 = CGAffineTransform(translationX: -rect.origin.x, y: -rect.origin.y)
// Scale - <0,0> will not move, width & height will
let ts = CGAffineTransform(scaleX: 2, y: 3) // Whatever
// Translation to restore origin
let t1 = CGAffineTransform(translationX: rect.origin.x, y: rect.origin.y)
//Compound transform:
let t = t0.concatenating(ts).concatenating(t1)
// Test it:
let tRect = rect.applying(t) // 1, 2, 6, 12 as required
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);
I am getting error
fatal error: Can't unwrap Optional.None
(lldb)
with this code
var context:CGContextRef = UIGraphicsGetCurrentContext()
let radius:CGFloat = 5
CGContextSetRGBStrokeColor(context, 1, 1, 1, 1)
var tempRect:CGRect = CGRectMake(5, 5, 20, 20)
let minX = tempRect.minX, midX = tempRect.midX, maxX = tempRect.maxX
let minY = tempRect.minY, midY = tempRect.midY, maxY = tempRect.maxY
CGContextMoveToPoint(context, minX, minY)
CGContextAddArcToPoint(context, minX, minY, midX, midY, radius)
CGContextAddArcToPoint(context, maxX, minY, maxX, midY, radius)
CGContextAddArcToPoint(context, maxX, maxY, midX, maxY, radius)
CGContextAddArcToPoint(context, minX, maxY, minX, midY, radius)
CGContextClosePath(context)
CGContextDrawPath(context, kCGPathFillStroke)
I don't understand what the problem is. What I am trying to do is create a rectangle with rounded corners in Swift. Why am I getting this error and is there a much easier way to do this?
The problem is that there is no current context. Thus UIGraphicsGetCurrentContext() would like to return NULL (nil). You should test context in a conditional before going on.
Also you should type context as a CGContext!, since that is what UIGraphicsGetCurrentContext() returns — a context, or nil, wrapped up in an Optional, implicitly unwrapped. Thus, when you get a context, you will be able to use it without using question marks everywhere. Even better, don't give it an explicit type at all; Swift knows better than you do what UIGraphicsGetCurrentContext() returns.
i refered from this example
i will like to draw the line from 6 o'clock instead of 3 o'clock
//Create the path
CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, radius, 0, M_PI *2, 0);
//Set the stroke color to black
[[UIColor colorWithRed:241.0/255.0 green:90.0/255.0 blue:36.0/255.0 alpha:1.0]setStroke];
//Define line width and cap
CGContextSetLineWidth(ctx, TB_BACKGROUND_WIDTH);
CGContextSetLineCap(ctx, kCGLineCapButt);
//draw it!
//CGContextDrawPath(ctx, kCGPathFill);
CGContextDrawPath(ctx, kCGPathStroke);
currently
required to be like this drawing from 6 o'clock instead of 3 0'clock
For CGContextAddArc(), the method signature is as follow:
void CGContextAddArc (
CGContextRef c,
CGFloat x,
CGFloat y,
CGFloat radius,
CGFloat startAngle,
CGFloat endAngle,
int clockwise
);
To modify the start position from 6 o'clock to 3 o'clock , that is to set the startAngle to -90 degree, which is :
angle in degree = angle in radian x 180 / M_PI
i.e.
-90 = radian x 180 / M_PI
radian = -90 x M_PI / 180
radian = -M_PI / 2
modify the 1st line as follow :
CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, radius, -M_PI/2, ToRad(270), 0);
Note: the actual direction of the final path is dependent on the current transformation matrix of the graphics context.