iOS MapKit 拖拽注解(MKAnnotationView)不再随地图平移

Posted

技术标签:

【中文标题】iOS MapKit 拖拽注解(MKAnnotationView)不再随地图平移【英文标题】:iOS MapKit dragged annotations (MKAnnotationView) no longer pan with map 【发布时间】:2014-03-01 06:48:46 【问题描述】:

我正在学习在我刚刚起步的 ios 应用中使用 MapKit。我正在使用我的一些模型实体作为注释(将<MKAnnotation> 协议添加到它们的头文件中)。我还创建了自定义 MKAnnotationViews 并将draggable 属性设置为YES

我的模型对象有一个location 属性,即CLLocation*。为了符合<MKAnnotation> 协议,我在该对象中添加了以下内容:

- (CLLocationCoordinate2D) coordinate 
    return self.location.coordinate;


- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate 
    CLLocation* newLocation = [[CLLocation alloc]
        initWithCoordinate: newCoordinate
        altitude: self.location.altitude
        horizontalAccuracy: self.location.horizontalAccuracy
        verticalAccuracy: self.location.verticalAccuracy
        timestamp: nil];
    self.location = newLocation;


- (NSString*) title 
    return self.name;


- (NSString*) subtitle 
    return self.serialID;

所以,我有 4 个必需的方法。而且它们非常简单。当我在MKAnnotationView@draggable 属性上阅读苹果文档时,它显示以下内容:

将此属性设置为 YES 使用户可以拖动注释。如果是,关联的注解对象也必须实现 setCoordinate: 方法。此属性的默认值为 NO。

在其他地方,MKAnnotation 文档说:

您对该属性的实现必须符合键值观察 (KVO)。有关如何实现对 KVO 的支持的更多信息,请参阅 Key-Value Observing Programming Guide。

我已经阅读了那个(简短的)文档,我完全不清楚我应该做些什么来完成它,所以我从我的location 属性派生的coordinate 是本身就是一个适当的属性。

但我有理由确定它不能正常工作。当我拖动图钉时,它会移动,但是当我平移地图时它不再重新定位。

更新

所以我尝试使用股票 MKPinAnnotationView。为此,我简单地注释掉了我的委托的mapView:viewForAnnotation: 方法。我发现这些默认情况下是不可拖动的。我将mapView:didAddAnnotationViews: 添加到我的委托中,以将添加的视图的draggable 属性设置为YES

一旦如此配置,Pin 视图(如下面的 John Estropia 所暗示)似乎可以正常工作。我决定使用mapView:annotationView:didChangeDragState:fromOldState:委托钩子来仔细看看发生了什么:

- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)annotationView didChangeDragState:(MKAnnotationViewDragState)newState fromOldState:(MKAnnotationViewDragState)oldState 
    NSArray* states = @[@"None", @"Starting", @"Dragging", @"Cancelling", @"Ending"];
    NSLog(@"dragStateChangeFrom: %@ to: %@", states[oldState], states[newState]);

对于库存引脚,您将看到如下所示的日志输出:

2014-02-05 09:07:45.924 myValve[1781:60b] dragStateChangeFrom: None to: Starting
2014-02-05 09:07:46.249 myValve[1781:60b] dragStateChangeFrom: Starting to: Dragging
2014-02-05 09:07:47.601 myValve[1781:60b] dragStateChangeFrom: Dragging to: Ending
2014-02-05 09:07:48.006 myValve[1781:60b] dragStateChangeFrom: Ending to: None

这看起来很合乎逻辑。但是如果你切换到配置的MKAnnotationView,你会看到的输出是这样的:

2014-02-05 09:09:41.389 myValve[1791:60b] dragStateChangeFrom: None to: Starting
2014-02-05 09:09:45.451 myValve[1791:60b] dragStateChangeFrom: Starting to: Ending

它错过了两个转换,从开始到拖动,从结束到无。

所以我开始怀疑我需要对属性做一些不同的事情。但我仍然对为什么这不起作用感到沮丧。

更新 2

我创建了我自己的 Annotation 对象来站在我的模型对象之间,它可能有一个属性 coordinate 属性。行为保持不变。这似乎与MKAnnotationView有关。

【问题讨论】:

参见***.com/questions/18933632/…,它指向了这个可能的解决方案:***.com/a/4457772/467105 @Anna 这帮助很大,谢谢。我不是子类化,但这个想法大部分是相同的。我几乎让它工作了。我将发布我的解决方案,并提出一个新问题。 【参考方案1】:

有很多关于如何使用委托方法mapView:viewForAnnotation: 的示例,这些示例显示了如何设置MKAnnotationView。但不那么明显的是,仅仅因为您将MKAnnotationView 实例的draggable 属性设置为YES,您仍然需要编写一些代码来帮助它转换一些状态。 MapKit 将负责将您的实例的dragState 移动到MKAnnotationViewDragStateStartingMKAnnotationViewDragStateEnding,但它不会执行其他转换。您可以在有关子类化MKAnnotationView 和需要覆盖`setDragState:animated:' 的文档说明中看到这一点的提示

当拖动状态变为 MKAnnotationViewDragStateStarting 时,将状态设置为 MKAnnotationViewDragStateDragging。如果您执行动画以指示拖动的开始,并且动画参数为 YES,请在更改状态之前执行该动画。

当状态更改为 MKAnnotationViewDragStateCanceling 或 MKAnnotationViewDragStateEnding 时,将状态设置为 MKAnnotationViewDragStateNone。如果在拖动结束时执行动画,并且动画参数为 YES,则应在更改状态之前执行该动画。

在这种情况下,我没有子类化,但似乎MKAnnotationView 仍然难以自行进行转换。所以你必须实现委托的mapView:annotationView:didChangeDragState:fromOldState: 方法。例如

- (void)mapView:(MKMapView *)mapView
        annotationView:(MKAnnotationView *)annotationView
        didChangeDragState:(MKAnnotationViewDragState)newState
        fromOldState:(MKAnnotationViewDragState)oldState 
    if (newState == MKAnnotationViewDragStateStarting) 
        annotationView.dragState = MKAnnotationViewDragStateDragging;
    
    else if (newState == MKAnnotationViewDragStateEnding || newState == MKAnnotationViewDragStateCanceling) 
        annotationView.dragState = MKAnnotationViewDragStateNone;
    

这可以让事情适当地完成,这样当你在拖动注解后平移地图时,注解会随着平移而移动。

【讨论】:

有人可以解释为什么这个答案没有被投票 100 次吗?这如此很重要!谢谢(100 次)。 你救了我的命!这个解决方案很完美【参考方案2】:

您是否使用自定义地图图钉?我以前也看到过这个。似乎是 iOS 7 中的一个错误。作为一种解决方法,我们最终使用MKPinAnnotationView 的默认 pin。

【讨论】:

MKPinAnnotationView 的 setCoordinate 内置了正确的键值观察,这就是它起作用的原因。请参阅我的答案,了解如何将其添加到您自己的 setCoordinate 方法中。【参考方案3】:

您的 setCoordinate 缺少必需的键值观察方法 (willChangeValueForKey/didChangeValueForKey),这些方法是地图检测注释何时被移动以便它可以移动注释视图以匹配所需的,例如

- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate 

    [self willChangeValueForKey:@"coordinate"];

    CLLocation* newLocation = [[CLLocation alloc]
        initWithCoordinate: newCoordinate
        altitude: self.location.altitude
        horizontalAccuracy: self.location.horizontalAccuracy
        verticalAccuracy: self.location.verticalAccuracy
        timestamp: nil];

    self.location = newLocation;

    [self didChangeValueForKey:@"coordinate"];

来源: https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKAnnotation_Protocol/index.html#//apple_ref/occ/intfp/MKAnnotation/coordinate

“您对该属性的实现必须符合键值观察 (KVO)。有关如何实现对 KVO 的支持的更多信息,请参阅键值观察编程指南。”

【讨论】:

【参考方案4】:

抱歉,Apple 在文档中并不清楚,他们的意思是 setCoordinate 需要 Key-Value 观察方法 willChangeValueForKey 和 didChangeValueForKey 允许地图检测注释何时被移动,以便它可以移动注释视图匹配,例如

- (void) setCoordinate:(CLLocationCoordinate2D)newCoordinate 

    [self willChangeValueForKey:@"coordinate"];

    CLLocation* newLocation = [[CLLocation alloc]
        initWithCoordinate: newCoordinate
        altitude: self.location.altitude
        horizontalAccuracy: self.location.horizontalAccuracy
        verticalAccuracy: self.location.verticalAccuracy
        timestamp: nil];

    self.location = newLocation;

    [self didChangeValueForKey:@"coordinate"];

【讨论】:

以上是关于iOS MapKit 拖拽注解(MKAnnotationView)不再随地图平移的主要内容,如果未能解决你的问题,请参考以下文章

iOS核心笔记—MapKit框架-基础

IOS-MapKit

iOS 5 MapKit,MKPlacemark,在 iOS 5 上运行 iOS 4.3 应用程序

iOS MapKit,细节较少

iOS 6 上的 MapKit 崩溃

IOS/MapKit:通过单击 MKPlacemark 启动本机地图应用程序