Add a map to your iOS app (Objective-C)

1. Before You Begin

Abstract

In this codelab you'll learn everything you need to get started using Google Maps Platform for building iOS apps in Objective-C. You'll learn all the basics from getting set up to loading the Maps SDK for iOS, displaying your first map, working with markers and marker clustering, drawing on the map, and handling user interaction.

What you'll build

342520482a888519.png

In this codelab, 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 clicked

What you'll learn

  • Getting started with Google Maps Platform
  • Loading the Maps SDK for iOS and the Google Maps SDK for iOS Utility Library
  • Loading a map
  • Using markers, custom markers, and marker clustering
  • Working with the Maps SDK for iOS event system to provide user interaction
  • Controlling the map programmatically
  • Drawing on the map

Prerequisites

You'll need to familiarize with the items below to complete this codelab. If you are already familiar with working with Google Maps Platform, skip ahead to the codelab!

Required Google Maps Platform products

In this codelab, you'll use the following Google Maps Platform products:

  • Maps SDK for iOS
  • Google Maps SDK for iOS Utility Library

Get started with Google Maps Platform

If you haven't used Google Maps Platform before, follow the Get Started with Google Maps Platform guide or watch the Getting Started with Google Maps Platform playlist to complete the following steps:

  1. Create a billing account.
  2. Create a project.
  3. Enable Google Maps Platform APIs and SDKs (listed in the previous section).
  4. Generate an API key.

Other requirements for this codelab

To complete this codelab, you'll need the following accounts, services, and tools:

  • A Google Cloud Platform account with billing enabled
  • A Google Maps Platform API key with the Maps SDK for iOS enabled
  • Basic knowledge of Objective-C
  • Xcode 12.0 with a target SDK of 12.0 or later

2. Get set up

For the enablement step below, you will 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.

  1. In the Cloud Console, click the project drop-down menu and select the project that you want to use for this codelab.

  1. 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.
  2. 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.

Project Starter Template Setup

Before you begin this codelab, do the following to download the starter project template as well as the complete solution code:

  1. Download or fork the GitHub repo for this codelab at https://github.com/googlecodelabs/maps-platform-101-objc.

The StarterApp project is located in the /starter directory and includes the basic file structure you'll need to complete the codelab. Everything you need to work with is located in the /starter/StarterApp directory.

If you'd like to see the full solution code running, you can complete the setup steps above and view the solution 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.

  1. Add the Maps SDK for iOS and Maps SDK for iOS Utility Library to Podfile.

In this codelab, you will use 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 Pods > Podfile and update the file to include the Maps SDK for iOS and Utility Library dependencies under use_frameworks!:

pod 'GoogleMaps'
pod 'Google-Maps-iOS-Utils'
  1. 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 will automatically download the dependencies, as well as create StarterApp.xcworkspace. 3. Once your dependencies are installed, open StarterApp.xcworkspace in Xcode, then run the app in the iPhone simulator by pressing Command+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! 4. Import the SDK in AppDelegate.h.

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 "AppDelegate.h" import statement:

@import GoogleMaps;
  1. Beneath the iOS SDK import statement, declare an NSString constant with the value set to your API key:
static NSString *const kMapsAPIKey = @"YOUR API KEY";
  1. Pass the API key to the iOS SDK by calling provideAPIKey on GMSServices in application: didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GMSServices provideAPIKey:kMapsAPIKey];
  return YES;
}

Your updated AppDelegate.m file should now look like this:

#import "AppDelegate.h"
@import GoogleMaps;

static NSString *const kMapsAPIKey = @"YOUR API KEY";

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GMSServices provideAPIKey:kMapsAPIKey];
  return YES;
}
@end

Your Podfile should look like this:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'

target 'StarterApp' do
  use_frameworks!

pod 'GoogleMaps', '5.1.0.0'
pod 'Google-Maps-iOS-Utils', '3.4.0'

end

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.

  1. Open ViewController.m.

This is where you will be doing all of the remaining work for this codelab. You'll notice loadView and viewDidLoad lifecycle events for the view controller are already stubbed out for you. 2. Import the Maps SDK for iOS by adding the following at the top of the file:

@import GoogleMaps;
  1. Declare a ViewController instance variable to store GMSMapView.

