确定是否拖动/移动了MKMapView


84

有没有办法确定是否拖动了MKMapView?

每当用户使用拖动地图时,我都希望获得中心位置,CLLocationCoordinate2D centre = [locationMap centerCoordinate];但是我需要一个委托方法或在用户浏览地图时立即触发的东西。

提前致谢

Answers:


22

查看MKMapViewDelegate参考。

具体来说,这些方法可能有用:

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

确保设置了地图视图的委托属性,以便调用这些方法。


1
非常感谢。- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated做好了
hgbnerd 2011年

2
很好的解决方案。当用户更改位置时非常适合在地图上重新加载注释
Alejandro Luengo 2013年

176
-1,因为此解决方案不会告诉您用户是否拖动了地图。如果用户旋转设备或其他方法缩放地图(不一定是响应拖动),则会发生regionWillChangeAnimated。
CommaToast 2014年

谢谢@CommaToast我发现这个“答案”存在相同的问题
cleverbit 2014年

3
@mobi的解决方案通过检查mapviews内部手势识别器来检测用户手势(是的,全部)。真好!
Felix Alcala 2014年

236

由于任何原因更改区域时,将触发接受的答案中的代码。要正确检测地图拖动,您必须添加UIPanGestureRecognizer。顺便说一句,这是拖动手势识别器(平移=拖动)。

步骤1:在viewDidLoad中添加手势识别器:

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

步骤2:将协议UIGestureRecognizerDelegate添加到视图控制器,以便它可以充当委托。

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

步骤3:并为UIPanGestureRecognizer添加以下代码,以使其与MKMapView中已经存在的手势识别器一起使用:

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

第4步:如果您要一次调用方法而不是每次拖动50次,请在选择器中检测到“拖动结束”状态:

- (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer {
    if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
        NSLog(@"drag ended");
    }
}

我知道这是一个相当老的帖子,但是我喜欢上面的想法,我在实现过程中努力地使用regionDidChange方法来组织我的应用程序,当我看到它全部被单击时,您是如此正确,regionDidChange会由于任何原因而触发我可以得到的可能并不完美,它可能会映射为完全按照我的意愿来做,因此为此赞誉!
Alex McPherson

3
如果你想赶上捏过,你要添加一个UIPinchGestureRecognizer,以及
格雷戈里·科斯莫焕

32
请注意,地图视图滚动条带有动量,并且手势结束后但在地图视图停止移动之前,将触发上述示例。也许有更好的方法,但是我要做的是在手势停止时设置一个标志readyForUpdate,然后在中检查该标志- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
tvon 2014年

14
请注意,用户可以双击一个或两个手指进行缩放,这将更改区域,但不会调用此声像识别器。
2014年

2
为什么此解决方案位于底部?这是最好的一个!是的,@ mobi的解决方案更简单,但是此解决方案更安全。
Leslie Godwin

77

这是检测用户发起的平移和缩放更改的唯一方法:

- (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
    }
}

2
这对我有用,但是应该注意,这取决于MKMapViewiOS中的内部实现。该实现可以在任何iOS更新中更改,因为它不是API的一部分。
progrmr 2015年

这行得通,而且我不喜欢领先答案,因为它不会改变那里的内容。
2015年

感谢您提供出色的代码和用户映射操作解决方案。
djneely 2015年

32

(仅)@mobi出色解决方案的Swift版本:

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
    }
}

2
看起来不错,但我必须更改self.mapView.subviews[0]self.mapView.subviews[0] as! UIView
kmurph79,2015年

3
这个(和Moby的)解决方案不是那么好。无法保证Apple将保留mapViews第一个子视图。也许在将来的版本中,mapView的第一个subView不会是UIView。因此,您的代码不具有崩溃防护功能。尝试将自己的GestureRecognizers添加到MapView。
Samet DEDE 2015年

1
注意,self.map.delegate = self
要使

17

Swift 3解决上述Jano问题的方法

将协议UIGestureRecognizerDelegate添加到您的ViewController

class MyViewController: UIViewController, UIGestureRecognizerDelegate

在中创建UIPanGestureRecognizerviewDidLoad并将其设置delegate为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)
}

添加一个Protocol方法,以便您的手势识别器可以使用现有的MKMapView手势

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

添加选择器将在平移手势中调用的方法

func didDragMap(_ sender: UIGestureRecognizer) {
    if sender.state == .ended {

        // do something here

    }
}

这就是解决方案!谢谢!
希拉卡'18

8

根据我的经验,类似于“键入时搜索”,我发现计时器是最可靠的解决方案。它消除了为平移​​,捏合,旋转,敲击,双击等添加其他手势识别器的需求。

解决方案很简单:

  1. 当地图区域更改时,设置/重置计时器
  2. 当计时器触发时,为新区域加载标记

    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

另一个可能的解决方案是在保存地图视图的视图控制器中实现touchesMoved:(或touchesEnded:等),如下所示:

-(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;
        }
    }
}

在某些情况下,这可能比使用手势识别器更简单。


6

您还可以在Interface Builder中将手势识别器添加到地图中。将其链接到其在viewController中的动作的插座,我将其称为“ mapDrag” ...

然后,您将在viewController的.m中执行以下操作:

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

确保那里也有这个:

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

