【发布时间】:2011-07-30 05:49:51
【问题描述】:
有没有办法确定 MKMapView 是否被拖拽?
我想在每次用户使用CLLocationCoordinate2D centre = [locationMap centerCoordinate]; 拖动地图时获取中心位置,但我需要一个委托方法或在用户使用地图导航时立即触发的东西。
提前致谢
【问题讨论】:
有没有办法确定 MKMapView 是否被拖拽?
我想在每次用户使用CLLocationCoordinate2D centre = [locationMap centerCoordinate]; 拖动地图时获取中心位置,但我需要一个委托方法或在用户使用地图导航时立即触发的东西。
提前致谢
【问题讨论】:
当区域因任何原因发生更改时,已接受答案中的代码会触发。要正确检测地图拖动,您必须添加 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");
}
}
【讨论】:
UIPinchGestureRecognizer
readyForUpdate,然后在- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated 中检查该标志。
这是检测用户发起的平移和缩放更改的唯一方法:
- (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
}
}
【讨论】:
MKMapView 的内部实现。该实现可能会在任何 iOS 更新中发生变化,因为它不是 API 的一部分。
(只是)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
self.map.delegate = self 到 viewDidLoad
查看MKMapViewDelegate 参考。
具体来说,这些方法可能有用:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
确保您的地图视图的委托属性已设置,以便调用这些方法。
【讨论】:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated 完成了这项工作。
上述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
}
}
【讨论】:
根据我的经验,类似于“边打字边搜索”,我发现计时器是最可靠的解决方案。它无需为平移、捏合、旋转、点击、双击等添加额外的手势识别器。
解决方法很简单:
当计时器触发时,为新区域加载标记
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
}
}
【讨论】:
另一种可能的解决方案是在保存地图视图的视图控制器中实现 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;
}
}
}
在某些情况下,这可能比使用手势识别器更简单。
【讨论】:
这些解决方案中的很多都是 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。虽然这由开发人员自行决定,但这种特殊用法不会改变地图的行为,并且已经为我工作了三年多,没有发生任何事故。然而,过去的表现并不代表未来的兼容性,所以谨慎购买。
【讨论】:
您还可以在 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 做得很好
我知道这是一篇旧帖子,但这里是我的 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
}
}
}
享受吧!
【讨论】:
识别地图视图上的任何手势何时结束:
这对于仅在用户完成缩放/旋转/拖动地图后执行数据库查询非常有用。
对我来说,regionDidChangeAnimated 方法只在手势完成后被调用,并且在拖动/缩放/旋转时没有被多次调用,但知道它是否是由于手势引起的很有用。
【讨论】:
您可以检查动画属性 如果为 false 则用户拖动地图
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if animated == false {
//user dragged map
}
}
【讨论】:
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 步:并确保您的方法不像 Jano 正确指出的那样被称为“每次拖动 50 次”:
@objc func didDragMap(_ gestureRecognizer: UIPanGestureRecognizer) {
if (gestureRecognizer.state == UIGestureRecognizerState.ended) {
redoSearchButton.isHidden = false
resetLocationButton.isHidden = false
}
}
*注意最后一步添加了@objc。 XCode 会在你的函数上强制使用这个前缀以便它编译。
【讨论】:
我试图在地图中心放置一个注释,无论用途如何,它始终位于地图中心。我尝试了上面提到的几种方法,但都不够好。我最终找到了一种非常简单的解决方法,借鉴了 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()
}
【讨论】:
在此处输入代码我设法以最简单的方式实现了这一点,它处理了与地图的所有交互(用 1/2/N 根手指点击/双击/N 点敲击,用 1/2/N 根手指平移,捏合和旋转
gesture recognizer 并添加到地图视图的容器中gesture recognizer's delegate 设置为实现UIGestureRecognizerDelegate 的某个对象
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 }
【讨论】:
首先,确保您当前的视图控制器是地图的代表。因此,将您的 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
}
}
【讨论】: