5

Currently I am tracking my location on an MKMapView. My objective is to draw a bezier path identical to an MKPolyline created from tracked locations.

What I have attempted is: Store all location coordinates in a CLLocation array. Iterate over that array and store the lat/lng coordinates in a CLLocationCoordinate2D array. Then ensure the polyline is in the view of the screen to then convert all the location coordinates in CGPoints.

Current attempt:

@IBOutlet weak var bezierPathView: UIView! 

var locations = [CLLocation]() // values from didUpdateLocation(_:)

func createBezierPath() {
    bezierPathView.isHidden = false

        var coordinates = [CLLocationCoordinate2D]()
        for location in locations {
            coordinates.append(location.coordinate)
        }

        let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
        fitPolylineInView(polyline: polyline)

        let mapPoints = polyline.points()

        var points = [CGPoint]()

        for point in 0...polyline.pointCount
        {
            let coordinate = MKCoordinateForMapPoint(mapPoints[point])
            points.append(mapView.convert(coordinate, toPointTo: polylineView))
        }

        print(points)

        let path = UIBezierPath(points: points)
        path.lineWidth = 2.0
        path.lineJoinStyle = .round

        let layer = CAShapeLayer(path: path, lineColor: UIColor.red, fillColor: UIColor.black)
        bezierPathView.layer.addSublayer(layer)
}

extension UIBezierPath {
    convenience init(points:[CGPoint])
    {
        self.init()

        //connect every points by line.
        //the first point is start point
        for (index,aPoint) in points.enumerated()
        {
            if index == 0 {
                self.move(to: aPoint)
            }
            else {
                self.addLine(to: aPoint)
            }
        }
    }
}

extension CAShapeLayer
{
    convenience init(path:UIBezierPath, lineColor:UIColor, fillColor:UIColor)
    {
        self.init()
        self.path = path.cgPath
        self.strokeColor = lineColor.cgColor
        self.fillColor = fillColor.cgColor
        self.lineWidth = path.lineWidth

        self.opacity = 1
        self.frame = path.bounds
    }
}

I am able to output the points to the console that stored from the convert(_:) method( not sure if they are correct ). Yet the there is not output on the bezierPathView-resulting in an empty-white background-view controller.

