3

This is my problem:

enter image description here

When zooming out and property showsUserLocation is set to true and the annotation are centered around the users location, clusters will disappear when zooming out. I don't want this, they should always be visible. The displayPriority's default value is set to required so I am not sure how to fix this. Every annotation and cluster should be always visible. It works when I set the property showsUserLocation to false, or make sure the annotations/clusters are not near the current location.

There are 2 ways to reproduce my problem:

  1. Clone and run https://github.com/Jasperav/AnnotationsGlitch
  2. Copy paste and run the code below

Code

import UIKit
import MapKit

class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
    let mapView = MKMapView(frame: .zero)

    private let locationManager = CLLocationManager()

    override func viewDidLoad() {
        view.addSubview(mapView)

        mapView.translatesAutoresizingMaskIntoConstraints = false
        mapView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        mapView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
        mapView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        mapView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        mapView.delegate = self

        mapView.showsUserLocation = true
    }

    override func viewDidAppear(_ animated: Bool) {
        let authorizationStatus = CLLocationManager.authorizationStatus()

        switch authorizationStatus {
        case .authorizedWhenInUse, .authorizedAlways:
            break
        case .notDetermined, .restricted, .denied:
            locationManager.requestWhenInUseAuthorization()
        @unknown default:
            fatalError()
        }

        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
            for _ in 0...20 {
                let currentLocation = self.mapView.userLocation.coordinate
                let newLocation = currentLocation.shiftRandomPosition(meters: 30000, randomMeters: true)

                self.mapView.addAnnotation(Annotation(coordinate: newLocation))
            }

            self.mapView.setRegion(MKCoordinateRegion(center: self.mapView.userLocation.coordinate, span: MKCoordinateSpan(latitudeDelta: 0, longitudeDelta: 0)), animated: false)
        }
    }

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

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

    }

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        guard let annotation = annotation as? Annotation else { return nil }

        let identifier = "marker"
        let view: MKMarkerAnnotationView

        if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView {
            dequeuedView.annotation = annotation
            view = dequeuedView
        } else {
            view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
        }

        view.clusteringIdentifier = "identifier"


        return view
    }
}

class Annotation: NSObject, MKAnnotation {
    let coordinate: CLLocationCoordinate2D

    init(coordinate: CLLocationCoordinate2D) {
        self.coordinate = coordinate

        super.init()
    }
}

// https://stackoverflow.com/a/55459722/7715250
extension CLLocationCoordinate2D {
    /// Get coordinate moved from current to `distanceMeters` meters with azimuth `azimuth` [0, Double.pi)
    ///
    /// - Parameters:
    ///   - distanceMeters: the distance in meters
    ///   - azimuth: the azimuth (bearing)
    /// - Returns: new coordinate
    private func shift(byDistance distanceMeters: Double, azimuth: Double) -> CLLocationCoordinate2D {
        let bearing = azimuth
        let origin = self
        let distRadians = distanceMeters / (6372797.6) // earth radius in meters

        let lat1 = origin.latitude * Double.pi / 180
        let lon1 = origin.longitude * Double.pi / 180

        let lat2 = asin(sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(bearing))
        let lon2 = lon1 + atan2(sin(bearing) * sin(distRadians) * cos(lat1), cos(distRadians) - sin(lat1) * sin(lat2))
        return CLLocationCoordinate2D(latitude: lat2 * 180 / Double.pi, longitude: lon2 * 180 / Double.pi)
    }

    func shiftRandomPosition(meters: Double, randomMeters: Bool) -> CLLocationCoordinate2D {
        let finalMeters: Double

        if randomMeters {
            finalMeters = Double.random(in: -meters..<meters)
        } else {
            finalMeters = meters
        }

        let randomAzimuth = Double.random(in: -Double.pi..<Double.pi)

        return shift(byDistance: finalMeters, azimuth: randomAzimuth)
    }
}
J. Doe
  • 12,159
  • 9
  • 60
  • 114

2 Answers2

5

YMMV, but I was able to fix this problem by explicitly setting the displayPriority to .required. The docs say that this is the default value, but I noticed while debugging, that the Annotations actually had a lower priority, which would cause them to be occluded by the user location annotation.

Update: This works for a little bit, but then it stops working again, whenever views get clustered and/or reused through dequeuing. I tried setting the displayPriority in the AnnotationView's init(), prepareForDisplay(), etc. Problem still appears. What did work for me in all cases is, to reset the displayPriority every single time it gets created or dequeued. If you add view.displayPriority = .required to the code just above or below where you set the clusteringIdentifier, before you return the view, it should work. Worked for me! You may also have to aggressively/explicitly reset it for the MKClusterAnnotationViews as well.

rdsquared
  • 51
  • 1
  • 3
1

The current user's annotation doesn't have a cluster identifier. If you provide a cluster identifier for the current user's annotation, it works.

J. Doe
  • 12,159
  • 9
  • 60
  • 114