11

There's almost a 0.5 second delay between tapping and when the callout is shown for an annotation on an MKMapView.

Does anyone know why this is the case, and how I can make it instantaneously responsive when a user taps on the map?

This happens even with the user location annotation that displays "Current Location" in a callout when tapped. I want it to display that instantly when tapped, no weird delay.

EDIT: I think it's due to the setSelected function that didSelectAnnotationView calls. setSelected has an 'animated' property that might be slowing it. How do I eliminate that animation?

ARMATAV
  • 604
  • 6
  • 26

5 Answers5

16

after doing a lot of research I found a solution for this! It's a tiny tiny bit hacky, but it works like a charm.

The secret is, that when turning off zoom for the map, the didSelect listener triggers immediately. As we need zoom (of course), what we need to do is, to temporarily disable the zoom, just for the moment of the didSelect event!

In Swift:

let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
gestureRecognizer.numberOfTapsRequired = 1
gestureRecognizer.numberOfTouchesRequired = 1
gestureRecognizer.delegate = self
mapView.addGestureRecognizer(gestureRecognizer)

and

@objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
    // disabling zoom, so the didSelect triggers immediately
    mapView.isZoomEnabled = false
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.mapView.isZoomEnabled = true
    }
}

This gesture event triggers before the didSelect event. So the moment the didSelect events is called, zoom is turned off and it does trigger without delay!

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
    mapView.isZoomEnabled = true // Not really necessary
    // Triggered immediately, do something
}

Note: This disables doubleTap gestures for the map, but I guess they are not used too much. So if you want a quick response, you need to live with it!

tobidude
  • 480
  • 1
  • 7
  • 11
  • 1
    OMG! This is amazing! I was going to re-implement annotation selection with my own gesture recognizers which would involve handling of quite a few edge cases. This has saved me a ton of time. Thank you! – Dmitry Miller Apr 06 '21 at 01:58
  • 1
    Perfect Solution! Bravo – yaozhang Nov 08 '22 at 03:15
9

Unfortunately, there's nothing you can do about this. It's for the exact same reason that tapping links in Mobile Safari is slow: the gesture recognizers have to jostle for a while to decide whether you might be scrolling (dragging) before they agree that you are tapping.

So, it has nothing to do with the animation. It's just the nature of gesture recognition in this situation.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • What if I remove the scroll/drag/other gestures? – ARMATAV Mar 13 '16 at 02:37
  • Or what if I add a button on top of each annotationView and use that to dictate whether or not it's selected? Because buttons do not have the jostling issue. – ARMATAV Mar 13 '16 at 02:37
  • If you're thinking of removing the scroll gesture, then why don't you just turn off `scrollEnabled` and `zoomEnabled`? – matt Mar 13 '16 at 02:43
  • Need the map to scroll and zoom... BUT I see what you mean -- the MapView is detecting the touch, then checking if it's a scroll, then a drag, then a tap. Is there a way I can tell the MapView "let the annotations do their checks first, then check if you got touched"? – ARMATAV Mar 13 '16 at 02:45
  • Because there's literally a 250ms lag time when tapping annotations just to, for example, change their color -- demonstrated my app to some people and that part killed their exploration of it. – ARMATAV Mar 13 '16 at 02:46
  • Maybe this? http://stackoverflow.com/questions/17200910/mkannotationview-and-tap-detection – ARMATAV Mar 13 '16 at 02:48
  • I see what you're saying but it's hard for me to believe you'd be able to hook that deeply into the workings of the map view... What I would do is try this with a very plain vanilla map view with one simple annotation and no other code, and prove to yourself that this always happens. If it doesn't, then it's the fault of other code of yours that you haven't told us about. – matt Mar 13 '16 at 02:48
  • 1
    It does always happen, I tried it with a vanilla MapView. Hell, try it in FindMyFriends and tap your location -- that's the exact same amount of 'delay' that I am trying to eliminate. However, it's not the callout -- even with canShowCallout = false it's still the same delay. – ARMATAV Mar 13 '16 at 02:54
  • Actually this seems legit -- http://stackoverflow.com/questions/19692098/prevent-touch-events-on-mkmapview-being-detected-when-a-mkannotation-is-tapped?rq=1 – ARMATAV Mar 13 '16 at 03:03
  • Clearly there is something I can do about it in this situation, I'll try it in a moment. – ARMATAV Mar 13 '16 at 03:06
2

@tobidude's answer works, but it can be slightly improved.

Instead of adding the TapGestureRecognizer to the mapview, add it your AnnotationView subclass on initialization. This way, you don't have to ignore all double taps on the mapview.

In Swift 5:

override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
    super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    clusteringIdentifier = MKMapViewDefaultClusterAnnotationViewReuseIdentifier
    setupGestureRecognizerToPreventInteractionDelay()
}

Then:

private func setupGestureRecognizerToPreventInteractionDelay() {
    let quickSelectGestureRecognizer = UITapGestureRecognizer()
    quickSelectGestureRecognizer.delaysTouchesBegan = false
    quickSelectGestureRecognizer.delaysTouchesEnded = false
    quickSelectGestureRecognizer.numberOfTapsRequired = 1
    quickSelectGestureRecognizer.numberOfTouchesRequired = 1
    quickSelectGestureRecognizer.delegate = self
    self.addGestureRecognizer(quickSelectGestureRecognizer)
}

Lastly:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    mapView?.isZoomEnabled = false
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.mapView?.isZoomEnabled = true
    }
    return false
}
Plato
  • 111
  • 2
  • 7
  • How can you get mapView from AnnotationView ? – LiangWang Dec 07 '22 at 14:02
  • @LiangWang I added a computed property of type MKMapView? which loops through its parent views one by one until it finds a view which is a MKMapView – Plato Dec 09 '22 at 22:06
1

My solution is to enable zooming on map and add individual tap handler on MKAnnotationView subclass.

Native zoom MKOneHandedZoomGestureRecognizer, MKZoomingPanGestureRecognizer and MKConditionalPanZoomGestureRecognizer will work.

But also immediate reaction on tap will be handled by button or tap recognizer on annotation view.

AlKozin
  • 904
  • 8
  • 25
0

With this code, you can make it work fairly easy.

Add a new file and copy paste this:

import Foundation
import MapKit

class MKAnnotationViewWithoutSlowTap: MKAnnotationView, UIGestureRecognizerDelegate {
    unowned let map: MKMapView

    init(annotation: MKAnnotation, reuseIdentifier: String, map: MKMapView) {
        self.map = map

        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

        setupGestureRecognizerToPreventInteractionDelay()
    }

    required init?(coder: NSCoder) {
        fatalError("Not used")
    }

    private func setupGestureRecognizerToPreventInteractionDelay() {
        let quickSelectGestureRecognizer = UITapGestureRecognizer()

        quickSelectGestureRecognizer.delaysTouchesBegan = false
        quickSelectGestureRecognizer.delaysTouchesEnded = false
        quickSelectGestureRecognizer.numberOfTapsRequired = 1
        quickSelectGestureRecognizer.numberOfTouchesRequired = 1
        quickSelectGestureRecognizer.delegate = self

        addGestureRecognizer(quickSelectGestureRecognizer)
    }

    @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        map.isZoomEnabled = false

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            self?.map.isZoomEnabled = true
        }

        return false
    }
}

Next, change your calls from the initializer for MKAnnotationView to MKAnnotationViewWithoutSlowTap

J. Doe
  • 12,159
  • 9
  • 60
  • 114