Search code examples
iosmkmapviewmkannotationview

MKPinAnnotationView loses pinColor after MKMapView zoom in


I have a MKMapView with a lot of annotation pins defined from a parser xml; thats my code:

-(IBAction)LoadAnnotation:(id)sender {

    RXML element ...
    RXML iterate....

    [myMap removeAnnotations:myMap.annotations];
    annotation.title = // NSString from RXML parser
    annotation.subtitle = // NSString from RXML parser
    myValue = // float value from RXML parser
    [mymap addAnnotation:annotation];
}

and then

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation2 {

       MKPinAnnotationView *pinView=[[MKPinAnnotationView alloc] initWithAnnotation:annotation2 reuseIdentifier:@"MyPin"];

        if ( myValue > 0 && myValue < 10) {
            pinView.canShowCallout = YES;
            pinView.pinColor = MKPinAnnotationColorRed;
            pinView.animatesDrop=YES;
            return pinView;
        }

        else if ( myValue > 10 && myValue < 20 ) {
        pinView.canShowCallout = YES;
        pinView.pinColor = MKPinAnnotationColorGreen;
        pinView.animatesDrop=YES;
        return pinView;
    }

        pinView.canShowCallout = YES;
        pinView.pinColor = MKPinAnnotationColorPurple;
        pinView.animatesDrop=YES;
        return pinView;
}

All right, when my MKMapView is loaded, I can see title annotations, subtitle annotations and all the pins with different colours.

But if I scroll and zoom IN the map at a certain level, then zoom OUT again, all the pins become PURPLE. What's happening there?

I have tried also using same "annotation" (id) in the two methods (and not "annotation" and "annotation2"), but I have no result.

Is there a way to avoid that and keep pinColors after map scroll and zoom?


Solution

  • The viewForAnnotation delegate method isn't necessarily called only once for each annotation nor is it guaranteed to be called in the order that you add the annotations. It's also called for the user location (blue dot) if you set showsUserLocation to YES.

    When you zoom or pan the map, the map view will call the delegate method (again) as annotations come back into view. At that time, your myValue will have no relevance to the annotation the map is requesting a view for.

    Instead of using a class-level ivar, add myValue as a property of your annotation class and set it along with the title and subtitle before you call addAnnotation:

    annotation.title = // NSString from RXML parser
    annotation.subtitle = // NSString from RXML parser
    annotation.myValue = // float value from RXML parser
    ^^^^^^^^^^^
    

    Then in viewForAnnotation, use the myValue property from the annotation parameter instead of an ivar. This way, the delegate method is always using information specific to the annotation it is requesting a view for:

    if ( ! [annotation isKindOfClass:[MyCustomAnnotationClass class]])
    {
        return nil;  //return a default view if not your custom class
    }
    
    //cast annotation to custom class so we can get the custom property...
    MyCustomAnnotationClass *myPin = (MyCustomAnnotationClass *)annotation;
    
    //use the custom property in the annotation instead of ivar...
    if (myPin.myValue > 0 && myPin.myValue < 10) {
        ....
    

    Changing the name of the annotation parameter to annotation2 is not necessary.


    Unrelated but you should be implementing annotation view re-use by using dequeueReusableAnnotationViewWithIdentifier (search for it in the SDK or on SO). It can help with performance if there are a lot of annotations.


    Your MyAnnotation class should look like this:

    @interface MyAnnotation : NSObject <MKAnnotation> {
        float myValue;
    }
    @property (nonatomic, assign) CLLocationCoordinate2D coordinate; //<-- add
    @property (nonatomic, copy) NSString *title;                     //<-- add
    @property (nonatomic, copy) NSString *subtitle;                  //<-- add
    @property (nonatomic, assign) float myValue;
    @end
    
    @implementation MyAnnotation
    @synthesize coordinate; //<-- add
    @synthesize title;      //<-- add
    @synthesize subtitle;   //<-- add
    @synthesize myValue;
    @end
    

    The place where you add the annotation should look like this:

    MyAnnotation *annotation = [[MyAnnotation alloc] init];
    annotation.title = someTitle;       // NSString from RXML parser
    annotation.subtitle = someSubTitle; // NSString from RXML parser
    annotation.myValue = someValue;     // someValue from RXML parser
    [mapView addAnnotation:annotation];
    

    Above, someValue is whatever myValue was in your original ViewController.

    Also, in viewForAnnotation, change annotation2 back to annotation and don't forget to put MKPinAnnotationView *pinView =[[MKPinAnnotationView alloc] init... before the MyAnnotation *myPin = ... line.