I have been following Sean Allens tutorial on MapKit and Directions. I used the code to fit it to my SwiftUI project.
Everything works fine, the screen zoom to fit start and destination on the screen, and calculated the rout correctly.
However, it does to actually display the route.
The main Code in my ViewModel (note: .delegate = self is actually in a function, I wrote it at top level for easier understanding how I am setting the Delegat)
func checkForLocationServices() {
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager!.delegate = self
}
}
//MARK: Directions
func getDirections() {
guard let location = locationManager?.location?.coordinate else {
return
}
let request = createDirectionRequest(from: location)
let directions = MKDirections(request: request)
directions.calculate { calcResponse, error in
guard let response = calcResponse else { return }
for route in response.routes {
print("Route \(route.name) is \(route.distance) long")
self.mapView.addOverlay(route.polyline, level: .aboveRoads)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
}
func createDirectionRequest(from coordinate: CLLocationCoordinate2D) -> MKDirections.Request {
let startLocation = MKPlacemark(coordinate: coordinate)
let destinationLocation = MKPlacemark(coordinate: bbcLocation.coordinate)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: startLocation)
request.destination = MKMapItem(placemark: destinationLocation)
request.transportType = .automobile
request.requestsAlternateRoutes = true
return request
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay as! MKPolyline)
renderer.strokeColor = .blue
renderer.lineWidth = 5.0
print(renderer.strokeStart)
print(renderer.strokeEnd)
return renderer
}
The Delegate function rendererFor is never called, despite me setting the Delegate as seen above. I also checked via Breakpoints that it is actually set, as well as downloaded Seans source code, which set the Delegate the same way I did.
My VM conforms to the MKMapViewDelegate Protocol.
Another user had the same problem, but since he got no response, I thought I might try I someone can help me.
Core-question: Why is my delegate method not called, despite me setting it correctly, and knowing (through console logs) that my route is found, calculated, and in fact zoomed in on?
If you need more code, feel free to ask.
Thanks in advance.
*Seans Project
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func getDirections() {
guard let location = locationManager.location?.coordinate else {
//TODO: Inform user we don't have their current location
return
}
let request = createDirectionsRequest(from: location)
let directions = MKDirections(request: request)
resetMapView(withNew: directions)
directions.calculate { [unowned self] (response, error) in
//TODO: Handle error if needed
guard let response = response else { return } //TODO: Show response not available in an alert
for route in response.routes {
self.mapView.addOverlay(route.polyline)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)
}
}
}
func createDirectionsRequest(from coordinate: CLLocationCoordinate2D) -> MKDirections.Request {
let destinationCoordinate = getCenterLocation(for: mapView).coordinate
let startingLocation = MKPlacemark(coordinate: coordinate)
let destination = MKPlacemark(coordinate: destinationCoordinate)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: startingLocation)
request.destination = MKMapItem(placemark: destination)
request.transportType = .automobile
request.requestsAlternateRoutes = true
return request
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay as! MKPolyline)
renderer.strokeColor = .blue
return renderer
}
``
Related
I tried to add a polyline from the user's location to destination by using the following code, I am sure that I have conformed to the delegate and ensure that the user is in authorizedAlway mode in the authorization status. However, the console generated this error message saying "[UserSession] maps short session requested but session sharing is not enabled" I can not find anything related on how to solve this error.
func generatePolyLine(toDestination destination: MKMapItem) {
let request = MKDirections.Request()
//start from the user's current location to find the ride
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.transportType = .automobile
let directionRequest = MKDirections(request: request)
directionRequest.calculate { response, error in
if let error = error {
print("Error calculating direction request \(error)")
}
guard let response = response else { return }
self.route = response.routes.first
guard let polyLine = self.route?.polyline else { return }
self.mapView.addOverlay(polyLine, level: .aboveRoads)
}
}
Did you add the delegate method to specify the renderer? Something like:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polyline = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.lineWidth = 3.0
renderer.alpha = 0.5
renderer.strokeColor = UIColor.blue
return renderer
}
if let circle = overlay as? MKCircle {
let renderer = MKCircleRenderer(circle: circle)
renderer.lineWidth = 3.0
renderer.alpha = 0.5
renderer.strokeColor = UIColor.blue
return renderer
}
return MKCircleRenderer()
}
So I am creating an app to track a user's running routes. I am to show a polyline that represents where a user has ran, between a start annotation that is dropped when user begins and a end annotation that is dropped when user clicks a finish button. Seeing as how someone can run pretty much anywhere, is there a way to draw a polyline that is Not limited to just main roads and streets? For example if someone runs somewhere that does not have main roads/streets, how would I show the polyline going through that area on the map? (See Image) Or is that not even possible in swift?
In addition, is it possible to fix these gaps?
Here is my code for rendering the polyline:
func drawPolyline() {
guard let startingCoordinate = self.startingCoordinate, let endingCoordinate = self.endingCoordinate else { return }
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: startingCoordinate))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: endingCoordinate))
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate { [weak self] (response, error) in
guard let self = self else { return }
guard error == nil else { return }
guard response != nil else { return }
if let route = response?.routes.first {
self.mapView.addOverlay(route.polyline)
self.mapView.setVisibleMapRect(route.polyline.boundingMapRect, edgePadding: UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15), animated: true)
}
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay
renderer.strokeColor = .systemBlue
renderer.lineWidth = 3
return renderer
}
// code to get coordinate for annotations
guard let coordinate = locationManager.location?.coordinate else { return }
let annotation = MapAnnotation(title: title, coordinate: coordinate)
mapView.addAnnotation(annotation)
You will get an updated location every time it changes, and you can either build up an array of locations, or store them in a database for later retrieval.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
// the last location presented here is the current position of the runner
self.locations.append(locations.last)
// store this in a database, or as a class variable
}
You can set the desired accuracy when you initialise your location manager
locationManager.desiredAccuracy = kCLLocationAccuracyBest
and then when you're creating the map, create a line showing the runner's route
let polyline = MKPolyline(coordinates: self.locations, count: self.locations.count)
self.mapView.addOverlay(polyline)
Here's an example of the output - looks like the finish is in the middle of the run, but that's because I was heading back home
I am creating a route in the iPhone simulator from the user current location to another point. The route is created fine and I am able to grab the time and distance it would take to travel to the destination but the route polyline/overlay isn't displaying. The code is quite large so below are the essential parts I think which are needed to explain the issue.
func tapTocreateRoute(){
location = CLLocationCoordinate2D(latitude: 37.787359, longitude: -122.41)
let placemark = MKPlacemark(coordinate: location)
let mapItem = MKMapItem(placemark: placemark)
routeCreater(mapItem: mapItem)
}
func routeCreater(mapItem: MKMapItem){
let destinationPin = customPin(pinTitle: "end", pinSubtitle: "testing", location: carLocation)
self.mapView.addAnnotation(destinationPin)
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = mapItem
request.transportType = MKDirectionsTransportType.walking
let directions = MKDirections(request: request)
directions.calculate { (response, error) in
guard let response = response else {
print("MKDIRECTION error: \(error?.localizedDescription ?? "Error not found")")
return
}
self.route = response.routes[0]
self.mapView.addOverlay(self.route.polyline, level: .aboveRoads)
self.mapView.setVisibleMapRect(self.route.polyline.boundingMapRect, animated: true)
let rect = self.route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegion(rect), animated: true)
self.walkingTime = self.route.expectedTravelTime //displays correctly
self.walkingDistance = self.route.distance // displays correctly
}
}
//Line Renderer for Route
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let lineRenderer = MKPolylineRenderer(overlay: overlay)
lineRenderer.strokeColor = UIColor(red: 216/255, green: 71/255, blue: 30/255, alpha: 1)
lineRenderer.lineWidth = 3.0
return lineRenderer
}
am I missing something with the rendering?
Thanks for the help
Did you make sure to assign the MapView's delegate so it knows where to look for the renderer?
Ive made a view controller which tracks my location once the view is opened. Once the routeButton is pressed, I want the blue Polyline to show. However it does not. Ive set the delegates and wrote the rendering function but it still does not show. I don't get an error when calculating directions. It does run the self.map.add(route.polyline, level: .aboveRoads) line. But no polyline is added to the map.
Ive read multiple questions relating to this on here, but nothing fixes my issue.
Any help would be greatly appreciated.
Heres my code:
class ThirdViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate{
var startLocCoord = CLLocation()
var endLocCoord = CLLocation()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.05,0.05)
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude,location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
map.setRegion(region, animated: true)
self.map.showsUserLocation = true
}
#IBAction func routeButton (_ sender: Any) {
let destinationLoc = CLLocationCoordinate2D(latitude: endLocCoord.coordinate.latitude, longitude: endLocCoord.coordinate.longitude)
let sourceLoc = CLLocationCoordinate2D(latitude: startLocCoord.coordinate.latitude, longitude: startLocCoord.coordinate.longitude)
let sourcePlaceMark = MKPlacemark(coordinate: sourceLoc)
let destinationPlaceMark = MKPlacemark(coordinate: destinationLoc)
let directionRequest = MKDirectionsRequest()
directionRequest.source = MKMapItem(placemark: sourcePlaceMark)
directionRequest.destination = MKMapItem(placemark: destinationPlaceMark)
directionRequest.transportType = .automobile
let directions = MKDirections(request: directionRequest)
directions.calculate { (response, error) in
guard let directionResponse = response else {
if let error = error {
print("Error with directions==\(error.localizedDescription)")
}
return
}
let route = directionResponse.routes[0]
print("route ------->", route)
self.map.add(route.polyline, level: .aboveRoads)
let rect = route.polyline.boundingMapRect
self.map.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
print(self.srcLat, self.srcLon, self.lat, self.long)
}
override func viewDidLoad() {
manager.desiredAccuracy = kCLLocationAccuracyBest
UIApplication.shared.isIdleTimerDisabled = true
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
manager.allowsBackgroundLocationUpdates = true
manager.pausesLocationUpdatesAutomatically = false
self.map.isZoomEnabled = false
self.map.isScrollEnabled = false
self.map.delegate = self
map.mapType = MKMapType.standard
}
func mapView(_mapView: MKMapView, rendererFor overlay: MKOverlay)-> MKOverlayRenderer{
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 5.0
print("render called")
return renderer
}
}
Thanks
It's probably just a typo. Replace
func mapView(_mapView: MKMapView, rendererFor overlay: MKOverlay)-> MKOverlayRenderer{
with
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
There is no error but I can not get the location from my current location to what I search. Can someone please help. It says this with I click on the Car logo to get directions: MKDirectionsErrorCode=7, NSLocalizedDescription=Directions Not Available}
I am only missing the line to get to the direction. When I click on the car logo, it just does not do anything but zoom out to my current location. I am missing anything or did something wrong?
Here is my code:
protocol HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark)
}
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView! // Handle the Map Kit View
var selectedPin:MKPlacemark? = nil //Any incoming placemarks
// Destination for directions*********
var destination:MKMapItem = MKMapItem()
var MyPosition = CLLocationCoordinate2D()
var resultSearchController:UISearchController? = nil // Keep the UISearchController in memory after it's created
var locationManager = CLLocationManager() // Core Location
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest // Accuracy level
locationManager.requestWhenInUseAuthorization() // Triggers the location permission dialog
locationManager.startUpdatingLocation()
//locationManager.requestLocation() // Trigers a one-time location request
// Set up the search results table
let locationSearchTable = storyboard!.instantiateViewController(withIdentifier: "LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController?.searchResultsUpdater = locationSearchTable
// Set up the search bar
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for places"
navigationItem.titleView = resultSearchController?.searchBar
// Configure the UISearchController appearance
resultSearchController?.hidesNavigationBarDuringPresentation = false
resultSearchController?.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
locationSearchTable.mapView = mapView // Passes along a handle of the mapView
locationSearchTable.handleMapSearchDelegate = self // Handles the map search
}
//Getting direction of location
#objc func getDirections(sender: AnyObject){
if let selectedPin = selectedPin {
let mapItem = MKMapItem(placemark: selectedPin)
//let launchOptions = [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving]
//mapItem.openInMaps(launchOptions: launchOptions)
let request = MKDirectionsRequest()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.requestsAlternateRoutes = false
let directions = MKDirections(request: request)
// 8.
directions.calculate {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
let route = response.routes[0]
self.mapView.add((route.polyline), level: MKOverlayLevel.aboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
}
}
/*
directions.calculate(completionHandler: {(response: MKDirectionsResponse!, error: Error!) in
if error != nil {
print("Error \(error)")
} else {
self.displayRout(response)
var overlays = self.mapView.overlays
for route in response.routes {
self.mapView.add(route.polyline, level: MKOverlayLevel.aboveRoads)
for next in route.steps {
print(next.instructions)
}
}
}
})
}
*/
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 4.0
return renderer
}
}
//destination = MKMapItem(placemark: selectedPin!)
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController : CLLocationManagerDelegate {
// When user responds to the permission dialog
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
}
}
// When location information comes back (Zoom to the user's current location)
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
//****
MyPosition = location.coordinate
locationManager.stopUpdatingLocation()
let span = MKCoordinateSpanMake(0.01, 0.01)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
}
}
// Print out the error
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error finding location: \(error.localizedDescription)")
}
}
// Searches the location and city/state
extension ViewController: HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
mapView.removeAnnotations(mapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "\(city) \(state)"
}
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.01, 0.01)
let region = MKCoordinateRegionMake(placemark.coordinate, span)
mapView.setRegion(region, animated: true)
}
}
// Shows Pins and Car Logo
extension ViewController : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.pinTintColor = UIColor.red
pinView?.canShowCallout = true
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare))
button.setBackgroundImage(UIImage(named: "Car"), for: .normal)
button.addTarget(self, action: #selector(getDirections), for: .touchUpInside)
pinView?.leftCalloutAccessoryView = button
return pinView
}
}
Have a look at below code for reference hope it helps :
1 using two locations here as source and as destination and drawing a path according to locations
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
let sourceLocation = CLLocationCoordinate2D(latitude: 40.759011, longitude: -73.984472)
let destinationLocation = CLLocationCoordinate2D(latitude: 40.748441, longitude: -73.985564)
// 3.
let sourcePlacemark = MKPlacemark(coordinate: sourceLocation, addressDictionary: nil)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation, addressDictionary: nil)
// 4.
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
// 5.
let sourceAnnotation = MKPointAnnotation()
sourceAnnotation.title = "Times Square"
if let location = sourcePlacemark.location {
sourceAnnotation.coordinate = location.coordinate
}
let destinationAnnotation = MKPointAnnotation()
destinationAnnotation.title = "Empire State Building"
if let location = destinationPlacemark.location {
destinationAnnotation.coordinate = location.coordinate
}
// 6.
self.mapView.showAnnotations([sourceAnnotation,destinationAnnotation], animated: true )
// 7.
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceMapItem
directionRequest.destination = destinationMapItem
directionRequest.transportType = .walking
// Calculate the direction
let directions = MKDirections(request: directionRequest)
// 8.
directions.calculate {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
let route = response.routes[0]
self.mapView.add((route.polyline), level: MKOverlayLevel.aboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 4.0
return renderer
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Output :