Google Maps SDK Tutorial for iOS

The default map view for iOS, since 2012, is Apple Maps. However, you are still able to use Google Maps in your project thanks to the Google Maps SDK.

In order to use the Google Maps SDK in your projects, you must obtain an API key using Google Platform Console. You can create an API key for your project by clicking here.

Recently, Google changed their Cloud Platform pricing plans so that you must enable billing to receive an API key to use the Google Maps SDK in your project. View more details about pricing here before signing up.

The fastest way to import any of the Google SDKs into your project is through a Cocoapod. Adding a Cocoapod to your iOS app is fast and easy.

In this app we’re going to be using the Google Maps and Google Places SDK Cocopods. To add those libraries to our project copy and paste these pods into your podfile.

pod 'GoogleMaps'
pod 'GooglePlaces'

Save and exit your podfile. Type pod install in your terminal to install the pods we added.

Now, you need to initialize the Google Maps and Google Places SDK with your API key you created earlier. Open AppDelegate.swift file and provide the SDKs with an API key inside the application didFinishLaunchingWithOptions method like this:

import GoogleMaps 
import GooglePlaces

...

func application(_ application: UIApplication, 
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {  
    GMSServices.provideAPIKey("googleApiKey")
    GMSPlacesClient.provideAPIKey("googleApiKey")

    return true
}

Go to the Map.storyboard file. Click on the main view inside the scene or create a custom UIView with the size you need, and go to the identity inspector and give it a custom class of GMSMapView. Now we need to connect our map view to the MapViewController using an outlet.

There’s one last step to do before we have a working map view in our project. We need to set the map view’s delegate to be the MapViewController class.

Create an extension on MapViewController and conform to GMSMapViewDelegate protocol like this:

extension MapViewController: GMSMapViewDelegate { }

Lastly, in the viewDidLoad method, assign the map view delegate to be the MapViewController class.

Your MapViewController should look something like this now:

import UIKit
import GoogleMaps

class MapViewController: UIViewController {
    @IBOutlet var mapView: GMSMapView!

    override func viewDidLoad() {
        super.viewDidLoad()    
        mapView.delegate = self
    }
}

extension MapViewController: GMSMapViewDelegate {}

We can specify the initial location that will be displayed in the map. The following coordinates center the map in Paris. Besides the coordinates, we also need to specify the initial zoom level of the map.

let camera: GMSCameraPosition = GMSCameraPosition.cameraWithLatitude(48.857165, 
    longitude: 2.354613, zoom: 8.0)
mapView.camera = camera

Current Location

Getting the users current location requires that we use CoreLocation. With that, we get the current user location and use that value to update the camera on the Google Map.

In MapViewController, import CoreLocation, just below the import GoogleMaps line.

import UIKit
import GoogleMaps
import CoreLocation

We also need to create a CLLocationManager and set the delegate as well as request authorisation and start getting the locations. We do that by adding the following:

let locationManager = CLLocationManager()

override func viewDidLoad() {
    super.viewDidLoad()

    locationManager.requestWhenInUseAuthorization()
    locationManager.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
}

Next, adopt the CLLocationManagerDelegate protocol where the class is declared:

extension MapViewController: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, 
        didChangeAuthorization status: CLAuthorizationStatus) {
        guard status == .authorizedWhenInUse else {
            return
        }

        locationManager.startUpdatingLocation()

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

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

        let loc = location.coordinate

        let marker = GMSMarker(position: loc)
        marker.title = "You are here"
        marker.map = mapView

        mapView.camera = GMSCameraPosition(target: loc, zoom: 15, bearing: 0, viewingAngle: 0)

        locationManager.stopUpdatingLocation()
    }
}

We need to add an entry to Info.plist. Select the file, then right click on the first key (Information Property List) and select "add row".

You need to edit the key name for this and set it at NSLocationWhenInUseUsageDescription (Privacy – Location When In Use Usage Description) and "Testing Google Maps Integration" as value.

Adding a Marker

Adding a marker on the map is a really easy task, as doing so it only takes two lines of code. However, there are various properties that can be configured so a marker can be customized according to your needs or preferences. In the Google Maps SDK a marker is an object of the GMSMarker class. When initializing such an object, you must specify the longitude and latitude expressed as a CLLocationCoordinate2D type.

let coordinate = CLLocationCoordinate2D(latitude: 48.857165, longitude: 2.354613)

var locationMarker = GMSMarker(position: coordinate) 
locationMarker.map = viewMap
locationMarker.title = "Paris"
locationMarker.snippet = "The best place on earth."
locationMarker.appearAnimation = .pop
locationMarker.icon = GMSMarker.markerImageWithColor(UIColor.blueColor())
locationMarker.opacity = 0.75

InfoWindow

In Google Maps, an InfoWindow is a popup window with extra information about a given location. It is displayed when the user taps on the marker we added above. You can attach your own UIView with whatever components you need.

override func viewDidLoad() {
    super.viewDidLoad()
    let camera = GMSCameraPosition.camera(withLatitude: 51.5287352, longitude: -0.3817818, zoom: 8)
    mapView.camera = camera
    let marker = GMSMarker()
    marker.position = CLLocationCoordinate2D(latitude: 51.5287352, longitude: -0.3817818)
    marker.title = "My marker"
    marker.map = self.mapView
    self.mapView.delegate = self
}

Next, we want to show an InfoWindow when the user clicks on the marker. To achieve this, we will use a xib file. In XCode create a new View file and name it CustomInfoWindow.

In the xib file, click on the Attributes inspector and change the view’s size to Freeform. Then change the Size of the view using the Size inspector and set width to 300 and height to 200.

To add some functionality we add a UILabel and a UIButton by dragging them.

Now, we need to create a class for our custom InfoWindow. Create a new CocoaTouch file and name it CustomInfoWindow. This class should be a subclass of UIView.

In order to connect the view to our file we have to set it as a class in the Identity Inspector of the view.

Now we want to be able to show our InfoWindow. To do this, we firstly should be able to instantiate the xib file. In you CustomInfoWindow file add following code.

override init(frame: CGRect) {
    super.init(frame: frame)   
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

func loadView() -> CustomInfoWindow{
    let customInfoWindow = Bundle.main.loadNibNamed("CustomInfoWindow", 
        owner: self, options: nil)?[0] as! CustomInfoWindow
    return customInfoWindow
}

After we have done this, we can now proceed in the ViewController. We declare two attributes: tappedMarker and currentInfoWindow. We need to keep track of the tappedMarker and have a reference to the currentInfoWindow in order to change it or customize it.

var tappedMarker : GMSMarker?
var customInfoWindow : CustomInfoWindow?

Now we have to instantiate them in viewDidLoad.

self.tappedMarker = GMSMarker()
self.customInfoWindow = CustomInfoWindow().loadView()

As next, we have to implement two methods of the GMSMapDelegate; namely mapView(didTap, marker) and mapView(markerInfoWindow, marker) in order to catch the tap events on markers and display an info window. Now you can add this to your ViewController file.

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

func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
    return self.customInfoWindow
}

Geocoding

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

func reverseGeocodeCoordinate(_ coordinate: CLLocationCoordinate2D) {
    let geocoder = GMSGeocoder()

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

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

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

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

extension MapViewController: GMSMapViewDelegate {
    func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
        reverseGeocodeCoordinate(position.target)
    }        
}

Links