The instance of GMSMapView is the main object you will be working with throughout this codelab, and you will be referencing and acting 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:

@implementation ViewController {
  GMSMapView *_mapView;
}
  1. In loadView, create an instance of GMSCameraPosition.

GMSCameraPosition defines where the map will be centered and the zoom level that will be 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:

GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.86 longitude:151.20 zoom:12];
  1. In loadView, create an instance of GMSMapView to instantiate the map.

To create a new map instance, call mapWithFrame:camera:. Note how the frame is set to CGRectZero, 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.

To actually display the map, next you'll set the root view of the view controller to _mapview, which will make the map display fullscreen.

_mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
self.view = _mapView;
  1. Set GMSMapViewDelegate to the view controller.

When implemented, the map view delegate allows you to handle events from user interactions on the GMSMapView instance, which you will need in later steps.

First, update the interface of ViewController to conform to the protocol for GMSMapViewDelegate:

@interface ViewController ()<GMSMapViewDelegate>

Next, add the following to set GMSMapViewDelegate to the ViewController.

_mapView.delegate = self;

Now reload the app in the iOS simulator (Command+R), and the map should appear!

2e6ebac422323aa6.png

To recap, in this step you created an instance of GMSMapView to display a map centered on the city of Sydney, Australia.

Your ViewController.m file should now look like this:

#import "ViewController.h"
#import "LocationGenerator.h"
@import GoogleMaps;

@interface ViewController ()<GMSMapViewDelegate>
@end

@implementation ViewController {
  GMSMapView *_mapView;
}

- (void)loadView {
  [super loadView];
  GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.86 longitude:151.20 zoom:12];
  _mapView = [GMSMapView mapWithFrame:CGRectZero camera:camera];
  self.view = _mapView;
  _mapView.delegate = self;
}

