I am trying to set a custom image for an MKPointAnnotation in my MKMapView. The images are set correctly on all iPhones except for 6+:
But on iPhone 6+ and iPad, the annotation has the default pin instead of my picture:
Below is the code that sets the image of the annotations, as well as the entry for parking_spot_icon.png in Images.xcassets. None of my code is dependent on the size of the device, and there are no relevant errors or build warnings (in particular, no warnings about incorrect image size).
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = parkingLot->coordinate;
[_mapView addAnnotation:annotation];
[_mapView selectAnnotation:annotation animated:YES]; // this is needed for the image to be set correctly on iphone.
[_mapView deselectAnnotation:annotation animated:YES]; // this is needed for the image to be set correctly on iphone.
annotation.title = parkingLot->name;
UIImage *image = [UIImage imageNamed:@"parking_spot_icon.png"];
[[_mapView viewForAnnotation:annotation] setImage:image];
I have tried stepping through the code, and I saw that UIImage *image prints as the following object in both cases (iPhone and iPad):
(lldb) p *image
(UIImage) $0 = {
NSObject = {
isa = UIImage
}
_imageRef = 0x00007fd2d733fd30 // differs run-to-run
_scale = 3 // 2 on iPhone, 3 on iPad
_imageFlags = {
named = 1
imageOrientation = 0
cached = 0
hasPattern = 0
isCIImage = 0
renderingMode = 0
suppressesAccessibilityHairlineThickening = 0
hasDecompressionInfo = 0
}
}
Here is my image asset for parking_lot_icon:
The only difference I can see is that iPad uses a different image scale; but I included icons for all scales. I feel like I am missing something obvious. Can anyone think of a reason why my images could be getting set on iPhone but not iPad/6+?
To set a custom image for annotation views, you must implement the viewForAnnotation
delegate method.
You cannot call the map view's viewForAnnotation
instance method and set the image
on that view since that would be a one-time execution which will not "stick" to the view.
When the map view needs a view for this or other annotations later (this could be long after the annotation is added), it will call the delegate method (if implemented). If the delegate method isn't implemented, the map view shows the default red pin for you.
The fact that it "works" on an iPhone as opposed to an iPad is just random, unpredictable behavior which you should not rely on.
Sample implementation of the viewForAnnotation
delegate method:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
//if this is the user location, return nil so map view
//draws default blue dot for it...
return nil;
}
static NSString *reuseId = @"ann";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (av == nil) {
av = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
av.canShowCallout = YES;
UIImage *image = [UIImage imageNamed:@"parking_spot_icon.png"];
av.image = image;
}
else {
//we are recycling a view previously used for another annotation,
//update its annotation reference to the current one...
av.annotation = annotation;
}
return av;
}
Make sure the map view's delegate
property is set or that its outlet is connected to the view controller in the storyboard/xib otherwise the delegate method will not get called even though it's implemented.
selectAnnotation
and deselectAnnotation
when creating the annotation -- they're not necessary.