我在一个MKMapView
上绘制了几个MKAnnotations
。我希望VoiceOver用户能够像往常一样继续平移/缩放地图,但如果他们愿意,我也希望他们能够快速轻松地浏览我的MKAnnotations
。我觉得定制的转子是完美的解决方案。
在这里自我回答,因为我花了大量的时间来解决这个问题,并认为其他人可能需要这个。在我需要开发这个的时候,网上几乎没有任何关于创建自定义转子的详细例子,而且苹果的文档也很少。不过,在观看并关注(并在代码屏幕上暂停)WWDC会话202(从24:17开始)后,我终于明白了。
我需要弄清楚的最棘手的事情是如何可靠地返回UIAccessibilityCustomRotorItemResult
。对于MKMapView
,您希望返回MKAnnotationView
s,但注释不能保证具有关联的视图(它们被回收,如果注释在屏幕外,则很有可能其视图已被重用),因此我的第一次尝试一直忽略了一些或大部分注释。
神奇之处在于将animated:属性设置为false:
self.mapView.setCenter(requestedAnnotation.coordinate, animated: false)
由于上述原因,您不能使用视图(用于:MKAnnotation),所以上面的线所做的是移动地图,使您的接点位于中心。因为它没有设置动画,所以注释会立即创建它的视图,在下一行代码中,在我的测试中,它保证会返回MKAnnotationView
。
YVVM,但请随时添加改进建议,因为我觉得以这种方式导航地图对VoiceOver用户来说至关重要。
func configureCustomRotors() {
let favoritesRotor = UIAccessibilityCustomRotor(name: "Bridges") { predicate in
let forward = (predicate.searchDirection == .next)
// which element is currently highlighted
let currentAnnotationView = predicate.currentItem.targetElement as? MKPinAnnotationView
let currentAnnotation = (currentAnnotationView?.annotation as? BridgeAnnotation)
// easy reference to all possible annotations
let allAnnotations = self.mapView.annotations.filter { $0 is BridgeAnnotation }
// we'll start our index either 1 less or 1 more, so we enter at either 0 or last element
var currentIndex = forward ? -1 : allAnnotations.count
// set our index to currentAnnotation's index if we can find it in allAnnotations
if let currentAnnotation = currentAnnotation {
if let index = allAnnotations.index(where: { (annotation) -> Bool in
return (annotation.coordinate.latitude == currentAnnotation.coordinate.latitude) &&
(annotation.coordinate.longitude == currentAnnotation.coordinate.longitude)
}) {
currentIndex = index
}
}
// now that we have our currentIndex, here's a helper to give us the next element
// the user is requesting
let nextIndex = {(index:Int) -> Int in forward ? index + 1 : index - 1}
currentIndex = nextIndex(currentIndex)
while currentIndex >= 0 && currentIndex < allAnnotations.count {
let requestedAnnotation = allAnnotations[currentIndex]
// i can't stress how important it is to have animated set to false. save yourself the 10 hours i burnt, and just go with it. if you set it to true, the map starts moving to the annotation, but there's no guarantee the annotation has an associated view yet, because it could still be animating. in which case the line below this one will be nil, and you'll have a whole bunch of annotations that can't be navigated to
self.mapView.setCenter(requestedAnnotation.coordinate, animated: false)
if let annotationView = self.mapView.view(for: requestedAnnotation) {
return UIAccessibilityCustomRotorItemResult(targetElement: annotationView, targetRange: nil)
}
currentIndex = nextIndex(currentIndex)
}
return nil
}
self.accessibilityCustomRotors = [favoritesRotor]
}