5. Cloud-based map styling (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:

  1. Create a map ID.
  2. Associate a map ID to a map style.

Adding the Map ID to your app

To use the map ID you created in the previous step, open the ViewController.m 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.m

- (void)loadView {
    GMSMapID *mapID = [[GMSMapID alloc] initWithIdentifier:@"YOUR_MAP_ID"];
    _mapView = [GMSMapView mapWithFrame:CGRectZero mapID:mapID camera:camera];
    // ...
}

Once you've completed this, go ahead and run the app to see your map in the style that you selected!

6. Add Markers to the Map

There's lots of things developers do with the Maps SDK for iOS, but putting markers on the map is definitely the most popular. Markers allow you to 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 this:

590815267846f166.png

In this step, you'll learn 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 you will complete these steps in the viewDidLoad lifecycle event, which is called after the view (and map) has been loaded.

  1. 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 can be accessed from the map view, using the camera.target.latitude and camera.target.longitude properties.

CLLocationCoordinate2D mapCenter = CLLocationCoordinate2DMake(_mapView.camera.target.latitude, _mapView.camera.target.longitude);
  1. 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.

GMSMarker *marker = [GMSMarker markerWithPosition:mapCenter];
  1. 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 very easy with the Maps SDK for iOS. You'll 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 imageNamed:@"custom_pin.png"];
  1. Render the marker to the map.

Your marker is created, but you'll notice it isn't on the map. 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!

a4ea8724f8c5ba20.png

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.m should now look like this:

- (void)viewDidLoad {
  [super viewDidLoad];

  CLLocationCoordinate2D mapCenter = CLLocationCoordinate2DMake(_mapView.camera.target.latitude, _mapView.camera.target.longitude);
  GMSMarker *marker = [GMSMarker markerWithPosition:mapCenter];
  marker.icon = [UIImage imageNamed:@"custom_pin.png"];
  marker.map = _mapView;
}

7. Enable Marker Clustering

When using a lot of markers or markers that are in close proximity to one another, you may encounter an issue where the markers overlap or appear too crowded together, which causes a bad user experience. For example, if two markers are very close together, you could end up with a situation like this:

6e39736160c6bce4.png

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, like this:

4abb38cd97cab3f1.png

The algorithm for marker clustering divides the visible area of the map into a grid, then clusters icons that are in the same cell. Luckily you don't have to worry about any of that, since the Google Maps Platform team has created a helpful, open-source utility library called the Google Maps SDK for iOS Utility Library. This library, among many other things, handles marker clustering for you automatically. You can read more about marker clustering in the Google Maps Platform documentation, or check out the source for the iOS Utility Library on GitHub.

  1. Add a lot more markers to the map.

To see marker clustering in action, you'll need to have a lot of markers on the map. To make this easy, a convenient marker generator is provided for you in the starter project in LocationGenerator.m.

To add as many markers to your map as you'd like, simply call generateMarkersNear:count: 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 just pass it the mapCenter variable that you created earlier. The markers are returned in an NSArray.

NSArray<GMSMarker *> *markerArray = [LocationGenerator generateMarkersNear:mapCenter count:100];
  1. Import the Google Maps SDK for iOS Utility Library.

To add the Maps iOS utility library as a dependency to your project, add the following to the list of dependencies at the top of ViewController.m:

@import GoogleMapsUtils;
  1. 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, but the Maps iOS utility library provides default implementations to make the process easy. Just add the following:

id<GMUClusterAlgorithm> algorithm = [[GMUNonHierarchicalDistanceBasedAlgorithm alloc] init];

id<GMUClusterIconGenerator> clusterIconGenerator = [[GMUDefaultClusterIconGenerator alloc] init];

id<GMUClusterRenderer> renderer = [[GMUDefaultClusterRenderer alloc] initWithMapView:_mapView clusterIconGenerator:clusterIconGenerator];
  1. 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:

@implementation ViewController {
  GMSMapView *_mapView;
  GMUClusterManager *_clusterManager;
}

Next, create the instance of GMUClusterManager in the viewDidLoad lifecycle event:

_clusterManager = [[GMUClusterManager alloc] initWithMap:_mapView algorithm:algorithm renderer:renderer];
  1. Add the markers and run the marker clusterer.

Now that your marker clusterer instance is configured, all you have to do is pass the cluster manager the array of markers to be clustered by calling addItems: then run the clusterer by calling cluster.

[_clusterManager addItems:markerArray];
[_clusterManager cluster];

Reload your app, and you should now see a ton of markers all nicely clustered. 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.

c49383b07752bfc4.png

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.m should now look like this:

- (void)viewDidLoad {
  [super viewDidLoad];

  CLLocationCoordinate2D mapCenter = CLLocationCoordinate2DMake(_mapView.camera.target.latitude,
                                                                _mapView.camera.target.longitude);
  GMSMarker *marker = [GMSMarker markerWithPosition:mapCenter];
  marker.icon = [UIImage imageNamed:@"custom_pin.png"];
  marker.map = _mapView;
  NSArray<GMSMarker *> *markerArray = [LocationGenerator generateMarkersNear:mapCenter count:100];

  id<GMUClusterAlgorithm> algorithm = [[GMUNonHierarchicalDistanceBasedAlgorithm alloc] init];
  id<GMUClusterIconGenerator> clusterIconGenerator = [[GMUDefaultClusterIconGenerator alloc] init];
  id<GMUClusterRenderer> renderer = [[GMUDefaultClusterRenderer alloc] initWithMapView:_mapView clusterIconGenerator:clusterIconGenerator];
  _clusterManager = [[GMUClusterManager alloc] initWithMap:_mapView algorithm:algorithm renderer:renderer];

  [_clusterManager addItems:markerArray];
  [_clusterManager cluster];
}

8. Add User Interaction

Now you 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 allow you to execute code when various user interactions occur. For example, the mapview delegate includes methods that allow you to trigger code execution for interactions like the user clicking on the map and markers, panning the view of the map, zooming in and out, and more.

In this step, you'll programmatically make the map pan to center on any marker that is tapped by the user.

  1. Implement the marker tap listener.

mapView:didTapMarker is called anytime the user taps one of the markers you created earlier, as well as anytime 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.m before the end statement.

You'll notice that the method is returning NO. Doing this tells the iOS SDK to continue executing the default GMSMarker functionality, such as showing an info window if one is configured, after executing your event handler code.

- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {

  return NO;
}
  1. Handle the tap event and animate the camera to recenter the map when a marker or marker cluster is tapped.

When called, mapView:didTapMarker passes the instance of GMSMarker that was tapped, allowing you to handle it in your code. You can use this instance to recenter the map by calling animateToLocation: on the map view from inside the event handler, and passing it the position of the marker instance, which is available in the position property.

[_mapView animateToLocation:marker.position];
  1. 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 allows users to view the clustered markers, since the cluster will expand at lower zoom levels.

As noted earlier, the marker cluster icon is actually just 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 then call animateToZoom: on the map view instance and set the zoom to the current zoom level plus one. The current zoom level is available on the map view instance in the camera.zoom property.

Also, notice how the code below returns YES. 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 ([marker.userData conformsToProtocol:@protocol(GMUCluster)]) {
    [_mapView animateToZoom:_mapView.camera.zoom +1];
    return YES;
}

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:didTapMarker method should look like this:

- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
  [_mapView animateToLocation:marker.position];

  if ([marker.userData conformsToProtocol:@protocol(GMUCluster)]) {
    [_mapView animateToZoom:_mapView.camera.zoom +1];
    return YES;
  }

  return NO;
}

