Frage

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.

War es hilfreich?

Lösung

There are lots of examples about how to use the delegate method mapView:viewForAnnotation: that show setting up an MKAnnotationView. But what is not as obvious is that just because you set the draggable property of your MKAnnotationView instance to YES, you still have to write some code to help it transition some of the states. MapKit will take care of moving your instance's dragState to MKAnnotationViewDragStateStarting and MKAnnotationViewDragStateEnding, but it will not do the other transitions. You see hints of this in the docs notes about subclassing MKAnnotationView and the need to override the `setDragState:animated:'

When the drag state changes to MKAnnotationViewDragStateStarting, set the state to MKAnnotationViewDragStateDragging. If you perform an animation to indicate the beginning of a drag, and the animated parameter is YES, perform that animation before changing the state.

When the state changes to either MKAnnotationViewDragStateCanceling or MKAnnotationViewDragStateEnding, set the state to MKAnnotationViewDragStateNone. If you perform an animation at the end of a drag, and the animated parameter is YES, you should perform that animation before changing the state.

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's mapView:annotationView:didChangeDragState:fromOldState: method. E.g.

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

This allows things to complete appropriately, so that when you pan the map after dragging the annotation, the annotation moves with the pan.

Andere Tipps

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.

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.

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

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.

- (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"];
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top