Question: How do I prevent an MKClusterAnnotation
from displaying a callout?
Background: I'm a beginner at programming, please be gentle. I'm trying to make a MapKit-based app which shows a number of sites on a map. I have populated the map with dummy locations from a GeoJSON, which works fine. However, while callouts make sense from individual annotations, I would rather not have them appear from a cluster annotation. I haven't been able to figure ot how to remove callouts from clusters.
My annotations are created like so:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
} else {
let identifier = "site"
var marker: MKMarkerAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(
withIdentifier: identifier) as? MKMarkerAnnotationView {
dequeuedView.annotation = annotation
marker = dequeuedView
} else {
marker = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
}
marker.canShowCallout = true // How can I turn this false if callout is a cluster?
marker.markerTintColor = .systemBlue
marker.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
marker.glyphImage = UIImage(systemName: "star")
marker.clusteringIdentifier = "clusteringIdentifier"
return marker
}
}
And here is my MKClusterAnnotation:
func mapView(_ mapView: MKMapView, clusterAnnotationForMemberAnnotations memberAnnotations: [MKAnnotation]) -> MKClusterAnnotation {
let cluster = MKClusterAnnotation(memberAnnotations: memberAnnotations)
cluster.title = "More things to see here"
cluster.subtitle = "Zoom further in"
return cluster
}
This is probably incredibly simple, but I wasn't able to find it out on my own (or by looking at Apple docs). Any help is appreciated! (Also if you see anything silly in my code, don't be afraid to point it out).
First, we do not need to write viewFor
or method anymore (except where you need custom logic for more than just a single annotation and cluster annotation). Nowadays, we let the OS do that for us. We just move the configuration of these into their own objects, and register them with the mapview.
So, in viewDidLoad
:
mapView.register(MyAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
mapView.register(MyClusterAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
And define the former to use callout:
// MyAnnotationView.swift
import MapKit
class MyAnnotationView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? { didSet { update(for: annotation) } }
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
configure(for: annotation)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
}
private extension MyAnnotationView {
func configure(for annotation: MKAnnotation? = nil) {
canShowCallout = true
markerTintColor = .systemBlue
rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
glyphImage = UIImage(systemName: "star")
update(for: annotation)
}
func update(for annotation: MKAnnotation?) {
clusteringIdentifier = "clusteringIdentifier"
displayPriority = .required
}
}
And the latter to not use callout:
// MyClusterAnnotationView.swift
import MapKit
class MyClusterAnnotationView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? { didSet { update(for: annotation) } }
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
update(for: annotation)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
update()
}
}
private extension MyClusterAnnotationView {
func update(for annotation: MKAnnotation? = nil) {
markerTintColor = .systemGray
}
}
The class names of MyAnnotationView
and MyClusterAnnotationView
should probably be replaced with something more meaningful, but the appropriate name probably depends upon the functional purpose of the annotation views.