0

I have an app with a map section displaying devices. In some cases devices may be either really close to each other or even have exactly the same coordinates. In order to display it I use default clustering mechanism in MapKit.

func mapView(_ mapView: MKMapView, clusterAnnotationForMemberAnnotations memberAnnotations: [MKAnnotation]) -> MKClusterAnnotation {
    return MKClusterAnnotation(memberAnnotations: memberAnnotations)
}

When user taps on cluster annotation, the app supposed to zoom in maximally and then in case of cluster still existing on map (member annotations are too close to be presented separately) open custom view describing cluster objects.

To achieve that, I have few custom functions, their implementation contains only standard calls setRegion or setVisibleMapRect.

setRegionCovering(_ annotations: [MKAnnotation], on mapView: MKMapView)
getZoom(delta: CGFloat)
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
    if let annot = view.annotation as? MKClusterAnnotation {
        let clusteredAnnotations = annot.memberAnnotations as? [CustomAnnotation] ?? []
        let currentZoom = mapView.getZoom(delta: CGFloat(mapView.region.span.longitudeDelta))
        guard currentZoom == self.maxZoom else {
             self.setRegionCovering(clusteredAnnotations, on: mapView)
             return
        }
        self.delegate?.drawCluster(annotations: clusteredAnnotations)
    }
}

In order to invoke cluster after zooming in I use regionDidChangeAnimated method:

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if let activeCluster = mapView.selectedAnnotations.first(where: { $0.isKind(of: MKClusterAnnotation.self)}) as? MKClusterAnnotation {
        self.delegate?.drawCluster(annotations: activeCluster.memberAnnotations as? [CustomAnnotation] ?? [])
    }
}

Everything so far works as expected, except one thing - regionDidChangeAnimated is called after camera focus (region) is finalized. However, I found out that in some cases clustering continues after regionDidChangeAnimated was called. Because of that, in some rare cases after tapping Cluster annotation user will see a short 'blink' of cluster contents and then it will disappear as well as an old cluster, replaced by smaller clusters or member annotations.


Earlier I've tried to do the same by embedding map zooming actions inside of animate function in order to handle cluster view opening in completion.

MKMapView.animate(withDuration: 1.0, animations: {}, completion: { _ in })

But found out that completion fires before regionDidChangeAnimated and is even more inaccurate. Afterwards I have also found out Apple is strongly encouraging against completions when working with map.


Any ideas what I'm doing wrong or how can I solve this?

AlexR
  • 33
  • 6
  • After some research I've found out that currently there are no native methods to ensure that clustering has finished. I've solved my problem by debouncing cluster view drawing call between didSelect, didDeselect and regionDidChangeAnimated delegate methods. – AlexR Jul 08 '21 at 11:31

1 Answers1

0

You can try implementing mapViewDidFinishRenderingMap(_:fullyRendered:) and see if it provides better results than mapView(_:regionDidChangeAnimated:) implementation.

From the docs -

This method lets you know when the map view finishes rendering all of the currently visible tiles to the best of its ability. This method is called regardless of whether all tiles were rendered successfully. If there were errors loading one or more tiles that prevented map view from rendering them, the fullyRendered parameter is set to false.

This does not provide a solid answer for your problem. It may be an improvement over your current situation.

Tarun Tyagi
  • 9,364
  • 2
  • 17
  • 30
  • Thanks for a quick response! But it doesn't seem like a solution in my case. This method may fire multiple times during zooming action, the only possible way for implementing this would be putting some kind of debouncer to ensure that latest fired, but I would like to avoid that as for me it looks like incorrect method usage. Correct me, if I'm wrong. And thanks again for your help! – AlexR Jul 02 '21 at 14:22
  • You are right that it will fire multiple times. This can be handled with a single `Bool` variable. Say `isWaitingForDeclusteringToComplete`. By default, it is `false`. When user taps on an annotation, you can set this to `true`. Now your code inside this new method will react only when this flag is `true`, as soon as you have reacted once to this, you reset it to `false`. After a zoom starts, MapKit will call this when zoom rendering has finished. – Tarun Tyagi Jul 02 '21 at 14:30
  • I was able to reproduce the same error with mapViewDidFinishRenderingMap. It was called last time before the cluster annotation was splitted into smaller ones. So it doesn't solve my problem in the end. – AlexR Jul 02 '21 at 15:33