Does MKOverlayPathView need drawMapRect? - ios

I'm having some inconsistencies modifying the Breadcrumb example, to have the CrumbPathView subclassed from MKOverlayPathView (like it's supposed to) rather than subclassed from MKOverlayView.
Trouble is, the docs are limited in stating the difference in how these 2 should be implemented. For a subclass of MKOverlayPathView it's advised to use:
- createPath
- applyStrokePropertiesToContext:atZoomScale:
- strokePath:inContext:
But is this in place of drawMapRect, or in addition to? It doesn't seem like much point if it's in addition to, because both would be used for similar implementations. But using it instead of drawMapRect, leaves the line choppy and broken.
Struggling to find any real world examples of subclassing MKOverlayPathView too...is there any point?
UPDATE - modified code from drawMapRect, to what should work:
- (void)createPath
{
CrumbPath *crumbs = (CrumbPath *)(self.overlay);
CGMutablePathRef newPath = [self createPathForPoints:crumbs.points
pointCount:crumbs.pointCount];
if (newPath != nil) {
CGPathAddPath(newPath, NULL, self.path);
[self setPath:newPath];
}
CGPathRelease(newPath);
}
- (void)applyStrokePropertiesToContext:(CGContextRef)context atZoomScale:(MKZoomScale)zoomScale
{
CGContextSetStrokeColorWithColor(context, [[UIColor greenColor] CGColor]);
CGFloat lineWidth = MKRoadWidthAtZoomScale(zoomScale);
CGContextSetLineWidth(context, lineWidth);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);
}
- (void)strokePath:(CGPathRef)path inContext:(CGContextRef)context
{
CGContextAddPath(context, path);
CGContextStrokePath(context);
[self setPath:path];
}
This draws an initial line, but fails to continue the line...it doesn't add the path. I've confirmed that applyStrokePropertiesToContext and strokePath are getting called, upon every new location.
Here's a screenshot of the broken line that results (it draws for createPath, but not after that):
Here's a screenshot of the "choppy" path that happens when drawMapRect is included with createPath:

Without having seen more of your code I'm guessing, but here goes.
I suspect the path is being broken into segments, A->B, C->D, E->F rather than a path with points A,B,C,D, E and F. To be sure of that we'd need to see what is happening to self.overlay and whether it is being reset at any point.
In strokePath you set self.path to be the one that is being stroked. I doubt that is a good idea since the stroking could happen at any time just like viewForAnnotations.
As for the choppiness it may be a side effect or a poor bounds calculation on Apple's part. If your like ends near the boundary of a tile that Apple uses to cover the map it would probably only prompt the map to draw the one the line is within. But your stroke width extends into a neighbouring tile that hasn't been draw. I'm guessing again but you could test this out by moving the point that is just north of the W in "Queen St W" a fraction south, or by increasing the stroke width and see if the cut off line stays in the same place geographically.

Related

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

iOS CALayer and TapGestureRecognizer