9. Draw on the Map

So far, you've created a map of Sydney that shows markers at 100 random points, and handles user interaction. For the last step of this codelab, you'll 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 easily understand what other destinations are within an easy walking 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. Next, you'll render a circle to show a 800 meter (approximately half mile) radius around a marker when it is clicked.

  1. Add a _circ instance variable to the implementation of the ViewController.

This instance variable will be used to save the most recently drawn circle, so that it can be destroyed before another is drawn. Afterall, it wouldn't be very helpful to the user and would look pretty ugly if every tapped marker had a circle drawn around it!

To do this, update the implementation of ViewController like so:

@implementation ViewController {
  GMSMapView *_mapView;
  GMUClusterManager *_clusterManager;
  GMSCircle *_circ;
}
  1. Draw the circle when a marker is tapped.

At the bottom of the mapView:didTapMarker method, add the following code to create an instance of the iOS SDK's GMSCircle class to draw a new 800 meter radius circle by calling circleWithPosition:radius: and passing it the position of the tapped marker, just as you did above when you recentered the map.

_circ = [GMSCircle circleWithPosition:marker.position radius:800];
  1. Style the circle.

By default, GMSCircle draws a circle with a black stroke and transparent fill. That will work for showing the radius, but 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 following code will add a gray fill with 50% transparency:

_circ.fillColor = [UIColor colorWithRed: 0.67 green: 0.67 blue: 0.67 alpha: 0.5];
  1. Render the circle on the map.

Just as when you created markers earlier, you'll notice that just creating an instance of GMSCircle does not make it appear on the map. To do this, simply assign the map view instance to the map property of the circle.

_circ.map = _mapView;
  1. Remove any previously rendered circles.

As noted earlier, it wouldn't be a very good user experience to just keep adding circles to the map. To remove the circle rendered by a previous tap event, set the map property of _circ to nil at the top of mapView:didTapMarker.

_circ.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.

342520482a888519.png

To recap, in this step you used the GMSCircle class to render a circle whenever a marker is tapped.

You mapView:didTapMarker method should look like this:

- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
  _circ.map = nil;
  [_mapView animateToLocation:marker.position];

  if ([marker.userData conformsToProtocol:@protocol(GMUCluster)]) {
    [_mapView animateToZoom:_mapView.camera.zoom +1];
    return YES;
  }

  _circ = [GMSCircle circleWithPosition:marker.position radius:800];
  _circ.fillColor = [UIColor colorWithRed: 0.67 green: 0.67 blue: 0.67 alpha: 0.5];
  _circ.map = _mapView;
  return NO;
}

10. Congratulations

You've successfully built your first iOS app using Google Maps Platform, including loading the Maps SDK for iOS, loading a map, working with markers, controlling and drawing on the map, and adding user interaction.

To see the completed code, check out the finished project is the /solution directory.

What's next?

In this codelab, we've covered only the basics of what you can do with the Maps SDK for iOS. Next, try adding some of these features to the map:

  • Change the map type to display satellite, hybrid, and terrain maps.
  • Customize other user interactions like zoom and map controls.
  • Add info windows to display information when markers are clicked.
  • Check out the Places SDK for iOS to add rich Google Maps Platform place features and data to your app.

To continue learning more ways you can work with Google Maps Platform on the web, check out these links: