Question

I have a map on which I can add multiple annotations with a customisable subtitle. I want to put the annotations into an array with the updated subtitle.

Currently when I add an annotation it goes into the array, but I don't know how to update the array with the subtitle and remove the annotation from the array if it gets removed from the map.

I was also thinking to add all the annotations to the array when switching to the next view, but not sure how to do that.

My current code:

Adding the annotation with a UIGestureRecognizer

    MapAnnotation *mapPoint = [[MapAnnotation alloc]init];

    mapPoint.coordinate = touchMapCoordinate;
    mapPoint.title = address;
    mapPoint.subtitle = @"";

    [self.map addAnnotation:mapPoint];

    self.mapLatitudeString = [NSString stringWithFormat:@"%f",mapPoint.coordinate.latitude];
    self.mapLongitudeString = [NSString stringWithFormat:@"%f",mapPoint.coordinate.longitude];
    self.mapTitleString = [NSString stringWithFormat:@"%@", mapPoint.title];
    self.mapSubtitleString = [NSString stringWithFormat:@"%@", mapPoint.subtitle];

    self.mapAnnotation = [NSString stringWithFormat:@"%d,%@,%@,%@,%@",self.mapAnnotationArray.count, self.mapLatitudeString, self.mapLongitudeString, self.mapTitleString, self.mapSubtitleString];

    if (!self.mapAnnotationArray) {
        self.mapAnnotationArray = [[NSMutableArray alloc] init];
    }
    [self.mapAnnotationArray addObject:self.mapAnnotation];
    NSLog(@"%@", self.mapAnnotationArray);

Editing the subtitle and removing the annotation from an alert view

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    if (buttonIndex == 0) {
    }
    if (buttonIndex == 1) {
        if (self.map.selectedAnnotations.count > 0)
        {
            id<MKAnnotation> ann = [self.map.selectedAnnotations objectAtIndex:0];

            if ([ann isKindOfClass:[MapAnnotation class]])
            {
                MKPointAnnotation *pa = (MKPointAnnotation *)ann;
                pa.subtitle = [[alertView textFieldAtIndex:0] text];
            }
        }
    }
    if (buttonIndex == 2) {
        [self.map removeAnnotations:self.map.selectedAnnotations];
    }
}
Was it helpful?

Solution

With the current approach of adding a formatted string representation (that includes the sequence number) of each annotation to the mapAnnotationArray, the process of maintaining the array is tedious:

  1. To update a field inside one of the formatted strings, you have to:
    a. Loop through the array,
    b. Parse the string (eg. using componentsSeparatedByString),
    c. Check if the field(s) match the annotation you are looking for,
    d. Rebuild the formatted string with new values,
    e. Update the string in the array using replaceObjectAtIndex:withObject:
  2. To remove an entry from the array, you have to:
    a. Do the same steps as 1a to 1c,
    b. Remove the string using removeObjectAtIndex:
    c. Update the annotation sequence number in all the remaining annotations

These steps would have to be done every time you want to retrieve or search for a field in mapAnnotationArray. It's not efficient or flexible (what if user decides to use a comma in the subtitle, what if address contains a comma, etc).


Instead, store the annotation objects themselves in mapAnnotationArray and only generate the formatted string representation when you need to submit the data to the server. Storing a proper object in the array lets you take advantage object-oriented methods.

  1. In the method that adds the annotation to the map, instead of adding the formatted string to mapAnnotationArray, add the annotation itself:

    [self.mapAnnotationArray addObject:mapPoint];
    
  2. Where you update the annotation's subtitle in the alert view delegate method, you don't need to do anything to mapAnnotationArray now because the annotation object in the array is the exact same object you updated the subtitle of. You can NSLog the array here to see the change.

  3. Where you remove the annotation in the alert view delegate method, you need to remove the same object from mapAnnotationArray (the map view doesn't know about your array):

    if (buttonIndex == 2) 
    {
        if (self.map.selectedAnnotations.count > 0)
        {
            id<MKAnnotation> ann = [self.map.selectedAnnotations objectAtIndex:0];
            if ([ann isKindOfClass:[MapAnnotation class]])
            {
                [self.mapAnnotationArray removeObject:ann];
            }
            [self.map removeAnnotations:self.map.selectedAnnotations];
        }
    }
    
  4. Next, to make it easier to generate the formatted string and to debug the contents of mapAnnotationArray, add a helper method and an override for the description method to your MapAnnotation class:

    -(NSString *)stringForServer
    //You can name this method whatever you want.
    //Builds the comma-delimited representation 
    //of this annotation (excluding the sequence number).
    //Note this format still "breaks" if title or subtitle contain commas.
    {
        NSString *result = [NSString stringWithFormat:@"%f,%f,%@,%@",
                            self.coordinate.latitude, 
                            self.coordinate.longitude, 
                            self.title, 
                            self.subtitle];
        return result;
    }
    
    -(NSString *)description
    //This method must be named this.
    //When you NSLog this object, it will call this method to get
    //what string to display for it.
    {
        return [NSString stringWithFormat:@"%@ (%@)", 
                   [super description], [self stringForServer]];
    }
    
  5. Finally, wherever you submit the data to the server, loop through the array and generate the formatted strings from the annotation objects (and prefix the sequence number). This example just loops through the array and NSLogs the formatted string for each annotation:

    for (int i=0; i < self.mapAnnotationArray.count; i++)
    {
        MapAnnotation *ma = (MapAnnotation *)[self.mapAnnotationArray objectAtIndex:i];
        NSString *maString = [ma stringForServer];
        NSString *fullStringForServer = [NSString stringWithFormat:@"%d,%@", i, maString];
        NSLog(@"maa[%d] = %@", i, fullStringForServer);
    }
    

    Prefixing the sequence number when you are actually ready to save the data avoids re-numbering and duplication problems. Of course, this assumes that annotations don't have to keep the same sequence number across saves.

    Also consider using JSON instead of a comma-delimited format. NSJSONSerialization makes it relatively easy. See Convert an iOS objective c object to a JSON string for an example.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top