iosswiftmapkitcallouts

How do I make a pin annotation callout in Swift?


I tried to make the callout work but that didn't happen as I did something wrong in my prepare for segue. I want to know how to be able to make a pin annotation callout to another view?


Solution

  • The process of segueing to another scene when the button in the callout is tapped is like so:

    1. Set the delegate of the map view to be the view controller. You can do this either in Interface Builder's "Connections Inspector" or programmatically. You want to specify that the view controller conforms to MKMapViewDelegate, too.

    2. When you create the annotation, make sure to set the title, too:

      let annotation = MKPointAnnotation()
      annotation.coordinate = coordinate
      annotation.title = ...
      mapView.addAnnotation(annotation)
      
    3. Define an annotation view subclass with callout with a button:

      class CustomAnnotationView: MKPinAnnotationView {  // or nowadays, you might use MKMarkerAnnotationView
          override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
              super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
      
              canShowCallout = true
              rightCalloutAccessoryView = UIButton(type: .infoLight)
          }
      
          required init?(coder aDecoder: NSCoder) {
              super.init(coder: aDecoder)
          }
      }
      
    4. Instruct your MKMapView to use this annotation view. iOS 11 has simplified that process, but I’ll describe how to do it both ways:

      • If your minimum iOS version is 11 (or later), you’d just register the custom annotation view in as the default and you’re done. You generally don't implement mapView(_:viewFor:) at all in iOS 11 and later. (The only time you might implement that method is if you needed to register multiple reuse identifiers because you had multiple types of custom annotation types.)

        override func viewDidLoad() {
            super.viewDidLoad()
        
            mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
        }
        
      • If you need to support iOS versions prior to 11, you would make sure to specify your view controller as the delegate for the MKMapView and then would implement mapView(_:viewFor:):

        extension ViewController: MKMapViewDelegate {
            func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
                if annotation is MKUserLocation { return nil }
        
                let reuseIdentifier = "..."
        
                var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
        
                if annotationView == nil {
                    annotationView = CustomAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
                } else {
                    annotationView?.annotation = annotation
                }
        
                return annotationView
            }
        }
        

      For example, that yields a callout something that looks like the following, with the .infoLight button on the right:

      enter image description here

    5. Implement calloutAccessoryControlTapped that programmatically performs the segue:

      func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
          performSegue(withIdentifier: "SegueToSecondViewController", sender: view)
      }
      

      Obviously, this assumes that you've defined a segue between the two view controllers.

    6. When you segue, pass the necessary information to the destination scene. For example, you might pass a reference to the annotation:

      override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
          if let destination = segue.destination as? SecondViewController,
              let annotationView = sender as? MKPinAnnotationView {
              destination.annotation = annotationView.annotation as? MKPointAnnotation
          }
      }
      

    For more information, see Creating Callouts in the Location and Maps Programming Guide.

    For Swift 2 implementation of the above, see previous revision of this answer.