© Jeffrey Linwood 2020
J. LinwoodBuild Location Apps on iOS with Swifthttps://doi.org/10.1007/978-1-4842-6083-8_10

10. Using Google Places in Your iOS App

Jeffrey Linwood1 
(1)
Austin, TX, USA
 

Google Places provides a rich database of locations and photos to iOS apps through the Google Places SDK. With the SDK, your mobile app can augment its location abilities with additional details and photos or even provide a places search with autocomplete.

We are going to build on the discussion in Chapter 7 about CocoaPods and using Google APIs in your mobile app. In particular, getting a Google Places API key is similar to getting an API key for the Google Directions API or the Google Maps API.

Google Places is on a pay-per-use billing model, with billing credits that should cover usage in development and testing. Please check the Google Maps Platform billing website (https://developers.google.com/maps/billing/gmp-billing) for the latest information. Also, you will find more details there about what usage costs and how changing the fields in a place request affects pricing.

Building a places finder with a map

Our project in this chapter will be to create a search interface for businesses in the Google Places database, based on the currently visible region in a Google Maps map view. We are going to start with the Google Places autocomplete view controller first and then add the Google Maps marker at the end of the chapter.

This project demonstrates how to search for places by name in Google Places with autocomplete, as well as how to integrate the Google Places SDK for iOS into your project. We will use the full-screen autocomplete search view controller for Google Places, which makes adding this functionality pretty simple.

Creating the project

Create a new Single View Application in Xcode, using Swift. Choose storyboard as the user interface framework. Name the app PlacesApp . The example project for this chapter uses com.buildingmobileapps as the organization identifier, making the app’s bundle identifier com.buildingmobileapps.PlacesApp. We’ll use this bundle identifier in the next section of this chapter, when we create an API key and restrict it for use.

Getting a Google Places API key

Similar to the previous chapter with the Google Directions API, or Chapter 7 with the Google Maps API, you will need to get a Google Places API key. You need to go to the Google Cloud Console, which can be found at this URL, https://console.cloud.google.com, and then enable the Google Places API for your project.

You will also need to create a new API key. Start by going to the Credentials screen, which is in found in the Google Cloud Platform menu under the APIs & Services section (Figure 10-1).
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig1_HTML.jpg
Figure 10-1

Credentials location in APIs & Services menu

Click the CREATE CREDENTIALS button, and choose API key (Figure 10-2).
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig2_HTML.jpg
Figure 10-2

Create an API key

When the dialog box appears with your API key, copy the API key to your clipboard, or retrieve it later from the console. Click the Restrict button on the dialog box, which will open up a page that lets you set which APIs and applications this API key works with.

The first step is to name your API key something memorable, for instance, Places App Key, as shown in Figure 10-3. The next is to restrict the use of this key to iOS apps.
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig3_HTML.jpg
Figure 10-3

Naming the API key and restricting to iOS apps

After choosing iOS apps, you will see a place to enter in your app’s bundle identifier. Go ahead and put in your app’s bundle identifier, and then click the DONE button underneath the text field (Figure 10-4).
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig4_HTML.jpg
Figure 10-4

Restricting API key use to a single bundle identifier

Last, you need to restrict which APIs the key can be used for. For the project in this chapter, we are using the Places API and the Maps SDK for iOS. Choose to restrict the key, and then choose those two APIs from the drop-down. If you do not see those APIs, you need to enable them for your project on the main dashboard screen (Figure 10-5).
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig5_HTML.jpg
Figure 10-5

Restricting API key use to two APIs

Save those changes, and then the console will return to the API keys screen. We will use the API key in the app shortly, so leave the page open. You can always go back into the console and retrieve your API keys, however, unlike some vendors that only provide a secret authorization token once.

Our next step is to include the Google Places SDK and the Google Maps SDK in our app project with CocoaPods.

Setting up CocoaPods

If you are new to CocoaPods, see the discussion in Chapter 7 on how to install it on your Mac. We are going to follow a similar process to get our app project set up. The first step is to open up the Terminal app on your Mac and change directories to the directory that has our project. Then, create a Podfile for your project with this command:
pod init
That command creates a Podfile file . We are going to add two dependencies, one for the Google Places SDK for iOS and one for the Google Maps SDK for iOS. Go ahead and open the Podfile in an editor, and add these dependencies, so it looks similar to Listing 10-1.
target 'PlacesApp' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
  # Pods for PlacesApp
  pod 'GooglePlaces', '3.8.0'
  pod 'GoogleMaps', '3.8.0'
end
Listing 10-1

Podfile for Google Places and Google Maps SDKs

After editing your Podfile, run the following command to install your dependencies, and then create an Xcode workspace:
pod install

Now, only use the PlacesApp.xcworkspace file with Xcode, not the PlacesApp.xcodeproj file, or the CocoaPods dependencies won’t be included in the build.

Open the PlacesApp.xcworkspace file in Xcode, and we can get started building the app!

Providing an API key for Google Places and Google Maps

The next step to building our project is to specify API keys for the Google Places SDK for iOS, as well as the Google Maps SDK for iOS. We can use the same API key for each, as we enabled support for both APIs in the first part of this chapter. For Google Places, the GMSPlacesClient class has a static method named provideAPIKey() that you use before calling anything else in the Google Places SDK. Similarly, we will call the provideAPIKey() method on the GMSServices class for Google Maps. Add the GooglePlaces framework and the GoogleMaps frameworks as import statements in the AppDelegate class. We will provide the API keys for both of the Google SDKs in the app delegate’s application(_:didFinishLaunchingWithOptions:) method .

Listing 10-2 is a selection from the AppDelegate class, missing some boilerplate methods that we did not modify. Replace the value of the apiKey constant with the API key you created earlier in this chapter.
import UIKit
import GooglePlaces
import GoogleMaps
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let apiKey = "AIza..."
        GMSPlacesClient.provideAPIKey(apiKey)
        GMSServices.provideAPIKey(apiKey)
        return true
    }
}
Listing 10-2

