swiftmapkitmkannotationmkclusterannotation

How to stop an MKClusterAnnotation from displaying a callout


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).


Solution

  • 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.