MapKit(MKMapView):zPosition在iOS11上不再起作用

18
在iOS11上,annotationView.layer的zPosition停止工作。每次地图区域发生变化。

enter image description here

  • 原来的解决方案没有成功:layer.zPosition = X;
  • 使用bringViewToFront/SendViewToBack方法也没有成功

Xcode 8.3/9

更新(感谢Elias Aalto提供的解决方案):

创建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];

}

在MeLikeSingleton或其他观察者对象中:

- (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);
           }

       } 
}
  • 这个解决方案使用layer.name值来跟踪zOrder。如果您有多层zPosition(用户位置,聚类,图钉,弹出);)
  • 没有for循环,只有KVO
  • 我使用了一个单例对象来观察层的值变化。如果您在应用程序中使用多个MKMapViews。

在iOS11之前它是如何工作的

...是使用

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views

在此设置zPosition

但是在iOS11中,这种方法(对于我们中的一些人来说,仍然不知道为什么)已经不起作用了!


2
你找到解决方案了吗?我也在处理完全相同的问题。 - d3020
是的,请查看原始帖子。 - Yaro
给所有人提醒:在iOS 11中,zPosition似乎出现了问题。请花些时间提交错误报告:https://developer.apple.com/bug-reporting/。如果有更多人提交,这个问题就会得到更多的关注。 - Stack
你得喜欢苹果啊,每次主要更新都能把东西搞得一团糟,就像这根本不是什么大事一样。 - Jony Thrive
2个回答

11

zPosition属性是有效的,只是由于MKFeatureDisplayPriority存在问题且无用,导致MKMapView在内部覆盖了它。如果您只需要少量注释保持在“所有其他内容”的顶部,您可以使用KVO (键值观察) 来实现半清晰化操作。只需将观察者添加到注释视图的层的zPosition上,并在MKMapView试图对其进行更改时进行覆盖。

(请原谅我的ObjC)

添加观察者:

        [self.annotationView.layer addObserver:self forKeyPath:@"zPosition" options:0 context:nil];

覆盖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;
    }
}

利润


这对我来说比使用相当奇怪的[self.annotationView setDisplayPriority:MKFeatureDisplayPriorityDefaultLow/High]要好得多,因为那种方法实际上会隐藏低优先级视图,而不仅仅是重新排序它们的z-indexes——如果它是一个纯色元素,那么这样做是可以的,但如果它是半透明的,则不好。谢谢! - Breeno
这个解决方案开始让我的应用程序崩溃了。http://crashes.to/s/0f3a2ba5fef - Tommy Sadiq Hinrichsen

5
我们可以完全忽略 MKMapView 尝试修改 MKAnnotationView 层的 zPosition。由于 MKAnnotationView 使用标准的 CALayer 作为其层,而不是一些私有类,因此我们可以对其进行子类化并覆盖其 zPosition。要实际设置 zPosition,我们可以提供自己的访问器。
这比 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
    }
}

更新:

当在重叠的注释上点击时,我有一种非常不优雅的方法来选择最顶部的注释视图。

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)
    }
}

是的,它在生产环境中。你确定你从mapView(MKMapView, viewFor: MKAnnotation)返回的是子类,而不是 MKAnnotationView 吗? - bteapot
是的,我返回了正确的子类。我在ResistantLayer的空set中放置了一个print语句,并且它被调用了。 - onmyway133
这对于集群注释无效,会导致崩溃。(如果您不需要集群注释,则可以正常工作!) - nolanw
1
@nolanw 为什么呢?我为聚合注释返回了 MKMarkerAnnotationView 的子类,对空的 zPosition.set 进行了命中,但没有发生崩溃。 - bteapot
@bteapot,没错,这个很好用。谢谢你的提醒,否则我可能就放弃了! - nolanw
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接