How to close triangle path? - ios

I have drawn this shape:
here is my code:
/* build path */
let bottomBezierPath = UIBezierPath()
//first thing draw bottom line
bottomBezierPath.moveToPoint(CGPointMake(0, TRIANGLE_EDGE))
bottomBezierPath.addLineToPoint(CGPointMake(bottomShapeLayer.frame.size.width,TRIANGLE_EDGE))
//now draw triangle
bottomBezierPath.moveToPoint(CGPointMake(bottomShapeLayer.frame.size.width,TRIANGLE_EDGE))
bottomBezierPath.addLineToPoint(CGPointMake(bottomShapeLayer.frame.size.width,0))
bottomBezierPath.moveToPoint(CGPointMake(bottomShapeLayer.frame.size.width,0))
bottomBezierPath.addLineToPoint(CGPointMake(bottomShapeLayer.frame.size.width - TRIANGLE_EDGE ,TRIANGLE_EDGE))
bottomShapeLayer.path = bottomBezierPath.CGPath
bottomShapeLayer.fillColor = UIColor.redColor().CGColor
bottomShapeLayer.strokeColor = UIColor.redColor().CGColor
But I need to fill the triangle with red color, bottomShapeLayer.fillColor should do it, right ?

You're creating 2 separate line segments for your triangle.
Each time you use moveToPoint(), you're starting a new line segment. Therefore, when the CAShapeLayer comes to fill the triangle, it is unable to fill it, as it's 'incomplete'.
The fix to this is really simple, you just have to create a continuous path for your triangle by removing one of the moveToPoint() calls:
let bottomBezierPath = UIBezierPath()
//first thing draw bottom line
bottomBezierPath.moveToPoint(CGPointMake(0, TRIANGLE_EDGE))
bottomBezierPath.addLineToPoint(CGPointMake(bottomShapeLayer.frame.size.width,TRIANGLE_EDGE))
//now draw triangle
//bottomBezierPath.moveToPoint(CGPointMake(bottomShapeLayer.frame.size.width,TRIANGLE_EDGE)) // this line is redundant btw, as you're moving to the same point. I've left it in incase you want to start your triangle somewhere else.
bottomBezierPath.addLineToPoint(CGPointMake(bottomShapeLayer.frame.size.width,0))
bottomBezierPath.addLineToPoint(CGPointMake(bottomShapeLayer.frame.size.width - TRIANGLE_EDGE ,TRIANGLE_EDGE))
It's also worth noting that your moveToPoint() call at the beginning of the triangle is also redundant, as it's moving to the same point.
Now you have a complete triangle and it should get filled correctly.

Related

Increasing UIBezierPath linewidth changes geometry...?

I've created a circular gauge as a visual indicator for a timer. There are multiple segments of the gauge to indicate different stages of the timer. Image here
The problem is that when I add a line width to the paths, the math no longer adds up. For example, the blue and red segment in the image above should be the same size (same % of the circle), but because of the line width, the red is overlapping the blue and, likewise, the grey segment overlaps the blue (I am drawing the segments counter clockwise)—so the blue appears smaller. I know that my segments are created correctly, because if I set the linewidth to 1.0, I get the following, correct result. Image here
If anyone has any insights on this, that would be great.
Code to give context:
//create path
let circularPath = UIBezierPath(arcCenter: view.center, radius: 100, startAngle: -CGFloat.pi/2, endAngle: 3*CGFloat.pi/2, clockwise: true)
//create segments - this is in a loop that adjusts start/end for all the segments
var tempShapeLayer = CAShapeLayer()
tempShapeLayer.path = circularPath.cgPath
tempShapeLayer.lineWidth = 10
tempShapeLayer.fillColor = UIColor.clear.cgColor
tempShapeLayer.lineCap = CAShapeLayerLineCap.square
tempShapeLayer.strokeStart = start
tempShapeLayer.strokeEnd = end
//next simply add some colors and add to view
You are using the wrong line cap style. You should be using .butt.
The different styles of line cap can be found in CGLineCap.
butt:
A line with a squared-off end. Core Graphics draws the line to extend only to the exact endpoint of the path.
square:
A line with a squared-off end. Core Graphics extends the line beyond the endpoint of the path for a distance equal to half the line width.
Using a square cap will cause the line to be a tiny bit longer on both sides than it should. For all segments except the last one drawn, this is not a problem, because their endpoints will be covered by other segments. But for the last segment drawn, it will appear longer than it should on both sides.
You should use .butt for the line cap style.

