// // AccessibilityCustomRotorable.swift // VDS // // Created by Matt Bruce on 8/24/23. // import Foundation import UIKit public protocol CustomRotorable: UIViewController { var customRotors: [CustomRotorType] { get set } } extension CustomRotorable { internal func addCustomRotor(with name: String, for trait: UIAccessibilityTraits) { accessibilityCustomRotors = (accessibilityCustomRotors ?? []).filter { $0.name != name } let newRotor = UIAccessibilityCustomRotor(name: name) { [weak self] predicate in guard let self = self else { return nil } let elements = self.view.accessibleElements(with: trait) guard !elements.isEmpty else { return nil } let currentIndex = elements.firstIndex(where: { ($0 as AnyObject) === predicate.currentItem.targetElement }) let count = elements.count let nextIndex: Int switch predicate.searchDirection { case .next: nextIndex = currentIndex.map { ($0 + 1) % count } ?? 0 case .previous: nextIndex = currentIndex.map { ($0 - 1 + count) % count } ?? 0 @unknown default: nextIndex = 0 } guard let element = elements[nextIndex] as? NSObjectProtocol else { return nil } return UIAccessibilityCustomRotorItemResult(targetElement: element, targetRange: nil) } accessibilityCustomRotors?.append(newRotor) } public func loadCustomRotors() { customRotors.forEach { addCustomRotor(with: $0.name, for: $0.trait) } } } public extension UIView { func accessibleElements(with trait: UIAccessibilityTraits) -> [Any] { var elements: [Any] = [] if isAccessibilityElement, accessibilityTraits.contains(trait) { elements.append(self) } if let customAccessibilityElements = accessibilityElements { elements.append(contentsOf: customAccessibilityElements.filter { element in if let element = element as? UIAccessibilityElement { return element.accessibilityTraits.contains(trait) } return false }) } subviews.forEach { elements.append(contentsOf: $0.accessibleElements(with: trait)) } return elements } } public struct CustomRotorType { public var name: String public var trait: UIAccessibilityTraits public init(name: String, trait: UIAccessibilityTraits) { self.name = name self.trait = trait } }