lifewithelliott
  • 1,012
  • 2
  • 16
  • 35
  • You probably need to show how you convert from gps coordinates to UIView coordinates. – MirekE Jan 31 '17 at 07:51
  • @MirekE Adjusted now! I am getting the coordinates from the CLLocationManagerDelegate method didUpdateLocations. I am able to create an MKPolyline successfully, I just do not know how to create a UIBezierPath that is identical the polyline so I can place it in a UIView or some object to represent a path taken. – lifewithelliott Jan 31 '17 at 08:22
  • Capture the MKPolyline as image, using http://stackoverflow.com/questions/4334233/how-to-capture-uiview-to-uiimage-without-loss-of-quality-on-retina-display – Sachin Vas Jan 31 '17 at 08:30
  • @SachinVas I'm not entirely sure what is ment by capture MKPolyline as an image? Is it really possible to get just the polyline and the modify its attributes such as lineWidth, color etc...? Not sure how that would be implemented but advice would be appreciated! – lifewithelliott Jan 31 '17 at 09:06
  • Try printing the points to check the values. Do they look correct? What is that UIBezierPath(points:) initializer doing? I don't see it in the documentation... – MirekE Jan 31 '17 at 09:09
  • @MirekE I have the mapview continuously update to show my current location in the center, so thinking that the previous approach was purely plotting the center point when originally I thought it was plotting all the points on the polyline. I have since completely changed the approach but still unable to get a correct uibezierpath onto the uiview. :( – lifewithelliott Jan 31 '17 at 18:26
  • You are talking about a polyline, but I don't see it anywhere in your code. If you are creating and drawing an actual MKPolyline, I wonder why you need the Bezier. – MirekE Jan 31 '17 at 19:37
  • @MirekE My apologies on the code confusion. Also the polyline is used during the tracking process. The bezier path will be used so I can persist it onto other views and modify the path attributes as desired. Thanks! – lifewithelliott Feb 01 '17 at 08:03
  • @MirekE I feel i am so close to coming to a solution, I have updated the code to my latest findings, yet still unable to draw the path. Any possible suggestions? Thank you! – lifewithelliott Feb 02 '17 at 19:18
  • Is the y in the range of the target view? Points 500-800... – MirekE Feb 02 '17 at 23:27
  • @MirekE No matter what I've done to try and get the points within the range there is no BezierPath being drawn. – lifewithelliott Feb 03 '17 at 00:17

2 Answers2

1

Your extensions work fine. The problem may be in the code that adds the layer to the view (which you do not show).

I'd suggest that you simplify your project, for example use predefined array of points that definitely fit to your view. For example, for a view that is 500 pixels wide and 300 pixels high, you could use something like:

let points = [
    CGPoint(x: 10, y: 10),
    CGPoint(x: 490, y: 10),
    CGPoint(x: 490, y: 290),
    CGPoint(x: 10, y: 290),
    CGPoint(x: 10, y: 10)
]

Use colors that are clearly visible, like black and yellow for your stroke and fill.

Make sure that your path is correctly added to the view, for example:

let path = UIBezierPath(points: points)

let shapeLayer = CAShapeLayer(path: path, lineColor: UIColor.blue, fillColor: UIColor.lightGray)

view.layer.addSublayer(shapeLayer)

Inspect the controller that contains the view in Xcode's Interface Builder. In the debug view hierarchy function:

enter image description here

MirekE
  • 11,515
  • 5
  • 35
  • 28
  • Not entirely sure what I was able to change. But Progress has definitely been made. For a better represention I made the background of the UIView transparent. Now trying to figure out how to ensure that the entire polyline is drawn within the bounds of the view. Is it possible the size of the BezierPath, and center/size it to fit in any given view? I thought I was able to make the points of the polyline get repositioned inside the view. but 2000+ x point is definitely out of the screen as you said before! Sorry for dragging you down my rabbit hole! – lifewithelliott Feb 03 '17 at 18:19
  • https://developer.apple.com/reference/uikit/uibezierpath/1624340-apply For pointa outsider the boundaries of the patent, Inspect the controller that contains the view in Xcode's Interface Builder in the "debug view hierarchy" functionality – MirekE Feb 03 '17 at 21:09
0

this might help you, in case you haven't solved it yet.

I wanted the shape of an MKPolyline as an image without any background. I used the code above as an inspiration and had the same troubles as you had, the route was not shown.

In fact it was kind a scaling problem I think. At least it looked like that in the playground. Anyway, with this methods I get an image of the polylines shape.

private func createPolylineShapeAsImage() -> UIImage? {
    let vw = UIView(frame: mapView.bounds)
    var image : UIImage?
    if let polyline = viewModel.tourPolyline {
        let path = createBezierPath(mapView, polyline, to: mapView)
        let layer = getShapeLayer(path: path, lineColor: UIColor.white, fillColor: .clear)
        vw.layer.addSublayer(layer)
        image = vw.asImage()
    }
    return image
}

func createBezierPath(_ mapView : MKMapView, _ polyline : MKPolyline, to view : UIView) -> UIBezierPath  {
    let mapPoints = polyline.points()
    var points = [CGPoint]()
    let max = polyline.pointCount - 1
    for point in 0...max {
       let coordinate = mapPoints[point].coordinate
       points.append(mapView.convert(coordinate, toPointTo: view))
    }
    let path = UIBezierPath(points: points)
    path.lineWidth = 5.0
    return path
}

private func getShapeLayer(path:UIBezierPath, lineColor:UIColor, fillColor:UIColor) -> CAShapeLayer {
    let layer = CAShapeLayer()
    layer.path = path.cgPath
    layer.strokeColor = lineColor.cgColor
    layer.fillColor = fillColor.cgColor
    layer.lineWidth = path.lineWidth

    layer.opacity = 1
    layer.frame = path.bounds
    return layer
}

And to get the image of the view use this extension

import UIKit
extension UIView {
  // Using a function since `var image` might conflict with an existing variable
  // (like on `UIImageView`)
  func asImage() -> UIImage {
    if #available(iOS 10.0, *) {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    } else {
        UIGraphicsBeginImageContext(self.frame.size)
        self.layer.render(in:UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return UIImage(cgImage: image!.cgImage!)
    }
  }
}
Vario
  • 490
  • 4
  • 13