How to draw arc/curve line with MKOverlayView on MKMapView - iphone

Help needed now.
I can draw lines with MKPolyline and MKPolylineView, but how to draw an arc or curve lines between two coordinates on the MKMapView?
Great thanks.

Before answering the question it is important to mention that MKOverlayView is deprecated and from iOS7 and later we should use MKOverlayRenderer:
In iOS 7 and later, use the MKOverlayRenderer class to display overlays instead.
We can now break it down on how to implement the arc/curve line:
First, we need to define and add our poly line. Let's create one with 2 coordinates:
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
mapView.addOverlay(polyline)
After adding the polyline, MKMapView will want us to provide a proper MKOverlayRenderer in corresponding to the MKPolyline we've created at section 1. The method we need is:
mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer
Which basically:
Asks the delegate for a renderer object to use when drawing the specified overlay.
So let's provide that object. We will want to subclass MKOverlayPathRenderer which obviously inherits from MKOverlayRenderer and as documentation states:
Use this renderer when your overlay's shape is defined by a CGPath object. By default, this renderer fills the overlay's shape and represents the strokes of the path using its current attributes.
So if we will use our newly subclass object as is, we will get an out-of-the-box solution which is a solid line from the 1st coordinate to the 2nd one we've defined in section 1, but since we want a curved line we will have to override a method for that:
You can use this class as-is or subclass to define additional drawing behaviors. If you subclass, override the createPath() method and use that method to build the appropriate path object. To change the path, invalidate it and recreate the path using whatever new data your subclass has obtained.
It means that inside our CustomObject: MKOverlayPathRenderer we will override createPath:
override func createPath() {
let polyline = overlay as! MKPolyline
// Getting the coordinates from the polyline
let points = polyline.points()
// Taking the center of the polyline (between the 2 coordiantes) and converting to CGPoint
let centerMapPoint = MKMapPoint(polyline.coordinate)
// Converting coordinates to CGPoint corresponding to the specified point on the map
let startPoint = point(for: points[0])
let endPoint = point(for: points[1])
let centerPoint = point(for: centerMapPoint)
// I would like to thank a co-worker of mine for the controlPoint formula :)
let controlPoint = CGPoint(x: centerPoint.x + (startPoint.y - endPoint.y) / 3,
y: centerPoint.y + (endPoint.x - startPoint.x) / 3)
// Defining our new curved path using Bezier path
let myPath = UIBezierPath()
myPath.move(to: startPoint)
myPath.addQuadCurve(to: endPoint,
controlPoint: controlPoint)
// Mutates the solid line with our curved one
path = myPath.cgPath
}
We are basically done. You might want to consider adding width/stroke/color etc so you could see the curved line.
(Optional) If you are interested in a gradient curved line, there is a really great solution here but there are 2 changes you will have to make in the code in order for this to work:
4.1. After overriding override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext), just before adding the gradient, you will have to add the same code from section 3 but instead of changing the inner path you will have to add it to the provided context:
context.move(to: startPoint)
context.addQuadCurve(to: endPoint,
control: controlPoint)
This basically gives us a curved line we need regarding the gradient coloring.
4.2. Instead of using path.boundingBoxOfPath we will need to use path.boundingBox because:
... including control points for Bézier and quadratic curves.
Unlike boundingBoxOfPath:
... not including control points for Bézier and quadratic curves.
Hope that helps :)

Reading the documentation, it seems that you can create an instance of MKOverlayPathView and assign an arbitrary CGPathRef object to its path property. This path can contain both straight lines and arcs.
I don't know (and the documentation doesn't mention) in what format the coordinates of the path should be (MKMapPoint or CLLocationCoordinate2D come to mind) but you can probably find that out by experimenting a bit.

You answered yourself in your comment already, but I'd like to point everyone at the excellent AIMapViewWrapper project on GitHub which includes sample code for plotting an arc path over a set of coordinates. In that project it's being used to draw the path a plane takes including a shadow path underneath it (and other stuff such as animating a plane along that path). Should come in handy for anyone taking a stab at this.

