Google Maps iOS SDK Tutorial: Getting Started

In this tutorial, you’ll learn how to use the Google Maps iOS SDK to make an app that searches for nearby places to eat, drink or shop for groceries. By Ron Kliffer.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Updating the Address

You’ll want to call the method above every time the user changes position on the map. To do so, you’ll use GMSMapViewDelegate.

Add another extension to the bottom of MapViewController.swift as follows:

// MARK: - GMSMapViewDelegate
extension MapViewController: GMSMapViewDelegate {
}

This will declare that MapViewController conforms to GMSMapViewDelegate.

Next, add the following line of code to viewDidLoad():

mapView.delegate = self

This makes MapViewController the map view’s delegate.

Finally, add the following method to the newly-added extension:

func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
  reverseGeocode(coordinate: position.target)
}

This method is called each time the map stops moving and settles in a new position. You then make a call to reverse geocode the new position and update the addressLabel‘s text.

Build and run. You’ll see the address of your current location — real or simulated — at the bottom of the screen.

App screenshot showing the user's address

Notice anything wrong with this picture?

[spoiler]The Google logo and Locate button are hiding behind the label![/spoiler]

Fixing the Street Address UI

Fortunately, GMSMapView provides a very simple solution for this: padding. When you apply padding to the map, all of the visual elements will be placed according to that padding.

Head back to reverseGeocodeCoordinate(_:) and add these lines just before the animation block:

// 1
let labelHeight = self.addressLabel.intrinsicContentSize.height
let topInset = self.view.safeAreaInsets.top
self.mapView.padding = UIEdgeInsets(
  top: topInset,
  left: 0,
  bottom: labelHeight,
  right: 0)

Now, replace the animation block with:

UIView.animate(withDuration: 0.25) {
  //2
  self.pinImageVerticalConstraint.constant = (labelHeight - topInset) * 0.5
  self.view.layoutIfNeeded()
}

This does two things:

  1. Adds padding to the top and bottom of the map before the animation block. The top padding equals the view’s top safe area inset, while the bottom padding equals the label’s height.
  2. Updates the location pin’s position to match the map’s padding by adjusting its vertical layout constraint.

Build and run again. This time, the Google logo and locate button move to their new position once the label becomes visible.

App screenshot showing the user's address after adding padding to the map

Move the map around; you’ll notice that the address changes every time the map settles to a new position. You’ll add animation to dampen this effect.

Add the following method to the GMSMapViewDelegate extension:

func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
  addressLabel.lock()
}

This method is called every time the map starts to move. It receives a Bool that tells you if the movement originated from a user gesture, such as scrolling the map, or if the movement originated from code.

If the movement came from the user, you call lock() on addressLabel to give it a loading animation.

When there’s a lock(), there must also be a corresponding unlock(). Add the following as the first line of the closure passed to reverseGeocode(coordinate:). It should come right before the guard let:

self.addressLabel.unlock()
Note: For the full implementation of lock() and unlock(), check out UIView+Extensions.swift.

Build and run. As you scroll the map, you’ll see a loading animation on the address label.

App screenshot showing the user's address loading with animation

Finding Places

Now that you’ve set up the map and have the user’s location in hand, it’s time to get this user fed!

You’ll use Google Places API to search for places to eat and drink around the user’s location. This is a free web service API you can use to query to find establishments, geographic locations or other points of interest near any given point.

Marking Locations on the Map

Google Maps iOS SDK provides you with the GMSMarker class to mark locations on a map. Each marker object holds a coordinate and an icon image and renders on the map when you add it.

For this app, you’ll provide additional information on each marker. To do this, you’ll create a subclass of GMSMarker.

Create a new Cocoa Touch Class, name it PlaceMarker and make it a subclass of GMSMarker. Ensure you choose Swift as the language for this file.

Replace the contents of PlaceMarker.swift with the following:

import UIKit
import GoogleMaps

class PlaceMarker: GMSMarker {
  // 1
  let place: GooglePlace

  // 2
  init(place: GooglePlace, availableTypes: [String]) {
    self.place = place
    super.init()

    position = place.coordinate
    groundAnchor = CGPoint(x: 0.5, y: 1)
    appearAnimation = .pop

    var foundType = "restaurant"
    let possibleTypes = availableTypes.count > 0 ? 
      availableTypes : 
      ["bakery", "bar", "cafe", "grocery_or_supermarket", "restaurant"]

    for type in place.types {
      if possibleTypes.contains(type) {
        foundType = type
        break
      }
    }
    icon = UIImage(named: foundType+"_pin")
  }
}

Here’s what this code is doing:

  1. Adds a property of type GooglePlace to the PlaceMarker.
  2. Declares a new designated initializer that accepts a GooglePlace and available location types. Fully initializes a PlaceMarker with a position, icon image and anchor for the marker’s position as well as an appearance animation.

Searching for Nearby Places

Next, add two more properties to MapViewController:

let dataProvider = GoogleDataProvider()
let searchRadius: Double = 1000

You’ll use dataProvider, defined in GoogleDataProvider.swift, to make calls to the Google Places Web API. And you’ll use searchRadius to set the distance from the user for the search. You’ve set it to 1,000 meters.

Add the following method to MapViewController:

func fetchPlaces(near coordinate: CLLocationCoordinate2D){
  // 1
  mapView.clear()
  // 2
  dataProvider.fetchPlaces(
    near: coordinate,
    radius:searchRadius,
    types: searchedTypes
  ) { places in
    places.forEach { place in
      // 3
      let marker = PlaceMarker(place: place, availableTypes: self.searchedTypes)
      // 4
      marker.map = self.mapView
    }
  }
}

In this method, you:

  1. Clear the map of all markers.
  2. Use dataProvider to query Google for nearby places within the searchRadius, filtered to the user’s selected types.
  3. Iterate through the results returned in the completion closure and create a PlaceMarker for each result.
  4. Set the marker’s map. This line of code is what tells the map to render the marker.

Here’s the $64,000 question: When do you want to call this method?

The answer is: When the app launches. Your user will expect to see some places to eat when they open an app called “Feed Me”!

Locate locationManager(_:didUpdateLocations:) and add the following line of code at the end:

fetchPlaces(near: location.coordinate)

Next, because the user has the ability to change the types of places to display on the map, you’ll update the search results if the selected types change.

To do this, locate typesController(_:didSelectTypes:) and add the following line of code to the end:

fetchPlaces(near: mapView.camera.target)

Finally, you’ll give the user the option to fetch new places when their location changes.