Remove line drawn on view

I am new to CoreGraphics . I am trying to create view which contains two UIImageview added in scrollview programatically. After adding it i want to connect both center with line. I have used bezier path as well as CAShapelayer. But line is drawn on UIImageview also so i want to remove line above UIImageview or send line to back to UIImageview. I have done below code.
let path: UIBezierPath = UIBezierPath()
path.moveToPoint(CGPointMake(personalProfile.center.x, personalProfile.center.y))
path.addLineToPoint(CGPointMake(vwTwo.center.x, vwTwo.center.y))
let shapeLayer: CAShapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.strokeColor = UIColor.blueColor().CGColor
shapeLayer.lineWidth = 3.0
shapeLayer.fillColor = UIColor.clearColor().CGColor
self.scrollView.layer.addSublayer(shapeLayer)
Please also check screenshot, i want to remove red marked portion of blue line .
You can do this simply by reducing the zPosition of your shapeLayer
This will allow the layer to be drawn underneath your two views (and far easier than trying to calculate a new start and end point of your line). If you look at the documentation for zPosition:
The default value of this property is 0. Changing the value of this property changes the the front-to-back ordering of layers onscreen. Higher values place this layer visually closer to the viewer than layers with lower values. This can affect the visibility of layers whose frame rectangles overlap.
Therefore, as it defaults to 0, and UIViews are just wrappers for CALayers, you can use a value of -1 on your shapeLayer in order to have it drawn behind your other views.
For example:
shapeLayer.zPosition = -1
Side Note
Most of the time in Swift, you don't need to explicitly supply a type when defining a variable. You can just let Swift infer it. For example:
let path = UIBezierPath()
I would see 2 options, an easy and a harder option.
Move the UIImageView to the front after drawing the line, effectively hiding the line behind the UIImageView.
Calculate the points at which you want the line to start and end and draw a line from these points instead of the centers.

Erase only part of an image (currently entire image is erased)

I'm trying to use a finger tap or drag to erase part of a UIImageView.
Here's what I have so far:
let panErase = UIPanGestureRecognizer(target: self, action: "erase:")
let tapErase = UITapGestureRecognizer(target: self, action: "erase:")
imageBeingEdited.addGestureRecognizer(panErase)
imageBeingEdited.addGestureRecognizer(tapErase)
I'm not quite sure how to debug graphics context modifications, but this erases the whole image:
let erasurePoint: CGPoint = gesture.locationInView(imageBeingEdited)
println("\(erasurePoint.x) \(erasurePoint.y)")
let image:UIImage = imageBeingEdited.image!
let s = image.size
UIGraphicsBeginImageContext(s)
let g = UIGraphicsGetCurrentContext()
CGContextBeginPath(g);
CGContextAddEllipseInRect(g, CGRectMake(erasurePoint.x, erasurePoint.y, 5, 5))
CGContextEOClip(g)
image.drawAtPoint(CGPointZero)
imageBeingEdited.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
The goal is to erase a circle at the tap location. What did I miss here? It looks like the image is cropped into a 5x5 circle, not necessarily at the tapped point.
It looks to me like the code you've posted would result in clipping your image to a 5x5 ellipse, exactly as you describe.
Did you write this code, or copy it from somewhere else?
It needs to be rearranged so it first draws the image into the context, then draws your ellipse using a clear color and the drawing mode where the alpha of the source pixels is written to the destination. Don't muck around with the context's clipping path at all.
This doesn't look like very efficient code to me. On every change in the pan gesture you're creating a context, drawing an image into it, and then copying out a new image. Then, presumably, you're drawing the resulting image. That's not likely to be fast enough to keep up with the user's pan gesture.
Instead I would probably add a CAShapeLayer as a mask layer to my image view's layer and modify that mask layer's path, appending an ellipse to the mask path for each point the user touches. Even that might not be fast enough for smooth drawing. You might need to write code that interpolates between a beginning and end touch position and filling the whole segment.

Error attempting to create polygon with CGPathAddEllipseInRect

I am trying to create an ellipses. I used bodyWithEdgeLoopFromPath and it worked but there seems to be something wrong with it because sometimes other objects get caught in the middle of it.
But I want the ellipses to be solid, So I tried bodyWithPolygonFromPath (I want it static)
horizontalOval = [[SKShapeNode alloc] init];
theRect = CGRectMake(0, 0, self.frame.size.width/6 , 15);
CGMutablePathRef ovalPath = CGPathCreateMutable();
CGPathAddEllipseInRect(ovalPath, NULL, theRect);
horizontalOval.path = ovalPath;
horizontalOval.fillColor = [UIColor blueColor];
horizontalOval.physicsBody.dynamic = NO;
horizontalOval.physicsBody = [SKPhysicsBody bodyWithPolygonFromPath:ovalPath];
But got the error
SKPhysicsBody: Error attempting to create polygon with 17 vertices, maximum is 12
How do I create complex paths and make them solid?
Also when I pud it in position self.frame.size.width/2 and self.frame.size.height/2 It doesn't stay center, it goes a little to the right.
I had to theRect = CGRectMake(-40, 0........) to make it center but why is that?
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: _paddleRect];
But has 13 vertices. Trying to use PaintCode.
You can think of an edge body as one that has no volume, just the edges, a body with 'negative space' - that's why your objects got caught in the middle of it. As the Sprite Kit Programming Guide says:
The main difference between a edge and a volume is that an edge permits movement inside its own boundaries, while a volume is considered a solid object.
Since you want your oval to be a solid object, you do need a volume-based body. For these bodies, you have three options to create their shape: a circle (bodyWithCircleOfRadius:), a rectangle (bodyWithRectangleOfSize:), or a polygon (bodyWithPolygonFromPath:).
For an oval shape, you probably have to draw a polygon - however, the Sprite Kit physics engine will only accept those with a maximum of 12 vertices (that's why you were getting an error when drawing an actual ellipse). Your best bet is drawing a polygon using a helper tool, such as this one: http://dazchong.com/spritekit/ - just drag and drop your sprite and draw the path. Remember that the polygon must be convex (no angles over 180 degrees inside it) and that it can have a maximum of 12 vertices.
Also check out this answer for a similar issue: Ellipse SKPhysicsBody

how to get buffer zone around a uibezierpath

i have some uibezierpaths. as paths, they don't really have thickness.
but, I am hoping to find a way to define an area around a path like the grayish areas around the lines in this picture
basically, i want to test whether drawn lines fall within the buffer zone around the lines.
i thought this would be simple, but it's turning out to be much more complex than i thought. I can use the CGPathApply function to examine the points along my path, and then getting a range +or- each point, but it's more complicated than that with angles and curves. any ideas?
Expanding the width of a path is actually quite difficult. However, you could just stroke it with a thicker width and get pretty much the same effect. Something like...
CGContextSetRGBStrokeColor(context, 0.4, 0.4, 0.4, 1.0);
[path setLineWidth:15];
[path stroke];
CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0);
[path setLineWidth:3];
[path stroke];
...would produce a picture like the one in your question. But I doubt that's news to you.
The real trick is the test of "whether drawn lines fall within the buffer zone." That problem is very similar to one which I just answered for myself in another question. Take a look at the LineSample.zip code I shared there. This implements a bitmap/bitwise data comparison to detect hits on lines much like you need. You could just draw the thicker "buffer" paths into the bitmap for testing and show the thinner lines in your view.
Basically, you want to check if any point falls inside a region of specified size around your path.
It is actually very simple to do. First, you need a value which will define the amount of space around path you want to test. Let's say 20 points. So what you need to do is start a FOR loop, starting from -20 to 20, and at each iteration, create a copy of your path, translate the path's x and y co-odrinates, check each of them.
All of this is more clear in this code sample.
CGPoint touchPoint = /*get the point*/;
NSInteger space = 20;
for (NSInteger i = -space; i < space; i++) {
UIBezierPath *pathX = [UIBezierPath bezierPathWithCGPath:originalPath.CGPath];
[pathX applyTransform:CGAffineTransformMakeTranslation(i, 0)];
if ([pathX containsPoint:touchPoint]) {
/*YEAH!*/
}
else {
UIBezierPath *pathY = [UIBezierPath bezierPathWithCGPath:originalPath.CGPath];
[pathY applyTransform:CGAffineTransformMakeTranslation(0, i)];
if ([pathY containsPoint:touchPoint]) {
/*YEAH!*/
}
}
}

Resources