Using the Google Places API With MapKit

jvanlint
Learn how to use the Google Places API with MapKit!

Learn how to use the Google Places API with MapKit!

This is a post by iOS Tutorial Team Member Jason van Lint, the founder and owner of Dead Frog Studios, a boutique design and app building studio in Neuchatel, Switzerland.

In the past, we wrote a tutorial on MapKit showing you how you can display your current location on the map and plot some information on it.

That’s a great start, but what if you want to show your users the locations of nearby locations like bars, restaurants, or cafes? It’s impractical for a small developer to build up a database like this yourself, so what do you do?

This is where the Google Places API comes to the rescue! This is a free web service API you can use to query to find establishment, geographic locations, or other points of interest near any given point, and you’ll get to try it out in this tutorial!

This tutorial assumes some familiarity with Objective C and iOS programming. You’ll be using Xcode 4.2 and such iOS5 features as Storyboards and ARC.

In addition, this tutorial assumes you have a working knowledge of JSON. If you are unfamiliar with JSON, you may want to check out this tutorial before you proceed.

Getting Started

To use Google Places, you use HTTP requests to query for places of a certain type, and Google Places will return search results as latitude/longitude coordinates.

Let’s say, for example, that you wanted to search for bars around a particular point on the map – perhaps your current location. You can send your local coordinates to the Google Places API asking it to search for places of type “bar,” and in return you’ll receive a list of places matching that search criteria. The places can then be added as “pins” on your application’s map interface.

Sound familiar? Yep, Google Places is an integral part of the Maps app that comes with the iPhone, and is also widely used by travel apps.

The API is, in fact, even more powerful that this brief introduction suggests. This tutorial, though, will focus only on how to send and receive information from the API, so you can use the map-plotting feature in your own app.

Ready to get started?

Fire up Xcode, go to File\New\Project, select iOS\Application\Single View Application, and click Next. Type GooglePlaces as the project name. Make sure the Use Storyboard and Use Automatic Reference Counting options are checked, click Next, and choose a place to save your project.

Places1

Click on MainStoryboard.storyboard to bring up Interface Builder. Bring up the Object library by selecting the third tab in the View toolbar (Utilities) and selecting the third tab in the library toolbar (Object library), as shown in the screenshot below.

Places2

From the Object library, drag a toolbar to the top of the screen, and a Map View to the middle of the screen. Rename the toolbar button “Bar,” as below:

The Bar button will serve as your trigger for the app to search for bars and plot them on the map. To show the features of the Google Places API, you’re going to need a few more place types. From the Objects library, drag four more Bar Button items onto the toolbar and name them “Café,” “Florist,” “ATM,” and “Park.”

Your interface should now look like this:

Adding the Required Frameworks

Before you can take what you’ve done for a test run, there are a couple of frameworks you need to make the map and user location-finder work. If these frameworks aren’t added, the app will crash before you even get a chance to click anything.

Click on the name of your project in the Project Navigator, select the GooglePlaces target, and switch to the Build Phases tab. Under the Link Binary With Libraries section, click the Plus button, find the entry for MapKit.framework, and click Add (+). Next, find the entry for CoreLocation.framework and click Add once again.

At this point your screen should look like the one below:

Compile and run, and you should have the beginnings of a working map. If you are using the iOS simulator, you should see a screen like this:

Wait, there’s something off about this map… where’s the little blue dot showing my current location? Where am I?!

Don’t get disoriented. There’s no blue dot because you haven’t set the property of the map view that enables it. You can always do this by ticking the “Shows User Location” box in the attributes pane for the map view. But for this tutorial, you’re going to set this property in code when you start up the application.

You are also zoomed out quite a bit and, ideally, you’d like to see your blue dot nicely framed in a 1-km area. It’s time to get your code on!

Zooming to Your Location

You need to import the two frameworks you added above because you are about to reference objects in your code that depend on them! Replace the contents of ViewController.h with this:

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
 
@interface ViewController : UIViewController <MKMapViewDelegate, CLLocationManagerDelegate>
@end

Now connect the Map View object you added to your interface to the view controller that was created for you when you started a new project in Xcode. To do this, select MainStoryboard.storyboard and click on the Assistant Editor icon seen below:

You should now see your storyboard alongside another window that most likely contains code. You need ViewController.h to appear in the code window. If it doesn’t appear automatically, make sure that the toolbar above the code window has Automatic selected, as seen here:

Hold down the Ctrl key and drag a line from the Map View to ViewController.h, between the @interface and @end.

A popup will appear. Set the connection type to Outlet and the name to mapView, keep the Type as MKMapView, and click Connect. This will make a property for you and automatically hook it up to Interface Builder. Your ViewController.h should look like this:

Note: The filled-in gray dot next to your @property statement tells you that it is connected to an object in Interface Builder. If this dot is not filled in, you have no connection with Interface Builder and probably need to reconnect it to something.

To double-check whether it is connected, you can always select the Map View object in your storyboard and click on the Outlets button in the inspector window. It should look like this:

Now that your Map View object is connected to ViewController.h, you can start to interact with it and manipulate it via code.

The first thing you want to do is get your current location so that you can zoom into it. To do this, you’re going to make use of some standard functionality of the Core Location framework. The idea is to query your device for the current location, then send this location with a “zoom” factor to resize the map to a within a 1-km zone.

To begin, create an instance variable in the class that holds this information. Add this to ViewController.h above the @property statement:

{
    CLLocationManager *locationManager;
}

The space between the curly braces contains the declarations for your instance variables. You declared a variable called locationManager that will hold information about your current location. It uses a special class from your Core Location framework.

Now switch to ViewController.m and replace viewDidLoad with:

-(void)viewDidLoad {
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
 
    //Make this controller the delegate for the map view.
    self.mapView.delegate = self;     
 
    // Ensure that you can view your own location in the map view.
    [self.mapView setShowsUserLocation:YES];
 
    //Instantiate a location object.
    locationManager = [[CLLocationManager alloc] init];
 
    //Make this controller the delegate for the location manager.
    [locationManager setDelegate:self];
 
    //Set some parameters for the location object.
    [locationManager setDistanceFilter:kCLDistanceFilterNone];
    [locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
 
 
}

Here, you set the delegate of the Map View object to self. You’ll use some delegate methods later, so you set this up now to make sure you won’t forget it. Then you set the Shows User Location property of the Map View. You instantiate the locationManager variable, and also set its delegate to self.

Finally, you set some basic parameters on the locationManager object that will ensure you get accurate results from the device, and that the distance is measured relative to the previously-delivered location. You use the value kCLDistanceFilterNone to be notified of all movements.

Build and run your app now. The first thing you’ll notice is that the simulator asks for your permission to use your current location.

This is the Core Location framework in action. It realizes that you are probably going to be asking for some location information from the device, so the first time it executes, it prompts the user to see whether this is all right:

Click OK and you will notice that, finally, your blue dot appears. But still no zoom. You’ll implement that now.

Add the following method at the bottom of ViewController.m (but above the final @end):

#pragma mark - MKMapViewDelegate methods. 
- (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views {    
    MKCoordinateRegion region;
    region = MKCoordinateRegionMakeWithDistance(locationManager.location.coordinate,1000,1000);
 
 
    [mv setRegion:region animated:YES];
}

You are using a delegate method of the mapView class that gets called every time an “annotation” or pin is added to the map. In this case, the blue location dot is also considered an annotation, so this gets called as soon as your location dot is added to the map.

You declare a variable that will hold the coordinates of the region you want to display on the map. Then you call the handy setRegion method provided by your MapKit framework. setRegion centers the visible area of the map on your position with a 1000-m x 1000-m area around it. How nice of it to do the work for you. :]

Build and run your app, and you should zoom in nicely on your current location.

Take Your Places!

To retrieve Google Places data, you send a query to a specific Google URL, and get a JSON response from Google that you can parse for the relevant info. The URL request takes this form:

https://maps.googleapis.com/maps/api/place/search/output?parameters

The output can be either JSON or xml. For this app, you’ll use JSON.

Certain parameters are required to initiate a Place Search request. As is standard in URLs, all parameters are separated using the ampersand (&) character. Here is a list of the mandatory parameters:

  • key — Your application’s API key. This key identifies your application for purposes of quota management, and to ensure that Places added from your application are made immediately available to your app. Visit the API Console to create an API Project and obtain your Google Places API key.
  • location — The latitude/longitude for which you want to retrieve Place information. This must be specified as latitude,longitude.
  • radius — Defines the distance (in meters) within which to return Place results. The maximum allowed radius is 50,000 m.
  • sensor — Indicates whether or not the Place request came from a device using a location sensor (e.g., a GPS) to determine the location sent in this request. This value must be either true or false.

There’s an additional optional parameter of interest to you:

  • types — Restricts the results to Places matching at least one of the specified types. Types should be separated with a pipe symbol (type1|type2|etc.).

An example request might look like this:

https://maps.googleapis.com/maps/api/place/search/json?location=-33.8670522,151.1957362&radius=500&types=food&sensor=true&key=AddYourOwnKeyHere

You’re going to have to build this request string using a location in latitude and longitude form, and include the “type” of establishment you’re looking for. Your app will just look for bars, cafes, florists, ATMs and parks, but there are a whole host of keywords that Google accepts as parameters for “type.” The full list can be seen here.

With regard to the “key” parameter mentioned above, the Google Places API uses an API key to identify your application. API keys are managed through the Google API Console. To activate the Places API and create your key:

  1. Visit the API Console and log in with your Google Account.
  2. A default project called API Project is created for you when you first log in to the console. You can use the project or create a new one by clicking the API Project button at the top of the window and selecting Create.
  3. Click the Services link from the left-hand menu.
  4. Click the Status switch next to the Places API entry. The switch slides to On.
  5. Click API access from the left navigation. Your key is listed in the Simple API Access section.

Once you have your key, it’s a good idea to define it as a constant in your header file.

Add this line to ViewController.h below the #import code. Make sure you replace the “API Google Key Here” text with the key you obtained from the steps above.

#define kGOOGLE_API_KEY @"API Google Key here"

In the screenshot below, you can see how I’ve defined my API key as a constant.

So, you have your key and a basic understanding of what you want to send to Google for place data. Here are the remaining steps that will tie the pieces of your app together:

  1. You need to build a search string based on where you are on the map and the zoom level. This may have changed from your start location if the user has moved around on the map.
  2. You need the toolbar buttons to initiate a search and to indicate in your URL string which type of establishment you want to look for.
  3. You need to send this request to Google and parse the JSON response received.
  4. You need to plot these locations as pins on the map.

Get Set

It may be #2 in the list above, but hooking up your toolbar buttons is the best place to start. After all, you need them to help build your Google query URL.

Select MainStoryboard.storyboard and click on the Assistant Editor icon again (if the Assistant editor isn’t open). If you have ViewController.h open in the code window, use the toolbar above the code window to select ViewController.m instead:

Select the Bar toolbar button and Ctrl-drag a line from the toolbar button to your code window, just above the #pragma mark indicating mapView delegates. A popup will appear. In the name field type “toolBarButtonPress” and click OK.

This will create a new method and connect it to the toolbar button. Now select the “Cafe” button and Ctrl-drag a line from the button to the top line of the method you just created. A blue box should highlight the entire method, informing you that you’re about to connect this button to the existing method in your code.

Repeat the above step for all the buttons in the toolbar. Ctrl-drag a line from each button to same method you created with the very first Ctrl-drag.

With all the buttons connected to the same method, how will you identify which one has been pressed and what value it represents? For that, you’re going to use a little code to get the title of the button.

Add this code to your newly-created method:

    UIBarButtonItem *button = (UIBarButtonItem *)sender; 
    NSString *buttonTitle = [button.title lowercaseString];

The sender value for the method is always the button that was pressed to initiate the action. So in the above code, you convert the sender into a UIBarButtonItem to retrieve the button title.

You then convert it to lowercase, as the Google URL request will only accept lowercase place types. The string buttonTitle is the first component of your query string for Google!

Ready Query

This is a good time to start thinking about how you’re going to build the string to send to the Google API. In ViewController.m, insert the following code:

-(void) queryGooglePlaces: (NSString *) googleType {
    // Build the url string to send to Google. NOTE: The kGOOGLE_API_KEY is a constant that should contain your own API key that you obtain from Google. See this link for more info:
    // https://developers.google.com/maps/documentation/places/#Authentication
    NSString *url = [NSString stringWithFormat:@"https://maps.googleapis.com/maps/api/place/search/json?location=%f,%f&radius=%@&types=%@&sensor=true&key=%@", currentCentre.latitude, currentCentre.longitude, [NSString stringWithFormat:@"%i", currenDist], googleType, kGOOGLE_API_KEY];
 
    //Formulate the string as a URL object.
    NSURL *googleRequestURL=[NSURL URLWithString:url];
 
    // Retrieve the results of the URL.
    dispatch_async(kBgQueue, ^{
        NSData* data = [NSData dataWithContentsOfURL: googleRequestURL];
        [self performSelectorOnMainThread:@selector(fetchedData:) withObject:data waitUntilDone:YES];
    });
}

This method receives a “type” of place and uses it to build the string. The “url” variable stores your created query to the Google API, and converts the NSString into a NSURL. Finally, you use the asynchronous data-fetching technique that you can read more about in the JSON tutorial available on this site.

The retrieving code has a method called fetchedData that needs to be defined, so go ahead and create that method:

-(void)fetchedData:(NSData *)responseData {
    //parse out the json data
    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"]; 
 
    //Write out the data to the console.
    NSLog(@"Google Data: %@", places);
}

This method simply processes the results you receive from the Google API. You are interested in the results array of the returned JSON file, so you assign an array called “places” to read all values from the key called “results.” You use NSLog to print the results to the console. That will show you the data you get back from Google.

You’re not ready to build or run just yet. Notice that your queryGooglePlaces: method is complaining about a couple of undeclared or missing variables. And there’s a constant in your asynchronous results retriever that you haven’t defined.

Fix the constant first. Go to ViewController.h and add the following code below the #import section:

#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

If you want to learn more about the above line, please refer again to the iOS 5.0 JSON tutorial available here.

The second error is Xcode complaining about a missing “currentCentre” variable. You can see that you’re trying to construct a URL query using four elements:

  1. Your place “type”
  2. Your Google API key
  3. A place from which to begin your search. This will be a central point that serves as the center of the circle from which you will spread out to find places of the desired type.
  4. A distance out from this center point to which the search will be limited. You want the distance to match the current “zoom” level on the map the user has selected. Zoomed out would mean searching a greater area than being centered on, say, just one city block.

Your place type is provided as a parameter passed in to your method. Your Google API key is defined by the constant you added before. However, you haven’t yet defined how you are going to obtain from and send to Google your current position on the map. Moreover, you haven’t worked out how to determine the map zoom level.

To do this, you’ll use two instance variables in ViewController.h and a delegate method of MapKit to update your position and zoom level as the user moves the map around on the device.

First declare your variables. Go to ViewController.h and add the following variables below the existing declaration for locationManager:

    CLLocationCoordinate2D currentCentre;
    int currenDist;

The variable that will hold the coordinates of the center position on the map needs to be of the special type CLLocationCoordinate2D. Your zoom level, however, is just an integer and requires no special type.

Next, ensure that these two instance variables are updated as the user interacts with the map. Go to ViewController.m and add the following method:

-(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.
    currenDist = MKMetersBetweenMapPoints(eastMapPoint, westMapPoint);
 
    //Set your current center point on the map instance variable.
    currentCentre = self.mapView.centerCoordinate;
}

This delegate method will be called every time the user changes the map by zooming or by scrolling around to a new position.

To get the current zoom distance on the map, you need to do a little math. First you get the east and west points of the map, and then you make a call to the MKMetersBetweenMapPoints method that calculates the distance between these two points. You store the result in the currentDist instance variable.

Getting the center point is much easier. You simply get the centerCoordinate property of your map view and assign it to your instance variable.

This should have removed any warnings from your string building method, but before you build and run, you need to fix one more thing.

toolBarButtonPress: is collecting a place “type” for you, but you need to send this type to your URL-building method to generate the query.

Add this code to the end of toolbarButtonPress::

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

This is how you’ll send your button press string to your query string-building method.

OK, now you can finally build and run your code!

Try pressing the buttons on your toolbar, and watch the results that appear in the console output window. You should see a long list of places matching the type of place you selected, along with latitude and longitude data required to plot these points on the map.

Places3

If you click on a place type that can’t be found close to the coordinates you passed, you will just receive a blank JSON response and there will be no data in the console output window.

Now that you know you are getting data, let’s start plotting these places on the map!

Go!(ing Places)

In order to plot pins (called annotations in iOS terminology), you first need to create a class that conforms to the MKAnnotation protocol. For more information on how this works, refer to this site’s MapKit tutorial .

Going into more detail about why you need to create this class is out of scope for this tutorial. For the time being, just trust that this class is required to pinpoint places on the map interface.

Create a new class by selecting File\New\File. Select Objective-C class and make sure it is of type NSObject by ensuring this is selected in the drop-down list. Call the class MapPoint. Make sure the two checkboxes are unselected and click Next.

Paste this into MapPoint.h, replacing its contents:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface MapPoint : NSObject <MKAnnotation>
{
 
    NSString *_name;
    NSString *_address;
    CLLocationCoordinate2D _coordinate;
 
}
 
@property (copy) NSString *name;
@property (copy) NSString *address;
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
 
 
- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate;
 
@end

And paste this code into MapPoint.m:

#import "MapPoint.h"
 
@implementation MapPoint
@synthesize name = _name;
@synthesize address = _address;
@synthesize coordinate = _coordinate;
 
-(id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate  {
    if ((self = [super init])) {
        _name = [name copy];
        _address = [address copy];
        _coordinate = coordinate;
 
    }
    return self;
}
 
-(NSString *)title {
    if ([_name isKindOfClass:[NSNull class]]) 
        return @"Unknown charge";
    else
        return _name;
}
 
-(NSString *)subtitle {
    return _address;
}
 
@end

Again, if you would like to know why these files are structured this way, please refer to Ray’s excellent MapKit tutorial. For your purposes, you just need a class that will hold the name, address, and coordinates of the point you want to plot on the map.

Now that you have your MapPoint class ready, you can use it to place annotations on your map.

There’s an important new method to create first. Each time you retrieve data from Google, you need to clear any pins that may have been plotted, and draw new pins based on the new data received. This new method will handle both tasks.

Before you add this method, make sure that, when you refer to your MapPoint object, your code knows what to do with it. Go to ViewController.h and add this to the import section.

#import "MapPoint.h"

Add the new method to ViewController.m:

-(void)plotPositions:(NSArray *)data {
    // 1 - Remove any existing custom annotations but not the user location blue dot.
    for (id<MKAnnotation> annotation in mapView.annotations) {
        if ([annotation isKindOfClass:[MapPoint class]]) {
            [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];
        [mapView addAnnotation:placeObject];
    }
}

This method does a lot of stuff, so let’s break it down.

  1. Remove any annotations you may have drawn previously. This is a little tricky though. The pulsing blue dot that shows your current position is also an annotation. If you just did a blanket removal of all annotations, then your blue dot would disappear.

    Whenever you add an annotation to the map, it will be created using your MapPoint class. So to remove only the annotations you’ve added, you use a loop to go through all annotations and check to see whether the annotation is of class “MapPoint.” If so, you remove it.

  2. The plotPositions method receives an array of values obtained from your Google API query. You now need to iterate through each item in the array and extract the information needed for each annotation.
  3. You obtain your latitude and longitude information from the “geometry” and “location” keys in the JSON data.
  4. The name and address are obtained from the “name” and “vicinity” keys. Your location coordinates need to be assigned to a special CLLocationCoordinate2D variable for storing the latitude and longitude in a way MapKit understands.
  5. Once you read all the values from the index of the array, you assign the name, address, and place coordinates to a variable of type MapPoint and call the AddAnnotation method of the mapView class, sending it this variable to plot on the map.

You need to execute this method every time you get new data from Google, so add this line to the end of the fetchedData: method:

[self plotPositions:places];

Build and run. There shouldn’t be any errors, but pressing the toolbar buttons still does nothing except show your Google results string in the output console.

What’s missing? You need another MapKit delegate method that will take your annotations as you add them using [mapView addAnnotation:placeObject], and draw them on the map.

Add this method to ViewController.m:

-(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;
        return annotationView;
    }
    return nil;    
}

This method sets up a reuse identifier named “MapPoint” and uses it to draw the pins on the map. Notice that you use some properties of the MKPinAnnotationView object you created to show animation and enable call outs when the pin is tapped.

Build and run your code again, and you should be able to click your toolbar and see points drawn on the map representing your selected place type.

There’s still one small problem. Your mapView:didAddAnnotationViews: delegate method is still set up to zoom in at your current location.

That’s OK for the initial launch of the app – you don’t want to be zoomed out to see the whole world. But after that, you want the map view to always recenter on the current location using the same zoom factor you set when interacting with the map.

To put the finishing touches on your app, you’ll to create an instance variable that checks to see if this is the first launch of the app and, if not, redraws the map according to how the user has set it up.

Add the instance variable to ViewController.h below the declaration for currenDist:

    BOOL firstLaunch;

Change viewDidLoad in ViewController.m to set your instance variable to YES when you first launch the app. Add this line to the end of the method:

firstLaunch=YES;

Now change mapView:AddAnnotationViews: to look like this:

-(void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views {    
    //Zoom back to the user location after adding a new set of annotations.
    //Get the center point of the visible map.
    CLLocationCoordinate2D centre = [mv centerCoordinate];
    MKCoordinateRegion region;
    //If this is the first launch of the app, then set the center point of the map to the user's location.
    if (firstLaunch) {
        region = MKCoordinateRegionMakeWithDistance(locationManager.location.coordinate,1000,1000);
        firstLaunch=NO;
    }else {
        //Set the center point to the visible region of the map and change the radius to match the search radius passed to the Google query string.
        region = MKCoordinateRegionMakeWithDistance(centre,currenDist,currenDist);
    }
    //Set the visible region of the map.
    [mv setRegion:region animated:YES];
}

All you are doing here is checking to see if the Boolean value for firstLaunch is set to YES. If so, then you set the map up to show your current location with a zoom factor of 1000 x 1000 m.

If it is NO, then you set the visible region to where the user is currently looking at (which may be a different location than the user’s position).

Build and Run your code. You should notice a default setting for the first launch, but from then on your map should always remain in the position and at the zoom level you set when you interact with the device.

Where to Go From Here?

Here is the complete example project from the above tutorial.

The Google Places API can really add some value to your app. It is relatively straightforward to use now that iOS5 gives us some great JSON parsing tools natively.

I hope this tutorial inspires you to exploit the wealth of information Google supplies with its API. Lift your MapKit-based apps to the next level!

But you’ve only scratched the surface of what the Google Places API can do. In addition to reading places, you can also use your iPhone app to write places from your device [write to where, for example?], along with other key metadata about the place. The Google Places API also has a rating API, which lets your app users rate particular places.

In addition, the Google Places API provides a unique ID for each place it returns. You can use these IDs to make additional calls to the API for detailed information about that place – the kind of info you might display on a separate screen that appears after the user taps the callout.

There is also a “check-in” capability (similar to Foursquare) built into the API. As more users rate and check in to a place, that place rises to the top of the search results.

As involved as this tutorial was, you’ve only touched the tip of the iceberg. If you would like to learn more about the Google Places API, please visit the Google Places API Documentation, which does a great job structuring and explaining the API. With the experience you’ve gained in this tutorial, it should be much easier for you to digest and implement it in your own app.


This is a post by iOS Tutorial Team Member Jason van Lint, the founder and owner of Dead Frog Studios, a boutique design and app building studio in Neuchatel, Switzerland.

User Comments

50 Comments

[ 1 , 2 , 3 , 4 ]
  • Hello,
    for showing the result replace kGOOGLE_API_KEY with your own key and also replace your bundle identifier to your project to show the result
    darshit
  • This page has been very useful, but I'm trying to modify the code to automatically run the "type" search when the app is opened, so I've moved some code to the viewDidLoad section, e.g: [self queryGooglePlaces:@"cafe"]; , but the search fails because the current position is 0.000 heres the NSLog: https://maps.googleapis.com/maps/api/pl ... ue&key=xxx
    how can I make it so the currentCentre.longitude & latitude have non-zero coordinates (or the current coordinates) from viewDidLoad?

    Also is there a way to change the GoogleQuery to be a custom search or in other words not a specific type?

    Thanks and Keep up the good work here!
    rmky
  • Is there an extension to this tutorial that shows how to get the pins/annotations to be selectable and then navigate to that point from the current location?

    I've been looking around and I've found a few samples, but can't seem to get it to work. What I'd like to solve first is to grab the pin lat/long when "touched" then use those coordinates in a MKDirectionsRequest.

    Any tips here?

    Thanks
    rmky
  • Has anyone used foursquare places in prod successfully ? I have it working but they have api calls per day limit. Facebook sdk also has places but seems to require user authorization. Any other options if Google is not an option ?
    TimSimeonov
  • Hello!

    Great tuto you provide us here! However I've made the update to Xcode 6 /IOS8 and now it seems that there are huge changes in the CoreLocation framework. I have the following message in the debug output: "Trying to start MapKit location updates without prompting for location authorization. Must call -[CLLocationManager requestWhenInUseAuthorization] or -[CLLocationManager requestAlwaysAuthorization] first." I've googled it, a lot of persons claimed to have found the solution but it does not work for me :(
    Could you help us here? I am sure we are a lot of persons in the same case...

    Thanks a lot,
    Best regads
    polo492
[ 1 , 2 , 3 , 4 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Barbara Reichart
  • Jean-Pierre Distler
  • Matt Galloway

... 52 total!

Update Team

  • Zouhair Mahieddine

... 14 total!

Editorial Team

  • Alexis Gallagher

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!