Search code examples
iosmapsmkannotationclgeocodercompletionhandler

In iPhone how do I know when all of my map's annotations have loaded while using forward geocode?


In my app I have a table view that toggles the annotations on a map view. I can go back and forth between the table view and map view through the tab bar controller. My map will reload the annotations (from the selected items that are in the table view) on view did appear. I need to know when those annotations are done loading so that I can run my method that zooms to the generated region determined by the annotation cluster.

The problem was when I ran the zoom method directly after the addAnnotations method in the view did appear, it would start the zoom process before my annotations could get the correct coordinates. Thus resulting in an incorrect zoom, and my annotations moving to the correct location.

I would also like to note that I am using forward geocoding to get my annotations coordinates.

Here is my view did appear:

[super viewDidAppear:animated];

[businessMap removeAnnotations:businessPoints];
[businessPoints removeAllObjects];

UINavigationController *navVC = (UINavigationController *) [self.tabBarController.viewControllers objectAtIndex:0];

FirstViewController *VC = [navVC.viewControllers objectAtIndex:0];

for (businessInfo *business_info in VC.selectedBusinessesArray) {



    businessInfoAnnotation *businessAnnotation = [[businessInfoAnnotation alloc] init];

    businessAnnotation.businessInfoClass = business_info;

    CLGeocoder *geocoder = [[CLGeocoder alloc] init];

    [geocoder geocodeAddressString:business_info.location
                 completionHandler:^(NSArray* geocoded, NSError* error){
                     if (geocoded && geocoded.count > 0) {
                         CLPlacemark *placemark = [geocoded objectAtIndex:0];
                         CLLocation *location = placemark.location;
                         CLLocationCoordinate2D business_cords = location.coordinate;
                         businessAnnotation.coordinate = business_cords;
                     }
                 }];

    businessAnnotation.title = business_info.name;

    [businessPoints addObject:businessAnnotation];

}

[businessMap addAnnotations:businessPoints];

[businessMap setZoomEnabled:YES];

[self zoomToFitMapAnnotations:businessMap withArray:businessPoints];

Here is my zoom method:

-(void)zoomToFitMapAnnotations:(MKMapView*)mapViews withArray:(NSArray*)anAnnotationArray
{
    if([mapViews.annotations count] == 0) return;

    CLLocationCoordinate2D topLeftCoord;
    topLeftCoord.latitude = -90;
    topLeftCoord.longitude = 180;
    NSLog(@"zooming");

    CLLocationCoordinate2D bottomRightCoord;
    bottomRightCoord.latitude = 90;
    bottomRightCoord.longitude = -180;



    for(MKPointAnnotation* annotation in anAnnotationArray)
    {
        topLeftCoord.longitude = fmin(topLeftCoord.longitude, annotation.coordinate.longitude);
        topLeftCoord.latitude = fmax(topLeftCoord.latitude, annotation.coordinate.latitude);

        bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, annotation.coordinate.longitude);
        bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, annotation.coordinate.latitude);
    }

    MKCoordinateRegion region;
    region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5;
    region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;
    region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1;
    region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1;

    region = [mapViews regionThatFits:region];
    [businessMap setRegion:region animated:YES];
}

Your help is appreciated, thanks. Sorry if this is sloppy, this is my first post.

Edit:
Here is my edited geocoder method according to nevan king's answer.

[geocoder geocodeAddressString:business_info.location
             completionHandler:^(NSArray* geocoded, NSError* error){
                 if (geocoded && geocoded.count > 0) {
                     CLPlacemark *placemark = [geocoded objectAtIndex:0];
                     CLLocation *location = placemark.location;
                     CLLocationCoordinate2D business_cords = location.coordinate;
                     businessAnnotation.coordinate = business_cords;

                     businessAnnotation.title = business_info.name;

                     [businessPoints addObject:businessAnnotation];

                     [businessMap addAnnotations:businessPoints];

                     [self zoomToFitMapAnnotations:businessMap withArray:businessPoints];
                 }
             }
];

Also not that I tried using the count of the annotation array to determine the last annotation's geocode completionHandler to change the region only on that specific one. But this produced an inconsistent result for my region. This is the only way it was consistently keeping all annotations within view.


Solution

  • It seems the best way to run a method after all annotations have been geocoded and loaded on the map is to run a simple count and an if statement checking the count number with an array count.

    Here is what I came up with: int pointsArrayCount = contactArray.count; NSLog(@"Count : %d", pointsArrayCount); int numberOfPoints = pointsArrayCount; NSLog(@"Count Number : %d", numberOfPoints); i = 0;

    for (contactInfo *contact_info in contactArray) {
    
        contatcInfoAnnotation *annotation = [[contatcInfoAnnotation alloc] init];
    
        annotation.contactInfoClass = contact_info;
    
        CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    
        [geocoder geocodeAddressString:contact_info.address
                     completionHandler:^(NSArray* geocoded, NSError* error){
                         if (geocoded && geocoded.count > 0) {
                             CLPlacemark *placemark = [geocoded objectAtIndex:0];
                             CLLocation *location = placemark.location;
                             CLLocationCoordinate2D contact_cords = location.coordinate;
                             annotation.coordinate = contact_cords;
    
                             annotation.title = contact_info.name;
    
                             [mapPoints addObject:annotation];
    
                             [mapView addAnnotations:mapPoints];
    
                             i++;
    
                             NSLog(@"Count Number of Geocoded: %d", i);
    
                             if(i == numberOfPoints) {
                                 [self zoomToFitMapAnnotations:mapView withArray:mapPoints];
                             }
                         }
                     }//end completionHandler
         ];
    

    However, the completion handler is only able to handle up to 40 some geocodes. So this only works well when you are loading small amounts to your map while geocoding. If you are doing more than that, then you should be storing all the coordinates somewhere and then loading them separately when the map loads.