I am trying to draw a path behind the user as they move, tracking their path (like Strava or FitBit apps do when a user starts a workout). So far, the map centres on the user's location but does not start drawing when the user moves. I have tried to implement this with renderForOverlay, but it fails to do so when tested. The code is as follows:
ViewController.swift
import UIKit
import MapKit
import CoreLocation
class StartWorkoutViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
@IBOutlet weak var mapsView: MKMapView!
@IBOutlet weak var startButton: UIButton!
var locationManager: CLLocationManager!
var allLocations: [CLLocation] = []
@IBAction func startButton(_ sender: Any) {
// Start the workout
}
override func viewDidLoad() {
super.viewDidLoad()
// Request user's current location
locationManager = CLLocationManager()
locationManager?.requestAlwaysAuthorization()
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.startUpdatingLocation()
locationManager?.startUpdatingHeading()
locationManager?.delegate = self
mapsView?.showsUserLocation = true
mapsView?.mapType = MKMapType(rawValue: 0)!
mapsView?.userTrackingMode = .follow
mapsView?.delegate = self
let noLocation = CLLocationCoordinate2D()
let viewRegion = MKCoordinateRegion(center: noLocation, latitudinalMeters: 100, longitudinalMeters: 100)
mapsView?.setRegion(viewRegion, animated: true)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Add location to the array and prepare to draw a line between last location and current location
print("Location Updated")
allLocations.append(locations[0])
let previousLocation = allLocations[allLocations.count - 1]
let newLocation = locations[0]
let previousCoordinates = previousLocation.coordinate
let newCoordinates = newLocation.coordinate
var area = [previousCoordinates, newCoordinates]
let polyline = MKPolyline(coordinates: &area, count: area.count)
mapsView.addOverlay(polyline)
}
// DOES NOT WORK
func mapView(_ mapsView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay is MKPolyline {
let polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.red
polylineRenderer.lineWidth = 4
return polylineRenderer
} else {
return MKPolylineRenderer()
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// If the authorisation for the user's location has changed, ask again
if status != .authorizedAlways {
locationManager = CLLocationManager()
locationManager?.requestAlwaysAuthorization()
}
}
}
Thank you!
The problem is that you’re grabbing a location, adding it to the array, and then creating a polyline from the last location in the array, allLocations[allLocations.count - 1]
, (which is now the current location) to the current location (i.e. to itself).
So, grab the last item, previousCoordinate
, from the array before you add the new location to it:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let currentLocation = locations.first(where: { $0.horizontalAccuracy >= 0 }) else {
return
}
let previousCoordinate = allLocations.last?.coordinate
allLocations.append(currentLocation)
if previousCoordinate == nil { return }
var area = [previousCoordinate!, currentLocation.coordinate]
let polyline = MKPolyline(coordinates: &area, count: area.count)
mapsView.addOverlay(polyline)
}
I'd also suggest, as you see above, checking for the horizontal accuracy of the location update, to make sure it’s non-negative.
Anyway, that yields:
A few other observations:
I'd suggest retiring the noLocation
pattern in viewDidLoad
. My above pattern doesn't require that dummy value in the array.
Another issue is that in didChangeAuthorization
, you are instantiating a new CLLocationManager
and not setting its properties. You are therefore losing the configuration of the original CLLocationManager
in viewDidLoad
. There’s no need to instantiate another one, but if you do, remember to configure it properly.