Determine if MKMapView has been moved / moved

Is there a way to determine if MKMapView has been moved?

I want to get the center location every time a user drags a map using CLLocationCoordinate2D centre = [locationMap centerCoordinate]; but I need a delegate method or something that fires as soon as the user moves around the map.

Thank you in advance

+73
ios mkmapview
Apr 05 2018-11-18T00:
source share
15 answers

Take a look at the MKMapViewDelegate link.

In particular, these methods may be useful:

 - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated 

Make sure that the delegate property of the map view is set so that the called methods are invoked.

+28
Apr 05 '11 at 19:10
source share

The code in the accepted answer is triggered when the region changes for some reason. To correctly detect the dragging of a map, you must add a UIPanGestureRecognizer. Btw, this is a gesture recognizer (pan = drag).

Step 1: Add a gesture recognizer to viewDidLoad:

 -(void) viewDidLoad { [super viewDidLoad]; UIPanGestureRecognizer* panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didDragMap:)]; [panRec setDelegate:self]; [self.mapView addGestureRecognizer:panRec]; } 

Step 2: Add the UIGestureRecognizerDelegate protocol to the view controller so that it works as a delegate.

 @interface MapVC : UIViewController <UIGestureRecognizerDelegate, ...> 

Step 3: And add the following code for UIPanGestureRecognizer to work with existing gesture recognizers in MKMapView:

 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } 

Step 4: If you want to call your method once instead of 50 times per drag, determine that your selector is in a “drag and drop” state:

 - (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateEnded){ NSLog(@"drag ended"); } } 
+223
Jul 26 '12 at 18:22
source share

