UIStackView 可访问性 - 在默认 accessibleElements 中插入可访问性视图?

UIStackView accessibility - Insert an accessibility view in default accessibleElements?

现在了解到 UIStackView 的辅助功能在默认情况下会在画外音打开时按顺序读取 arrangedSubviews。假设我有这个层次结构:

UIView
   - UIStackView
       -view1
       -view2
       -view3
       -view4
   - collapsibleSpecialView

collapsibleSpecialView 锚定在视图 4 的顶部:

        etc
|------------------|
|      view3       |
|------------------|
|    collapsible   |     <- when collapsed overlaps with vw3 to vw1
|------------------|
|      view4       |
|------------------|

我需要旁白才能按上面的顺序阅读我的元素。但是由于 stackView 需要先完成所有可访问的子视图,所以我的可折叠视图是最后读取的,但我需要在 view4 之前读取它。

我无法将它添加到 stackview,因为当 collapsibleView 折叠时,它会推高它上面的所有内容。而且我不想绕过自动布局来解决这个问题。

我认为可行的方法是在 view4 之前插入一个不可见的代理 UIView,并在该 ProxyView 中将其 methods 覆盖为 return 可访问元素。

public class ProxyView: UIView {
    private var view: UIView?

    /// view is the collapsibleView
    @objc public convenience init(view: UIView) {
        self.init()
        self.isAccessibilityElement = true
        self.view = view
    }

    public override func accessibilityElementCount() -> Int {
        return 1
    }

    public override func accessibilityElement(at index: Int) -> Any? {
        guard let menu = view else { return nil }
        return menu
    }

    public override func index(ofAccessibilityElement element: Any) -> Int {
        return 0
    }
}

使用此画外音可以在 view4 之前读取我的 ProxyView,但由于某种原因它没有进入 collapsibleView,向右滑动会将焦点转移到 view4。

如果我对 UIAccessibilityContainer 的理解是正确的,这在理论上应该可行,但事实并非如此。我错过了什么?谢谢

我能够解决这个问题。我的解决方案与我上面的解决方案差不多,只是做了一些修复。

class CollapsibleView: UIView {
     /// set isAccessibilityElement = true in init
    
     /// set it accessibilityElements in order you want

     /// this is to tell the  VoiceOver not to capture 
     /// our subviews because we are going to pass them to our proxy
     /// setting this to false will cause VO to loop back to 
     /// this view's subviews.    
}

class ProxyView: UIView {
    var lastFocusedElement: Any?
    
    private var view: CollapsibleView!
    
    /// Collapsible view is accessible element and so VO will still focus on it. We pass the focus to last focused element since we want view before collapsibleView be the last focuseable element.

    /// If you have other views to focus after your view, you have to figure out how to tell VO to skip collapsibleView without setting its isAccessibilityElement to false
    @objc func voFocused(notification: Notification) {
        guard let focused = notification.userInfo?[UIAccessibility.focusedElementUserInfoKey] else {
            return
        }

        if let asView = focused as? UIView, asView == view {
            ///change focus to last
            UIAccessibility.post(notification: .screenChanged, argument: lastFocusedElement)
        } else {
            lastFocusedElement = focused
        }
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder) 
        /// This is an invisible container-only view. We
        /// It needs to be set to false to allow VO
        /// Recognize it as a container. 
        self.isAccessibilityElement = false
        NotificationCenter.default.addObserver(self, selector: #selector(voFocused(notification:)),
                                               name: UIAccessibility.elementFocusedNotification,
                                               object: nil)

    }
    
    func setView(vw: CollapsibleView){
        self.view = vw
        self.accessibilityElements = view.accessibilityElements
    }
}

剩下的就是添加 ProxyView() 以堆叠到我们想要宣布的任何顺序