1. Before You Begin
This codelab teaches you how get started using Google Maps Platform for building iOS apps in Swift. You'll build an iOS app that does the following:
- Loads the Maps SDK for iOS and the Maps SDK for iOS Utility Library.
- Displays a map centered on Sydney, Australia.
- Displays custom markers for 100 points around Sydney.
- Implements marker clustering.
- Enables user interaction that re-centers and draws a circle on the map when a marker is tapped.
Prerequisites
- Basic knowledge of Swift and iOS development.
What you'll do
- Load the Maps SDK for iOS and the Google Maps SDK for iOS Utility Library.
- Load a map.
- Use markers, custom markers, and marker clustering.
- Work with the Maps SDK for iOS event system to support user interaction.
- Control the map camera programmatically.
- Draw on the map.
What you'll need
To complete this codelab, you need the following accounts, services, and tools:
- Xcode 12.0 or higher with a target SDK of 12.0 or later.
- Cocoapods installed.
- A Google Cloud Platform account with billing enabled (see next step).
- A project in Cloud Console with the Maps SDK for iOS enabled (see next step).
2. Get set up
For the enablement step below, you need to enable the Maps SDK for iOS.
Set up Google Maps Platform
If you do not already have a Google Cloud Platform account and a project with billing enabled, please see the Getting Started with Google Maps Platform guide to create a billing account and a project.
- In the Cloud Console, click the project drop-down menu and select the project that you want to use for this codelab.
- Enable the Google Maps Platform APIs and SDKs required for this codelab in the Google Cloud Marketplace. To do so, follow the steps in this video or this documentation.
- Generate an API key in the Credentials page of Cloud Console. You can follow the steps in this video or this documentation. All requests to Google Maps Platform require an API key.
Quickstart
To get you started as quickly as possible, here's some starter code to help you follow along with this codelab.
- Clone the repository if you have
git
installed.
git clone https://github.com/googlemaps/codelab-maps-platform-101-swift.git
Alternatively, click Give me the code to download the source code.
- After downloading the code, open the StarterApp project in the
/starter
directory. This project includes the basic file structure you need to complete the codelab. Everything you need to work with is located in the/starter/StarterApp
directory.
To see the full solution code running, view the completed code in the /solution/SolutionApp
directory.
3. Install the Maps SDK for iOS
The first step to using the Maps SDK for iOS is to install the needed dependencies. There are two steps to this process: installing the Maps SDK for iOS and the Maps SDK for iOS Utility Library from the Cocoapods dependency manager, and providing your API key to the SDK.
- Add the Maps SDK for iOS and Maps SDK for iOS Utility Library to
Podfile
.
This codelab uses both the Maps SDK for iOS, which provides all of the core functionality of Google Maps, and the Maps iOS Utility Library, which provides a variety of utilities to enrich your map, including marker clustering.
To start, in Xcode (or your preferred text editor) open Podfile
and update the file to include the Maps SDK for iOS and Utility Library dependencies under the comment # Pods for StarterApp
:
pod 'GoogleMaps', '6.1.0' pod 'Google-Maps-iOS-Utils', '3.4.0'
Check the Maps SDK for iOS Versions documentation for the latest version of the SDK and guidance for maintenance.
Your Podfile
should look like this:
source 'https://github.com/CocoaPods/Specs.git' platform :ios, '12.0' target 'StarterApp' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for StarterApp pod 'GoogleMaps', '6.1.0' pod 'Google-Maps-iOS-Utils', '3.4.0' end
- Install the Maps SDK for iOS and Maps SDK for iOS Utility Library pods.
To install the dependencies, run pod install
in the /starter
directory from the command line. Cocoapods automatically downloads the dependencies and creates StarterApp.xcworkspace
.
- Once your dependencies are installed, run
open StarterApp.xcworkspace
from the/starter
directory to open the file in Xcode, and then run the app in the iPhone simulator by pressingCommand+R
. If everything is set up correctly, the simulator will launch and show a black screen. Don't worry, you haven't built anything yet, so this is expected! - Import the SDK in
AppDelegate.swift
.
Now that your dependencies are installed, it's time to provide your API key to the SDK. The first step is to import the Maps SDK for iOS as a dependency by putting the following beneath the import UIKit
import statement:
import GoogleMaps
- Pass your API key to the iOS SDK by calling
provideAPIKey
onGMSServices
inapplication: didFinishLaunchingWithOptions:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GMSServices.provideAPIKey("YOUR_API_KEY")
return true
}
Your updated AppDelegate.swift
file should now look like this:
import UIKit
import GoogleMaps
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GMSServices.provideAPIKey("YOUR_API_KEY")
return true
}
}
Replace YOUR_API_KEY
with the API key that you created in Cloud Console.
Now that your dependencies are installed and your API key is provided, you're ready to start making calls to the Maps SDK for iOS.
4. Display a Map
Time to display your first map!
The most commonly used part of the Maps SDK for iOS is the GMSMapView
class, which provides many of the methods that allow you to create and manipulate map instances. Here's how that's done:
- Open
ViewController.swift
.
This is where you'll do the remainder of the work for this codelab. Notice loadView
and viewDidLoad
lifecycle events for the view controller are already stubbed out for you.
- Import the Maps SDK for iOS by adding this at the top of the file:
import GoogleMaps
- Declare a
ViewController
instance variable to storeGMSMapView
.
The instance of GMSMapView
is the main object you work with throughout this codelab, and you'll reference and act on it from various view controller lifecycle methods. To make it available, update the implementation of ViewController
to declare an instance variable to store it:
class ViewController: UIViewController {
private var mapView: GMSMapView!
...
}
- In
loadView
, create an instance ofGMSCameraPosition
.
GMSCameraPosition
defines where the map is centered and the zoom level that is displayed. This code calls the method cameraWithLatitude:longitude:zoom:
to center the map on Sydney, Australia, at a latitude of -33.86 and longitude of 151.20, with a zoom level of 12:
let camera:GMSCameraPosition = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 12)
- In
loadView
, create an instance ofGMSMapView
to instantiate the map.
To create a new map instance, call GMSMapView(frame: CGRect, camera: GMSCameraPosition)
. Note how the frame is set to CGRect.zero
, which is a global variable from the iOS CGGeometry
library that specifies a frame of 0 width, 0 height, located at position (0,0) inside the view controller. The camera is set to the camera position you just created.
Next, to display the map, set the root view of the view controller to mapView
, which makes the map display fullscreen.
mapView = GMSMapView(frame: .zero, camera: camera)
self.view = mapView
- Set
GMSMapViewDelegate
to the view controller.
When implemented, the map view delegate lets you handle events from user interactions on the GMSMapView
instance, which you'll need later.
First, update the interface of ViewController
to conform to the protocol for GMSMapViewDelegate:
class ViewController: UIViewController, GMSMapViewDelegate
Next, add the following line in the loadView
function to set GMSMapViewDelegate
to the ViewController
.
mapView.delegate = self
Now reload the app in the iOS simulator (Command+R
), and the map should appear as shown in the following screenshot:
Figure 1. iOS app showing a Google Map.
To recap, in this step you created an instance of GMSMapView
to display a map centered on the city of Sydney, Australia.
Your ViewController.swift
file should now look like this:
import UIKit
import GoogleMaps
class ViewController: UIViewController, GMSMapViewDelegate {
private var mapView: GMSMapView!
override func loadView() {
// Load the map at set latitude/longitude and zoom level
let camera:GMSCameraPosition = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 11)
mapView = GMSMapView(frame: .zero, camera: camera)
self.view = mapView
mapView.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
5. Style the Map (optional)
You can customize the style of your map using Cloud-based map styling.
Create a Map ID
If you have not yet created a map ID with a map style associated to it, see the Map IDs guide to complete the following steps:
- Create a map ID.
- Associate a map ID to a map style.
Add the Map ID to your app
To use the map ID you created in the previous step, open the ViewController.swift
file and within the loadView
method create a GMSMapID
object and provide it the map ID. Next, modify the GMSMapView
instantiation by providing the GMSMapID
object as a parameter.
ViewController.swift
override func loadView() {
// Load the map at set latitude/longitude and zoom level
let camera:GMSCameraPosition = GMSCameraPosition.camera(withLatitude: -33.86, longitude: 151.20, zoom: 11)
let mapID = GMSMapID(identifier: "YOUR_MAP_ID")
mapView = GMSMapView(frame: .zero, mapID: mapID, camera: camera)
self.view = mapView
mapView.delegate = self
}
When you've completed this, run the app to see your map in the style that you selected.
6. Add Markers to the Map
There are lots of things developers do with the Maps SDK for iOS, but putting markers on the map is definitely the most popular. Markers show specific points on the map, and are a common UI element for handling user interaction. If you've used Google Maps before, then you're probably familiar with the default marker, which looks like the red pins in Figure 2:
Figure 2. Map with red markers.
This step demonstrates how to use the GMSMarker
class to put markers on the map.
Note that markers cannot be placed on the map until after the map is loaded from the previous step in the view controller's loadView
lifecycle event, so complete these steps in the viewDidLoad
lifecycle event, which is called after the view (and map) has been loaded.
- Define a
CLLocationCoordinate2D
object.
CLLocationCoordinate2D
is a struct made available by the iOS CoreLocation library, which defines a geographic location at a set latitude and longitude. To begin creating your first marker, define a CLLocationCoordinate2D
object and set the latitude and longitude to the center of the map. The coordinates of the center of the map are accessed from the map view using the camera.target.latitude
and camera.target.longitude
properties.
// Add a single marker with a custom icon
let mapCenter = CLLocationCoordinate2DMake(mapView.camera.target.latitude, mapView.camera.target.longitude)
- Create an instance of
GMSMarker
.
The Maps SDK for iOS provides the GMSMarker
class. Each instance of GMSMarker
represents an individual marker on the map and is created by calling markerWithPosition:
and passing it a CLLocationCoordinate2D
object to tell the SDK where to place the marker on the map.
let marker = GMSMarker(position: mapCenter)
- Set a custom marker icon.
The default red pin marker for Google Maps is great, but so is customizing your map! Luckily, using a custom marker is straightforward with the Maps SDK for iOS. Notice that the StarterApp project includes an image called "custom_pin.png" for you to use, but you can use any image you want.
To set the custom marker, set the icon
property of the marker to an instance of UIImage
.
marker.icon = UIImage(named: "custom_pin.png")
- Render the marker to the map.
Your marker is created, but it isn't on the map yet. To do this, set the map
property of the GMSMarker
instance to an instance of GMSMapView
.
marker.map = mapView
Now reload the app and behold your first map with a marker as shown in Figure 3!
Figure 3. iOS app featuring a Google Maps with a red marker in the center.
To recap, in this section you created an instance of the GMSMarker class and applied it to the map view to display a marker on the map. Your updated viewDidLoad lifecycle event in ViewController.swift
should now look like this:
override func viewDidLoad() {
super.viewDidLoad()
// Add a single marker with a custom icon
let mapCenter = CLLocationCoordinate2DMake(mapView.camera.target.latitude, mapView.camera.target.longitude)
let marker = GMSMarker(position: mapCenter)
marker.icon = UIImage(named: "custom_pin.png")
marker.map = mapView
}
7. Enable Marker Clustering
If you use a lot of markers, or have markers that are in close proximity to one another, you may encounter an issue where the markers overlap or appear crowded together, which causes a bad user experience. For example, if two markers are very close together, you could end up with a situation as seen in Figure 4:
Figure 4. Two markers very close together.
This is where marker clustering comes in. Marker clustering is another commonly implemented feature, which groups nearby markers into a single icon that changes depending on the zoom level, as shown in Figure 5:
Figure 5. Example of markers clustered into a single icon.
The algorithm for marker clustering divides the visible area of the map into a grid, then clusters icons that are in the same cell. The Google Maps Platform team has created a helpful, open-source utility library called the Google Maps SDK for iOS Utility Library which, among many other things, handles marker clustering for you automatically. Read more about marker clustering in the Google Maps Platform documentation, or check out the source for the iOS Utility Library on GitHub.
- Add more markers to the map.
To see marker clustering in action, you'll need to have many markers on the map. To streamline this, use the marker generator provided in the starter project in MarkerGenerator.swift
.
To add a specified number of markers to your map, call MarkerGenerator(near:count:).markerArray
in the view controller's viewDidLoad
lifecycle below the code from the previous step. The method creates the number of markers specified in count
at random locations around the coordinates specified in a CLLocationCoordinate2D
object. In this case, you can pass it the mapCenter
variable that you created earlier. The markers are returned in a [GMSMarker]
.
// Generate many markers
let markerArray = MarkerGenerator(near: mapCenter, count: 100).markerArray
You can test how this many markers looks by adding these lines after the definition of markerArray
, and then running the app. Be sure to comment these lines out before moving to the next steps, which use the Marker Clusterer to manage the display of the markers instead:
// Comment the following code out if using the marker clusterer
// to manage markers instead.
for marker in markerArray {
marker.map = mapView
}
- Import the Google Maps SDK for iOS Utility Library.
To add the Maps iOS utility library as a dependency to your project, add this to the list of dependencies at the top of ViewController.swift
:
import GoogleMapsUtils
- Configure the marker clusterer.
To use the marker clusterer, you need to provide three things to configure how it works: a clustering algorithm, an icon generator, and a renderer. The algorithm determines the behavior of how markers are clustered, such as the distance between markers to include in the same cluster. The icon generator provides the cluster icons to be used at different zoom levels. The renderer handles the actual rendering of the cluster icons on the map.
You can write all of these from scratch if you prefer. Alternatively, the Maps iOS utility library provides default implementations to make the process faster. Add the following lines:
// Set up the cluster manager with a supplied icon generator and renderer.
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
let iconGenerator = GMUDefaultClusterIconGenerator()
let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
- Create an instance of
GMUClusterManager
.
GMUClusterManager
is the class that implements marker clustering using the algorithm, icon generator, and renderer specified by you. To create the renderer and make it available to the map view, first add an instance variable to the ViewController
implementation to store the cluster manager instance:
class ViewController: UIViewController, GMSMapViewDelegate {
private var mapView: GMSMapView!
private var clusterManager: GMUClusterManager!
}
Next, create the instance of GMUClusterManager
in the viewDidLoad
lifecycle event:
clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)
- Add the markers and run the marker clusterer.
Now that your marker clusterer instance is configured, pass the cluster manager the array of markers to be clustered by calling add(items:)
, and then run the clusterer by calling cluster
.
clusterManager.setMapDelegate(self)
clusterManager.add(markerArray)
clusterManager.cluster()
Reload your app, and you should now see a lot of markers all nicely clustered like the example in Figure 6. Go ahead and play around with different zoom levels by pinching and zooming on the map, to see the marker clusters adapt as you zoom in/out.
Figure 6. iOS app with a Google Maps and clustered markers.
To recap, in this step you configured an instance of the marker clusterer from the Google Maps SDK for iOS Utility Library, then used it to cluster 100 markers on the map. Your viewDidLoad
lifecycle event in ViewController.swift
should now look like this:
override func viewDidLoad() {
super.viewDidLoad()
// Add a single marker with a custom icon
let mapCenter = CLLocationCoordinate2DMake(mapView.camera.target.latitude, mapView.camera.target.longitude)
let marker = GMSMarker(position: mapCenter)
marker.icon = UIImage(named: "custom_pin.png")
marker.map = mapView
// Generate many markers
let markerArray = MarkerGenerator(near: mapCenter, count: 100).markerArray
// Comment the following code out if using the marker clusterer
// to manage markers instead.
// for marker in markerArray {
// marker.map = mapView
// }
// Set up the cluster manager with a supplied icon generator and renderer.
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
let iconGenerator = GMUDefaultClusterIconGenerator()
let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)
clusterManager.setMapDelegate(self)
clusterManager.add(markerArray)
clusterManager.cluster()
}
8. Add User Interaction
You now have a great looking map that displays markers and uses marker clustering. In this step, you'll add some additional handling of user interactions using GMSMapViewDelegate
, which you set to the view controller earlier, to improve the user experience of your map.
The Maps SDK for iOS provides a comprehensive event system that is implemented through the map view delegate, which includes event handlers that lets you execute code when various user interactions occur. For example, the MapView delegate includes methods that leet you trigger code execution for interactions, such as the user clicking on the map and markers, panning the view of the map, zooming in and out, and more.
In this step, you programmatically pan the map to center on any marker that is tapped by the user.
- Implement the marker tap listener.
mapView(_:didTap:)
is called any time the user taps one of the markers you created earlier, and any time a user taps a marker cluster (internally marker clusters are implemented as an instance of GMSMarker
).
To implement the event listener, start by stubbing it out at the bottom of ViewController.swift
before the closing curly brace.
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
return false
}
Notice that the method is returning false
. Doing this tells the iOS SDK to continue executing the default GMSMarker
behavior, such as showing an info window if one is configured, after executing your event handler code.
- Handle the tap event and animate the camera to recenter the map when a marker or marker cluster is tapped.
When called, mapView(_:didTap:)
passes the instance of GMSMarker
that was tapped so you to can handle it in your code. You can use this instance to recenter the map by calling animate(toLocation:)
on the map view from inside the event handler, and passing it the position of the marker instance from the position
property.
// Animate to the marker
mapView.animate(toLocation: marker.position)
- Zoom in on a marker cluster when it is tapped.
A common UX pattern is to zoom in on marker clusters when they are tapped. This lets users view the clustered markers, as the cluster expands at lower zoom levels.
As noted earlier, the marker cluster icon is actually an implementation of GMSMarker
with a custom icon. So how can you tell whether a marker or a marker cluster was tapped? When the marker clusterer manager creates a new cluster icon, it implements the instance of GMSMarker
to conform to a protocol called GMUCluster.
You can use a conditional to check whether the marker passed into the event handler conforms to this protocol.
Once you know programmatically that a cluster has been tapped, you can call animate(toZoom:)
on the map view instance and set the zoom to the current zoom level plus one. The current zoom level is available on the mapView instance in the camera.zoom
property.
Also, notice how the following code returns true
. This tells the event handler that you have completed handling the event and to not execute any further code in the handler. One of the reasons for doing this is to prevent the underlying GMSMarker
object from executing the rest of its default behavior, such as showing an info window, which wouldn't make much sense in the case of tapping a cluster icon.
// If the tap was on a marker cluster, zoom in on the cluster
if let _ = marker.userData as? GMUCluster {
mapView.animate(toZoom: mapView.camera.zoom + 1)
return true
}
Now reload the app and tap on some markers and marker clusters. When either is tapped, the map will recenter on the tapped element. When a marker cluster is tapped, the map will also zoom in by one level, and the marker cluster will expand to show the markers that are clustered underneath.
To recap, in this step you implemented the marker tap listener, and handled the event to recenter on the tapped element and zoom in if that element is a marker cluster icon.
Your mapView(_:didTap:)
method should look like this:
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
// Animate to the marker
mapView.animate(toLocation: marker.position)
// If the tap was on a marker cluster, zoom in on the cluster
if let _ = marker.userData as? GMUCluster {
mapView.animate(toZoom: mapView.camera.zoom + 1)
return true
}
return false
}
9. Draw on the Map
So far, you created a map of Sydney that shows markers at 100 random points and handles user interaction. For the last step of this codelab, you use the drawing features of the Maps SDK for iOS to add an additional useful feature to your map experience.
Imagine that this map is going to be used by users that want to explore the city of Sydney. A useful feature would be to visualize a radius around a marker when it is clicked. This would allow the user to quickly understand what other destinations are within an short distance of the clicked marker.
The iOS SDK includes a set of functions for drawing shapes on the map, such as squares, polygons, lines, and circles. In this step, render a circle to show an 800 meter (approximately half mile) radius around a marker when it is clicked.
- Add a
circle
instance variable to the implementation of the ViewController.
This instance variable is used to save the most recently drawn circle, so that it can be destroyed before another is drawn. After all, it wouldn't be very helpful to the user and wouldn't look good if every tapped marker had a circle drawn around it!
To do this, update the implementation of ViewController
like so:
class ViewController: UIViewController, GMSMapViewDelegate {
private var mapView: GMSMapView!
private var clusterManager: GMUClusterManager!
private var circle: GMSCircle? = nil
...
}
- Draw the circle when a marker is tapped.
At the bottom of the mapView(_:didTap:)
method just above the return false
statement, add the code shown here to create an instance of the iOS SDK's GMSCircle
class to draw a new 800 meter radius circle by calling GMSCircle(position:radius:)
and passing it the position of the tapped marker as you did when you recentered the map.
// Draw a new circle around the tapped marker
circle = GMSCircle(position: marker.position, radius: 800)
- Style the circle.
By default, GMSCircle
draws a circle with a black stroke and transparent fill. That works for showing the radius, but it doesn't look very good and is a little bit hard to see. Next, give the circle a fill color to improve the styling by assigning a UIColor
to the fillColor
property of the circle. The code shown here adds a gray fill with 50% transparency:
circle?.fillColor = UIColor(red: 0.67, green: 0.67, blue: 0.67, alpha: 0.5)
- Render the circle on the map.
Just as when you created markers earlier, creating an instance of GMSCircle
doesn't make it appear on the map. To do this, assign the map view instance to the map
property of the circle.
circle?.map = mapView
- Remove any previously rendered circles.
As noted earlier, it wouldn't be a very good user experience to keep adding circles to the map. To remove the circle rendered by a previous tap event, set the map
property of circle
to nil
at the top of mapView(_:didTap:)
.
// Clear previous circles
circle?.map = nil
Reload the app and tap on a marker. You should see a new circle drawn whenever a marker is tapped and any previously rendered circle removed as shown in Figure 7.
Figure 7. A circle drawn around the tapped marker.
To recap, in this step you used the GMSCircle
class to render a circle whenever a marker is tapped.
The mapView(_:didTap:)
method should look like this:
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
// Clear previous circles
circle?.map = nil
// Animate to the marker
mapView.animate(toLocation: marker.position)
// If the tap was on a marker cluster, zoom in on the cluster
if let _ = marker.userData as? GMUCluster {
mapView.animate(toZoom: mapView.camera.zoom + 1)
return true
}
// Draw a new circle around the tapped marker
circle = GMSCircle(position: marker.position, radius: 800)
circle?.fillColor = UIColor(red: 0.67, green: 0.67, blue: 0.67, alpha: 0.5)
circle?.map = mapView
return false
}
10. Congratulations
You've successfully built an iOS app featuring an interactive Google map.
What you learned
- Loading and configuring the Maps SDK for iOS and the Google Maps SDK for iOS Utility Library
- Loading a map
- Styling a map
- Using markers, custom markers, and marker clustering
- The event system to provide user interaction
- Controlling the map camera programmatically
- Drawing on the map
What's next?
- Explore or fork the
maps-sdk-for-ios-samples
GitHub repository of samples and demos for more inspiration - Learn from more Swift codelabs for building iOS apps with Google Maps Platform
- Help us create the content that you would find most useful by answering the following survey:
What other codelabs would you like to see?
Can't find the codelab you're most interested in? Request it with a new issue here.