5

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?

Isuru
  • 27,485
  • 56
  • 174
  • 278
BOB
  • 85
  • 1
  • 6

1 Answers1

16

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.

Rob
  • 371,891
  • 67
  • 713
  • 902
  • but when i set the mapView.delgate = self it crashes because of my geocode that I have for something else. Do you have any idea how I can fix this? – BOB Nov 30 '15 at 01:09
  • The only reason I can imagine that `mapView.delegate = self` would crash was if `mapView` was implicitly unwrapped, but was `nil` (e.g., the outlet wasn't hooked up). – Rob Nov 30 '15 at 02:02