0

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

asked Nov 3, 2023 at 16:52

1 Answer 1

0

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.

answered Nov 3, 2023 at 20:47
Sign up to request clarification or add additional context in comments.

2 Comments

It was my understanding from reading the docs that when you register an annotation it does what you describe per view (developer.apple.com/documentation/mapkit/mkmapview/…)
viewFor annotation: overrides register.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.