Reputation: 2596
I am doing clustering of Annotations.
The below code works fine and clusters the points correctly in iOS 11 & iOS 12.
This fails to cluster the points decluster the points in iOS 13.
I am not using any beta versions.
The TTMapView class is wrapper for MKMapView.
class TTMapView: UIView {
var mapView = MKMapView()
private var mapObjects: Dictionary<TTShape, MKShape?> = [:]
private var _isClusteringEnabled = true
func addMarker(_ marker: TTPoint) -> TTPoint {
removeMarker(marker)
let coordinate = marker.coordinate
let pointAnnotation = MKPointAnnotation()
pointAnnotation.coordinate = convertTTCoordinateToCLLocationCoordinate2D(coordinate)
pointAnnotation.title = marker.title
pointAnnotation.subtitle = marker.subtitle
mapObjects.updateValue(pointAnnotation, forKey: marker)
mapView.addAnnotation(pointAnnotation)
return marker
}
}
extension TTMapView: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
if _isClusteringEnabled {
let point = mapObjects.filter ({ $0.value === annotation }).first?.key as? TTPoint
print("point ", point)
return TTClusterAnnotationView(annotation: annotation, reuseIdentifier: TTClusterAnnotationView.ReuseID, image: point?.image, color: point?.tintColor)
} else {
let reuseId = "simplePin"
var pinAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if pinAnnotationView == nil {
pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinAnnotationView?.isDraggable = true
pinAnnotationView?.canShowCallout = true
}
return pinAnnotationView
}
}
}
class TTClusterAnnotationView: MKMarkerAnnotationView {
/// Use this Id for setting annotation
static let ReuseID = "clusterAnnotation"
init(annotation: MKAnnotation?, reuseIdentifier: String?, image: UIImage?, color: UIColor? = nil) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
// Enable clustering by just setting the clusteringIdentifier
clusteringIdentifier = "clusteringIdentifier"
glyphImage = image
glyphTintColor = color
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForDisplay() {
super.prepareForDisplay()
displayPriority = .required
}
}
Upvotes: 0
Views: 1198
Reputation: 437542
Make the cluster annotation view (this is the annotation view for the cluster, not to be confused with your existing annotation view that has a clusteringIdentifier
, which I’ve renamed to CustomAnnotationView
to avoid confusion) have a displayPriority
of .required
:
class CustomClusterAnnotationView: MKMarkerAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
displayPriority = .required
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var annotation: MKAnnotation? {
didSet {
displayPriority = .required
}
}
}
Then register that class:
mapView.register(CustomClusterAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
And then use it:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
if annotation is MKClusterAnnotation {
return nil
}
...
}
A few unrelated observations:
I’d suggest just adding your image and color properties to a custom annotation type rather than having viewFor
filter through mapObjects
. So:
class CustomAnnotation: MKPointAnnotation {
let image: UIImage
let color: UIColor
init(coordinate: CLLocationCoordinate2D, title: String?, image: UIImage, color: UIColor) {
self.image = image
self.color = color
super.init()
self.coordinate = coordinate
self.title = title
}
}
Then, if you use that rather than MKPointAnnotation
, your custom annotation view can pull the color and image info right out of the annotation.
class CustomAnnotationView: MKMarkerAnnotationView {
static let reuseID = "CustomAnnotationView"
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
updateForAnnotation()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var annotation: MKAnnotation? {
didSet {
updateForAnnotation()
}
}
}
private extension CustomAnnotationView {
func updateForAnnotation() {
clusteringIdentifier = "CustomAnnotationView"
displayPriority = .required
if let annotation = annotation as? CustomAnnotation {
glyphImage = annotation.image
glyphTintColor = annotation.color
} else {
glyphImage = nil
glyphTintColor = nil
}
}
}
Note, above I’m resetting the cluster identifier, image, glyph, etc. in the didSet
of annotation
. This enables the reuse of annotation views. (See next point.)
The reuse logic for your pin annotation view is not correct. And you’re not doing reuse at all if clustering is turned on. If targeting iOS 11 and later, I’d use dequeueReusableAnnotationView(withIdentifier:for:)
which takes care of all of this for you. So, I can register this reuse id:
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomAnnotationView.reuseID)
And I’d repeat that process for the “simple pin” annotation view that you show if you turn off clustering.
mapView.register(CustomSimplePinAnnotationView.self, forAnnotationViewWithReuseIdentifier: CustomSimplePinAnnotationView.reuseID)
And
class CustomSimplePinAnnotationView: MKPinAnnotationView {
static let reuseID = "CustomSimplePinAnnotationView"
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
isDraggable = true
canShowCallout = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And then your viewFor
is simplified:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
switch annotation {
case is MKUserLocation:
return nil
case is MKClusterAnnotation:
return nil
default:
if _isClusteringEnabled {
return mapView.dequeueReusableAnnotationView(withIdentifier: CustomAnnotationView.reuseID, for: annotation)
} else {
return mapView.dequeueReusableAnnotationView(withIdentifier: CustomSimplePinAnnotationView.reuseID, for: annotation)
}
}
}
Upvotes: 3