I'm developing an app on iOS 6.1 for iPad.
I've a problem with the CALayer and a TapGestureRecognizer.
I've 7 CALayers forming a rainbow (every layer is a colour).
Every layer is built using a CAShapeLayer generate from a CGMutablePathRef. Everything works fine. All the layers are drawn on screen and I can see a beautiful rainbow.
Now I want to detect the tap above a single color/layer. I try this way:
- (void)tap:(UITapGestureRecognizer*)recognizer
{
//I've had the tapGestureRecognizer to rainbowView (that is an UIView) in viewDidLoad
CGLayer* tappedLayer = [rainbowView.layer.presentationlayer hitTest:[recognizer locationInView:rainbowView];
if (tappedLayer == purpleLayer) //for example
NSLog(#"PURPLE!");
}
I don't understand why this code won't work! I've already red other topics in here: all suggest the hitTest: method for solving problems like this. But in my case I can't obtain the desired result.
Can anyone help me? Thanks!!
EDIT:
Here's the code for the creation of paths and layers:
- (void)viewDidLoad
{
//Other layers
...
...
//Purple Arc
purplePath = CGPathCreateMutable();
CGPathMoveToPoint(purplePath, NULL, 150, 400);
CGPathAddCurveToPoint(purplePath, NULL, 150, 162, 550, 162, 550, 400);
purpletrack = [CAShapeLayer layer];
purpletrack.path = purplePath;
purpletrack.strokeColor = [UIColor colorWithRed:134.0/255.0f green:50.0/255.0f blue:140.0/255.0f alpha:1.0].CGColor;
purpletrack.fillColor = nil;
purpletrack.lineWidth = 25.0;
[rainbowView.layer insertSublayer:purpletrack above:bluetrack];
}
This was my first approach to the problem. And the touch didn't work.
I also tried to create a RainbowView class where the rainbow was drawing in drawRect method using UIBezierPaths.
Then I follow the "Doing Hit-Detection on a Path" section in http://developer.apple.com/library/ios/#documentation/2ddrawing/conceptual/drawingprintingios/BezierPaths/BezierPaths.html
In this case the problem was the path variable passed to the method. I try to compare the UIBezierPath passed with the paths in RainbowView but with no results.
I could try to create curves instead of paths. In this case maybe there isn't a fill part of figure and the touching area is limited to the stroke. But then how can I recognize the touch on a curve?
I'm so confused about all of these stuff!!! :D
The problem you are facing is that you are checking agains the frame/bounds of the layer when hit testing and not agains the path of the shape layer.
If your paths are filled you should instead use CGPathContainsPoint() to determine if the tap was inside the path. If your paths aren't filled but instead stroked I refer you to Ole Begemann's article about CGPath Hit Testing.
To make your code cleaner you could do the hit testing in your own subclass. Also, unless the layer is animating when hit testing it makes no sense using the presentationLayer.

How to draw the vertices on a CGContext CGMutablePathRef?

I am drawing a set of connected lines as the user enters clicks to specify vertices. I am build a CGMutablePathRef
- (void)addPointToCGMPR: (CGPoint)p
forNewPolygon: (BOOL)newPoly
{
if (newPoly)
{
CGPathMoveToPoint(cgmpr, NULL, p.x, p.y);
}
else
{
CGPathAddLineToPoint(cgmpr, NULL, p.x, p.y);
}
[self setNeedsDisplay];
}
drawRect: is then called after each point is entered, and the context CGMutablePathRef is added to a CGContext for display
- (void)drawRect:(CGRect)rect
{
// Set up a context to display
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, cgmpr);
CGContextSetLineWidth(context, 1);
CGContextStrokePath(context);
// If NOT empty then Close the context so that box comparison can occur later
if (!CGContextIsPathEmpty(context))
{
CGContextClosePath(context);
}
}
I am getting lines on the screen as one would expect. My trouble is that after the user picks the first point there is nothing rendered on the screen and the user is left not knowing if the system got his first pick until after the second point is entered. I would like for the system to render the first point even before the next point is entered. I would also like for the system to render each one of the vertices picked in a visually distinct way - right now I am not getting vertices rendered, only lines. Is there a way to ask the CGContext to render points? Is there a way to specify the style in which these points are rendered? Thanks.
You're free to draw whatever you want to represent the points: a small square, a small circle, an image of a pushpin, etc. You already know how to draw into a context; just loop over your points and draw them however you want them to appear. I personally tend to use CGContextFillEllipseInRect for things like this. For each point, make a rect that surrounds the point and draw it:
CGFloat pointSize = 4;
CGRect pointRect = CGRectMake(point.x - pointSize / 2, point.y - pointSize / 2, pointSize, pointSize);
CGContextFillEllipseInRect(context, pointRect);
No. You'll need to render them yourself by adding another CGPath to describe whatever you want to represent the vertex. Since you'll probably draw this often, you may want to render it to a CGLayer so you can easily copy it where you need it. You could also draw your vertices onto CALayers so you can easily move them around. But it's up to you to design and manage this part of the drawing.

Simple 2D "collision detection" iOS

I'm writing an application that will calculate a CGPoint and show a mark in an envelope (a diagram if you like). My envelope is just part of the background image in an UIImageView. What I want to do is to construct a sort of "line", corresponding to the envelopes limits (they're not straight lines, but curves) so that if the calculated CGPoint is to the left of this line, or to the right of another line, then the calculated point is not approved. Where it to be in the middle of these two, it's approved.
I was first thinking of drawing lines using CoreGraphics, but I'm not sure if one could check whether the calculated CGPoint is to the right or left of those lines.
The envelope is only 149px high, so I was also thinking of putting together a dictionary, where the keys where the y position and the values where the x position of the pixels that represented that defining boundary line.
The application is rather simple and is not animating anything. Does anybody have an idea of how to best come up with a solution for this sort of behavior?
You can do this by creating a CGPath that represents your boundary lines (the outline of your envelope) and testing that a point is contained in it with CGPathContainsPoint.
You'll have to do some trial and error to construct a CGPath that matches your envelope shape, try filling it in the drawRect method to see what your path actually is.
Here's an example with a circle path:
CGPoint viewCenter = CGPointMake(100,100);
CGPoint checkPoint = CGPointMake(110,110);
UIBezierPath *bpath = [UIBezierPath bezierPathWithArcCenter:viewCenter radius:50 startAngle:0 endAngle:DEGREES_TO_RADIANS(360) clockwise:YES];
CGPathRef path = [bpath CGPath];
BOOL inPath = CGPathContainsPoint(path, NULL, checkPoint, NO);
Here I have DEGREES_TO_RADIANS defined like this:
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)

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