Question

I have a conditional statement to add map annotation icons/pins in the method below. The problem I'm having is that the map is being populated with all the same icon. It should detect the cat id and display the icons depending on which cat id is detected. I'm not sure what the problem is because this did work in iOS 6 and now in iOS 7 the map only displays all the same annotation icon images.

- (MKAnnotationView *) mapView:(MKMapView *)mapingView viewForAnnotation:(id <MKAnnotation>) annotation {
annView = nil;
if(annotation != mapingView.userLocation)
{
    
    static NSString *defaultPinID = @"";
    annView = (MKAnnotationView *)[mapingView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
    if ( annView == nil )
        annView = [[MKAnnotationView alloc]
                   initWithAnnotation:annotation reuseIdentifier:defaultPinID] ;
    
    
    UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
    [rightButton setTitle:annotation.title forState:UIControlStateNormal];
 
    annView.rightCalloutAccessoryView = rightButton;
    
    MyAnnotation* annotation= [MyAnnotation new];
    
    annotation.catMapId = categoryIdNumber;
    NSLog(@"categoryIdNumber %@",categoryIdNumber);
    NSLog(@"annotation.catMapId %@",annotation.catMapId);

    
        if (annotation.catMapId == [NSNumber numberWithInt:9]) {
            annView.image = [UIImage imageNamed:@"PIN_comprare.png"];
            
            NSLog(@"annview 9");
            
        }
        
        else if (annotation.catMapId == [NSNumber numberWithInt:10]) {
            annView.image = [UIImage imageNamed:@"PIN_mangiare.png"];
            
            NSLog(@"annview 10");
            
        }
        
        else if (annotation.catMapId == [NSNumber numberWithInt:11]) {
            annView.image = [UIImage imageNamed:@"PIN_visitare.png"];
            
            NSLog(@"annview 11");
            
        }
        
        else if (annotation.catMapId == [NSNumber numberWithInt:12]) {
            annView.image = [UIImage imageNamed:@"PIN_vivere.png"];
            
            NSLog(@"annview 12");
            
        }
 
    annView.canShowCallout = YES;
    
}

return annView;

}

enter image description here

Was it helpful?

Solution

If, as you say, "this did work in iOS 6", you should consider it quite fortunate that it did (or seemed to) and this approach of setting the annotation's image should not be relied on under any version.

Although @Ar Ma is correct that the annotation view's annotation property should be set (in case the view is being re-used), that won't solve the main issue.

The annotation view's image is set based on the value of categoryIdNumber which seems to be some variable outside the viewForAnnotation delegate method.

You cannot assume that:

  1. viewForAnnotation will be called immediately after you call addAnnotation. Even in iOS 6 or earlier, this is not guaranteed.
  2. viewForAnnotation will be called only once for each annotation. The delegate method can be called multiple times for the same annotation as the user pans or zooms the map and the annotation comes back onto the screen.
  3. viewForAnnotation will be called in the same order that you add the annotations. This is a result of points 1 and 2.

I assume that just before you call addAnnotation, the categoryIdNumber is set correctly and then based on the above incorrect assumptions, viewForAnnotation uses categoryIdNumber to set the image.

What is happening is that viewForAnnotation is being called by the map view sometime after all or some of the addAnnotation calls are done at which point categoryIdNumber is probably the value connected with the last annotation added and all the annotations use the image applicable to the last annotation.


To fix this (regardless of the iOS version), you must put the correct categoryIdNumber value into each annotation object before calling addAnnotation.

It looks like your annotation class is MyAnnotation and you already have a catMapId property in it.

You must set this property in the annotation before calling addAnnotation -- not inside the viewForAnnotation method which is too late. (By the way, you are creating a MyAnnotation object inside the viewForAnnotation method which is pointless.)


So where you create and add the annotations (not in viewForAnnotation):

MyAnnotation* myAnn = [[MyAnnotation alloc] init];
myAnn.coordinate = ...
myAnn.title = ...
myAnn.catMapId = categoryIdNumber;  // <-- set catMapId BEFORE addAnnotation
[mapView addAnnotation:myAnn];

Then the code in viewForAnnotation should be like this:

- (MKAnnotationView *) mapView:(MKMapView *)mapingView viewForAnnotation:(id <MKAnnotation>) annotation
{
    annView = nil;
    if(annotation != mapingView.userLocation)
    {

        static NSString *defaultPinID = @"MyAnnId";
        annView = (MKAnnotationView *)[mapingView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
        if ( annView == nil )
        {
            annView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID] ;
            annView.canShowCallout = YES;
        }
        else
        {
            //view is being re-used, re-set annotation to current...
            annView.annotation = annotation;
        }

        UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
        [rightButton setTitle:annotation.title forState:UIControlStateNormal];

        annView.rightCalloutAccessoryView = rightButton;


        //Make sure we have a MyAnnotation-type annotation
        if ([annotation isKindOfClass:[MyAnnotation class]])
        {
            //Do not CREATE a local MyAnnotation object here.
            //Instead, get the catMapId from the annotation object
            //that was PASSED INTO the delegate method.
            //MyAnnotation* annotation= [MyAnnotation new];
            //annotation.catMapId = categoryIdNumber;

            MyAnnotation *myAnn = (MyAnnotation *)annotation;

            //The value of the external variable categoryIdNumber is irrelevant here.
            //NSLog(@"categoryIdNumber %@",categoryIdNumber);

            NSLog(@"myAnn.catMapId %@",myAnn.catMapId);


            //Put the NSNumber value into an int to simplify the code below.
            int myAnnCatMapId = [myAnn.catMapId intValue];

            NSString *imageName = nil;
            switch (myAnnCatMapId)
            {
                case 9:
                {
                    imageName = @"PIN_comprare.png";
                    break;
                }

                case 10:
                {
                    imageName = @"PIN_mangiare.png";
                    break;
                }

                case 11:
                {
                    imageName = @"PIN_mangiare.png";
                    break;
                }

                case 12:
                {
                    imageName = @"PIN_vivere.png";
                    break;
                }

                default:
                {
                    //set some default image for unknown cat ids...
                    imageName = @"default.png";
                    break;
                }
            }

            annView.image = [UIImage imageNamed:imageName];

            NSLog(@"annview %d", myAnnCatMapId);
        }
    }

    return annView; 
}

OTHER TIPS

add this line at the end:

annView.annotation = annotation;

Same trouble, for me the decision was not to use

pinView.animatesDrop   = YES;

custom icons just not work with animated drop for me.

In case anyone needs to use MapView annotations as a tableView, i.e. have an array of points to display on map.

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    let identifier = MKMapViewDefaultAnnotationViewReuseIdentifier
    if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? TrailAnnotationView {
            ///TrailAnnotation has an object of type trail ( can be any model you ///want ). and so is TrailAnnotationView
///inside TrailAnnotationView we extract data from trail and display it.
        annotationView.trail = (annotation as? TrailAnnotation)?.trail
        annotationView.annotation = annotation
        return annotationView
    }
    let annotationView = TrailAnnotationView(annotation: annotation, reuseIdentifier: identifier)
    annotationView.trail = (annotation as? TrailAnnotation)?.trail
    annotationView.canShowCallout = true
    return annotationView
}

This is a TrailAnnotationView

protocol AnnotationViewProtocol {
    func didTapOnAnnotation()
}

class TrailAnnotationView: MKPinAnnotationView {

/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
    // Drawing code
}
*/

var trail: TrailModel? = nil


override var annotation: MKAnnotation? { didSet { configureDetailView() } }

override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
    super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    configure()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    configure()
}

}

private extension TrailAnnotationView {
    func configure() {
        canShowCallout = true
        configureDetailView()
    }

func configureDetailView() {
    guard let annotation = annotation else { return }

    let rect = CGRect(origin: .zero, size: CGSize(width: 300, height: 200))

    let snapshotView = UIView()
    snapshotView.translatesAutoresizingMaskIntoConstraints = false

    if let trail = self.trail, !trail.imageUrl.isEmpty {
        AppUtils.sharedInstance.fetchImageFor(path: trail.imageUrl) { (image) in
            guard let imageData = image else { return }
            DispatchQueue.main.async {
                let imageView = UIImageView(frame: rect)
                imageView.image = imageData
                snapshotView.addSubview(imageView)
            }
        }
    } else {
        let options = MKMapSnapshotter.Options()
        options.size = rect.size
        options.mapType = .satelliteFlyover
        options.camera = MKMapCamera(lookingAtCenter: annotation.coordinate, fromDistance: 250, pitch: 65, heading: 0)

        let snapshotter = MKMapSnapshotter(options: options)
        snapshotter.start { snapshot, error in
            guard let snapshot = snapshot, error == nil else {
                print(error ?? "Unknown error")
                return
            }

            let imageView = UIImageView(frame: rect)
            imageView.image = snapshot.image
            snapshotView.addSubview(imageView)
        }
    }

    detailCalloutAccessoryView = snapshotView
    NSLayoutConstraint.activate([
        snapshotView.widthAnchor.constraint(equalToConstant: rect.width),  
        snapshotView.heightAnchor.constraint(equalToConstant: rect.height)
    ])
}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top