SimonBarker SimonBarker - 4 months ago 15
Objective-C Question

mapView:viewForAnnotation: not called when delegate set in code

I have a custom class for my MKAnnotations, I want to override the default

mapView:viewForAnnotatation
method so that I can add extra information in the callout. When I set my delegate in code (as per the code below) the annotations are dropped on the map and are selectable but my
mapView:viewForAnnoation
is never called.

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
NSLog(@"viewForAnnotation: called");
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"mapPin"];
if(!aView){
aView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"mapPin"];
}
aView.annotation = annotation;

return aView;
}

- (void)viewDidLoad
{
[super viewDidLoad];

// Do any additional setup after loading the view.
self.mapView.delegate = self;

}


I know the delegate is being set as I can override the method
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
and I see an NSLog when I select an annotation.

When I change from setting the delegate in code to setting it in the Storyboard the method is called (NSLog(@"viewForAnnotation: called"); statements appear) but the annotations don't appear on the map and sometimes this error appears:

<Error>: ImageIO: CGImageReadSessionGetCachedImageBlockData *** CGImageReadSessionGetCachedImageBlockData: bad readSession [0x8618480]

Rob Rob
Answer

This seems like two separate issues:

  1. Regarding the setting delegate in code v storyboard, it's hard to reconcile your various observations (delegate method didSelectAnnotationView is getting called in both scenarios, but viewForAnnotation is not). The only difference between setting it in code v in the storyboard is the timing of when the delegate is getting set. You're not showing us the process of adding the annotations, so it's hard to diagnose on the basis of what you've described. If none of your delegate methods were getting called, I'd suspect the mapView IBOutlet, but if some are working and others aren't, I can only suspect a timing issue.

  2. Regarding the setting of the MKAnnotationView, the default implementation does nothing. You either need to write your own subclass of MKAnnotationView, set its image if you're using your own image, or, much easier, just use MKPinAnnotationView. But just creating a MKAnnotationView won't do anything. You really want:

    - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
        // If it's the user location, just return nil.
        if ([annotation isKindOfClass:[MKUserLocation class]])
            return nil;
    
        // Handle any custom annotations.
        MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:@"mapPin"];
        if(aView){
            aView.annotation = annotation;
        } else {
            aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"mapPin"];
        }
    
        aView.canShowCallout = NO;
    
        return aView;
    }
    

    (Note, I'm not only creating a MKPinAnnotationView, but I'm also making sure that it's not a MKUserLocation, in case you ever choose to show user location on the map. I'm also going to explicitly set the canShowCallout, as that's presumably the reason you're writing this method at all.)

Bottom line, if you want to show a simple pin annotation view, use MKPinAnnotationView. Using MKAnnotationView alone will result in no annotations from appearing.

Comments