How to get nearby places by implementing the Google Place Picker in iOS

The google maps SDK is full of interesting features. If you are planning to make a map app in iOS, you should consider it to extend your app with cool services such as the Place autocomplete API.

In this tutorial, you are going to use another cool service from the Google Places API, the service is called Place picker and will provide you with a nice ready-to-use widget filled in with a set of nearby places around you on the map.

Without further ado, let’s build it πŸ™‚

Begin by downloading the starter project here.

Open the project and run it on the simulator, the interface is composed of a search bar button item and a map container. The search button will launch the place picker UI widget while the map view will fill in the rest of the screen.

Setting up the project with Google Maps SDK for iOS

First of all, the project should include the Google Maps SDK for iOS. To set this up, it’s important to follow this quick guide to get you ready to go and able to carry on the rest of the tutorial. You don’t have to finish all the steps in that guide, just be sure that steps 1 to 5 are completed successfully. This include getting an API key to work with, and integrating the Google Maps SDK with cocoapods.

Install the Google Maps SDK for iOS with cocoapods

After you finish the 5 steps guide, you should use the xcworkspace file to open the project instead of the xcodeproj file.

After installing a pod dependency, you must use the .xcworkspace file to open the project

Cool, you are done with the setup work, now you have an API Key valid to use, and your project includes the Google Maps SDK you will work with. You are all set and ready to continue πŸ™‚

Open the PlacePicker.xcworkspace file and select AppDelegate.swift from the Project navigator to load it in the editor. First, import the Google Maps SDK at the top of the file (copy the following import statement just under the import UIKit statement):

import GoogleMaps

Next, locate the application:didFinishLaunchingWithOptions: method and place the following code inside (just before the return of the method):

GMSServices.provideAPIKey("ENTER_YOUR_API_KEY_HERE")

Obviously, you need to replace the string inside the parentheses with the API Key you generated previously while setting up the Google Maps SDK with the project.

Now switch to the ViewController.swift file by selecting it from the Project navigator.

Let’s first load the Google Maps view on the screen. Place the following import statement at the top of the file:

import GoogleMaps

Next, you will need a reference for the map view itself in order to access it from the methods. Copy the following map view property declaration right after the class name:

var googleMapView: GMSMapView!

GMSMapView is the main class in the Google Maps SDK, and serves as the entry point for all the methods related to the map.

Let’s implement the viewDidAppear method to take care of the map view layout. Place the following implementation before the class closing bracket:

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
  self.googleMapView = GMSMapView(frame: self.mapViewContainer.frame)
  self.googleMapView.animateToZoom(18.0)
  self.view.addSubview(googleMapView)
}

The code above will instantiate a GMSMapView object with the same frame as the mapViewContainer view, then it will set its zoom level to 18 and add it to the main view. Remember the mapViewContainer object is just a UIView, we need it to get the map view dimensions programmatically.

Note: You may wonder why you setup the map view inside the viewDidAppear method instead of the viewDidLoad. The reason behind is that the viewDidLoad gets called too soon, before even the mapViewContainer frame is set, whereas the viewDidAppear is called after all the user interface elements are shown on the screen. Hence at that time only, we can get the right frames to draw the map view πŸ™‚

Run the project to make sure the map is loading correctly.

GMSMapView

So far so good, time to implement some location code, isn’t?

Locate the viewDidLoad method and copy the following code inside:

self.locationManager = CLLocationManager()
self.locationManager.delegate = self
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startUpdatingLocation()

The code above will instantiate a location manager object in order to be able to get the user location coordinates, since nearby places depend on the user location, those coordinates are mandatory to request nearby places later on.

Go ahead and declare the location manager as well as some other useful properties you gonna need right away, place the following code right below the class name:

var locationManager: CLLocationManager!
var placePicker: GMSPlacePicker!
var latitude: Double!
var longitude: Double!

In addition to the location manager, the rest of properties declared above are references for the google maps place picker as well as the longitude and latitude of the user.

Let’s change the class so that it conforms to the CLLocationManagerDelegate. To do so, make the following changes to the class name:

