创建自定义 VoiceOver 转子来导航 MKAnnotationViews?

Posted

技术标签:

【中文标题】创建自定义 VoiceOver 转子来导航 MKAnnotationViews?【英文标题】:Create a custom VoiceOver Rotor to navigate MKAnnotationViews? 【发布时间】:2017-02-11 00:37:39 【问题描述】:

我在MKMapView 上绘制了几个MKAnnotations。我希望 VoiceOver 用户能够像往常一样继续平移/缩放地图,但如果他们愿意,我也希望他们能够快速轻松地浏览我的 MKAnnotations。我觉得定制转子是解决这个问题的完美解决方案。

【问题讨论】:

【参考方案1】:

在这里自我回答,因为我花了很多时间来解决这个问题,并且认为其他人可能需要这个。在我需要开发它的时候,网上几乎没有任何关于创建自定义转子的详细示例,而且 Apple 的文档非常稀少。不过,在观看并关注(并在代码屏幕上暂停)WWDC Session 202(从 24:17 开始)之后,我终于想通了。

我需要弄清楚的最棘手的事情是如何可靠地返回UIAccessibilityCustomRotorItemResult。对于MKMapView,您想返回MKAnnotationViews,但不能保证注释具有关联的视图(它们会被回收,如果注释不在屏幕上,则很有可能它的视图已被重用),所以我的第一次尝试总是遗漏一些或大部分注释。

神奇之处在于将 animated: 属性设置为 false:

self.mapView.setCenter(requestedAnnotation.coordinate, animated: false)

由于上述原因,您不能使用 view(for: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]

【讨论】:

以上是关于创建自定义 VoiceOver 转子来导航 MKAnnotationViews?的主要内容,如果未能解决你的问题,请参考以下文章

将子视图添加到自定义 viewForHeaderInSection 会中断 VoiceOver 导航

我可以影响 VoiceOver 转子中显示的内容吗?

VoiceOver 标题转子中未列出链接的标题

VoiceOver 忽略 UIBarButton 中的可访问性提示

在自定义视图上处理来自 VoiceOver 的“点击”

MKAnnotationView 自定义标注对辅助功能 (VoiceOver) 不可见