This is the only way that worked for me that detects panning as well as zoom changes initiated by the user:

 - (BOOL)mapViewRegionDidChangeFromUserInteraction { UIView *view = self.mapView.subviews.firstObject; // Look through gesture recognizers to determine whether this region change is from user interaction for(UIGestureRecognizer *recognizer in view.gestureRecognizers) { if(recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateEnded) { return YES; } } return NO; } static BOOL mapChangedFromUserInteraction = NO; - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated { mapChangedFromUserInteraction = [self mapViewRegionDidChangeFromUserInteraction]; if (mapChangedFromUserInteraction) { // user changed map region } } - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { if (mapChangedFromUserInteraction) { // user changed map region } } 
+68
Sep 23 '14 at 18:25
source share

(Just the) Fast version excellent solution @mobi :

 private var mapChangedFromUserInteraction = false private func mapViewRegionDidChangeFromUserInteraction() -> Bool { let view = self.mapView.subviews[0] // Look through gesture recognizers to determine whether this region change is from user interaction if let gestureRecognizers = view.gestureRecognizers { for recognizer in gestureRecognizers { if( recognizer.state == UIGestureRecognizerState.Began || recognizer.state == UIGestureRecognizerState.Ended ) { return true } } } return false } func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) { mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction() if (mapChangedFromUserInteraction) { // user changed map region } } func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) { if (mapChangedFromUserInteraction) { // user changed map region } } 
+24
Jun 18 '15 at 20:04
source share

Swift 3 solution for Jano answer above:

Add UIGestureRecognizerDelegate Protocol to Your ViewController

 class MyViewController: UIViewController, UIGestureRecognizerDelegate 

Create a UIPanGestureRecognizer in viewDidLoad and set the delegate to self

 viewDidLoad() { // add pan gesture to detect when the map moves let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didDragMap(_:))) // make your class the delegate of the pan gesture panGesture.delegate = self // add the gesture to the mapView mapView.addGestureRecognizer(panGesture) } 

Add a protocol method for your gesture recognizer to work with existing MKMapView gestures

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

Add a method that will be called by the selector in your pan gesture

 func didDragMap(_ sender: UIGestureRecognizer) { if sender.state == .ended { // do something here } } 
+9
Dec 14 '16 at 20:25
source share

In my experience, similar to “typing search,” I found that a timer is the most reliable solution. This eliminates the need to add additional gesture recognizers for panning, pinching, rotating, threading, double tap, etc.

The solution is simple:

  • When the map area changes, set the / reset timer
  • When the timer fires, load markers for the new area

     import MapKit class MyViewController: MKMapViewDelegate { @IBOutlet var mapView: MKMapView! var mapRegionTimer: NSTimer? // MARK: MapView delegate func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) { setMapRegionTimer() } func setMapRegionTimer() { mapRegionTimer?.invalidate() // Configure delay as bet fits your application mapRegionTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "mapRegionTimerFired:", userInfo: nil, repeats: false) } func mapRegionTimerFired(sender: AnyObject) { // Load markers for current region: // mapView.centerCoordinate or mapView.region } } 
+7
Feb 29 '16 at 19:59
source share

You can also add gesture recognizers to your map in Interface Builder. Associate it with the output for your action in your viewController, I named my "mapDrag" ...

Then you will do something like this in your viewController.m:

 - (IBAction)mapDrag:(UIPanGestureRecognizer *)sender { if(sender.state == UIGestureRecognizerStateBegan){ NSLog(@"drag started"); } } 

Make sure you have this too:

 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } 

Of course, you need to make your viewController UIGestureRecognizerDelegate in your .h file for this to work.

Otherwise, the card responder is the only one who hears the gesture event.

+6
Jan 28 '14 at 2:26
source share

Another possible solution is to implement touchtsMoved: (or touchesEnded: etc.) in the view controller that contains your map view, for example:

 -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event]; for (UITouch * touch in touches) { CGPoint loc = [touch locationInView:self.mapView]; if ([self.mapView pointInside:loc withEvent:event]) { #do whatever you need to do break; } } } 

This may be easier than using gesture recognizers in some cases.

+6
Aug 01 '14 at 19:43
source share

To find out when a gesture ended on a map:

http://b2cloud.com.au/tutorial/mkmapview-determining-whether-region-change-is-from-user-interaction/

This is very useful for querying the database only after the user has zoomed / rotated / moved the map around.

For me, the zoneDidChangeAnimated method was called only after the gesture was made, and it was not called many times when dragging / scaling / rotating, but it is useful to know if it was due to gestures or not.

+3
May 27 '15 at 3:45
source share

Many of these solutions are on the hacked / wrong side of Swift, so I chose a cleaner solution.

I just subclass MKMapView and override touchsMoved. Although this snippet does not include it, I would recommend creating a delegate or notification to convey any information that you need regarding the movement.

 import MapKit class MapView: MKMapView { override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) print("Something moved") } } 

You will need to update the class in the storyboard files to point to this subclass, as well as change any maps created by other means.

+3
Jan 12 '17 at 22:03
source share

Yano's answer worked for me, so I decided to leave an updated version for Swift 4 / XCode 9, since I am not very good at Objective-C and I am sure that there are others that are not.

Step 1: Add this code to viewDidLoad:

 let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didDragMap(_:))) panGesture.delegate = self 

Step 2. Verify that your class matches UIGestureRecognizerDelegate:

 class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate { 

Step 3: Add the following function to make your panGesture work simultaneously with other gestures:

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

Step 4. And make sure that your method is not called “50 times per drag”, as Jano rightly notes:

 @objc func didDragMap(_ gestureRecognizer: UIPanGestureRecognizer) { if (gestureRecognizer.state == UIGestureRecognizerState.ended) { redoSearchButton.isHidden = false resetLocationButton.isHidden = false } } 

* Note the addition of @objc in the last step. Xcode forcibly uses this prefix for your function to compile it.

+2
Feb 08 '18 at 2:29
source share

You can check the animated property, if false, then the user dragged the map

  func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { if animated == false { //user dragged map } } 
+2
Aug 06 '18 at 10:08
source share

enter the code here I was able to implement this in the simplest way that handles all interaction with the card (pressing / double / N slicing with fingers with 1/2 / N fingers, panning with 1/2 fingers, clamping and rotating

  • Create a gesture recognizer and add displays to the container
  • Set gesture recognizer's delegate for some object that implements UIGestureRecognizerDelegate
  • Implement gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) method gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch)
 private func setupGestureRecognizers() { let gestureRecognizer = UITapGestureRecognizer(target: nil, action: nil) gestureRecognizer.delegate = self self.addGestureRecognizer(gestureRecognizer) } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { self.delegate?.mapCollectionViewBackgroundTouched(self) return false } 
0
Nov 08 '16 at 13:00
source share

I tried to create an annotation in the center of the map, which is always in the center of the map, no matter what the usage does. I tried several approaches mentioned above and none of them were good enough. In the end, I found a very simple way to solve this, borrowing the answer from Anna and combining with the answer of Eneko. It basically considers the WillChangeAnimated region as the start of a drag and drop, and the regionDidChangeAnimated as the end of one and uses a timer to update the output in real time:

 var mapRegionTimer: Timer? public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { mapRegionTimer?.invalidate() mapRegionTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { (t) in self.myAnnotation.coordinate = CLLocationCoordinate2DMake(mapView.centerCoordinate.latitude, mapView.centerCoordinate.longitude); self.myAnnotation.title = "Current location" self.mapView.addAnnotation(self.myAnnotation) }) } public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { mapRegionTimer?.invalidate() } 
0
Oct 25 '17 at 12:40
source share

First , make sure your current view controller is a map delegate. Therefore, set your Map View delegate to yourself and add MKMapViewDelegate to your view controller. An example is below.

 class Location_Popup_ViewController: UIViewController, MKMapViewDelegate { // Your view controller stuff } 

And add this to your map view

 var myMapView: MKMapView = MKMapView() myMapView.delegate = self 

Secondly , add this function, which starts when you move the map. It will filter out any animations and will only work when interacting with it.

 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { if !animated { // User must have dragged this, filters out all animations // PUT YOUR CODE HERE } } 
0
Jan 13 '19 at 0:42
source share



All Articles