Add annotation on click and show callout, callout disappears

Viewed 139

I am trying to add an annotation to the map on click, and would also like to show the callout.

I have added a tap gesture recognizer:

override func viewDidLoad() {
    super.viewDidLoad()
        
    mapView.delegate = self
    
    let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGesture))
    gestureRecognizer.numberOfTapsRequired = 1
    gestureRecognizer.cancelsTouchesInView = true
    
    mapView.addGestureRecognizer(gestureRecognizer)
    gestureRecognizer.delegate = self
}

And on tap:

@objc func tapGesture(_ sender: UITapGestureRecognizer? = nil) {
    if let removeAnnotation = annotation {
        mapView.removeAnnotation(removeAnnotation)
    }
    
    guard let point = sender?.location(in: mapView) else { return }
    let annotation = MKPointAnnotation()
    annotation.coordinate = mapView.convert(point, toCoordinateFrom: mapView)
    annotation.title = "Title"
    annotation.subtitle = "Subtitle"
    mapView.addAnnotation(annotation)
    
    self.annotation = annotation
}

From there I have a map delegate to show the annotation:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    let identifier = "Annotation"
    var annotationView: MKAnnotationView? = nil
    
    if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
      dequeuedView.annotation = annotation
      annotationView = dequeuedView
    } else {
        annotationView = MKAnnotationView(annotation: annotation,reuseIdentifier: identifier)
        annotationView?.canShowCallout = true
        annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
        annotationView?.image = UIImage(named: "icon")
    }
    
    return annotationView
}

And when the annotationview is added I select the annotation to show the callout:

func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
    if let annotation = views.first?.annotation {
        mapView.selectAnnotation(annotation, animated: true)
    }
}

However shortly after the annotation callout is shown it disappears

enter image description here

Digging in a little deeper, I think that there is a tap gesture recognizer on the mapview that is fired after my tap that adds the callout, which then deselects the annotation removing the callout.

Which lead me to add a delegate method to my tap gesture;

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    if (otherGestureRecognizer is UITapGestureRecognizer) {
        return true
    }
    
    return false
}

So at that point I am disabling all existing MapView taps gestures, not really what I want. Looking further at all the tap gestures that are on the map view, this one (_handleSelectGestureToDeselect) looks to be handling the deselect of annotations on map click. Which I imagine what is deselecting my annotation.

<UITapGestureRecognizer: 0x7f99e2418db0; state = Possible; delaysTouchesEnded = NO; view = <_MKMapContentView 0x7f99b2404bc0>; target= <(action=_handleSelectGestureToDeselect:, target=<MKMapView 0x7f99e302ae00>)>; must-fail = {
        <UITapGestureRecognizer: 0x7f99e2418a90; state = Possible; delaysTouchesEnded = NO; view = <_MKMapContentView 0x7f99b2404bc0>; target= <(action=_handleSelectGestureToSelect:, target=<MKMapView 0x7f99e302ae00>)>>,
        <UITapGestureRecognizer: 0x7f99e2417b50; state = Possible; view = <_MKMapContentView 0x7f99b2404bc0>; target= <(action=handleTwoFingerTap:, target=<MKMapGestureController 0x7f99e2409940>)>; numberOfTouchesRequired = 2>,
        <MKVariableDelayTapRecognizer: 0x7f99e24077e0; baseClass = UITapGestureRecognizer; state = Possible; view = <_MKMapContentView 0x7f99b2404bc0>; target= <(action=handleDoubleTap:, target=<MKMapGestureController 0x7f99e2409940>)>; numberOfTapsRequired = 2>,
        <_MKOneHandedZoomGestureRecognizer: 0x7f99e241e7b0; baseClass = UIGestureRecognizer; state = Possible; delaysTouchesEnded = NO; view = <_MKMapContentView 0x7f99b2404bc0>; target= <(action=handlePanZoom:, target=<MKMapGestureController 0x7f99e2409940>)>>,
        <UIPanGestureRecognizer: 0x7f99e240bb60; state = Possible; view = <_MKMapContentView 0x7f99b2404bc0>; target= <(action=handlePan:, target=<MKMapGestureController 0x7f99e2409940>)>>
    }>

However all I can do is get a list of gesture recognizers for the map view, there is really no good way to pick out a specific tap gesture and disable it.

Last thing. This works but is a complete hack and makes the UI laggy. I can add a delay, seems it needs to be over 500 milliseconds to be clear of the mapview gesture unselecting the annotation:

let after = DispatchTime.now() + .milliseconds(500)
DispatchQueue.main.asyncAfter(deadline: after) {
   mapView.selectAnnotation(annotation, animated: true)
}

So short of making the UI super slow, I don't really have any good options. Anyone run into this an come up with a work around? Is this a bug that I should file with Apple? Seems I should be able to add a gesture recognizer that cancels/overrides the other internal mapview gesture recognizers.

0 Answers
Related