DEV Community

Khoa Pham
Khoa Pham

Posted on

How to do clustering with Google Maps in iOS

Basic with Google Maps

Add to Podfile pod 'GoogleMaps'

Add a custom marker

import GoogleMaps

final class StopMarker: GMSMarker {
    let stop: Stop

    init(stop: Stop) {
        self.stop = stop
        super.init()
        self.title = stop.name
        self.position = stop.toCoordinate()

        let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        imageView.layer.cornerRadius = 15
        imageView.image = UIImage(named: "pin")
        self.iconView = imageView
    }
}

Show markers

func handle(stops: [Stop]) {
    self.stops = stops
    stops
        .map({ StopMarker(stop: $0) })
        .forEach {
            $0.map = mapView
        }

}

Assigning $0.map = mapView means telling GMSMapView to start rendering markers

Handle tap

extension ViewController: GMSMapViewDelegate {
    func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
        zoomIn(coordinate: marker.position)
        return false
    }

    func mapView(_ mapView: GMSMapView, didTapInfoWindowOf marker: GMSMarker) {
        guard let stopMarker = marker as? StopMarker else {
            return
        }

        let detailViewController = StopDetailViewController(stop: stopMarker.stop)
        presentPanModal(detailViewController)
    }
}

Clustering

Add google-maps-ios-utils manually by following https://github.com/googlemaps/google-maps-ios-utils/blob/master/Swift.md

Otherwise, with CocoaPods, we get error

[!] The 'Pods-MyApp' target has transitive dependencies that include static binaries: (/Users/khoa/Projects/MyApp/Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework, /Users/khoa/Projects/MyApp/Pods/GoogleMaps/Maps/Frameworks/GoogleMaps.framework, and /Users/khoa/Projects/MyApp/Pods/GoogleMaps/Maps/Frameworks/GoogleMapsCore.framework)

Add ClusterItem

import Foundation
import CoreLocation

class ClusterItem: NSObject, GMUClusterItem {
    let position: CLLocationCoordinate2D
    let stop: Stop

    init(stop: Stop) {
        self.stop = stop
        self.position = stop.toCoordinate()
    }
}

Set up cluster manager

let iconGenerator = GMUDefaultClusterIconGenerator()
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
renderer.delegate = self
clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)
clusterManager.setDelegate(self, mapDelegate: self)

Default algorithm GMUNonHierarchicalDistanceBasedAlgorithm

 A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not
 * hierarchical.
 * High level algorithm:
 * 1. Iterate over items in the order they were added (candidate clusters).
 * 2. Create a cluster with the center of the item.
 * 3. Add all items that are within a certain distance to the cluster.
 * 4. Move any items out of an existing cluster if they are closer to another cluster.
 * 5. Remove those items from the list of candidate clusters.
 * Clusters have the center of the first element (not the centroid of the items within it).

Make ClusterItem and add to clusterManager. In the end, call clusterManager.cluster()

func handle(stops: [Stop]) {
    self.stops = stops
    stops
        .map({ StopMarker(stop: $0) })
        .forEach {
            let item = ClusterItem(stop: $0.stop)
            clusterManager.add(item)
        }

    clusterManager.cluster()
}

By default, clusterManager uses renderer to render default pin marker. Implement GMUClusterRendererDelegate to use our custom StopMarker

extension ViewController: GMUClusterRendererDelegate {
    func renderer(_ renderer: GMUClusterRenderer, markerFor object: Any) -> GMSMarker? {
        switch object {
        case let clusterItem as ClusterItem:
            return StopMarker(stop: clusterItem.stop)
        default:
            return nil
        }
    }
}

Finally handle tapping on cluster and cluster item

extension ViewController: GMUClusterManagerDelegate {
    func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool {
        print("tap cluster")
        return false
    }

    func clusterManager(_ clusterManager: GMUClusterManager, didTap clusterItem: GMUClusterItem) -> Bool {
        print("tap cluster item")
        return false
    }
}

Original post https://github.com/onmyway133/blog/issues/191

Discussion (0)