创建自定义VoiceOver Rotor以导航MKAnnotationViews?

时间:2017-02-11 00:37:39

标签: ios swift voiceover uiaccessibility rotor

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

1 个答案:

答案 0 :(得分:5)

在这里自我回答,因为我花了很多时间来做这件事,并且认为其他人可能需要这个。当我需要开发这个时,几乎没有任何关于创建定制转子的在线示例,Apple的文档非常稀疏。在观察并关注(并在代码屏幕上暂停)WWDC Session 202(从24:17开始)之后,我终于想通了。

我需要弄清楚的最棘手的事情是如何可靠地返回UIAccessibilityCustomRotorItemResult。对于MKMapView,您希望返回MKAnnotationView,但不能保证注释具有关联的视图(它们会被重复使用,如果注释位于屏幕外,则会出现' sa很有可能它的观点被重用了),所以我的第一次尝试一直遗漏了一些或大部分我的注释。

神奇的是将animated:property设置为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]
}