当然,您必须在.h文件中将viewController设置为UIGestureRecognizerDelegate才能起作用。

否则,地图的响应者是唯一听到手势事件的人。


非常适合情节提要解决方案。做得很好UIGestureRecognizerStateBegan
雅各布2014年

5

要识别任何手势在地图视图上何时结束:

[ https://web.archive.org/web/20150215221143/http://b2cloud.com.au/tutorial/mkmapview-determining-whether-region-change-is-from-user-interaction/

这对于仅在用户完成缩放/旋转/拖动地图之后仅执行数据库查询非常有用。

对我来说,regionDidChangeAnimated方法仅在完成手势后才被调用,并且在拖动/缩放/旋转时并没有被多次调用,但是了解是否是由于手势引起的很有用。



这种方法对我不起作用。一旦mapView区域的代码更改,它就会触发它是来自用户的……
Maksim Kniazev

5

这些解决方案中有很多都存在问题,而不是Swift想要的,所以我选择了一个更干净的解决方案。

我只是将MKMapView子类化,并覆盖touchesMoved。尽管此代码段不包含此代码段,但我建议创建一个委托或通知以传递您想要的有关该运动的任何信息。

import MapKit

class MapView: MKMapView {
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)

        print("Something moved")
    }
}

您将需要更新情节提要文件上的类以指向此子类,以及修改通过其他方式创建的任何地图。

如评论中所述,Apple不鼓励使用子类化MKMapView。尽管这取决于开发人员的判断,但这种特殊用法不会更改地图的行为,并且已经为我工作了三年多,没有发生任何事件。但是,过去的性能并不表示将来的兼容性,因此请警告


1
这似乎是最好的解决方案。我已经对其进行了测试,并且看起来工作正常。我认为让其他人知道Apple建议不要对MKMapView类进行子类化是一件好事:“尽管您不应该对MKMapView类本身进行子类化,但是可以通过提供委托对象来获取有关地图视图行为的信息。” 链接:developer.apple.com/documentation/mapkit/mkmapview。但是,对于忽略他们的建议而不是不继承MKMapView的建议,我没有强烈的意见,并且我愿意就此向其他人学习更多。
安德烈(Andrej)

1
“尽管苹果公司表示不这样做,但这似乎是最好的解决方案”,似乎并不是最好的解决方案。
dst3p

3

Jano的答案对我有用,所以我认为我会留下Swift 4 / XCode 9的更新版本,因为我不太精通Objective C,而且我敢肯定还有其他一些都不是。

步骤1:在viewDidLoad中添加以下代码:

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

步骤2:确保您的类符合UIGestureRecognizerDelegate:

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {

步骤3:添加以下功能,以确保您的panGesture与其他手势同时使用:

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

步骤4:并确保您的方法不被称为“每次拖动50次”,正如Jano正确指出的那样:

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

*请注意在最后一步中添加了@objc。XCode将在您的函数上强制使用此前缀,以便对其进行编译。


3

您可以检查动画属性是否为false,然后用户拖动地图

 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if animated == false {
        //user dragged map
    }
}

用户可以缩放地图。
维克托·恩格尔

2

我知道这是一篇旧文章,但在这里是我的Jano的Swift 4/5代码,它回答了Pan和Pinch手势。

class MapViewController: UIViewController, MapViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didDragMap(_:)))
        let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(self.didPinchMap(_:)))
        panGesture.delegate = self
        pinchGesture.delegate = self
        mapView.addGestureRecognizer(panGesture)
        mapView.addGestureRecognizer(pinchGesture)
    }

}

extension MapViewController: UIGestureRecognizerDelegate {

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

    @objc func didDragMap(_ sender: UIGestureRecognizer) {
        if sender.state == .ended {
            //code here
        }
    }

    @objc func didPinchMap(_ sender: UIGestureRecognizer) {
        if sender.state == .ended {
            //code here
        }
    }
}

请享用!


如前所述,它不能识别变焦,但大多数情况下应该是Good Enough™。
Skoua

1
它甚至适用于变焦。我刚刚测试过;)
Baran Emre

1

我试图在地图的中心添加一个注释,无论其用途如何,该注释始终位于地图的中心。我尝试了上述几种方法,但都不够好。最终,我找到了一种非常简单的解决方法,该方法借鉴了Anna的答案,并与Eneko的答案相结合。它基本上将regionWillChangeAnimated视为拖动的开始,将regionDidChangeAnimated视为拖动的结束,并使用计时器实时更新销钉:

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

我设法以最简单的方式实现了此目的,该方式可以处理与地图的所有交互(用1/2 / N的手指轻敲/双击/ N轻敲,用1/2 / N的手指轻摇,捏和旋转)

  1. 创建gesture recognizer并添加到地图视图的容器
  2. 设置gesture recognizer's delegate为一些对象实现UIGestureRecognizerDelegate
  3. 实施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
}

-1

首先,确保您当前的视图控制器是地图的委托。因此,将您的Map View委托设置为self并添加MKMapViewDelegate到您的视图控制器中。下面的例子。

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

并将其添加到您的地图视图

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

其次,添加此功能,该功能在地图移动时触发。它将滤除所有动画,并且仅在与之交互时才触发。

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
   if !animated {
       // User must have dragged this, filters out all animations
       // PUT YOUR CODE HERE
   }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.