Directions to annotation from current location in MapKit

Hello, Im trying to implement directions (navigation) from my app. All I want to do is to open map app (Google Maps or Apple Maps) with ready to go directions after user click button - in my case leftCalloutAccessoryView called leftButton. I've been trying some options, but without a good result. Here is my code:

    func findPlace(_ places: [Place]) {

      for place in places {

        let annotations = MKPointAnnotation()

        annotations.title = place.name

        annotations.subtitle = place.description

        annotations.coordinate = CLLocationCoordinate2D(latitude:

          place.lattitude, longitude: place.longtitude)

        mapView.addAnnotation(annotations)

      }

    }



    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

        guard !(annotation is MKUserLocation) else { return nil }

        mapView.delegate = self

        let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))

        let identifier = "identifier"

        annotationView.canShowCallout = true

        guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as? MKMarkerAnnotationView else { return nil }

        let rightButton = UIButton(type: .detailDisclosure)

        rightButton.tag = annotation.hash

        annotationView.canShowCallout = true

        annotationView.rightCalloutAccessoryView = rightButton

        

        let leftButton = UIButton(frame: CGRect(

          origin: CGPoint.zero,

          size: CGSize(width: 25, height: 25)))

        leftButton.setBackgroundImage(#imageLiteral(resourceName: "nav"), for: .normal)

        annotationView.leftCalloutAccessoryView = leftButton

        leftButton.addTarget(self, action: #selector(didClickDetailDisclosureNavigation(button:)), for: .touchUpInside)

    @objc func didClickDetailDisclosureNavigation(button: UIButton) {

            let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]

             ????.openInMaps(launchOptions: launchOptions)
//Here I've tried some solutions like `annotations.openInMaps(launchOptions: launchOptions)`- but it does not make sense.
    }

You have already the map displayed in mapView.

Why do you need to open yet another map ?

Anyway, to open an app (e.g., Google Maps), you have first to authorise in plist, then you pass an URL like

  • https://www.google.fr/maps/@48.856912,2.2912735,16z

or with your additional infos :

  • https://www.google.fr/maps/place/Tour+Eiffel/@48.8317476,2.3692721,12.3z/data=!4m5!3m4!1s0x47e66e2964e34e2d:0x8ddca9ee380ef7e0!8m2!3d48.8583701!4d2.2944813

You call:

          var mapPlace = "https://www.google.fr/maps/@48.8317476,2.3692721,12.3z"
          guard var mapsUrl = URL(string: mapPlace) else { return }

          if UIApplication.shared.canOpenURL(mapsUrl) {
               UIApplication.shared.open(mapsUrl, options: [:])
          } else {
              // redirect to safari because the user has not authorised a maps app (not the case here)
               mapsUrl = URL(string: "http://www.google.fr/maps")!
              UIApplication.shared.open(mapsUrl, options: [:])
          }

See details here : https://stackoverflow.com/questions/48072650/how-to-programmatically-check-and-open-an-existing-app-in-swift-4

Your initial question seemed to be about opening the maps app. Which the code I posted should allow you to do.

Does it work ?

I understand you have further need to help user navigate. What do you want to provide exactly ?

  • Route planning inside the map ?

Then passing the additional info should be OK. To find what to pass exactly in HTTP header, do the test in Maps: select an itinerary to search and look at the url ; it is like this (here to go from Paris-Orly airport to Toulouse city):

https://www.google.fr/maps/dir/Paris-Orly,+Orly/Toulouse/@46.1636622,-0.0010695,7z/data=!3m1!4b1!4m14!4m13!1m5!1m1!1s0x47e675b1fa6a3b1d:0x9d78ded743db8422!2m2!1d2.3652422!2d48.7262321!1m5!1m1!1s0x12aebb6fec7552ff:0x406f69c2f411030!2m2!1d1.444209!2d43.604652!3e0

  • Something else ?
Accepted Answer

I would like to do this like here: https://www.raywenderlich.com/7738344-mapkit-tutorial-getting-started

If you want to do things as shown in the tutorial, you need to instantiate a MKMapItem as instructed.

As you are using MKPointAnnotation, you can define an extension like this:

extension MKPointAnnotation {
    var mapItem: MKMapItem {
        let placemark = MKPlacemark(coordinate: self.coordinate)
        return MKMapItem(placemark: placemark)
    }
}

To utilized this extension, you may need to pass the right annotation (which needs to be an MKPointAnnotation) to didClickDetailDisclosureNavigation(button:).

Which may be hard using a usual UIButton. You should better define your own button type which can hold an annotation.

Not too complex:

class AnnotationButton: UIButton {
    var annotation: MKPointAnnotation?
}

(Please do not put the extension and the class definition inside some other type. They need to be toplevel definitions.)


Your code would become something like this:

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        
        guard !(annotation is MKUserLocation) else { return nil }
        
        //mapView.delegate = self //->Move this to `viewDidLoad()`
        
        //This `annotationView` will be shadowed by the following `annotationView`, not used.
        //let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: String(annotation.hash))
        
        let identifier = "identifier"
        
        //annotationView.canShowCallout = true
        
        guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as? MKMarkerAnnotationView else { return nil }
        
        //...
        
        let leftButton = AnnotationButton(frame: CGRect( //<-
            origin: .zero,
            size: CGSize(width: 25, height: 25)))
        leftButton.setBackgroundImage(#imageLiteral(resourceName: "nav"), for: .normal)
        annotationView.leftCalloutAccessoryView = leftButton
        leftButton.addTarget(self, action: #selector(didClickDetailDisclosureNavigation(button:)), for: .touchUpInside)
        //↓↓
        if let pointAnnotation = annotation as? MKPointAnnotation {
            leftButton.annotation = pointAnnotation
        }
        //...
        return annotationView
    }
    
    @objc func didClickDetailDisclosureNavigation(button: AnnotationButton) { //<-

        let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]

        //↓↓
        if let mapItem = button.annotation?.mapItem {
            mapItem.openInMaps(launchOptions: launchOptions)
        }
    }

I haven't tried it myself (as you are not showing many parts of your code), but please try.

@OOPer I've got Value of type 'UIButton' has no member 'annotation' when I try to add:

if let pointAnnotation = annotation as? MKPointAnnotation {

                    leftButton.annotation = pointAnnotation

                }
Directions to annotation from current location in MapKit
 
 
Q