1
votes

I expect once clicking the toolbar button like "cafe", cafes will show in the visible map region. Data are fetched from Google Places API. Everything works fine except pins don't display after clicking the button.

A latency is expected here for sure. Yet i do the fetch in a background queue and puts up a spinning wheel while waiting, and the spinning wheel hides when fetching and parsing is done. So I am quite sure the data is there at the moment that spinning wheel disappears. But the pins don't show up until I scroll the map.

I figure that scrolling map only triggers mapView:regionDidChangeAnimated:. But I can't figure out how it relates the problem. Can anyone help?

The source code of ViewController.m, where pretty much everything happens.

#import "ViewController.h"
#import "MapPoint.h"
#import "MBProgressHUD.h"

#define kGOOGLE_API_KEY @"AIzaSyCHqbAoY7WCL3l7x188ZM4ciiTixejzQ4Y"
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)



@interface ViewController () <CLLocationManagerDelegate, MKMapViewDelegate>
@property (weak, nonatomic) IBOutlet MKMapView *mapView;
@property (strong, nonatomic) CLLocationManager *locationManager;
@property int currentDist;
@property CLLocationCoordinate2D currentCentre;
@end

@implementation ViewController
@synthesize mapView = _mapView;
@synthesize locationManager = _locationManager;
@synthesize currentDist = _currentDist;
@synthesize currentCentre = _currentCentre;

- (void)viewDidLoad{
    [super viewDidLoad];
}


- (void)viewDidUnload{
    [self setMapView:nil];
    [super viewDidUnload];
}

// set the map region after launching
-(void)viewWillAppear:(BOOL)animated
{
    //Instantiate a location object.
    self.locationManager = [[CLLocationManager alloc] init];

    //Make this controller the delegate for the location manager.
    self.locationManager.delegate = self;

    //Set some parameters for the location object.
    [self.locationManager setDistanceFilter:kCLDistanceFilterNone];
    [self.locationManager setDesiredAccuracy:kCLLocationAccuracyBest];

    // order: latitude(纬度), longitude(经度)
    CLLocationCoordinate2D center = self.locationManager.location.coordinate;

    // 单位是degree
    MKCoordinateSpan span = MKCoordinateSpanMake(0.03, 0.03);

    MKCoordinateRegion region = MKCoordinateRegionMake(center, span);

    [self.mapView setRegion:region animated:YES];

    // NSLog(@"currentCentre is (%f , %f)", self.currentCentre.latitude, self.currentCentre.longitude);
}

// Get place tpye from button title
// All buttons share this one method
- (IBAction)toolbarButtonPressed:(id)sender
{
    UIBarButtonItem *button = (UIBarButtonItem *)sender;
    NSString *buttonTitle = [button.title lowercaseString];

    //Use this title text to build the URL query and get the data from Google.
    [self queryGooglePlaces:buttonTitle];

}

// Parse response JSON data
-(void)parseData:(NSData *)responseData {

    NSError* error;
    NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData
                                                         options:kNilOptions
                                                        error:&error];

    //The results from Google will be an array obtained from the NSDictionary object with the key "results".
    NSArray* places = [json objectForKey:@"results"];
    [self plotPositions:places];
    NSLog(@"Plot is done");
}

// Format query string
-(void) queryGooglePlaces: (NSString *) googleType {

    // query string
    NSString *url = [NSString stringWithFormat:@"https://maps.googleapis.com/maps/api/place/search/json?location=%f,%f&radius=%@&types=%@&sensor=true&key=%@", self.currentCentre.latitude, self.currentCentre.longitude, [NSString stringWithFormat:@"%i", _currentDist], googleType, kGOOGLE_API_KEY];

    //string to URL 
    NSURL *googleRequestURL=[NSURL URLWithString:url];

    // Retrieve data from the query URL by GCD
    dispatch_async(kBgQueue, ^{
        NSData* data = [NSData dataWithContentsOfURL: googleRequestURL];
        [self parseData:data];
        [MBProgressHUD hideHUDForView:self.view animated:YES];
    });

    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    hud.labelText = @"Please Wait..";
}

#pragma mark - Map View Delegate

// called many times when map scrolling or zooming
// Use this to get currentCentre and currentDist (radius)

-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    //Get the east and west points on the map so you can calculate the distance (zoom level) of the current map view.
    MKMapRect mRect = self.mapView.visibleMapRect;
    MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
    MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));

    //Set your current distance instance variable.
    self.currentDist = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);

    //Set your current center point on the map instance variable.
    self.currentCentre = self.mapView.centerCoordinate;

   // NSLog(@"currentCentre is (%f , %f)", self.currentCentre.latitude, self.currentCentre.longitude);

}

// Setup annotation objects
-(void)plotPositions:(NSArray *)data {
    // 1 - Remove any existing custom annotations but not the user location blue dot.

    for (id<MKAnnotation> annotation in self.mapView.annotations) {
        if ([annotation isKindOfClass:[MapPoint class]]) {
            [self.mapView removeAnnotation:annotation];
        }
    }
    // 2 - Loop through the array of places returned from the Google API.
    for (int i=0; i<[data count]; i++) {
        //Retrieve the NSDictionary object in each index of the array.
        NSDictionary* place = [data objectAtIndex:i];
        // 3 - There is a specific NSDictionary object that gives us the location info.
        NSDictionary *geo = [place objectForKey:@"geometry"];
        // Get the lat and long for the location.
        NSDictionary *loc = [geo objectForKey:@"location"];
        // 4 - Get your name and address info for adding to a pin.
        NSString *name=[place objectForKey:@"name"];
        NSString *vicinity=[place objectForKey:@"vicinity"];
        // Create a special variable to hold this coordinate info.
        CLLocationCoordinate2D placeCoord;
        // Set the lat and long.
        placeCoord.latitude=[[loc objectForKey:@"lat"] doubleValue];
        placeCoord.longitude=[[loc objectForKey:@"lng"] doubleValue];

        // 5 - Create a new annotation.
        MapPoint *placeObject = [[MapPoint alloc] initWithName:name address:vicinity coordinate:placeCoord];
        [self.mapView addAnnotation:placeObject];
    }
    NSLog(@"addAnnotation is done");

}

// Setup annotation view
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
    // Define your reuse identifier.
    static NSString *identifier = @"MapPoint";

    if ([annotation isKindOfClass:[MapPoint class]]) {
        MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];

        if (annotationView == nil) {
            annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
        } else {
            annotationView.annotation = annotation;
        }
        annotationView.enabled = YES;
        annotationView.canShowCallout = YES;
        annotationView.animatesDrop = YES;


        // NSLog(@"annotation view is added");

        return annotationView;

    }
    return nil;
}

@end
1

1 Answers

6
votes

A couple of things:

Move your removeAnnotation and addAnnotation code to run on the UI thread, e.g.:

dispatch_async (dispatch_get_main_queye(), ^
{
 [self.mapView addAnnotation:placeObject]; 
});

Move your viewWillAppear initialization code to viewDidLoad. viewWillAppear may be called multiple times during the lifetime of your view controller