Home · iOS & Swift Tutorials

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.

5/5 2 Ratings

Version

  • Swift 5, iOS 13, Xcode 11
Update note: Ron Kliffer updated this tutorial to Xcode 11, iOS 13, Swift 5 and Google Maps SDK 3.7.0. Ron also wrote the original.

Before 2012, Google Maps was the mapping engine for all iOS devices. That year, Apple made a dramatic change in iOS 6 by replacing Google Maps with an in-house mapping engine: MapKit.

A few months later, Google released its own standalone Google Maps app for iOS, along with the Google Maps iOS SDK for developers.

MapKit and the Google Maps iOS SDK each have their strengths and weaknesses. You’ll have to decide which one best fits your use case. If you decide to use the Google Maps SDK, this tutorial is for you!

In this tutorial, you’ll build an app called Feed Me, which gets the user’s current location and searches for nearby places to eat, drink or shop for groceries. In the process, you’ll learn how to use the Google Maps iOS SDK to:

  • Get the user’s location.
  • Show street addresses.
  • Present nearby search results.
  • Provide additional information for the places found.
Note:This tutorial uses CocoaPods to install the Google Maps SDK. To learn how to install and work with CocoaPods, read our CocoaPods Tutorial for Swift.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the project materials. This project already uses CocoaPods, so open Feed Me.xcworkspace.

Take a look around to get familiar with the project. Important things to notice are:

  • MapViewController.swift: The main view controller of this project and the only one you’ll modify in this tutorial.
  • GoogleDataProvider.swift: Wrapper class for making Google API calls. You’ll review the methods it contains later in the tutorial.
  • GooglePlace.swift: Model for place results returned from Google.
  • MarkerInfoView.swift: Subclass of UIView that displays details of places. It comes with a matching .xib file.

Before you start coding, build and run your app. You’ll see the following screen appear:

Starter project initial screen

Right now, all you’ll see is a blank screen with a pin in the middle. Next, press the action button on the right side of the navigation bar to see the TypesTableViewController screen:

Starter project type selection

That’s all there is to see in the app at the moment. It’s up to you to add some magic!

Creating the API Key

The first thing you’ll need is an API key for the Google Maps SDK and the Google API you’ll use. If you don’t already have a Google account, create one (they’re free!) and log in to the Google Developers Console.

Click Create to create a new project, name your project Feed Me and click Create:

Google developers console - create project

If your project doesn’t appear right away, refresh the page until it does. In the newly-created project, select Enable APIs and services:

Google developers console - enable APIs

Search for and enable these APIs:

  • Maps SDK for iOS
  • Places API

Select Credentials under APIs & Services in the left menu panel. Click Create Credentials, and then click API key to create the key:

Google developers console - generate API key

Before you can use the key, you’ll add the actual Google Maps iOS SDK. So keep the window open for the moment.

Adding the SDK

Open Podfile in the Pods project and add the following, right above end:

pod 'GoogleMaps', '~> 3.7.0'

Next, Open Terminal and navigate to the directory that contains your Feed Me project by using the cd command:

cd ~/Path/To/Folder/Containing/Feed Me

Enter the following command to install the Google Maps iOS SDK:

pod install

You should see output like the following:

Analyzing dependencies
Downloading dependencies
Installing GoogleMaps 3.7.0
Generating Pods project
Integrating client project

You now have GoogleMaps in your project. CocoaPods makes life a lot easier! :]

Open AppDelegate.swift and replace its contents with the following:

import UIKit
import GoogleMaps

//1
let googleApiKey = "ENTER_KEY_HERE"

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?

  func application(
    _ application: UIApplication, 
    didFinishLaunchingWithOptions launchOptions: 
      [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    //2
    GMSServices.provideAPIKey(googleApiKey)
    return true
  }
}

There are two new elements here:

  1. A constant to hold your Google API key. Replace ENTER_KEY_HERE with the Google API key you created earlier.
  2. The instantiation of Google Maps services with your API Key using the GMSServices class method provideAPIKey().

Creating the Map View

Now that you have your API key in place you can log out and close the Google Developers Console window.

Adding a UIView

Start by opening Main.storyboard to bring up Interface Builder. Locate the MapViewController scene and drag a simple UIView from the Object Library to the approximate center of MapViewController‘s view. Use View ▸ Show Library to reveal the Object Library.

Change the view’s background color to light gray. Next, open the Document Outline using Editor ▸ Outline and re-order the view hierarchy so the object tree looks like this:

