Apple provides a native and excellent clustering of MKMapKit. If you work with many annotations, Apple's solution is better for up to tens of thousands. But if you have even more, it may have performance problems. This open-source library aggregates annotation in a background thread using an efficient method (QuadTree). Use demo project to compare performance and choose better solution for your task.
Comparison with 20,000 annotations. For a detailed comprasion use Example.
- Adding/Removing Annotations
- Clustering Annotations
- Multiple Managers
- Dynamic Cluster Disabling
- Custom Cell Size
- Custom Annotation Views
- Animation Support
The Example is a great place to get started. It demonstrates how to:
- Integrate the library
- Add/remove annotations
- Reload annotations
- Configure the annotation view
- Configure the manager
- Compare Apple's native implementation with to library
ClusterMap is available via Swift Package Manager
Add the following dependency to your Package.swift file:
.package(url: "https://github.com/vospennikov/ClusterMap.git", from: "1.0.0")
ClusterMap is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'ClusterMap', '1.0.0'
The ClusterManager
class generates, manages and displays annotation clusters.
let clusterManager = ClusterManager()
Create an object that conforms to the MKAnnotation
protocol. Next, add the annotation object to an instance of ClusterManager
with add(annotation:)
.
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661)
manager.add(annotation)
Implement the map view’s mapView(_:viewFor:)
delegate method to configure the annotation view. Return an instance of MKAnnotationView
to display as a visual representation of the annotations.
To display clusters, return an instance of ClusterAnnotationView
.
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? ClusterAnnotation {
return CountClusterAnnotationView(annotation: annotation, reuseIdentifier: "cluster")
} else {
return MKPinAnnotationView(annotation: annotation, reuseIdentifier: "pin")
}
}
}
For performance reasons, you should generally reuse MKAnnotationView
objects in your map views. See the Example to learn more.
The ClusterAnnotationView
class exposes a countLabel
property. You can subclass ClusterAnnotationView
to provide custom behavior as needed. Here's an example of subclassing the ClusterAnnotationView
and customizing the layer borderColor
.
class CountClusterAnnotationView: ClusterAnnotationView {
override func configure(_ annotation: ClusterAnnotation) {
super.configure(annotation)
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 1.5
}
}
See the AnnotationView to learn more.
To remove annotations, you can call remove(annotation:)
. However the annotations will still display until you call reload()
.
manager.remove(annotation)
In the case that shouldRemoveInvisibleAnnotations
is set to false
, annotations that have been removed may still appear on map until calling reload()
on visible region.
Implement the map view’s mapView(_:regionDidChangeAnimated:)
delegate method to reload the ClusterManager
when the region changes.
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
clusterManager.reload(mapView: mapView) { finished in
// handle completion
}
}
You should call reload()
anytime you add or remove annotations.
The ClusterManager
class exposes several properties to configure clustering:
var zoomLevel: Double // The current zoom level of the visible map region.
var maxZoomLevel: Double // The maximum zoom level before disabling clustering.
var minCountForClustering: Int // The minimum number of annotations for a cluster. The default is `2`.
var shouldRemoveInvisibleAnnotations: Bool // Whether to remove invisible annotations. The default is `true`.
var shouldDistributeAnnotationsOnSameCoordinate: Bool // Whether to arrange annotations in a circle if they have the same coordinate. The default is `true`.
var distanceFromContestedLocation: Double // The distance in meters from contested location when the annotations have the same coordinate. The default is `3`.
var clusterPosition: ClusterPosition // The position of the cluster annotation. The default is `.nearCenter`.
The ClusterManagerDelegate
protocol provides a number of functions to manage clustering and configure cells.
// The size of each cell on the grid at a given zoom level.
func cellSize(for zoomLevel: Double) -> Double? { ... }
// Whether to cluster the given annotation.
func shouldClusterAnnotation(_ annotation: MKAnnotation) -> Bool { ... }
The documentation for releases and main
are available here:
This project is based on the work of Lasha Efremidze, who created the Cluster.
ClusterMap is available under the MIT license. See the LICENSE file for more info.