b2cloud

4th July 2014

MKMapView: determining whether region change is from user interaction

Guides | Tutorial By 2 years ago

Knowing whether an MKMapView‘s region has changed by the user or by other means seems to be asked time and time again.

I’ve seen various solutions. Most of them come down to adding your own UIPanGestureRecognizer to the MKMapView. There are problems with this solution. Firstly, you can actually change the map’s region without panning. You can double tap to zoom in, two finger tap to zoom out, and also pinch to zoom. Adding four gesture recognizers doesn’t sound like a good idea. Secondly, when the panning gesture ends the map’s region continues to decelerate.

In one of my recent apps I needed a better solution.

Instead of adding my own gesture recognizer(s), I decided to look at the ones that already exist, as under the hood the map uses gesture recognizers itself.

First, you need to use the MKMapView‘s delegate and implement the mapView:regionWillChangeAnimated: and mapView:regionDidChangeAnimated: methods. In my code I assume the map is already setup and the delegate is also set.

During the will change method, we will take a peak at the gesture recognizers and their state. If we find one that has begun, then we set a flag so when the did change method is called we know whether or not it was caused by user interaction.

//	MapViewController.h
@interface MapViewController : UIViewController

@end

.

//	MapViewController.m
@interface MapViewController()

@property (nonatomic, assign) BOOL nextRegionChangeIsFromUserInteraction;

@end

@implementation MapViewController

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
	UIView* view = mapView.subviews.firstObject;
	
	//	Look through gesture recognizers to determine
	//	whether this region change is from user interaction
	for(UIGestureRecognizer* recognizer in view.gestureRecognizers)
	{
		//	The user caused of this...
		if(recognizer.state == UIGestureRecognizerStateBegan
		   || recognizer.state == UIGestureRecognizerStateEnded)
		{
			self.nextRegionChangeIsFromUserInteraction = YES;
			break;
		}
	}
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
	if(self.nextRegionChangeWasFromUserInteraction)
	{
		self.nextRegionChangeIsFromUserInteraction = NO;
		
		//	Perform code here
	}
}

@end
  • Felix Alcala

    Thanks for sharing this. Great idea and so much simpler than the other proposals! Thanks again, Made my day.

  • doug

    You sir, are a gentleman and a scholar. Exactly what I was looking for (stack overflow failed me).

    In order to know when to redo a query to get annotations on a map region (of course I don’t want to do it until the gesture whatever it may be ends).

  • WilfredGreyling

    This is great thanks!

    A quick and dirty Swift translation:

    func mapView(mapView: MKMapView!, regionWillChangeAnimated animated: Bool) {
    var tempView = mapView.subviews.first as! UIView
    var listOfGestures = tempView.gestureRecognizers as! [UIGestureRecognizer]
    for recognizer in listOfGestures {
    if recognizer.state == UIGestureRecognizerState.Began || recognizer.state == UIGestureRecognizerState.Ended {
    self.regionChangeIsFromUserInteraction = true
    break
    }
    }
    }

    func mapView(mapView: MKMapView!, regionDidChangeAnimated animated: Bool) {
    if self.regionChangeIsFromUserInteraction {
    self.regionChangeIsFromUserInteraction = false
    //Code here
    }
    }

  • Roberto Lopez

    Hi Tom, thanks for the tip. I have one doubt about it:

    In case I wanted to respond differently for different user gestures ( Swipe vs Tap vs Pin etc ), how can i do it in Swift ?

    Thanks

    * In case anyone needs Tom´s code in Swift 2:

    func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {

    let tempView = mapView.subviews.first

    let listOfGestures = tempView!.gestureRecognizers

    for recognizer in listOfGestures! {

    if recognizer.state == UIGestureRecognizerState.Began || recognizer.state == UIGestureRecognizerState.Ended {

    self.regionChangeIsFromUserInteraction = true

    break

    }

    }

    }

    func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {

    if self.regionChangeIsFromUserInteraction {

    self.regionChangeIsFromUserInteraction = false

    // Your Code

    }

    }

  • Kurotsuki Kaitou

    Thanks. I’ve been stumble upon this for a while.

Recommended Posts

In App Purchase store changes in StoreKit

Post by 2 years ago

Something I often see with pretty much every app on the AppStore with In App Purchases is that they don’t update prices if I login with an account linked to a different store than the...

Got an idea?

We help entrepreneurs, organizations and established brands from around
the country bring ideas to life. We would love to hear from you!