I created a custom annotation using this as an example: custom annotation. It's a white water drop with a blue background. I'm subclassing MKMarkerAnnotationView, rather than MKAnnotationView, to inherit all the normal behaviors of the default marker (ex. it becomes larger when clicked).
The problem is, after zooming and panning, the default red pin occasionally appears instead of my custom blue water marker. After zooming in and out several times, almost all the markers turn into the default red pin. Is this a Swift bug, or am I doing something wrong?
import UIKit
import MapKit
class ViewController: UIViewController {
let coordinates = [
CLLocationCoordinate2D(latitude: 37.3105042, longitude: -122.0380024),
CLLocationCoordinate2D(latitude: 37.3099890, longitude: -122.0404633),
CLLocationCoordinate2D(latitude: 37.3573748, longitude: -122.0692252),
CLLocationCoordinate2D(latitude: 37.3285758, longitude: -122.0787571),
CLLocationCoordinate2D(latitude: 37.3311494, longitude: -122.0588439),
CLLocationCoordinate2D(latitude: 37.3261889, longitude: -122.0626754),
CLLocationCoordinate2D(latitude: 37.3578807, longitude: -122.0535947),
CLLocationCoordinate2D(latitude: 37.3497026, longitude: -122.0562993),
CLLocationCoordinate2D(latitude: 37.3403821, longitude: -121.9736835),
CLLocationCoordinate2D(latitude: 37.3766856, longitude: -122.0302783),
CLLocationCoordinate2D(latitude: 37.3656187, longitude: -122.0378590),
CLLocationCoordinate2D(latitude: 37.3655657, longitude: -122.0374650),
CLLocationCoordinate2D(latitude: 37.3242644, longitude: -122.0387466),
CLLocationCoordinate2D(latitude: 37.3425773, longitude: -122.0247380),
CLLocationCoordinate2D(latitude: 37.3426291, longitude: -122.0253387),
CLLocationCoordinate2D(latitude: 37.3416711, longitude: -122.0256557),
CLLocationCoordinate2D(latitude: 37.3275180, longitude: -122.0507106),
CLLocationCoordinate2D(latitude: 37.3187770, longitude: -122.0688523),
CLLocationCoordinate2D(latitude: 37.3412129, longitude: -121.9753722),
CLLocationCoordinate2D(latitude: 37.3408061, longitude: -121.9757454),
CLLocationCoordinate2D(latitude: 37.3597423, longitude: -121.9893887),
CLLocationCoordinate2D(latitude: 37.3242567, longitude: -122.0117839)
]
var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MKMapView(frame: self.view.frame)
view.addSubview(mapView)
mapView.register(WaterAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
mapView.register(WaterAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)
addAnnotations()
// center map at first coordinate
let region = MKCoordinateRegion(center: coordinates[0], span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2))
mapView.setRegion(region, animated: true)
}
private func addAnnotations() {
for coordinate in coordinates {
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
}
}
}
class WaterAnnotationView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
didSet { configure(for: annotation) }
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
glyphImage = UIImage(systemName: "drop.fill")
markerTintColor = #colorLiteral(red: 0.005868499167, green: 0.5166643262, blue: 0.9889912009, alpha: 1)
configure(for: annotation)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(for annotation: MKAnnotation?) {
displayPriority = .required
clusteringIdentifier = MKMapViewDefaultClusterAnnotationViewReuseIdentifier
}
}
The problem is unrelated to clustering. Temporarily turn off clustering and you will see the same behavior.
You can move the setting of glyphImage
and markerTintColor
into the configure(for:)
method, to make sure they do not get reset upon reuse.
class WaterAnnotationView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
didSet { configure(for: annotation) }
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
// move these lines to `configure(for:)`
//
// glyphImage = UIImage(systemName: "drop.fill")
// markerTintColor = #colorLiteral(red: 0.005868499167, green: 0.5166643262, blue: 0.9889912009, alpha: 1)
configure(for: annotation)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(for annotation: MKAnnotation?) {
// move it here
glyphImage = UIImage(systemName: "drop.fill")
markerTintColor = #colorLiteral(red: 0.005868499167, green: 0.5166643262, blue: 0.9889912009, alpha: 1)
// and then as in your original …
displayPriority = .required
clusteringIdentifier = MKMapViewDefaultClusterAnnotationViewReuseIdentifier
}
}
Alternatively, you could set these in init
and reset them back to the desired settings in prepareForReuse
, but that seems no better than the above.
Unrelated, it seems curious to use WaterAnnotationView
for your clustering identifier. Specifically, if used for clustering, it seems strange to set the glyphImage
that will not be used. I might be inclined to have two different annotation views, one for the “water annotation” and one for the clustering of these annotation views:
class WaterAnnotationView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
didSet { configure(for: annotation) }
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
configure(for: annotation)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(for annotation: MKAnnotation?) {
glyphImage = UIImage(systemName: "drop.fill")
markerTintColor = #colorLiteral(red: 0.005868499167, green: 0.5166643262, blue: 0.9889912009, alpha: 1)
displayPriority = .required
clusteringIdentifier = MKMapViewDefaultClusterAnnotationViewReuseIdentifier
}
}
class WaterAnnotationClusterView: MKMarkerAnnotationView {
override var annotation: MKAnnotation? {
didSet { configure(for: annotation) }
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
configure(for: annotation)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(for annotation: MKAnnotation?) {
displayPriority = .required
markerTintColor = #colorLiteral(red: 0.005868499167, green: 0.5166643262, blue: 0.9889912009, alpha: 1)
}
}
And then, of course:
mapView.register(WaterAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
mapView.register(WaterAnnotationClusterView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultClusterAnnotationViewReuseIdentifier)