1
votes

I've a viewController (with a mapView) which is included in a tabBarController (my viewController called MapViewController is a section of TabBarController).

My TabBarController is included in a navigationController.

I created the annotationView that are added (via the delegate method MKMapViewDelegate) to the map. I allow the callout to show title and subtitle. Title and subtitle are taken from the queries made ​​to my database. From these queries I get title, details, ID (in string version) and images.

I have no problems in setting up title and subtitle in the callout of each annotation.

But when I set the images for each callout as LeftCalloutAccessoryView, the system puts me all the same image.

And also when I go to click on the RightCalloutAccessoryView (that is a button which push to another viewController) which should open (so push) as a new window navigationController, give me back the wrong ID (ID of another annotation) and therefore are the details wrong in the new window.

I know that maybe explaining it so it is a bit difficult to understand, but instead here's the code:

    + (CGFloat)annotationPadding;
    {
        return 10.0f;
    }

    + (CGFloat)calloutHeight;
    {
        return 40.0f;
    }


    - (void)viewDidLoad
    {
        [super viewDidLoad];
        [_mapView setDelegate:self];
        _arrPosters = [[NSMutableArray alloc] init];
        _script = [[DBGMScr alloc] init]; //Is a class that provides script database connection
//With fieldsRequest I get the array with the field results sought
        _arrID = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:@"SELECT ID FROM Event ORDER BY DateTime DESC"]];
        _arrEventNames = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:@"SELECT Name FROM Event ORDER BY DateTime DESC"]];
        _arrEventLatitude = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:@"SELECT Latitude FROM Event ORDER BY DateTime DESC"]];
        _arrEventLongitude = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:@"SELECT Longitude FROM Event ORDER BY DateTime DESC"]];
        _arrEventDescription = [[NSMutableArray alloc] initWithArray:[_script fieldsRequest:@"SELECT Description FROM Event ORDER BY DateTime DESC"]];

        for (int i = 0; i < [_arrID count]; i++) {

//With getPoster I get the poster for the event (located on the server) by using a script (it works perfectly), and add it to the array of posters
        UIImage *poster = [_script getPoster:_arrID[i]];


            [_arrPosters insertObject:poster atIndex:i];
        }

        for (int i = 0; i < [_arrID count]; i++) {


            MKPointAnnotation *aAnnotationPoint = [[MKPointAnnotation alloc] init];
            CLLocationCoordinate2D theCoordinate;
            theCoordinate.latitude = [_arrEventLatitude[i] doubleValue];
            theCoordinate.longitude = [_arrEventLongitude[i] doubleValue];
            aAnnotationPoint.coordinate = theCoordinate;
            aAnnotationPoint.title = _arrEventNames[i];

            aAnnotationPoint.subtitle = _arrEventDescription[i];

            // Add the annotationPoint to the map
            [_mapView addAnnotation:aAnnotationPoint];

        }

    }


    #pragma mark - MKMapViewDelegate



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

        CGRect myRect = CGRectMake(-20, -20, 40, 40);

        EventAnnotationView *viewAnno = [[EventAnnotationView alloc] initWithFrame:myRect];

        viewAnno.canShowCallout = YES;
        UIButton* rightButton = [UIButton buttonWithType:
                                 UIButtonTypeDetailDisclosure];
        viewAnno.rightCalloutAccessoryView = rightButton;
        UIImageView *iconView = [[UIImageView alloc] initWithFrame:myRect];
        iconView.image = defaultImage; //defaultImage for now, but I want to show a different image for each annotation
        viewAnno.leftCalloutAccessoryView = iconView;
        NSString *viewTitle = [viewAnno.annotation title];

        return viewAnno;
    }


    - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
    {

        EventViewerViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"EventViewer"];
        [self.navigationController pushViewController:controller animated:YES];
        NSDictionary *selection = @{@"ID" : _arrID[_eventIndex] //but it get an incorrect ID,
                                    @"eventName" : _arrEventNames[_eventIndex]};

        [controller setValue:selection forKey:@"selection"];
    }

    -(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
    {
    //_eventIndex is an NSInteger declared in file .h
        _eventIndex = [_mapView.annotations indexOfObject:view.annotation];


    }

EventAnnotationView is subclass of MKAnnotationView:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // make sure the x and y of the CGRect are half it's
        // width and height, so the callout shows when user clicks
        // in the middle of the image
        CGRect  viewRect = CGRectMake(-20, -20, 40, 40);
        UIImageView* imageView = [[UIImageView alloc] initWithFrame:viewRect];

        // keeps the image dimensions correct
        // so if you have a rectangle image, it will show up as a rectangle,
        // instead of being resized into a square
        imageView.contentMode = UIViewContentModeScaleAspectFit;

        _imageView = imageView;

        [self addSubview:imageView];
    }
    return self;
}

