Wednesday, July 27, 2016

Marker Clustering with Google's Utility library for Maps SDK (Google-Maps-iOS-Utils)

This tutorial shows how to group multiple map markers with Marker Clustering utility library provided by Google-Maps-iOS-Utils(V1.0.1), which requires Google Maps SDK for iOS (V2.0). This tutorial is created in Swift 2.2 with Xcode 7.3.1. The following screenshots show a lot of map markers and the clustering results using GMUClusterManager with default circles or custom images.

Follow these steps:

1. Install CocoaPods as the dependency manager.

Type this command in the terminal:

sudo gem install cocoapods

2. Create a Single View Application project with Xcode and close it.

3. Go to the Xcode project directory in the terminal, type pod init or nano Podfile to create a Podfile and save it as:

platform :ios, '9.0'
target "ProjectName" do
    pod 'GoogleMaps'
    pod 'Google-Maps-iOS-Utils'

4.  Type this terminal command:

pod install

You should see something like this in the terminal:

If your GoogleMaps is an old version, update it with this terminal command:

pod update

5. Open the projectName.xcworkspace file just automatically created. (Don't open the original .xcodeproj file)

6. Add a temporary Objective-C file to your project. You may give it any name you like, e.g. Temp.m.

Select Create Bridging Header.

7. Delete the temporary Objective-C file (Temp.m) you just created.

8. In the projectName-Bridging-Header.h file just created, add this line:

#import <Google-Maps-iOS-Utils/GMUMarkerClustering.h>

9. Get the iOS API key like AIza................... from Google Developers Console. (For more details, see Step 6 of the Using Google Maps SDK for iOS in Swift tutorial.

10. Edit the AppDelegate.swift file:
    func application(application: UIApplicationdidFinishLaunchingWithOptions launchOptions: [NSObjectAnyObject]?) -> Bool {
        GMSServices.provideAPIKey("AIza....") //iOS API key
        return true


10. Modify ViewController.swift as below:

import UIKit

class ViewController: UIViewController, GMSMapViewDelegate, GMUClusterManagerDelegate {
    private var mapView : GMSMapView!
    private var clusterManager: GMUClusterManager!
    //true - marker clustering / false - map markers without clustering
    let isClustering : Bool = true
    //true - images / false - default icons
    let isCustom : Bool = false
    override func viewDidLoad() {
        mapView = GMSMapView(frame: view.frame)
        //Default position at Chungli (Zhongli) Railway Station, Taoyuan, Taiwan. = GMSCameraPosition.cameraWithLatitude(24.953232, longitude: 121.225353, zoom: 12.0)
        mapView.mapType = kGMSTypeNormal
        mapView.delegate = self
        if isClustering {
            var iconGenerator : GMUDefaultClusterIconGenerator!
            if isCustom {
                var images : [UIImage] = []
                for imageID in 1...5 {
                    images.append(UIImage(named: "m\(imageID).png")!)
                iconGenerator = GMUDefaultClusterIconGenerator(buckets: [ 10, 50, 100, 200, 500 ], backgroundImages: images)
            } else {
                iconGenerator = GMUDefaultClusterIconGenerator()

            let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
            let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
            clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)
            // Call cluster() after items have been added to perform the clustering and rendering on map.
            // Register self to listen to both GMUClusterManagerDelegate and GMSMapViewDelegate events.
            clusterManager.setDelegate(self, mapDelegate: self)
        } else {
    /// Point of Interest Item which implements the GMUClusterItem protocol.
    class POIItem: NSObject, GMUClusterItem {
        var position: CLLocationCoordinate2D
        var name: String!
        init(position: CLLocationCoordinate2D, name: String) {
            self.position = position
   = name

    func generateCoord(isCluster: Bool) {
        let latitudeMin   : Double = 24.79
        let latitudeMax   : Double = 25.10
        let latitudeDiff  : Double = latitudeMax - latitudeMin
        let longitudeMin  : Double = 120.99
        let longitudeMax  : Double = 121.50
        let longitudeDiff : Double = longitudeMax - longitudeMin
        for count in 1...5000 {
            let latitude  = latitudeMin  + Double(arc4random()%10000)/10000*latitudeDiff
            let longitude = longitudeMin + Double(arc4random()%10000)/10000*longitudeDiff
            let position = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
            if isCluster {
                let item = POIItem(position: position, name: "#\(count)")
            } else {
                let marker = GMSMarker(position: position)
                marker.title = "#\(count)"
       = mapView

    func clusterManager(clusterManager: GMUClusterManager, didTapCluster cluster: GMUCluster) {
        let newCamera = GMSCameraPosition.cameraWithTarget(cluster.position,
                                                           zoom: + 1)
        let update = GMSCameraUpdate.setCamera(newCamera)
    //Show the marker title while tapping
    func mapView(mapView: GMSMapView, didTapMarker marker: GMSMarker) -> Bool {
        let item : POIItem = marker.userData as! POIItem

        marker.title =
        mapView.selectedMarker = marker
        return true
    //Optional Feature:
    //Add new markers while tapping at coordinates without markers/clusters
    func mapView(mapView: GMSMapView, didTapAtCoordinate coordinate: CLLocationCoordinate2D) {
        let item = POIItem(position: coordinate, name: "NEW")
    override func didReceiveMemoryWarning() {


The above code is modified from Google's ViewController.swift of the SwiftDemoApp.

11. Add the file image files below to the Xcode project:

12. Edit the Info.plist file (Required for Xcode 7 and iOS 9):

Key: LSApplicationQueriesSchemes
Type: Array

Key: Item 0
Type: String
Value: googlechromes

Key: Item 1
Type: String
Value: comgooglemaps

Without modifying Info.plist, you'll get

Pressing the Google logo on the map in the iOS simulator shows:

-canOpenURL: failed for URL: "comgooglemaps://" - error: "This app is not allowed to query for scheme comgooglemaps"

-canOpenURL: failed for URL: "googlechromes://" - error: "This app is not allowed to query for scheme

This is because the iOS simulator does not include Google Maps and Chrome apps. So check this feature with a device.

13. Run the code. You should see result like this:

14. Set isCustom as true to see the custom clustering images:

    //true - images / false - default icons
    let isCustom : Bool = true

15. Try tap at different locations on the map to add new markers:

Related Information:

Marker Clustering
Google Maps SDK for iOS
CocoaPods Tutorial - Google Maps SDK for iOS