Document outline after adding the map UIView

To turn this simple UIView into a GMSMapView, select the view you just added and open the Identity inspector by selecting the third tab from the left in the Utilities toolbar. Change the view’s Class to GMSMapView, as shown in the screenshot below:

Change class to GMSMapView

Your MapViewController scene should now look like this:
Storyboard scene after adding the view

Adding the Constraints for the Map View

Next, you’ll add some constraints to make the map fill the entire screen.

Select Map View in the Document Outline then choose the middle button in the bottom-right corner of the Interface Builder window. This is the Pin button (also known as the “TIE fighter button”).

Ensure that Constrain to margins is unchecked — this ensures that the map will fill all the available space on the screen — and add 0 space constraints from the top, left, bottom and right of the superview.

Your Pin editor should look like this:

Pin editor for the map view's constraints

Click Add 4 Constraints to add the constraints to the map view.
Your MapViewController scene should look like the following, where the gray area represents the GMSMapView:

Storyboard scene after adding the constraints

Creating an Outlet for the Map View

Before you build and run the project, add an IBOutlet for the map view. To do that, bring up the Assistant Editor using the keyboard shortcut Command-Option-Control-Return.

Select the map view in Interface Builder, hold down the Control key and drag a line from the map view to MapViewController.swift. A pop-up will appear. Set the connection type to Outlet and Name to mapView. Keep the Type as GMSMapView and click Connect:

Adding the mapView outlet

This will create a GMSMapView property in MapViewController.swift and automatically connect it in Interface Builder. Finally, add the following to the top of the file, after import UIKit:

import GoogleMaps

Build and run; you’ll now see a map like this:

App screenshot after adding the map

You’re now using the Google Maps iOS SDK in your app. But you can do more than show a basic map, right? :] Your next step will be to customize the map for your user’s location.

Getting the Location

Feed Me is all about finding places near the user, and you can’t do that without getting the user’s location.

Adding MapViewController Extension

iOS uses a delegate to inform your app of your user’s location. Your next step is to make MapViewController conform to CLLocationManagerDelegate.

Add the following extension to the bottom of MapViewController.Swift:

// MARK: - CLLocationManagerDelegate
//1
extension MapViewController: CLLocationManagerDelegate {
  // 2
  func locationManager(
    _ manager: CLLocationManager,
    didChangeAuthorization status: CLAuthorizationStatus
  ) {
    // 3
    guard status == .authorizedWhenInUse else {
      return
    }
    // 4
    locationManager.requestLocation()

    //5
    mapView.isMyLocationEnabled = true
    mapView.settings.myLocationButton = true
  }

  // 6
  func locationManager(
    _ manager: CLLocationManager,
    didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.first else {
      return
    }

    // 7
    mapView.camera = GMSCameraPosition(
      target: location.coordinate,
      zoom: 15,
      bearing: 0,
      viewingAngle: 0)
  }

  // 8
  func locationManager(
    _ manager: CLLocationManager,
    didFailWithError error: Error
  ) {
    print(error)
  }
}

In this extension, you:

  1. Create a MapViewController extension that conforms to CLLocationManagerDelegate.
  2. Create a locationManager delegate method that accepts a CLAuthorizationStatus parameter. This method is called once the user grants or revokes location permissions.
  3. Verify the user has granted you permission while the app is in use.
  4. Ask the location manager for their location.
  5. Add the user’s location indicator and the location button.
  6. Create a different locationManager method that accepts a CLLocation array. This executes when the location manager receives new location data.
  7. Update the map’s camera to center near the user’s current location. The GMSCameraPosition class aggregates all camera position parameters and passes them to the map for display.
  8. Create a third locationManager that accepts an Error parameter. If the app throws an error, it prints to the console so that you can handle it and keep your app from crashing.

Creating an Instance of CLLocationManager

Now, you must do the work to request your user’s location.

First, add the following property to MapViewController:

let locationManager = CLLocationManager()

This will add and instantiate a CLLocationManager property named locationManager.

Next, find the definition of viewDidLoad() in the extension and replace it with the following:

override func viewDidLoad() {
  super.viewDidLoad()

  // 1
  locationManager.delegate = self

  // 2
  if CLLocationManager.locationServicesEnabled() {
    // 3
    locationManager.requestLocation()

    // 4
    mapView.isMyLocationEnabled = true
    mapView.settings.myLocationButton = true
  } else {
    // 5
    locationManager.requestWhenInUseAuthorization()
  }
}