- (void)setImage:(UIImage *)image
{
    // when an image is set for the annotation view,
    // it actually adds the image to the image view
    _imageView.image = image;
}
1

1 Answers

2
votes

There are several issues here but to answer your two main questions first:

  1. To set leftCalloutAccessoryView to a different image for each annotation, you need to look at some value in the annotation parameter that is passed to the viewForAnnotation delegate method. At the crudest level, you could set the image based on the value of annotation.title (eg. if title is "A" then set image to "1" else if title is "B" then set image to "2" etc).
  2. Tapping on the rightCalloutAccessoryView gives you the wrong Event Id because of this code in didSelectAnnotationView:

    _eventIndex = [_mapView.annotations indexOfObject:view.annotation];
    

    The above code assumes that the annotations array that the map view returns will be in the same exact order that you added the annotations in (and that it will only contain annotations that you added which isn't the case if showsUserLocation is YES). This assumption is false. Do not assume the annotations array is in any particular order -- do not rely on its order.

    A better solution is to use the annotation object itself that you can access directly from the calloutAccessoryControlTapped delegate method using view.annotation.

    Since you need the Event's Id and the default MKPointAnnotation class obviously has no property to store that anywhere, you should create your own class (eg. EventAnnotation) that implements MKAnnotation and add all the event properties you need to know for each annotation. Then, you'll be able to access event-specific values directly using the annotation object in the map view's delegate methods without trying to reference back to your original arrays.

    The EventAnnotation class might be declared like this:

    @interface EventAnnotation : NSObject<MKAnnotation>
    @property (assign) CLLocationCoordinate2D coordinate;
    @property (copy) NSString *title;
    @property (copy) NSString *subtitle;
    @property (copy) NSString *eventId;
      //Not sure if your event id is a string.
      //Change to "@property (assign) int eventId;" if it's an integer.
    @end
    

    Then to create the annotation, create an EventAnnotation instead of MKPointAnnotation:

    EventAnnotation *aAnnotationPoint = [[EventAnnotation alloc] init];
    CLLocationCoordinate2D theCoordinate;
    theCoordinate.latitude = [_arrEventLatitude[i] doubleValue];
    theCoordinate.longitude = [_arrEventLongitude[i] doubleValue];
    aAnnotationPoint.coordinate = theCoordinate;
    aAnnotationPoint.title = _arrEventNames[i];
    aAnnotationPoint.subtitle = _arrEventDescription[i];
    
    //store event id in the annotation itself...
    aAnnotationPoint.eventId = _arrID[i];
    
    [_mapView addAnnotation:aAnnotationPoint];
    

    Finally, in calloutAccessoryControlTapped (you won't need to implement didSelectAnnotationView), you could do this:

    EventViewerViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"EventViewer"];
    [self.navigationController pushViewController:controller animated:YES];
    
    //cast the view's annotation object to EventAnnotation...
    EventAnnotation *eventAnn = (EventAnnotation *)view.annotation;
    
    NSDictionary *selection = @{@"ID" : eventAnn.eventId,
                                @"eventName" : eventAnn.title };
    
    [controller setValue:selection forKey:@"selection"];
    


Another thing you should do in viewForAnnotation is explicitly set the view's annotation property immediately after creating it. Although it may be getting assigned automatically somehow, I'd rather do it explicitly to be safe:

EventAnnotationView *viewAnno = [[EventAnnotationView alloc] initWithFrame:myRect];
viewAnno.annotation = annotation;  // <-- add this



Two unrelated points:

  • Instead of creating a separate array for each event property (Ids, Names, Descriptions, Latitudes, etc), I would highly recommend creating an "Event" class with all those properties and then you could create just one array of Event objects. In fact, you could make this Event class implement MKAnnotation itself (instead of creating two classes Event and EventAnnotation).
  • The code is executing a separate SQL query for each column. All the queries are identical except for the column retrieved. This could be improved greatly by getting all the columns in a single SQL query (eg. SELECT ID, Name, Latitude, Longitude, Description FROM Event ORDER BY DateTime DESC). I am not familiar with this DBGMScr class but you should really look into it. If the table contains 500 annotations and 5 columns, you are currently retrieving a total of 2500 rows when it could be just 500 in one shot.