Selection from the AppDelegate class

Now that you have the Google Places and Maps SDKs set up, let’s use it to add some functionality to our project!

Creating the user interface

Your view controller needs two user interface elements – a Google Maps map view and a button to Find Businesses in the map area. Using the storyboard (Figure 10-6), you can drag a UIView and a UIButton onto the main view for your ViewController class. Change the identity of the UIView class to GMSMapView, to make it a Google Maps map view. Add constraints to the map view to have it fill all available space in the view, and then add constraints to the button to anchor it to the bottom of the screen. Change the button’s text to say Find Businesses, and choose any colors or fonts that you like to make it stand out against the map.
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig6_HTML.jpg
Figure 10-6

Storyboard showing map view and button

One thing to note: the button can’t be a child view of the map view; it has to be a child view of the main UIView for the ViewController screen .

After running your project, if everything is set up correctly, your project will look similar to Figure 10-7.
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig7_HTML.jpg
Figure 10-7

User interface for the application

You could also create the map view and the button programmatically, if you wish.

Create an outlet named mapView for the Google Maps map view, using the Assistant view in Xcode:
@IBOutlet weak var mapView: GMSMapView!
Next, create an action named findBusinesses for the button. You should have something similar to this action in your ViewController class:
@IBAction func findBusinesses(_ sender: Any) {
}

That concludes our work with the storyboard. The next part of the project will be to display the Google Places autocomplete view controller when a user taps on the button.

Understanding the autocomplete search

When you add some kind of places search to your project, you will want to include autocomplete – forcing users to type in the exact name as specified in the database won’t be successful. The Places SDK for iOS includes user interface elements for autocomplete, as well as a way to make autocomplete queries programmatically, so that you can implement your own user interface.

Two modern user interface elements are included in the Places SDK. The first is a stand-alone full-screen view controller that handles text search as well as displaying results. The other user interface element is a search results view controller that works with a search bar that you implement. For this chapter, we will work with the stand-alone view controller, but the concepts for the search are similar between the two different user interfaces.

The basic steps for configuring and displaying an autocomplete full-screen view controller are
  • Create an instance of the GMSAutocompleteViewController class.

  • Give geographic boundaries for the place search.

  • Decide which data fields you need back from the query.

  • Filter the results by type of place (city, street address, establishment).

  • Display the view controller.

  • Implement the delegate for callbacks when search is completed or canceled by user.

Let’s go through each of these steps in more detail, as we build out the places search functionality in our project.

Creating the autocomplete view controller

To start out our places search project, we will need to create an instance of the GMSAutocompleteViewController class. We’ll also need to set a delegate for the autocomplete view controller, and we will create an extension later in this chapter to handle these delegate methods. Go ahead and assign the autocomplete view controller’s delegate to self for now:
  let vc = GMSAutocompleteViewController()
  vc.delegate = self

All of the code snippets in this and the following sections will go into the action named findBusinesses() we created earlier. This function can be found printed out in full in Listing 10-3, which is after the discussion of each individual step.

Setting geographic boundaries for the search

We can tell Google Places what geographic area to search for places using the autocomplete search. You can either use this area as a preference, so that results within this geographic space get boosted up toward the top of the list, or as a restriction, where places outside of this geographic area won’t be shown at all, even if they are nearby.

