MapKit (MKMapView): zPosition больше не работает на iOS11
В iOS11
функция zPosition
перестала работать для annotationView.layer. Каждый раз, когда изменяется область карты.
![введите описание изображения здесь]()
- Не повезло с оригинальным решением: layer.zPosition = X;
- Не повезло с методами
bringViewToFront
/SendViewToBack
Xcode 8.3/9
ОБНОВЛЕНИЕ (РЕШЕНИЕ спасибо Элиасу Аалто):
При создании 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 (местоположение пользователя, кластер, контакт, выноска);)
- Нет для циклов, только KVO
- Я использовал объект Singleton, который отслеживает изменения значения слоя. Если у вас есть несколько MKMapViews, используемых в приложении.
КАК ЭТО РАБОТАЕТ ДО IOS11
.. - использовать
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
и установите здесь zPosition.
.. но что (для некоторых из нас, тем не менее, dunny why) больше не работает в iOS11!
Ответы
Ответ 1
ZPosition действительно работает, просто MKMapView перезаписывает его внутренне на основе некорректного и бесполезного MKFeatureDisplayPriority. Если вам просто нужно несколько аннотаций, чтобы упорствовать над "всем остальным", вы можете сделать это получисто, используя 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;
}
}
Profit
Ответ 2
Мы можем полностью игнорировать 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
}
}
UPDATE:
У меня есть один очень неэлегантный метод для выбора самого верхнего аннотационного представления при нажатии на перекрывающиеся аннотации.
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)
}
}