Custom MKClusterAnnotation failing on didSet

43 Views Asked by At

Have a custom MKClusterAnnotation named 'ClusterAnnotationView.' It is failing at 'assertionFailure' message when I'm not expecting it.

Here is the code:

//MARK: CLUSTER ANNOTATION
final class ClusterAnnotationView: MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
    super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
    collisionMode = .circle
    displayPriority = .defaultHigh
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

override var annotation: MKAnnotation? {
    didSet { 
        guard let annotation = annotation as? MKClusterAnnotation else {
            print(annotation)
            assertionFailure("Using ClusterAnnotationView with wrong annotation type")
            return
        }
        self.image = image(count: annotation.memberAnnotations.count)
    }
}

private func image(count: Int) -> UIImage {
    let bounds = CGRect(origin: .zero, size: CGSize(width: 40, height: 40))

    let renderer = UIGraphicsImageRenderer(bounds: bounds)
    return renderer.image { _ in
        //background
        Definitions.Colors.Water.setFill()
        UIBezierPath(ovalIn: bounds).fill()

        // Fill inner circle with white color inset so background shows as border
        UIColor.white.setFill()
        UIBezierPath(ovalIn: bounds.insetBy(dx: 4, dy: 4)).fill()

        // Finally draw count text vertically and horizontally centered
        let attributes: [NSAttributedString.Key: Any] = [
            .foregroundColor: UIColor.black,
            .font: UIFont.boldSystemFont(ofSize: 14)
        ]

        let text = "\(count)"
        let size = text.size(withAttributes: attributes)
        let origin = CGPoint(x: bounds.midX - size.width / 2, y: bounds.midY - size.height / 2)
        let rect = CGRect(origin: origin, size: size)
        text.draw(in: rect, withAttributes: attributes)
    }
}
}

Now if I comment out the 'assertionFailure("Using ClusterAnnotationView with wrong annotation type")' it'll shown the clustered annotations with the correct number. The print of annotations all print nil.

Reviewed the docs on Apple on Clustering and quite a few others. Many examples show it like I have it.

It's registered

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

and the delegate for it is like so

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    switch annotation {
    case is MapAnnotation:
        let eventAnnotation = MapAnnotationView(annotation: annotation, reuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
        eventAnnotation.delegate = self
        return eventAnnotation
    case is MKClusterAnnotation:
        //return ClusterAnnotationView(annotation: annotation, reuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
        
        return mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier, for: annotation)
    default:
        return nil
    }
}

I've tried the return in the delegate with both the class and just the dequeue with same results.

Something odd is happening because the clustered annotations causes the maps functionality to be really laggy and it's possibly related to the problem I'm seeing.

Anyone have any insights?

Thanks

1

There are 1 best solutions below

2
On

There is at least one mistake in func mapView:

You need a different reuseIdentifier for each different MKAnnotationView subclass for which you are calling mapView.dequeueReusableAnnotationView.

What happens here is random:

if return mapView.dequeueReusableAnnotationView is called first, it returns nil.

nil means mapView.register... kicks in and creates a ClusterAnnotationView instance and everything works fine. That's when you see numbers.

It gets interesting once you created several MapAnnotationView and the first is thrown away when it isn't displayed any more:

Since you use the same reuseIdentifier,

return mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier, for: annotation)

will return an object of type MapAnnotationView.

Also, after using dequeue... you should set the annotation of the dequeued MKMarkerAnnotaionView subclass object.

This is the pattern you use for each different annotation type:

if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: ClusterAnnotationView.REUSEIDENTIFIER) as? TimeAnnotationView {
    dequeuedView.annotation = clusterAnnotation
    return dequeuedView
} else {
    let clusterAnnotationView = ClusterAnnotationView(annotation: timeAnnotation, reuseIdentifier: ClusterAnnotationView.REUSEIDENTIFIER)
            returnclusterAnnotationView
}

You might do a similar pattern for MapAnnotationView.

It is always the same: you try to dequeue. If it fails, you create a new one. For each different MKMarkerAnnotaionView subclass, you use a different reuseIdentifier.