0

I add custom annotation in a mapview. Based on the internal model of the app the annotations can change color and title (although position may not change). I am using dequeueReusableAnnotationViewWithIdentifierin the method

- (MKAnnotationView *)mapView:(MKMapView *) viewForAnnotation:(id <MKAnnotation>)

The strange thing is that when the model changes the annotations are "refreshed" to use the correct title and color (I just use removeAnnotations: and add the new ones), but later when playing with the map some old annotations with wrong color are dequeued. I use a different identifier each time the model changes.

here is the code:

- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation{
    // in case it's the user location, we already have an annotation, so just return nil
    if ([annotation isKindOfClass:[MKUserLocation class]]){
        return nil;
    }
    if ([annotation isKindOfClass:[APGSAnnotation class]]){
        APGSAnnotation *gsn = (APGSAnnotation*) annotation;
        NSString *GSAnnotationIdentifier = [NSString stringWithFormat:@"gid_%lu_%@", (unsigned long)gsn.gs.gID, self.car.energyType];

        MKAnnotationView *markerView = [theMapView dequeueReusableAnnotationViewWithIdentifier:GSAnnotationIdentifier]; 
        if (markerView == nil) {
            MKAnnotationView *annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation                                                                            reuseIdentifier:GSAnnotationIdentifier];
            annotationView.canShowCallout = YES;
            annotationView.image = [self customizeAnnotationImage:gsn.gs];
            annotationView.opaque = NO;

            UIImageView *sfIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:gsn.logo]];
            annotationView.leftCalloutAccessoryView = sfIconView;

            // http://stackoverflow.com/questions/8165262/mkannotation-image-offset-with-custom-pin-image
            annotationView.centerOffset = CGPointMake(0,-annotationView.image.size.height/2);


            UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
            [rightButton addTarget:nil action:nil forControlEvents:UIControlEventTouchUpInside];
            annotationView.rightCalloutAccessoryView = rightButton;

            return annotationView;
        }else{
            markerView.annotation = annotation;
            return markerView;
        }
    }
    return nil;
}

and the customize method

- (UIImage*)customizeAnnotationImage:(APGS*)gs{
    UIImage *markerImage;

    if (gs.gID == self.best.gID) {
        markerImage = [UIImage imageNamed:@"marker_red.png"];
    }else if (gs.type == k1){
        markerImage = [UIImage imageNamed:@"marker_blue.png"];
    }else if (gs.type == k2){
        markerImage = [UIImage imageNamed:@"marker_green.png"];
    }else if (gs.type == k3){
        markerImage = [UIImage imageNamed:@"marker_purple.png"];
    }else if (gs.type == k4){
        markerImage = [UIImage imageNamed:@"marker_brown.png"];
    }
    UIImage *logoImage = [UIImage imageNamed:gs.logo];
    // size the flag down to the appropriate size
    CGRect resizeRect;
    resizeRect.size = markerImage.size;
    CGSize maxSize = CGRectInset(self.view.bounds, kAnnotationPadding, kAnnotationPadding).size;

    maxSize.height -= self.navigationController.navigationBar.frame.size.height + kCallOutHeight;

    if (resizeRect.size.width > maxSize.width)
        resizeRect.size = CGSizeMake(maxSize.width, resizeRect.size.height / resizeRect.size.width * maxSize.width);

    if (resizeRect.size.height > maxSize.height)
        resizeRect.size = CGSizeMake(resizeRect.size.width / resizeRect.size.height * maxSize.height, maxSize.height);

    resizeRect.origin = CGPointMake(0.0, 0.0);
    float initialWidth = resizeRect.size.width;

    UIGraphicsBeginImageContextWithOptions(resizeRect.size, NO, 0.0f);
    [markerImage drawInRect:resizeRect];
    resizeRect.size.width = resizeRect.size.width/2;
    resizeRect.size.height = resizeRect.size.height/2;

    resizeRect.origin.x = resizeRect.origin.x + (initialWidth - resizeRect.size.width)/2;
    resizeRect.origin.y = resizeRect.origin.y + kLogoHeightPadding;

    [logoImage drawInRect:resizeRect];


    // Create string drawing context
    UIFont *font = [UIFont fontWithName:@"DBLCDTempBlack" size:11.2];
    NSString * num = [NSString stringWithFormat:@"%4.3f",[gs getL]];
    NSDictionary *textAttributes = @{NSFontAttributeName: font,
                                     NSForegroundColorAttributeName: [UIColor whiteColor]};

    CGSize textSize = [num sizeWithAttributes:textAttributes];

    NSStringDrawingContext *drawingContext = [[NSStringDrawingContext alloc] init];

    //adjust center
    if (resizeRect.size.width - textSize.width > 0) {
        resizeRect.origin.x += (resizeRect.size.width - textSize.width)/2;
    }else{
        resizeRect.origin.x -= (resizeRect.size.width - textSize.width)/2;
    }

    resizeRect.origin.y -= kTextPadding;
    [num drawWithRect:resizeRect
              options:NSStringDrawingUsesLineFragmentOrigin
           attributes:textAttributes
              context:drawingContext];

    UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resizedImage;
}

Based on car type the annotations should change color.

Sanandrea
  • 2,112
  • 1
  • 27
  • 45
  • After dequeing the view you should reset all relevant properties before returning the view – Paulw11 Aug 31 '14 at 10:42
  • I have edited my question for the "As is" code. – Sanandrea Aug 31 '14 at 10:53
  • Also, your code doesn't return a value in all paths - this will cause a problem – Paulw11 Aug 31 '14 at 11:40
  • I just noticed the ... so perhaps the return is just not shown, but you should re-initialise all of the view properties in the else section – Paulw11 Aug 31 '14 at 11:51
  • 1
    Note that the properties of the annotation view should be updated based on the `annotation` argument whether the view is created _or_ dequeued. So if the `...` is doing that, it should go after the whole if/else statement and the `return` should be moved after that. –  Aug 31 '14 at 12:49
  • Right now your question is unclear as to what the exact problem is and your assumption that it can be "fixed" by "resetting the queue" will defeat the benefits of view re-use. –  Aug 31 '14 at 12:50
  • @Anna added missing code – Sanandrea Aug 31 '14 at 14:06
  • 1
    The problem appears to be that when a view is dequeued (when markerView != nil), the code is not updating the view's image and other properties (it is only updating the annotation property). So what can happen is an annotation re-uses a view from a previous annotation which had a different image, etc. –  Aug 31 '14 at 14:08

1 Answers1

1

Based on the title, the answer is NO :)

Normally you would dequeue the view and reset all relevant properties of the view

workaround:

What you can consider is: if the views would change too much, you could switch to a different reusingIdentifier, thereby switching the queue and 'circumventing' the cached views

Community
  • 1
  • 1
Daij-Djan
  • 49,552
  • 17
  • 113
  • 135