0

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];
    }
}
Ernie
  • 89
  • 1
  • 11
  • What is mapAnnotationArray used for? It would be much simpler if you added the `mapPoint` annotation object itself instead of the `self.mapAnnotation` string to the array. –  Mar 03 '14 at 19:52
  • @Anna Im going to pass `mapAnnotationArray` into a MySQL database from which I can retrieve later, the lat, long coordinates with title and subtitle and plot the annotations onto another mapview, `mapPoint` isn't showing me the full details as the NSStrings do. – Ernie Mar 03 '14 at 20:36
  • You're going to store the annotation data as a comma-delimited string in the database? That's not recommended. Still, you don't need to store the data as a formatted string while you have it in the array. You can add a `description` method override in the `MapAnnotation` class that outputs that formatted string on-demand from the distinct properties of the class. By storing the data as a delimited string, you'll have to constantly parse it to retrieve and search for values. –  Mar 03 '14 at 22:07
  • A separate potential issue is the annotation numbering scheme: if annotations are deleted, then new ones added later can get the same number as the deleted or existing ones resulting in a conflict/duplicates. You may want to re-think the overall design/approach before continuing. –  Mar 03 '14 at 22:09
  • @Anna Im sending the array to the database via php, php file removes any unwanted characters and stores the string to be retrieved and placed onto the map with the code Im using, currently this is working. I only have two problems with this, if I add an annotation to the array I cannot remove it and I cannot add the edited subtitle to the array. I do understand that the way you mentioned is better to work with especially if it will help me solve the two problems, but I don't know how to implement this. – Ernie Mar 03 '14 at 22:37

1 Answers1

1

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.

Community
  • 1
  • 1