Igor Kulman

Clustering annotations in MKMapView

· Igor Kulman

If you need to display many annotations in your MKMapView it is recommended to cluster them for better performance.

Map clustering

This means instead of showing all the visible annotations you group annotations that are close together into one single annotation cluster representing them instead.

This cluster annotation usually shows the number of annotations it represents. As you then zoom in to get finer detail the clusters break up and show the actual annotations.

Map clustering

Clustering is supported in MKMapView on iOS 11 and newer, no need to use any custom library. If you need to support older versions of iOS, there are libraries like Cluster that you can use.

Custom cluster view implementation

Let’s say you use a custom annotation view to show your annotations and you want to add support for clustering. You first create a custom MKAnnotationView in the same way

final class LocationDataMapClusterView: MKAnnotationView {

    // MARK: Initialization
    private let countLabel = UILabel()

    override var annotation: MKAnnotation? {
    	didSet {
			 guard let annotation = annotation as? MKClusterAnnotation else {
            	assertionFailure("Using LocationDataMapClusterView with wrong annotation type")
            	return
        	}

    		countLabel.text = annotation.memberAnnotations.count < 100 ? "\(annotation.memberAnnotations.count)" : "99+"
    	}
    }

    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

		displayPriority = .defaultHigh
        collisionMode = .circle

        frame = CGRect(x: 0, y: 0, width: 40, height: 50)
        centerOffset = CGPoint(x: 0, y: -frame.size.height / 2)

       
        setupUI()
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: Setup
    private func setupUI() {
        ...
    }
}

The idea is the same as when custom annotation view, but there is a difference. You need to define displayPriority to tell the map that your cluster annotation has a higher priority that normal annotation.

Registering the custom cluster view with MKMapView

The next step is to tell MKMapView to user your custom class.

mapView.register(LocationDataMapClusterView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)

You can use MKMapViewDefaultClusterAnnotationViewReuseIdentifier as the reuse identifier if you do not plan to use more different custom cluster views.

Defining a clustering identifier

The last step you need to do is to define a clustering identifier for your custom annotation views. You can do it in the mapView(_:viewFor:) method of the MKMapViewDelegate

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    switch annotation {    
    case is LocationViewModel:
        let view = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier, for: annotation)
        view.clusteringIdentifier = String(describing: LocationDataMapAnnotationView.self)
        return view
    case is MKClusterAnnotation:
        return mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier, for: annotation)
    default:        
        return nil
    }
}

Or in the init method of your custom annotation view if you do not need to implement mapView(_:viewFor:) in your case.

Using MKMapView and MapKit on iOS