18

I have an MKMapView with 2 buttons on it: zoom in and zoom out.

I noticed that when I use them, I can't pinch the map to zoom anymore until the animation is done.

I have my buttons hooked up to setRegion on a smaller or larger span than it is now.

I tried adding a UIPinchGestureRecognizer to my map to stop the animation and allow the pinch to work. Here is how I did that:

I added a Bool variable that keeps whether the map is currently animating from a tap on the buttons.

func pinchRecognized() {
    if animating {
        var region = self.region
        region.span.latitudeDelta += 0.001
        setRegion(region, animated: false)
        animating = false
    }
}

I override setRegion like this:

override func setRegion(_ region: MKCoordinateRegion, animated: Bool) {
    if (animated)
    {
        animating = true
        super.setRegion(region, animated: animated)

        perform(#selector(noLongerAnimating), with: nil, afterDelay: 1)
    }
    else
    {
        super.setRegion(region, animated: animated)
    }
}

func noLongerAnimating() {
    animating = false
}

These work in stopping the animation, but the pinch is not recognised by the map itself to zoom, even though I do this:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

I guess the setRegion in pinchRecognized breaks it, but I don't know how else to stop the animation.

As requested, the button code, both buttons use this method, zoom in uses 0.5, zoom out uses 2:

func zoomTo(delta: Double, animated: Bool) {
    var region = self.region
    var span = region.span

    span.latitudeDelta *= delta
    span.longitudeDelta *= delta
    if (span.latitudeDelta < 180 && span.longitudeDelta < 180)
    {
        region.span = span
        setRegion(region, animated: animated)
    }
}

Edit: I tried setting the setRegion (the one that stops the animation) in gestureRecognizer:shouldRecognizeSimultaneouslyWith:, but there it doesn't get called while animating the map.

Edit: After trying what @robinb suggested, I saw that my annotations update quicker than my map itself, suggesting that the region gets set, it just waits for something to visually update the map.

vrwim
  • 13,020
  • 13
  • 63
  • 118
  • Can you post your button method code? – CodeChanger Feb 13 '17 at 10:37
  • @CodeChanger Updated the question – vrwim Feb 13 '17 at 10:47
  • any particular reason for overriding setRegion: function ? – aman.sood Feb 17 '17 at 10:40
  • @aman.sood It is to set a variable if the map is animating, this is set before the animation starts and is delayed 1 sec after the animation is started. I do this so that I know if the view is animating in my pinchrecognized, so that I can cancel the animation. – vrwim Feb 17 '17 at 11:26
  • ok. To sum up you require zoom as well as pinch to work without any delay? if yes let me find a solution for you. – aman.sood Feb 17 '17 at 11:38
  • @aman.sood Yes that is what I want. I have a "zoom in" button and a "zoom out" button (with animation) and I want to use pinch to zoom as well. They currently interfere, and I want help on how to make them work together. – vrwim Feb 17 '17 at 12:26

2 Answers2

2

Gesture recognizers don't fire on views that are being animated. Hold the views in a variable/array at the ViewController. Use the code in this post to execute the animations: https://stackoverflow.com/a/13814789/3970581

Here is the testproject: https://github.com/DuncanMC/iOS-CAAnimation-group-demo

Community
  • 1
  • 1
Robin Bruneel
  • 1,063
  • 8
  • 22
  • I thought this was going to work, but using `setRegion` on the map from the gesture recognizer does not seem to update the map. Stepping through the code does not reveal any errors, the `setRegion` gets updated with the right values, it just does not update the region. – vrwim Feb 13 '17 at 10:25
1

MapView animations set isUserInteractionEnabled to false upon start, However if you wanna force that, you can subclass MKMapView and override UIView's touchesBegan method to respond to users interaction. There you can first either call setCenter/setRegion (either the beginning or destination values) without animation to interrupt/quit the animation in progress and set isUserInteractionEnabled to true.

MKMapView uses CATiledLayer and unfortunately there is no easy way to get the region and or centre of the map at the time of interruption. On the other hand, I noticed the animation duration is constant though private so you can use Timer to make a better guess of the map's region when touchesBegun is called

private var isAnimating: Bool = false

override func setRegion(_ region: MKCoordinateRegion, animated: Bool) {
   isAnimating = animated
   ...
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    if isAnimating {
       isAnimating = false
       let region = // Destination region or the approximate region where the animation reached at this moment (although this is a nightmare todo)
       setRegion(region: region, animated: false) // or use setCenter
       isUserInteractionEnabled = true 
    }
}
Lukas
  • 3,423
  • 2
  • 14
  • 26
  • This gives the same result, the map uses setRegion(), which breaks the current pinch you just started. – vrwim Feb 20 '17 at 08:40