With our project, we are going to use the visible region from the Google map view as our boundary. That region needs to be transformed into a GMSCoordinateBounds object, which you can do with the GMSCoordinateBounds constructor:
let region = mapView.projection.visibleRegion()
let bounds = GMSCoordinateBounds.init(region: region)
vc.autocompleteBounds = bounds
We will also restrict our results to only those found within the geographic bounds:
  vc.autocompleteBoundsMode = .restrict

You do not have to be this restrictive – instead you can bias results toward those found in the map, but if a user is looking for something specific, and it happens to be close to the map but not in it, it will still return as a result. This is the default behavior for the autocomplete search. If you would like to use it, do not set the autocompleteBoundsMode parameter, or set it to the .bias value.

You also do not have to use any geographic bounds, in which case you would simply omit setting the autocompleteBounds property . You could also construct the geographic bounds yourself, for instance, if you were building a place search for a given region or city.

The next step is to determine which data fields your view controller needs to fetch from the Google Places API.

Requesting a subset of data fields

The Google Places API will return all data fields by default for autocomplete, including premium ones such as reviews that cost extra. As you probably do not need all fields, you can set a specific set of fields that you do need in your result.

To construct the list of fields, you will need to combine a set of GMSPlaceField enum values together with an OR (|) operator. The following is a list of several of these fields that you could use for your application:
  • coordinate

  • formattedAddress

  • name

  • openingHours

  • phoneNumber

  • photos

  • placeID

  • rating

  • website

For our purposes, we are going to stick to the basic place details and simply request the name, place ID, and coordinate. We will use these later to create a marker on the map.

Our code will combine the raw values from the enum values as unsigned integers and then construct a GMSPlaceField value from that. We also return from the function if the GMSPlaceField constructor fails to create an object. This particular API could have been made a little easier to use, for instance, by taking a set of values instead. We also need to set the placeFields property of the autocomplete view controller:
// needed for the map marker
let fields = UInt(GMSPlaceField.name.rawValue) |
  UInt(GMSPlaceField.placeID.rawValue) |
  UInt(GMSPlaceField.coordinate.rawValue)
guard let placeFields = GMSPlaceField(rawValue: fields) else {
  return
}
vc.placeFields = placeFields

Not to be confused with the fields, we now need to tell Google Places what type of search results we want.

Filtering results by type

The autocomplete view controller supports different types of places for search results. The supported types, as well as a no filter option, are all available on the GMSPlacesAutocompleteTypeFilter enum. The options are
  • noFilter

  • address

  • city

  • establishment

  • geocode

  • region

  • city

To use one of these options with the autocomplete view controller, you need to create a GMSAutocompleteFilter instance and then set the filter’s type property. On the autocomplete view controller, you will need to set the autocompleteFilter property with your filter object:
let filter = GMSAutocompleteFilter()
filter.type = .establishment
vc.autocompleteFilter = filter

We used the establishment type, but you could certainly use one of the other filters. Try them out to see what type of results they give back in your area.

Displaying the view controller

The autocomplete view controller expects to be full screen, so you can present it as a modal from your view controller, as seen in Figure 10-8:
  present(vc, animated: true, completion: nil)
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig8_HTML.jpg
Figure 10-8

Displaying the autocomplete view controller for Google Places

If you like, you could create the autocomplete view controller in one method and then display it from another method. For this project, we combined these two into one method for simplicity.

The complete findBusinesses() method , with all of the preceding steps, is listed in Listing 10-3.
@IBAction func findBusinesses(_ sender: Any) {
  let vc = GMSAutocompleteViewController()
  vc.delegate = self
  // search for places in this area
  let region = mapView.projection.visibleRegion()
  let bounds = GMSCoordinateBounds.init(region: region)
  vc.autocompleteBounds = bounds
  // only return results visible on the map
  vc.autocompleteBoundsMode = .restrict
  // needed for the map marker
  let fields = UInt(GMSPlaceField.name.rawValue) |
    UInt(GMSPlaceField.placeID.rawValue) |
    UInt(GMSPlaceField.coordinate.rawValue)
  guard let placeFields = GMSPlaceField(rawValue: fields) else {
    return
  }
  // only return asked for fields
  vc.placeFields = placeFields
  // we are looking for businesses/points of interest
  let filter = GMSAutocompleteFilter()
  filter.type = .establishment
  vc.autocompleteFilter = filter
  // display as modal
  present(vc, animated: true, completion: nil)
}
Listing 10-3

Displaying autocomplete view controller for Google Places search

When the user chooses a place from the list or they decide to cancel, your view controller will need to dismiss the autocomplete view controller. We will discuss that in the next section about implementing the delegate methods for the autocomplete search.