If you want to:
just draw arc path on map
just draw an image on map
combine arc path and image as overlay on map
So the solutions are:
There are MKPolyline and MKPolylineView to draw lines, MKPolygon and MKPolygonView to draw polygons, MKCircle and MKCircleView to draw circles. No one fit? Where is an arc? Oh, yes. Now the really solution: You create a custom class derived from MKOverlayPathView and override the -createPath method, in the -createPath you create an arc use CGContextAddArcToPoint or others functions you like, and associate the path you just create to the path property of MKOverlayPathView and the custom works are done. Then you add MKPolyline in the map(Yes! just MKPolyline!), Then in the -mapView:viewForOverlay: method you create the custom class take that polyline. Then, just run it, everything is runs as you wish. Magic? you can draw a MKPolyline as an arc!
Just an image? Use MKAnnotationView. That's done! you can do it! I believe that!
In my problem, I want to draw an arc with an image in the overlay. So I create a custom class conforms to the MKOverlay protocol, and a custom class derived from MKOverlayView to draw that overlay. Everything works fine but I can't draw any path on the map! I've set the lineWidth to 1,2,3,4... but it wasn't work! Ah..the solution is set the line width with MKRoadWidthAtZoomScale(zoomScale) function and you can see the path! That's done......
And some tips here:
Use Core Graphic to draw paths, not MapKit coordinates nor UIView coordinates!
In the CG functions just keep in mind the line width should be converted by this function:MKRoadWidthAtZoomScale(zoomScale)
Hope to help

Related

How to create round shape UIview instead of rectangle shape

Hi in my application i need a view which is of round shape instead of a rectangle shape. How to create a uiview object of round shape please let me know. Thanks in advance.
Technically, all UIView's will always be "rectangles", meaning they will be placed on the screen using {x, y} coordinates and they will be dimensioned with a height and a width (Making them rectangles). However, within the bounds of a UIView you can do a lot to make it appear as a circle. Here are some methods:
Use UIImageView and set it's Image to be an image of a circle. This is easy, but not very flexible.
Learn Core Graphics (also known as Quartz2D) and draw a circle in the UIView's -drawRect: method. Quartz 2D Programming Guide
Use a CAShapeLayer for the UIView's layers. CAShapeLayer Class Reference
There are certainly other methods but this should be a good start. If you need to detect touches within the circle, you can use either option 2. or 3. and keep a reference to the CGPathRef (or UIBezierPath) and use CGPathContainsPoint to determine if the touch is within the bounds of the circle and act accordingly.
You can set the cornerRadius of the layer of your view.
#import <QuartzCore/QuartzCore.h>
yourView.layer.cornerRadius = 20;

Alternative to CGPathAddEllipseInRect

I'm wondering, is there an alternative to CGPathAddElipseInRect for drawing an ellipse? It works well and all, but I need a custom ellipse that has less steeper curves. Any thing else that can be done? I am using this path with CTFramesetterCreateFrame in CoreText so I can draw text within different shapes.
No...
...But it can be done with Core Graphics
You would have to use something like CGPathAddQuadCurveToPoint, CGPathAddCurveToPoint or CGPathAddArcToPoint to create a custom path for you ellipse. If your not familiar with how control points for bezier paths work you can starts by reading the Wikipedia article. You don't need to read the math part about it, just look at this image (from Wikipedia).
For the ...AddCurveTo... method:
Your current point is p0 and the end point is p3. The two control points are p1 and p2.
For the ...AddQuadCurveTo.. method:
Same as above but with only one control point.
For the ...AddArcTo.. method:
Doen't use control points. Instead uses a fixed radius.
...You can also create the path with UIBezierPath if you prefer
There is also similar methods on UIBezier path like addCurveToPoint:controlPoint1:controlPoint2: or addCurveToPoint:controlPoint1:controlPoint2:. The bezier path can then be converted to a CGPath but that shouldn't be necessary if your familiar with Core Graphics.
You can create a UIBezierPath and then get its CGPath property.
How you create this path will depend on your needs. The method +(UIBezierPath *) bezierPathWithOvalInRect: looks like it most closely resembles CGPathAddElipseInRect.
You can also use its fill and stroke methods to draw into the current context.

