29

I'd like to a-synchronically load images for my custom MKAnnotationView; I'm already using the EGOImageView-framework (it goes very well with UITableViews), but I fail to make it work on MKMapView. Images seem to be loaded, but I cannot refresh them on the map - [myMap setNeedsDisplay] does nothing.

cocoapriest
  • 1,869
  • 3
  • 22
  • 36

7 Answers7

37

I haven't tried it myself, but it might work:
Get a view for annotation using MKMapView - (MKAnnotationView *)viewForAnnotation:(id <MKAnnotation>)annotation method and set new image to it.

Edit: Tried to do that myself and this approach worked fine for me:

//  Function where you got new image and want to set it to annotation view
for (MyAnnotationType* myAnnot in myAnnotationContainer){
    MKAnnotationView* aView = [mapView viewForAnnotation: myAnnot];
    aView.image = [UIImage imageNamed:@"myJustDownloadedImage.png"];
}

After calling this method all annotations images were updated.

Vladimir
  • 170,431
  • 36
  • 387
  • 313
  • that's how it's done now. But this way image is loaded synchronically. – cocoapriest Nov 10 '09 at 13:28
  • And if you call it after the image is loaded and set this image to the view - doesn't it work? – Vladimir Nov 10 '09 at 13:50
  • that's the question - how to set this image later. By doing so, image gets not updated on the MKAnnotationView – cocoapriest Nov 11 '09 at 14:09
  • As I've written - MKMapView allows you to obtain a view for any annotation. See in edited answer how it can be used. – Vladimir Nov 11 '09 at 14:31
  • I tried this, but the callout is no longer centered over the top of the pin, even though the pin is identical in position to the previous image. Seems to be exacerbated after zooming. – Joe D'Andrea Aug 08 '12 at 19:00
13

I found only one reliable way to update annotation, others methods didn't work for me: just removing and adding annotation again:

id <MKAnnotation> annotation;

// ...

[mapView removeAnnotation:annotation];
[mapView addAnnotation:annotation];

This code will make -[mapView:viewForAnnotation:] to be called again and so your annotation view will be created again (or maybe reused if you use dequeueReusableAnnotationViewWithIdentifier) with correct data.

ivanzoid
  • 5,952
  • 2
  • 34
  • 43
  • i ended up using this technique for single annotations refreshment – nurnachman Mar 28 '12 at 18:05
  • I'm trying to use this method, and SO want it to work, but it doesn't seem to behave reliably depending on the zoom level. Sometimes the pin appears offset to the left/right by too much, and the callout is no longer aligned. (This is with pin images that have subtle changes but are otherwise identical in composition, with the pin 'center' at the very center of the image.) Hmm ... – Joe D'Andrea Aug 08 '12 at 19:03
  • Yea I had to tediously addAnnotations all again to refresh them. – coolcool1994 Mar 19 '14 at 07:11
  • it won't work if you want to update a selected annotation – Vyachaslav Gerchicov Dec 10 '19 at 11:58
1

This is the only way I've found to force redraw of MKAnnotationViews:

// force update of map view (otherwise annotation views won't redraw)

CLLocationCoordinate2D center = mapView.centerCoordinate;

mapView.centerCoordinate = center;

Seems useless, but it works.

ZeroDiv
  • 290
  • 1
  • 5
1

This worked for me

#import "EGOImageView.h"

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

    static NSString *reuseIdentifier = @"reusedAnnView";
    annView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
    annView.canShowCallout = YES;
    annView.calloutOffset = CGPointMake(-5.0f, 0.0f);
    annView.opaque = NO;
    annView.image = [[UIImage imageNamed:@"tempImage.png"] retain];

    YourModel *p = annotation; // make this conform to <MKAnnotation>
    EGOImageView *egoIV = [[EGOImageView alloc] initWithPlaceholderImage:[UIImage imageNamed:@"tempImage.png"]];
    [egoIV setDelegate:self];
    egoIV.imageURL = [NSURL URLWithString:p.theUrlToDownloadFrom];
    [annView addSubview:egoIV];
    [egoIV release];


    return [annView autorelease];
}
johndpope
  • 5,035
  • 2
  • 41
  • 43
0

Thank you @Vladimir for the help! Your answer inspired my answer. I made some changes to make it clearer.

func addAnnotation(coordinate: CLLocationCoordinate2D) {
    let annotation = Annotation()
    annotation.coordinate = coordinate
    annotations.append(annotation)
    mapView.addAnnotation(annotation)
    for index in 0..<annotations.count {
        if let view = mapView.view(for: annotations[index]) as? AnnotationView {
            if index == 0 {
                view.imageView.image = NSImage(named: "icon_positioning")
            } else {
                view.imageView.image = NSImage(named: "icon_positioning")
            }
        }
    }
}

Also the protocol in MKMapview needs to implement:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    let view = mapView.dequeueReusable(withClass: AnnotationView.self, annotation: annotation)
    if index == 0 {
        view.imageView.image = NSImage(named: "icon_positioning")
    } else {
        view.imageView.image = NSImage(named: "icon_positioning")
    }
    return view
}

By the way, the AnnotationView initialization here is:

public extension MKMapView {
    func dequeueReusable<T: MKAnnotationView>(withClass name: T.Type,annotation: MKAnnotation?) -> T {
        if let view = dequeueReusableAnnotationView(withIdentifier: T.identifier()) as? T {
            return view
        }
        let view = T.init(annotation: annotation, reuseIdentifier: T.identifier())
        return view
    }
}

Thanks again @Vladimir for the answer

Karim
  • 322
  • 4
  • 20
-1

(I don't see how to indicate that this post pertains to zerodiv's answer.) I was able to get zerodiv's method to work by adding a kickstart of sorts, forcing a meaningless but different coordinate, then restoring the prior, correct one. The screen doesn't flash at all because of the first location.

CLLocationCoordinate2D center = mapView.centerCoordinate; 
CLLocationCoordinate2D forceChg;
forceChg.latitude = 0;
forceChg.longitude = curLongNum;

mapView.centerCoordinate = forceChg;
mapView.centerCoordinate = center;
Jack Bellis
  • 1,177
  • 8
  • 10
-1

I wanted to refresh user location pin with new selected avatar. I found another fast solution for that case. I have 2 screens. The first one is for selecting avatar and the second is for displaying user on map.

swift 5 You can change user location image by creating MKAnnotationView

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

    if annotation is MKUserLocation {
        let userIdentifier = "UserLocation"
        var userView = mapView.dequeueReusableAnnotationView(withIdentifier: userIdentifier)
        if userView == nil {
            userView = MKAnnotationView(annotation: annotation, reuseIdentifier: userIdentifier)
        } else {
            userView?.annotation = annotation
        }

        userView!.image = UIImage(named: "avatar1") //your image

        return userView
    }

    return nil
 }

So if user will change avatar to something else, for example to "avatar2" it won't refresh.

In order to refresh user location image i added this code in viewWillAppear()

 self.mapView.showsUserLocation = false
 self.mapView.showsUserLocation = true
ShadeToD
  • 403
  • 9
  • 22