Here’s what this does:

  1. Makes MapViewController the delegate of locationManager.
  2. Checks if the user previously authorized the use of location services for this app.
  3. Asks the location manager for the user’s location.
  4. Sets isMyLocationEnabled and myLocationButton to true. This draws a light blue dot indicating the user’s location and adds a button to center the map on it.
  5. Requests access to the user’s location while using the app if locationServicesEnabled is false.

You’re ready to check for permission but you still need to ask for it first. You’ll fix that next.

Asking for Permission to Get Location

Open the Feed Me project at the top of the Project navigator and select the Feed Me target. Then go to the Info tab and select the first line in the Custom iOS Target Properties section.

Control-click and select Raw Keys & Values from the menu. Now, click the + icon to add a new row.

Set NSLocationWhenInUseUsageDescription as the key, choose String for the type and enter the following text as the value:

By accessing your location, this app can find good places to eat for you.

When you’re finished, it will look like this:

Adding NSLocationWhenInUseUsageDescription to info.plist

Build and run. Once your app loads, you’ll be prompted with an alert asking for location permissions. Tap on Allow While Using App.

App screenshot of the system requesting location permissions

You now see a map centered on your location. Scroll the map a little, then tap the Locate button in the bottom-right corner. The map centers on your location again.

App screenshot showing the user's location

Showing the Street Address

Now that you have the user’s location, it would be nice to show the street address of that location. Google Maps has an object that does exactly that: GMSGeocoder. This takes a coordinate and returns a readable street address.

Creating the UI for the Address

First, you’ll add some UI to present the address to the user.

Open Main.storyboard and add a UILabel to the MapViewController scene. Make sure you add the label to MapViewController‘s view, and not GMSMapView.

Next, open the Attributes inspector, and set the following attributes for the label:

  • Alignment to center.
  • Lines to 0. Surprisingly, this lets the label take up as many lines as it needs to fit the text. Go figure! :]
  • Background to white with 85% opacity.

The label’s Attributes inspector and the scene’s Object Tree should look like this when done:

Attributes inspector for the user's address UILabel

Also, make sure that all the subviews are ordered as follows:

Document outline showing the user's address UILabel position

Finally, add 0 space constraints for left, bottom and right to the label as shown below:

Adding constraints for the user's address label

This pins the label to the bottom of the screen and stretches it over the entire width of the screen.

Your storyboard scene should look like this:

Storyboard scene after adding the address label

Next, create an outlet for the label.

Bring up Assistant Editor and Control-drag from the label in the Document Outline to MapViewController.swift. Set the connection type to Outlet, the name to addressLabel and click Connect.

This adds a property to your MapViewController that you can use in your code:

@IBOutlet weak var addressLabel: UILabel!

Getting Address From a Coordinate

Now that you have the address label, it’s time to get the address. Add the method below to MapViewController:

func reverseGeocode(coordinate: CLLocationCoordinate2D) {
  // 1
  let geocoder = GMSGeocoder()

  // 2
  geocoder.reverseGeocodeCoordinate(coordinate) { response, error in
    guard 
      let address = response?.firstResult(), 
      let lines = address.lines 
      else {
        return
    }

    // 3
    self.addressLabel.text = lines.joined(separator: "\n")

    // 4
    UIView.animate(withDuration: 0.25) {
      self.view.layoutIfNeeded()
    }
  }
}

Here’s what each commented section does:

  1. Creates a GMSGeocoder object to turn a latitude and longitude coordinate into a street address.
  2. Asks the geocoder to reverse geocode the coordinate passed to the method. It then verifies there is an address in the response of type GMSAddress. This is a model class for addresses returned by the GMSGeocoder.
  3. Sets the text of the addressLabel to the address returned by the geocoder.
  4. Once the address is set, animates the changes in the label’s intrinsic content size.

Your next step is to keep the map updated when the user changes locations.

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.

Adding a Refresh Map Option

Open Main.storyboard and drag a UIBarButtonItem from the Object Library to the left side of the MapViewController‘s navigation bar. Change the button’s System Item to Refresh, as shown below:

Adding a refresh button to the storyboard scene

Bring up Assistant Editor and Control-drag from the Refresh button to MapViewController.swift. Choose Action and name the method refreshPlaces. Insert the following code into the newly-added method:

fetchPlaces(near: mapView.camera.target)

Build and run and you’ll see location pins popping up around the map. Change the search types in the TypesTableViewController and see how the results change.

App screenshot with location pins on the map

Note: If you don’t see any new markers after changing the search types, this might be due to Google Places usage limits. Wait a few seconds and try refreshing. For more information, check out the official Google Places usage limits documentation.

These markers sure add some color to the map, but they’re not much use without additional data to give the user details about the pinned locations.

Showing Additional Place Information

Add the following method to the GMSMapViewDelegate extension in MapViewController.swift:

func mapView(
  _ mapView: GMSMapView, 
  markerInfoContents marker: GMSMarker
) -> UIView? {
  // 1
  guard let placeMarker = marker as? PlaceMarker else {
    return nil
  }

  // 2
  guard let infoView = UIView.viewFromNibName("MarkerInfoView") as? MarkerInfoView 
    else {
      return nil
  }

  // 3
  infoView.nameLabel.text = placeMarker.place.name
  infoView.addressLabel.text = placeMarker.place.address

  return infoView
}

Each tap on a marker calls this method. If you return a view, it appears above the marker. If you return nil, the button tap does nothing.

Here’s what you’re doing to create this behavior:

  1. You first cast the tapped marker to a PlaceMarker.
  2. Next, you create a MarkerInfoView from its nib. MarkerInfoView is a UIView subclass that comes with the starter project for this tutorial.
  3. Then you apply the place name to nameLabel and the place address to addressLabel.

Everything’s working nicely, but you have one more step to make sure that the UI looks right before finishing your app.

Tidying up the UI

You need to make sure the location pin doesn’t cover the info window. To do this, add the following method to the GMSMapViewDelegate extension:

func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
  mapCenterPinImage.fadeOut(0.25)
  return false
}

This method hides the location pin when the user taps a marker. The method returns false to indicate that you don’t want to override the default behavior of centering the map around the marker when tapping it.

But the pin needs to re-appear at some point. So add the following to the end of mapView(_:willMove:):

if gesture {
  mapCenterPinImage.fadeIn(0.25)
  mapView.selectedMarker = nil
}

This checks if the movement originated from a user gesture. If so, it unhides the location pin using fadeIn(_:). It also sets the map’s selectedMarker to nil to remove the currently-presented infoView.

Finally, add the following method to the GMSMapViewDelegate extension:

func didTapMyLocationButton(for mapView: GMSMapView) -> Bool {
  mapCenterPinImage.fadeIn(0.25)
  mapView.selectedMarker = nil
  return false
}

This method runs when the user taps the Locate button, causing the map to center on the user’s location. It also returns false so that it doesn’t override the button’s default behavior.

Build and run. Select a marker and you’ll see the location pin fade out. Scroll the map and notice that the infoView closes and brings the pin back:

App screenshot with location-specific info after pressing a marker

That’s it, you’ve done it! You now have a fully functioning Google Maps app. :]

Google Maps Versus Apple MapKit

As you consider building your own map-based app, you may wonder if you should use MapKit instead. Here are some of the advantages of each SDK to help you decide which to use in your situation:

Advantages of Google Maps iOS SDK

  • Frequent updates to the SDK.
  • Uniform experience for cross-platform (iOS and Android) apps.
  • Greater map detail than MapKit, especially outside the United States.

Apple Maps screenshot showing limited details around Moscow

Google Maps screenshot showing greater details around Moscow

Advantages of Apple’s MapKit

  • Native to iOS. MapKit always syncs with iOS and works with Swift out of the box.
  • Greater stability.
  • Better integration with CoreLocation and CoreAnimation.

Where to Go From Here?

Download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

The Google Maps SDK is too large to include in this download, so be sure to run pod install before running a build. Also, insert your API key as you did at the beginning of this tutorial.

This tutorial only showed you the basics of what the Google Maps SDK can do. There’s much more to learn; you should definitely check out the full documentation for other great features this SDK has to offer.

For example, the Google Maps iOS SDK can also show directions, indoor maps, overlays, tile layers and Street View. For extra credit, try to use some of these features to enhance the Feed Me app.

If you’d like to learn more about Apple’s MapKit, check out our MapKit Tutorial: Getting Started.

We hope you enjoyed this tutorial. If you have any questions, tips or just wanna show off your cool mapping app, feel free to post about it in the discussion below!

Average Rating

5/5

Add a rating for this content

2 ratings

More like this

Contributors

Comments