Создать собственный VoiceOver Rotor для навигации по MKAnnotationViews?

Я строю несколько MKAnnotations на MKMapView, Я бы хотел, чтобы пользователи VoiceOver могли продолжать панорамирование / масштабирование карты, как обычно, но я также хотел бы, чтобы они могли быстро и легко перемещаться по моим MKAnnotations если они выберут. Я чувствую, что нестандартный ротор - идеальное решение для этого.

1 ответ

Самостоятельно отвечая здесь, потому что я потратил безумное количество времени, чтобы понять это правильно, и подумал, что кому-то еще это может понадобиться. В то время, когда мне нужно было это разработать, в Интернете практически не было примеров, которые бы подробно описывали создание пользовательских роторов, а документация Apple очень скудна. Я наконец понял это, хотя, после просмотра и приостановки (и паузы на экранах кода) сеанса WWDC 202 (начинается в 24:17).

Самое сложное, что мне нужно было выяснить, это как надежно вернуть UIAccessibilityCustomRotorItemResult, Для MKMapViewхочешь вернуться MKAnnotationViews, но у аннотации не гарантируется наличие связанного вида (они перерабатываются, и если аннотация за кадром, есть хороший шанс, что вид был повторно использован), поэтому мои первые попытки не учитывали некоторые или большинство из них мои аннотации.

Волшебство заключается в установке свойства animated: false в значение 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]
}
Другие вопросы по тегам