4
votes

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.

2
You probably need to show how you convert from gps coordinates to UIView coordinates.MirekE
@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
Capture the MKPolyline as image, using stackoverflow.com/questions/4334233/…Sachin Vas
@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
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

2 Answers

1
votes

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

0
votes

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!)
    }
  }
}