为什么 UIAccessibility.post(notification: .announcement, argument: "arg") 没有在画外音中宣布?

bra*_*ith 11 ios voiceover uiaccessibility

在 iOS 中使用 Voice Over 时,调用UIAccessibility.post(notification:argument:)来宣布字段错误实际上并不会宣布错误。

我有一个提交按钮,当聚焦按钮时,旁白会如你所愿读取按钮标题。按下按钮时,画外音会再次朗读标题。当提交按钮被按下时,我正在做一些验证,当出现字段错误时,我试图通过调用来宣布它:

if UIAccessibility.isVoiceOverRunning {
    UIAccessibility.post(notification: .announcement, argument: "my field error")
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,如果我在调试器的断点处停下来,就会发生通知。当我不在断点处停止时,公告不会发生。

通知发布在主线程上,如果是这样NotificationCenter.default,我假设它是在发布的同一线程上处理的。我试图将调用分派到主队列,即使它已经在主线程上,但这似乎也不起作用。

我唯一能想到的是在旁白完成阅读提交按钮标题之前发布和观察通知,并且公告通知不会中断当前的旁白。

我真的很感激这方面的任何帮助。

ted*_*ock 10

这是一个公认的hacky解决方案,但我能够通过稍微延迟调度到主线程来防止系统公告抢占我自己的:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  UIAccessibility.post(notification: .announcement, argument: "<Your text>")
}
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案背后的问题是延迟的适当值应该根据要读出的单词来适应语速......而且我不会说i18n增加了另一层问题。 (2认同)

XLE*_*_22 5

您的问题可能会发生,因为系统需要在字段错误出现期间接管,在这种情况下,任何自定义的 VoiceOver 通知都会被取消。

我写了一个关于对多个 VoiceOver 通知进行排队问题的答案,这可能有助于您了解当前的情况。

您的通知使用断点工作,因为您延迟了它并且系统在此期间工作:您的通知和系统工作之间没有重叠。

一个简单的解决方案可能是在发送通知之前实现短暂的延迟,但延迟取决于语速,这就是为什么这只是临时解决方法。

您的重试机制很聪明,可以在多次重试的循环内进行改进,以防发生许多系统接管。


Amr*_*Amr 5

另一种解决方法是使用 .screenChanged 并传递错误标签,如下所示:

UIAccessibility.post(notification: .screenChanged, argument: errorLabel)
Run Code Online (Sandbox Code Playgroud)


小智 5

我面临着同样的问题,所以我采用了 @brandenesmith 的通知队列想法并编写了一个小帮助器类。

class AccessibilityAnnouncementQueue {
    
    static let shard = AccessibilityAnnouncementQueue()
    
    private var queue: [String] = []

    private init() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(announcementFinished(_:)),
                                               name: UIAccessibility.announcementDidFinishNotification,
                                               object: nil)
    }
    
    func post(announcement: String) {
        guard UIAccessibility.isVoiceOverRunning else { return }
        
        queue.append(announcement)
        postNotification(announcement)
    }
    
    
    private func postNotification(_ message: String) {
        let attrMessage: NSAttributedString = NSAttributedString(string: message, attributes: [.accessibilitySpeechQueueAnnouncement: true])
        UIAccessibility.post(notification: .announcement, argument: attrMessage)
    }
    
    @objc private func announcementFinished(_ sender: Notification) {
        guard
            let userInfo = sender.userInfo,
            let firstQueueItem = queue.first,
            let announcement = userInfo[UIAccessibility.announcementStringValueUserInfoKey] as? String,
            let success = userInfo[UIAccessibility.announcementWasSuccessfulUserInfoKey] as? Bool,
            firstQueueItem == announcement
        else { return }
        
        if success {
            queue.removeFirst()
        } else {
            postNotification(firstQueueItem)
        }
    }
    
}
Run Code Online (Sandbox Code Playgroud)


bra*_*ith 2

我可以使用重试机制来使其工作,在该机制中我注册为观察者UIAccessibility.announcementDidFinishNotification,然后从字典中提取公告和成功状态userInfo

如果成功状态为 false 并且公告与我刚刚发送的公告相同,我会再次发布通知。这种情况会重复发生,直到公告成功为止。

这种方法显然存在多个问题,包括必须取消注册、如果另一个对象设法发布相同的公告会发生什么(这在实践中不应该发生,但理论上可能会发生)、必须跟踪最后的公告已发送等

代码如下:

private var _errors: [String] = []
private var _lastAnnouncement: String = ""

init() {
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(announcementFinished(_:)),
        name: UIAccessibility.announcementDidFinishNotification,
        object: nil
    )
}

func showErrors() {
    if !_errors.isEmpty {
        view.errorLabel.text = _errors.first!
        view.errorLabel.isHidden = false

        if UIAccessibility.isVoiceOverRunning {
            _lastAnnouncement = _errors.first!
            UIAccessibility.post(notification: .announcement, argument: _errors.first!)
        }
    } else {
        view.errorLabel.text = ""
        view.errorLabel.isHidden = true
    }
}

@objc func announcementFinished(_ sender: Notification) {
    guard let announcement = sender.userInfo![UIAccessibility.announcementStringValueUserInfoKey] as? String else { return }
    guard let success = sender.userInfo![UIAccessibility.announcementWasSuccessfulUserInfoKey] as? Bool else { return }

    if !success && announcement == _lastAnnouncement {
        _lastAnnouncement = _errors.first!
        UIAccessibility.post(notification: .announcement, argument: _errors.first!)
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是,这个重试机制将始终被使用,因为第一次调用总是UIAccessibility.post(notification: .announcement, argument: _errors.first!)(除非我在断点处停止)。我还是不明白为什么第一篇文章总是失败。