Google Maps SDK を使用していてマップ上にマーカーを立てることはよくあると思います
ただ、マップ上にマーカーをたてすぎると、広域表示にした際にマーカーが密集しちゃって見辛くなってしまうってこともよくあると思います
そんな時マーカーを1つにまとめたいなーと思ったので、その実装方法を残しておきたいと追います
開発環境
Xcode:11.1(11A1027)
Swift 5
iOS:13.1
CocoaPods: 1.8.0
実装
ライブラリのインストール
Google Maps iOS Utils をインストールするために CocoaPods を使用します
以下のように Podfile を編集し、 pod install
を実行してください
target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
use_frameworks!
pod 'GoogleMaps'
pod 'Google-Maps-iOS-Utils'
end
こちらのセットアップガイドにも記載されていますが、
CocoaPods のバージョンが 1.8.1 以上はサポート対象外のようです
1.6.1 が推奨されているようですが、Xcode11 では 1.6.1 を使用した場合にビルドが通らなかったため、今回は 1.8.0 を使用しています
CocoaPods のバージョンを下げるにはこちらを参考にしてください
マーカーをまとめて表示する用の class を作成
普段は GMSMarker
class を使用してマーカーを表示していますが、
マーカーをまとめて表示するには GMUClusterItem
protocol に準拠した class を作成し、その class を使用してマーカーを表示する必要があります
import GoogleMapsUtils
class POIItem: NSObject, GMUClusterItem {
var position: CLLocationCoordinate2D
init(position: CLLocationCoordinate2D) {
self.position = position
}
}
Map 上にマーカーを配置
先ほど作成した独自 class POIItem
を使用してマーカーを Google Map 上に表示していきます
import UIKit
import GoogleMaps
import GoogleMapsUtils
class ViewController: UIViewController {
// デフォルトの位置情報(仮で東京駅付近にしています)
let defaultPositionLat = 35.681223
let defaultPositionLng = 139.767059
private var mapView: GMSMapView!
/// Map 上に表示するマーカーを管理するためのプロパティ
private var clusterManager: GMUClusterManager!
override func viewDidLoad() {
super.viewDidLoad()
// GoogleMapの初期位置
let camera = GMSCameraPosition.camera(withLatitude: defaultPositionLat, longitude: defaultPositionLng, zoom: 17.0)
mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
view = mapView
let iconGenerator = GMUDefaultClusterIconGenerator()
let algorithm = GMUNonHierarchicalDistanceBasedAlgorithm()
let renderer = GMUDefaultClusterRenderer(mapView: mapView, clusterIconGenerator: iconGenerator)
clusterManager = GMUClusterManager(map: mapView, algorithm: algorithm, renderer: renderer)
// マーカーをランダムに生成して Map 上に表示
generateClusterItems()
}
private func generateClusterItems() {
let extent = 0.01
for _ in 1...100 {
let lat = defaultPositionLat + extent * randomScale()
let lng = defaultPositionLng + extent * randomScale()
let item = POIItem(position: CLLocationCoordinate2DMake(lat, lng))
clusterManager.add(item)
}
// Map にマーカーを描画
clusterManager.cluster()
}
/// ランダムな位置にマーカーを表示するための乱数を生成
private func randomScale() -> Double {
return Double(arc4random()) / Double(UINT32_MAX) * 2.0 - 1.0
}
}
上記のコードを実行すると、最初に載せた gif のような挙動になるかと思います
Delegate とか
マーカーをタップした際のアクションを設定したい場合、以下のように GMUClusterManagerDelegate
と GMSMapViewDelegate
を設定してあげるとタップ時のアクションが設定可能なようです
class ViewController: UIViewController, GMUClusterManagerDelegate, GMSMapViewDelegate {
private var mapView: GMSMapView!
private var clusterManager: GMUClusterManager!
override func viewDidLoad() {
super.viewDidLoad()
// ... Rest of code omitted for easy reading.
// Register self to listen to both GMUClusterManagerDelegate and
// GMSMapViewDelegate events.
clusterManager.setDelegate(self, mapDelegate: self)
}
// MARK: - GMUClusterManagerDelegate
func clusterManager(clusterManager: GMUClusterManager, didTapCluster cluster: GMUCluster) {
let newCamera = GMSCameraPosition.cameraWithTarget(cluster.position,
zoom: mapView.camera.zoom + 1)
let update = GMSCameraUpdate.setCamera(newCamera)
mapView.moveCamera(update)
}
// MARK: - GMUMapViewDelegate
func mapView(mapView: GMSMapView, didTapMarker marker: GMSMarker) -> Bool {
if let poiItem = marker.userData as? POIItem {
NSLog("Did tap marker for cluster item \(poiItem.name)")
} else {
NSLog("Did tap a normal marker")
}
return false
}
}
終わりに
Google Maps iOS Utils というライブラリはマーカーをまとめるだけでなく、
マーカーの画像をカスタマイズしたり KML や GeoJSON をレンダリングできたりと他にも色々な機能が使用できるようなので、機会があれば触ってみようかと思います