class ViewController: UIViewController, CLLocationManagerDelegate {

The location manager will start tracking location and will notify the delegate once it got the user location or when the location changes. To react to such events, let’s implement the locationManager:didUpdateLocations: protocol method. Copy the following method code before the class closing bracket:

func locationManager(manager: CLLocationManager,
        didUpdateLocations locations: [CLLocation]){
    // 1
    let location:CLLocation = locations.last!
    self.latitude = location.coordinate.latitude
    self.longitude = location.coordinate.longitude
            
    // 2
    let coordinates = CLLocationCoordinate2DMake(self.latitude, self.longitude)
    let marker = GMSMarker(position: coordinates)
    marker.title = "I am here"
    marker.map = self.googleMapView
    self.googleMapView.animateToLocation(coordinates)
}

Some explanations are needed for the code above:

// 1: If the location manager succeeded to track locations, it will return an array with at least one CLLocation object. In case the array contains more than one entry, the last entry is the most recent location, that’s why you retrieve the last element in the array.

// 2: Here the code will construct a marker object with the longitude/latitude you extracted previously, then it will draw the marker on the map and move the map’s visible area to the marker coordinates.

Before you run the project, let’s add some mandatory keys to the Info.plist file in order to specify the reason why our app is going to access the user location. Select the Info.plist file from the Project navigator and add the following two keys entry as shown below:

Add the NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription keys to Info.plist

Although it’s optional, you can add some custom text to the string values to show to the user at the moment when the app is requesting the user location.

Ok, let’s run the project so far and see how it goes.

The app will request the user permission to get his location

If you are running on the simulator, you may need to check some simulated locations by clicking on the location arrow button at the top of the Debug area, as shown below:

How to simulate location in Xcode
Click on the picture for better resolution

After you select a location, switch back to the simulator, you will notice the map view is immediately moving to the selected location. In my case, I chose Tokyo, Japan πŸ™‚

Once selected , the simulated location will be loaded on the map

Cool, let’s implement the locationManager:didFailWithError: protocol method to react to any location tracking errors. Place the following implementation at the end of the class:

func locationManager(manager: CLLocationManager,
     didFailWithError error: NSError){
        
print("An error occurred while tracking location changes : \(error.description)")
}

You are almost there, now time for the main function. Once you click on the search button, you want to load the Place picker user interface to choose between a set of nearby places, right ?

Locate the showPlacePicker IBAction method and copy the following implementation inside:

        // 1
        let center = CLLocationCoordinate2DMake(self.latitude, self.longitude)
        let northEast = CLLocationCoordinate2DMake(center.latitude + 0.001, center.longitude + 0.001)
        let southWest = CLLocationCoordinate2DMake(center.latitude - 0.001, center.longitude - 0.001)
        let viewport = GMSCoordinateBounds(coordinate: northEast, coordinate: southWest)
        let config = GMSPlacePickerConfig(viewport: viewport)
        self.placePicker = GMSPlacePicker(config: config)
        
        // 2
        placePicker.pickPlaceWithCallback { (place: GMSPlace?, error: NSError?) -> Void in
            
            if let error = error {
                print("Error occurred: \(error.localizedDescription)")
                return
            }
            // 3
            if let place = place {
                let coordinates = CLLocationCoordinate2DMake(place.coordinate.latitude, place.coordinate.longitude)
                let marker = GMSMarker(position: coordinates)
                marker.title = place.name
                marker.map = self.googleMapView
                self.googleMapView.animateToLocation(coordinates)
            } else {
                print("No place was selected")
            }
        }

Let’s breakdown the code above:

// 1: The longitude/latitude of the user were first set in the locationManager:didUpdateLocations: protocol method, here you just reuse them to construct a GMSPlacePicker object. A GMSPlacePicker needs a specific configuration to describe the map behavior, that’s why you declared a GMSPlacePickerConfig object with a viewport area that the picker’s embedded map should display. In this case, the picker map will show the area where the current user is located.

// 2: This will present a full-screen window place picker widget with a block callback to prompt the user to select a place. The callback will not be called when the place picker widget is loading, but it will be called after the user interact with it (either the user select a place or not).

// 3: If the user has selected a place from the picker list, then the code you implemented above will create another marker with the place coordinates and draw it on the map before it get you back to the previous screen.

That’s it!

Run the project, change locations from the simulated locations list and discover cool places nearby πŸ™‚

As usual, you can download the final project here.

Feel free to leave a comment below, I’d love to hear from you. If you got questions regarding this tutorial, feel free to ask them here.

Malek
iOS developer with over than 11 years of extensive experience working on several projects with different sized startups and corporates.

13 Comments

  1. You guys are so awesome. All these tutorials gives us confidence to build a more complex app on top of these concepts. Or else we would have got stuck with these stuffs. Thank You so so much. Please let me know if I can do something to contribute or help you nerds. πŸ˜‰

  2. Thanks, that’s so kind of you!
    Feel free to subscribe, and if you can, share the blog with your network. That would be a nice contribution πŸ™‚

  3. is there any UI from Google for specific place? (When the user select Place from the Place Picker)

  4. Hey Malek,

    I used part of your code for my app and I get an error saying “Use of unresolved identifier ‘GMSPlacePickerConfig'”. I have imported GoogleMaps and GooglePlaces. Do u know why this may be?

    Thanks

  5. Hi its a very nice tutorial…
    I want to ask one point when we click on one of the nearby place in the place picker then it should draw a path from user current location to place picker place..How can i do it

Comments are closed.