I have several MKAnnotations (and their corresponding views) in my map, and it sometimes gets really crowded. Now, the annotations in my app come in two flavors: some are bo
Ok, so for solution use method from MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
In this method you should rearrange AnnotationView after it was added to mapKit View. So, code may looks like this:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
for (MKAnnotationView * annView in views) {
TopBottomAnnotation * ann = (TopBottomAnnotation *) [annView annotation];
if ([ann top]) {
[[annView superview] bringSubviewToFront:annView];
} else {
[[annView superview] sendSubviewToBack:annView];
}
}
}
This works for me.
In the delegate function, you can select the pin to force it on top:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?` {
...
if my annotation is the special one {
annotationView.isSelected = true
}
...
}
Under iOS 11 the implementation of displayPriority
broke all the solutions which use bringSubviewToFront
or zPosition
.
If you override the annotation view's CALayer, you can wrestle control of zPosition back from the OS.
class AnnotationView: MKAnnotationView {
/// Override the layer factory for this class to return a custom CALayer class
override class var layerClass: AnyClass {
return ZPositionableLayer.self
}
/// convenience accessor for setting zPosition
var stickyZPosition: CGFloat {
get {
return (self.layer as! ZPositionableLayer).stickyZPosition
}
set {
(self.layer as! ZPositionableLayer).stickyZPosition = newValue
}
}
/// force the pin to the front of the z-ordering in the map view
func bringViewToFront() {
superview?.bringSubviewToFront(toFront: self)
stickyZPosition = CGFloat(1)
}
/// force the pin to the back of the z-ordering in the map view
func setViewToDefaultZOrder() {
stickyZPosition = CGFloat(0)
}
}
/// iOS 11 automagically manages the CALayer zPosition, which breaks manual z-ordering.
/// This subclass just throws away any values which the OS sets for zPosition, and provides
/// a specialized accessor for setting the zPosition
private class ZPositionableLayer: CALayer {
/// no-op accessor for setting the zPosition
override var zPosition: CGFloat {
get {
return super.zPosition
}
set {
// do nothing
}
}
/// specialized accessor for setting the zPosition
var stickyZPosition: CGFloat {
get {
return super.zPosition
}
set {
super.zPosition = newValue
}
}
}
I really needed to do this, and none of the (current) answers seemed to provide a reliable implementation. They sort of worked, but panning the map, selecting annotations, or zooming in could mess up the order again.
The final, well behaved solution wasn't so trivial, so I'll just outline the steps I took here. The annotation ordering that MKMapView
uses doesn't respect the added order, or even the order of an overriden annotations
property. So...
• Create a CADisplayLink
• Every frame, reorder annotations using both the layer zPosition, and the view's ordering in the superview's subviews
array.
• If the view is selected, promote it to the front in your ordering scheme
• Tapping on annotations still respects internal MKMapView
ordering, despite the already made changes. To counter this, add an MKMapViewDelegate
• In the delegate object's mapView:didSelect:
method, check if the selected annotation is what you'd like it to be
• You can figure out the correct/prioritised annotation by running hit tests on the annotations yourself, with your own ordering taken into account
• If the selected annotation is correct, great. If not, manually select the correct annotation using selectAnnotation:animated:
And there you have it. The above method seems to work well, and the performance hit from running this each frame isn't too bad. You could also look at switching to MapBox, which I believe supports annotation ordering, but this isn't always an option for various reasons.
Try to setup annotation view layer's zPosition (annotationView.layer.zPosition) in:
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views;
I'm finding that this reordering the annotation views causes the callout that pops up when one is clicked to no longer be on top of all the annotations. I've even tried refining it so that instead of bringSubviewToFront
and sendSubviewToBack
, I use insertSubview:aboveSubview
and insertSubview:belowSubview:
where the second argument is the first annotationView in the list. This would seem to cause much less front to back scattering, but the call outs still pop up under some annotations.