On iOS11
the zPosition
stopped working for the annotationView.layer. Every time the map region changes.
- No luck with original solution: layer.zPosition = X;
- No luck with
bringViewToFront
/SendViewToBack
methods
Xcode 8.3/9
UPDATE (SOLUTION thanks Elias Aalto):
When creating MKAnnotationView:
annotationView.layer.zPosition = 50;
if (IS_OS_11_OR_LATER) {
annotationView.layer.name = @"50";
[annotationView.layer addObserver:MeLikeSingleton forKeyPath:@"zPosition" options:0 context:NULL];
}
In MeLikeSingleton or whatever observer object you have there:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (IS_OS_11_OR_LATER) {
if ([keyPath isEqualToString:@"zPosition"]) {
CALayer *layer = object;
int zPosition = FLT_MAX;
if (layer.name) {
zPosition = layer.name.intValue;
}
layer.zPosition = zPosition;
//DDLogInfo(@"Name:%@",layer.name);
}
}
}
- This solution uses the layer.name value to keep track of zOrder. In case you have many levels of zPosition (user location, cluster, pin, callout) ;)
- No for loops, only KVO
- I used a Singleton Object that observs the layer value changes. In case you have multiple MKMapViews used through out the app.
HOW IT WAS WORKING BEFORE IOS11
..is to use the
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
and set the zPosition here.
..but that (for some of us, still dunny why) does not work anymore in iOS11!
The zPosition does work, it's just that MKMapView overwrites it internally based on the somewhat broken and useless MKFeatureDisplayPriority. If you just need a handful of annotations to persist on top of "everything else", you can do this semi cleanly by using KVO. Just add an observer to the annotation view's layer's zPosition and overwrite it as MKMapView tries to fiddle with it.
(Please excuse my ObjC)
Add the observer:
[self.annotationView.layer addObserver:self forKeyPath:@"zPosition" options:0 context:nil];
Overrule MKMapView
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if(object == self.annotationView.layer)
{
self.annotationView.layer.zPosition = FLT_MAX;
}
}
Profit
We can completely ignore MKMapView
's attempts to modify MKAnnotationView
layer's zPosition
. Since MKAnnotationView
uses standard CALayer
as its layer and not some private class, we can subclass it and override its zPosition
. To actually set zPosition
we can provide our own accessor.
It will work much faster than KVO.
class ResistantLayer: CALayer {
override var zPosition: CGFloat {
get { return super.zPosition }
set {}
}
var resistantZPosition: CGFloat {
get { return super.zPosition }
set { super.zPosition = newValue }
}
}
class ResistantAnnotationView: MKAnnotationView {
override class var layerClass: AnyClass {
return ResistantLayer.self
}
var resistantLayer: ResistantLayer {
return self.layer as! ResistantLayer
}
}
UPDATE:
I've got one very inelegant method for selection of the topmost annotation view when tapping on overlapping annotations.
class MyMapView: MKMapView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// annotation views whose bounds contains touch point, sorted by visibility order
let views =
self.annotations(in: self.visibleMapRect)
.flatMap { $0 as? MKAnnotation }
.flatMap { self.view(for: $0) }
.filter { $0.bounds.contains(self.convert(point, to: $0)) }
.sorted(by: {
view0, view1 in
let layer0 = view0.layer
let layer1 = view1.layer
let z0 = layer0.zPosition
let z1 = layer1.zPosition
if z0 == z1 {
if let subviews = view0.superview?.subviews,
let index0 = subviews.index(where: { $0 === view0 }),
let index1 = subviews.index(where: { $0 === view1 })
{
return index0 > index1
} else {
return false
}
} else {
return z0 > z1
}
})
// disable every annotation view except topmost one
for item in views.enumerated() {
if item.offset > 0 {
item.element.isEnabled = false
}
}
// re-enable annotation views after some time
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
for item in views.enumerated() {
if item.offset > 0 {
item.element.isEnabled = true
}
}
}
// ok, let the map view handle tap
return super.hitTest(point, with: event)
}
}
来源:https://stackoverflow.com/questions/46518725/mapkit-mkmapview-zposition-does-not-work-anymore-on-ios11