I'm learning to use MapKit in my fledgling iOS app. I'm using some of my model entities as annotations (added the <MKAnnotation>
protocol to their header file). I also create custom MKAnnotationViews and set the draggable
property to YES
.
My model object has a location
property, which is a CLLocation*
. To conform to the <MKAnnotation>
protocol, I added the following to that object:
- (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;
}
So, I have the 4 required methods. And they're pretty straightforward. When I read the apple docs on MKAnnotationView
and the @draggable
property, it says the following:
Setting this property to YES makes an annotation draggable by the user. If YES, the associated annotation object must also implement the setCoordinate: method. The default value of this property is NO.
And elsewhere, the MKAnnotation
docs say:
Your implementation of this property must be key-value observing (KVO) compliant. For more information on how to implement support for KVO, see Key-Value Observing Programming Guide.
I have read that (brief) document, and it is not clear at all to me what I'm supposed to do to accomplish that so that the coordinate
, which I'm deriving from my location
property is a proper property in and of itself.
But I'm reasonably sure it's not working correctly. When I drag the pin, it moves, but then it no longer relocates when I pan the map.
UPDATE
So I tried playing with the stock MKPinAnnotationView. To do this, I simply commented out my delegate's mapView:viewForAnnotation:
method. I discovered that these aren't draggable by default. I added the mapView:didAddAnnotationViews:
to my delegate to set the draggable
property of the added views to YES
.
Once configured thus, the Pin views, as hinted by John Estropia below, seem to work fine. I decided to use the mapView:annotationView:didChangeDragState:fromOldState:
delegate hook to get a closer look at what is going on:
- (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]);
}
For the stock pins, one will see log output that looks like this:
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
Which looks pretty logical. But if you switch to the configured MKAnnotationView
, the output you will see looks like:
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
It misses TWO transitions, from Starting to Dragging, and from Ending to None.
So I begin to be skeptical that I need to do something different with properties. But I'm still frustrated with why this won't work.
UPDATE 2
I created my own Annotation
object to stand between my model objects, which could have a property coordinate
property. The behavior remains the same. It seems to be something with the MKAnnotationView
.
Are you using a custom map pin? I saw this before as well. Seems to be a bug in iOS 7. As a workaround, we just ended up using the default pin for
MKPinAnnotationView
.There are lots of examples about how to use the delegate method
mapView:viewForAnnotation:
that show setting up anMKAnnotationView
. But what is not as obvious is that just because you set thedraggable
property of yourMKAnnotationView
instance toYES
, you still have to write some code to help it transition some of the states. MapKit will take care of moving your instance'sdragState
toMKAnnotationViewDragStateStarting
andMKAnnotationViewDragStateEnding
, but it will not do the other transitions. You see hints of this in the docs notes about subclassingMKAnnotationView
and the need to override the `setDragState:animated:'In this case, I'm not subclassing, but it seems that
MKAnnotationView
still struggles to make the transitions on its own. So you have to implement the delegate'smapView:annotationView:didChangeDragState:fromOldState:
method. E.g.This allows things to complete appropriately, so that when you pan the map after dragging the annotation, the annotation moves with the pan.
Your setCoordinate was missing the required Key-Value observing methods (willChangeValueForKey/didChangeValueForKey) that are required for the map to detect when the annotation has been moved so it can move the annotation view to match, e.g.
Source: https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKAnnotation_Protocol/index.html#//apple_ref/occ/intfp/MKAnnotation/coordinate
"Your implementation of this property must be key-value observing (KVO) compliant. For more information on how to implement support for KVO, see Key-Value Observing Programming Guide."
Sorry Apple wasn't clear in the docs, what they meant to say was the setCoordinate requires the Key-Value observing methods willChangeValueForKey & didChangeValueForKey that allow the map to detect when the annotation has been moved so it can move the annotation view to match, e.g.