Implementing the delegate for autocomplete

With the GMSAutocompleteResultsViewControllerDelegate protocol , your application will get notified when certain events happen with the autocomplete view controller. We are going to implement several methods from this delegate as an extension to our ViewController class.

In particular, the functions called when a user selects a place, when a user cancels the search request, or when an unrecoverable error occurs are required. The optional methods inform the delegate that an autocomplete request has been made, an autocomplete request has been updated, and a user has chosen a value from the list (but the API has not been called to get the place details yet).

Add an extension to your ViewController class for the GMSAutocompleteResultsViewControllerDelegate protocol:
extension ViewController: GMSAutocompleteViewControllerDelegate {
}

If you try to compile your project at this point, Xcode will inform you that the protocol has required methods and will offer to create stubs for the three required methods. You can let Xcode do that or copy the definitions from Listing 10-4.

Let’s start by dismissing the autocomplete view controller in all three methods, as we see in Listing 10-4. Add these functions to your extension.
func viewController(_ viewController:
  GMSAutocompleteViewController,
      didAutocompleteWith place: GMSPlace) {
  viewController.dismiss(animated: true,
                          completion: nil)
}
func viewController(_ viewController:
  GMSAutocompleteViewController,
      didFailAutocompleteWithError error: Error) {
  viewController.dismiss(animated: true,
                          completion: nil)
}
func wasCancelled(_ viewController:
    GMSAutocompleteViewController) {
    viewController.dismiss(animated: true,
                            completion: nil)
}
Listing 10-4

Implementing methods in the autocomplete view controller delegate

Next, let’s add some functionality to the first method, when a user selects a place. We will print out the place details, with the fields we requested from the Google Places API. If there are any required third-party attributions, we will also print those.

When you work with places returned from the Google Places API, some of those places may have attributions that you are required to display. Those come back on the GMSPlace object as the attributions property. For more on Google’s current policy for attributions, see this web page: https://developers.google.com/places/ios-sdk/attributions.

Our updated method will look similar to this:
func viewController(_ viewController:
  GMSAutocompleteViewController,
      didAutocompleteWith place: GMSPlace) {
  viewController.dismiss(animated: true,
                          completion: nil)
  print(place)
  print(place.attributions ?? "No attributions")
}

Displaying the place on the map

If you are going to display Google Places data on a map, it must be a Google map, not an iOS MapKit map or a map from another provider. You can display places data outside of a map, but you need to display the Powered by Google image found at the attributions web page: https://developers.google.com/places/ios-sdk/attributions.

We are going to display the selected place on the map as a map marker. Start by adding a new function to your ViewController class named addMarker . This function will take a GMSPlace object as an argument named place:
func addMarker(_ place:GMSPlace) {
}
Now call that addMarker method at the end of the first method in the autocomplete view controller delegate extension, after printing out the place details:
  print(place)
  print(place.attributions ?? "No attributions")
  addMarker(place)
We discussed how to add map markers to Google Maps in Chapter 8. We’ll use the place fields we requested from the Google Places API to populate the marker and then display it on the map, as you can see with the code in Listing 10-5.
func addMarker(_ place:GMSPlace) {
  let marker = GMSMarker()
  marker.position = place.coordinate
  marker.title = place.name
  marker.icon = GMSMarker.markerImage(with: .blue)
  marker.userData = place
  marker.map = mapView
}
Listing 10-5

Displaying a place from the Google Places SDK on a Google map

When you run the project, you should see a Google map appear. Zoom or scroll on the map to find the location you want to search for places, and then tap the Find Businesses button. You will see the autocomplete view controller appear. When you select one of those places, you will see a blue marker appear on the map. Tapping the marker makes the name from the Google Places API appear, as you can see in Figure 10-9.
../images/485790_1_En_10_Chapter/485790_1_En_10_Fig9_HTML.jpg
Figure 10-9

Showing a map marker in Google maps for a Google Places place

We put the GMSPlace object into the userData property on the map marker so that we could have access to the data in the place later. For instance, you could extend this application to retrieve photos for the place when a user taps on the marker.

Additional functionality in Places SDK

There is a lot of additional functionality in the Google Places SDK for iOS that we did not cover in this chapter. We only discussed the full-screen view controller for autocomplete search and not how to implement a custom user interface for search. We also did not cover how to request place details for a given place id, which we could use to get more data fields from the API.

In addition, you can also translate the user’s current location into a place, letting you tell the user where they are. With the information in the photos data field for a place, you can retrieve photos, if there are any in Google Places.

This chapter concludes our discussion of Google’s Map Platform and its components for iOS. In the next chapter, we will learn how to get started with Mapbox, another mapping provider.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.240.222