【问题标题】:determine if MKMapView was dragged/moved确定 MKMapView 是否被拖动/移动
【发布时间】:2011-07-30 05:49:51
【问题描述】:

有没有办法确定 MKMapView 是否被拖拽?

我想在每次用户使用CLLocationCoordinate2D centre = [locationMap centerCoordinate]; 拖动地图时获取中心位置,但我需要一个委托方法或在用户使用地图导航时立即触发的东西。

提前致谢

【问题讨论】:

    标签: ios mkmapview


    【解决方案1】:

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

    第 1 步: 在 vi​​ewDidLoad 中添加手势识别器:

    -(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 出于任何不理想的原因而触发,我可以得到的地图可能会完全按照我的意愿行事,因此对此表示敬意!
    • 如果你也想抓紧,你也想添加一个UIPinchGestureRecognizer
    • 请注意,地图视图滚动带有动量,上面的示例将在手势结束但地图视图停止移动之前立即触发。可能有更好的方法,但我所做的是在手势停止时设置一个标志readyForUpdate,然后在- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated 中检查该标志。
    • 请注意,用户可以双击一个或两个手指进行缩放,这将更改区域但不会调用此平移识别器。
    • 为什么这个解决方案在底部?这是最好的!是的,@mobi 's solution 更简单,但这个更安全。
    【解决方案2】:

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

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

    【讨论】:

    • 这对我有用,但需要注意的是,这取决于 iOS 中 MKMapView 的内部实现。该实现可能会在任何 iOS 更新中发生变化,因为它不是 API 的一部分。
    • 这行得通,而且我比主要答案更喜欢它,因为它不会改变那里的内容。
    • 感谢代码与用户地图操作的优雅解决方案。
    【解决方案3】:

    (只是)Swift 版本的@mobi's excellent solution

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

    【讨论】:

    • 看起来不错,但我不得不将 self.mapView.subviews[0] 更改为 self.mapView.subviews[0] as! UIView
    • 这个(和 moby 的)解决方案不是那么好。不能保证 Apple 会保留 mapViews 的第一个子视图。也许在未来的某个版本中,mapView 的第一个 subView 不会是 UIView。所以你的代码不是防崩溃的。尝试将您自己的 GestureRecognizers 添加到 MapView。
    • 注意,为了让这个工作我必须添加 self.map.delegate = self 到 viewDidLoad
    【解决方案4】:

    查看MKMapViewDelegate 参考。

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

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

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

    【讨论】:

    • 非常感谢。 - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated 完成了这项工作。
    • 很好的解决方案。当用户更改位置时,非常适合在地图上重新加载注释
    • -1 因为此解决方案不会告诉您用户是否拖动了地图。 regionWillChangeAnimated 发生在用户旋转设备或其他方法缩放地图时,不一定响应拖动。
    • 谢谢@CommaToast 我发现这个“答案”也有同样的问题
    • @mobi 's solution 通过检查 mapviews 内部手势识别器来检测用户手势(是的,所有手势)。不错!
    【解决方案5】:

    上述Jano's answer的Swift 3解决方案:

    将协议 UIGestureRecognizerDelegate 添加到您的 ViewController

    class MyViewController: UIViewController, UIGestureRecognizerDelegate
    

    viewDidLoad 中创建 UIPanGestureRecognizer 并将 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
    
        }
    }
    

    【讨论】:

    • 这就是解决方案!谢谢!
    【解决方案6】:

    根据我的经验,类似于“边打字边搜索”,我发现计时器是最可靠的解决方案。它无需为平移、捏合、旋转、点击、双击等添加额外的手势识别器。

    解决方法很简单:

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

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

      【讨论】:

        【解决方案8】:

        这些解决方案中的很多都是 hacky/不是 Swift 想要的,所以我选择了一个更干净的解决方案。

        我只是将 MKMapView 子类化并覆盖 touchesMoved。虽然这个 sn-p 不包括它,但我建议创建一个委托或通知来传递您想要的有关运动的任何信息。

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

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

        如 cmets 中所述,Apple discourages 使用子类化 MKMapView。虽然这由开发人员自行决定,但这种特殊用法不会改变地图的行为,并且已经为我工作了三年多,没有发生任何事故。然而,过去的表现并不代表未来的兼容性,所以谨慎购买

        【讨论】:

        • 这似乎是最好的解决方案。我已经对其进行了测试,并且似乎可以正常工作。我认为让其他人意识到 Apple 建议不要子类化 MKMapView 是一件好事:“虽然您不应该子类化 MKMapView 类本身,但您可以通过提供委托对象来获取有关地图视图行为的信息。”链接:developer.apple.com/documentation/mapkit/mkmapview。但是,对于忽略他们不继承 MKMapView 的建议,我没有强烈的意见,我愿意从其他人那里了解更多关于此的信息。
        • “这似乎是最好的解决方案,即使 Apple 说不要这样做”似乎可能不是最好的解决方案。
        • 这对我有用,因为我已经出于其他一些原因对地图进行了子类化。我知道 Apple 建议(不禁止)MKMapView 子类化,依靠内部 Apple 的 UIView 实现,甚至实现各种手势识别器来检测用户活动,这对我来说似乎更糟糕。
        【解决方案9】:

        您还可以在 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 做得很好
        【解决方案10】:

        我知道这是一篇旧帖子,但这里是我的 Swift 4/5 代码 Jano's answer whit Pan and 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
                }
            }
        }
        

        享受吧!

        【讨论】:

        • 如前所述,这不能识别缩放,但对于大多数人来说应该足够好。
        【解决方案11】:

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

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

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

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

        【讨论】:

        【解决方案12】:

        您可以检查动画属性 如果为 false 则用户拖动地图

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

        【讨论】:

        • 用户可以放大地图。
        • 可爱,但不会将用户交互与编程区域更改区分开来,在 iOS 14 上测试。
        【解决方案13】:

        Jano 的回答对我有用,所以我想我会为 Swift 4 / XCode 9 留下一个更新版本,因为我不是特别精通 Objective C,而且我确信还有一些其他的也不是.

        第 1 步: 在 vi​​ewDidLoad 中添加此代码:

        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 步:并确保您的方法不像 Jano 正确指出的那样被称为“每次拖动 50 次”:

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

        *注意最后一步添加了@objc。 XCode 会在你的函数上强制使用这个前缀以便它编译。

        【讨论】:

          【解决方案14】:

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

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

          【讨论】:

            【解决方案15】:

            在此处输入代码我设法以最简单的方式实现了这一点,它处理了与地图的所有交互(用 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
            }
            

            【讨论】:

              【解决方案16】:

              首先,确保您当前的视图控制器是地图的代表。因此,将您的 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
                 }
              }
              

              【讨论】:

                猜你喜欢
                • 2016-01-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-08-09
                • 2020-07-25
                相关资源
                最近更新 更多