Arc's position on circle

I've got few pixels wide circle (circle is UIBezierPath based). I have to put an arc (which is basically UIView subclass with custom drawing) on the circle so the arc covers circle. I know how to calculate rotation of arc and position but something is not right. I mean I know the reason - it's beacause center property which is assigned to center of UIView, if it was center of the arc, everything would be great. But it's not.
I also know how to solve that. I have to calculate smaller radius where I will put arcs on. But how to do that. Basically it seems easy but because of the arc is in rectangular UIView it gets a bit harder. I'll show you some images so you can see the problem.
The easiest way to do this is to change the anchor point of each arc view's layer. You can read about the anchor point here if you don't already know about it.
You will need to add the QuartzCore framework to your build target and add #import <QuartzCore/QuartzCore.h>.
CGRect circleBounds = circleView.bounds;
topArcView.layer.anchorPoint = CGPointMake(.5, 0);
topArcView.layer.position = CGPointMake(CGRectGetMidX(circleBounds), 0);
bottomArcView.layer.anchorPoint = CGPointMake(.5, 1);
bottomArcView.layer.position = CGPointMake(CGRectGetMidX(circleBounds), CGRectGetMaxY(circleBounds));
leftArcView.layer.anchorPoint = CGPointMake(0, .5);
leftArcView.layer.position = CGPointMake(circleBounds.origin.x, CGRectGetMidY(circleBounds));
rightArcView.layer.anchorPoint = CGPointMake(1, .5);
rightArcView.layer.position = CGPointMake(CGRectGetMaxX(circleBounds), CGRectGetMidY(circleBounds));
Perhaps you could make the UIView subclass the same size and center point as the circle's rect. Then draw the arc in the correct location.

How can I determine if the current position is within a KML-defined area?

I have some KML data which defines an area on a map, such as the following:
131.0764742247829,-15.80786022929951,0 132.6357700620065,-16.54895727734661,0
134.1119108999971,-17.28040233069844,0 135.8545033459996,-18.1298170074137,0
137.7396886168202,-19.07860187595016,0 140.011948401144,-20.18462043802856,0
142.3114600417346,-21.19369381963822,0 144.1566045495202,-22.15731684591415,0
I'd like to determine within my iOS application if the user's current location is inside of this defined area. How can I do this?
If you know the center and radius of the circle then it is quite easy.
CLRegion *circle = [CLRegion initCircularRegionWithCenter:centerCoordinate radius:circleRadius identifier:#"myCircle"];
BOOL doesItContainMyPoint = [circle containsCoordinate:myLocation];
Update based on revised question
I've never tried this, but couldn't you create a UIBezierPath with your points (you don't have to actually draw the bezier path) and then use UIBezierPath's - (BOOL)containsPoint:(CGPoint)point to test for inclusion?

Is it ever possible to call pointInside: on an irregular shape?

I'm drawing an oblong 'egg' shape (on iOS), and want to use it as a boundary for particles. My thought is to use the curve paths to make a UIView and then use hitTest:withEvent: or pointInside:withEvent: to enforce boundary collision.
The problem is, of course, that UIView is always rectangular. How would you go about checking to see if a point is inside an irregular shape like this?
- (void)drawRect:(CGRect)rect {
int w = rect.size.width;
int h = rect.size.height;
CGContextBeginPath(context);
CGContextMoveToPoint(context, w/2, h/5);
CGContextAddCurveToPoint(context, w*0.1, h/4.3, w*0.1, h*0.82, w/2, h*0.8);
CGContextAddCurveToPoint(context, w*0.9, h*0.82, w*0.9, h/4.3, w/2, h*0.2);
I'm using openFrameworks, for what that's worth. This code is just Obj-C but I'm open to any C++/obj-C++ solutions out there.
If you make a CGPathRef you can use CGPathContainsPoint. You can use that same CGPathRef to render into the context. You could also call CGContextPathContainsPoint on the context containing the path, but depending on when you need to test you might not have a context. And another alternative is the containsPoint selector on UIBezierPath.
If you want to code this from scratch, http://www.softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm goes through a couple of different algorithms that will work for an arbitrary polygon.