Search code examples
iosmkannotationview

dynamically change leftCalloutAccessoryView based on the MKAnnotationView that is selected


I have an array of images, that are associated with each Annotation on my map. I can statically add an image to the leftCalloutAccessoryView but I am unsure how to make this dynamic. I hope its clear what I am asking. Each annotation has its own individual image that I want to display but I am unsure of how to reference the image in the following method;

- (MKAnnotationView *)mapView:(MKMapView *)mv viewForAnnotation:(id <MKAnnotation>)annotation
{
    if([annotation isKindOfClass:[MKUserLocation class]])
    return nil;

    NSString *annotationIdentifier = @"PinViewAnnotation";

    MyAnnotationView *pinView = (MyAnnotationView *) [mv dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];


    if (!pinView)
    {
        pinView = [[MyAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];

       pinView.canShowCallout = YES;

       UIImageView *houseIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Icon"]];//static image
        [houseIconView setFrame:CGRectMake(0, 0, 30, 30)];
        pinView.leftCalloutAccessoryView = houseIconView;

    }
    else
    {
        pinView.annotation = annotation;
    }

    return pinView;

}

My array "self.sandwiches" contains Sandwich objects that have a name (NSString) and an imageName ('NSString').

Im looking for a solution where I can get the index of the pin that is selected, similar to a UITableView where you can get its index, and access it from the array using indexPath.row.

My Annotation class; .h #import #import #import

@interface SandwichAnnotation : NSObject<MKAnnotation>
@property (nonatomic,assign) CLLocationCoordinate2D coordinate;
@property (nonatomic,copy) NSString * title;
@property (nonatomic,copy) NSString * subtitle;

@end

.m

#import "SandwichAnnotation.h"

@implementation SandwichAnnotation
@synthesize coordinate,title,subtitle;

@end

Solution

  • In viewForAnnotation, rather than "getting the index of the pin" (which would work but is less efficient here than with a UITableView), I suggest adding the data required to the annotation class itself.

    This way, the data is more self-contained and the code in the delegate method or elsewhere doesn't need to worry, know, or be kept in sync with where or what kind of structure the annotation object is stored in. As long as you have a reference to the annotation object, you will immediately have all the data needed for that annotation (or at least it will contain references to the related data within itself).

    The viewForAnnotation delegate method provides a reference to the annotation object it needs a view for (the annotation parameter). It's typed generically as id<MKAnnotation> but it is actually an instance of the exact type that was created (either SandwichAnnotation by you or MKUserLocation by the map view).


    One option is to make the parent Sandwich class itself implement MKAnnotation and eliminate the SandwichAnnotation class. This way, no searching or references are needed at all since the annotation parameter will actually be a Sandwich.


    However, you may want to keep a separate class for your annotation objects (which is fine). In this case, you can add a reference to the parent object(s) in the annotation class. Example:

    @interface SandwichAnnotation : NSObject<MKAnnotation>
    @property (nonatomic,assign) CLLocationCoordinate2D coordinate;
    @property (nonatomic,copy) NSString * title;
    @property (nonatomic,copy) NSString * subtitle;
    @property (nonatomic,retain) Sandwich * whichSandwich;  // <-- add reference
    @end
    

    When creating a SandwichAnnotation, set the reference to which Sandwich the annotation is for:

    for (Sandwich *currentSandwich in self.sandwiches) {
        SandwichAnnotation *sa = [[SandwichAnnotation alloc] init...];
        sa.coordinate = ...
        sa.title = ...
        sa.whichSandwich = currentSandwich; // <-- set reference
    
        [mapView addAnnotation:sa];
    }
    

    Finally, in viewForAnnotation, if annotation is of type SandwichAnnotation, set the leftCalloutAccessoryView:

    - (MKAnnotationView *)mapView:(MKMapView *)mv viewForAnnotation:(id <MKAnnotation>)annotation
    {
        if (! [annotation isKindOfClass:[SandwichAnnotation class]]) {
            //If annotation is not a SandwichAnnotation, return default view...
            //This includes MKUserLocation.
            return nil;
        }
    
        //At this point, we know annotation is of type SandwichAnnotation.
        //Cast it to that type so we can get at the custom properties.
        SandwichAnnotation *sa = (SandwichAnnotation *)annotation;
    
        NSString *annotationIdentifier = @"PinViewAnnotation";
    
        MyAnnotationView *pinView = (MyAnnotationView *) [mv dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
    
    
        if (!pinView)
        {
            pinView = [[MyAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
    
           pinView.canShowCallout = YES;
    
           //Here, just initialize a blank UIImageView ready to use.
           //Set image below AFTER we have a dequeued or new view ready.
           UIImageView *houseIconView = [[UIImageView alloc] init];
           [houseIconView setFrame:CGRectMake(0, 0, 30, 30)];
           pinView.leftCalloutAccessoryView = houseIconView;
        }
        else
        {
            pinView.annotation = annotation;
        }
    
        //At this point, we have a dequeued or new view ready to use
        //and pointing to the correct annotation.
        //Update image on the leftCalloutAccessoryView here
        //(not just when creating the view otherwise an annotation
        //that gets a dequeued view will show an image of another annotation).
    
        UIImageView *houseIconView = (UIImageView *)pinView.leftCalloutAccessoryView;
        NSString *saImageName = sa.whichSandwich.imageName;
        UIImage *houseIcon = [UIImage imageNamed: saImageName];
    
        if (houseIcon == nil) {
            //In case the image was not found,
            //set houseIcon to some default image.
            houseIcon = someDefaultImage;
        }
    
        houseIconView.image = houseIcon;
    
        return pinView;
    
    }