为什么未通过语音提示UIAccessibility.post(通知:.announcement,参数:“ arg”)?

时间:2019-04-04 18:01:22

标签: ios voiceover uiaccessibility

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

我有一个提交按钮,当聚焦按钮时,旁白会如您所愿地读取按钮标题。按下按钮时,旁白会再次读取标题。当按下提交按钮时,我正在做一些验证,当出现字段错误时,我正在尝试通过调用以下内容来宣布它:

if UIAccessibility.isVoiceOverRunning {
    UIAccessibility.post(notification: .announcement, argument: "my field error")
}

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

该通知正在主线程上发布,并且如果类似于NotificationCenter.default,则假定该通知是在与该线程相同的线程上处理的。我已经尝试将调用分派到主队列,即使它已经在主线程上了,这似乎也不起作用。

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

在此方面,我将不胜感激。

更新

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

如果成功状态为假,并且公告与我刚发送的公告相同,我将再次发布通知。重复进行此过程,直到成功发布为止。

此方法显然存在多个问题,包括必须注销,如果另一个对象设法发布相同的公告会发生什么(这在实践中永远不会发生,但从理论上讲可能会发生),必须跟踪最近发送的公告,等等

代码如下:

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!)
    }
}

问题在于,将始终使用此重试机制,因为始终始终对UIAccessibility.post(notification: .announcement, argument: _errors.first!)进行首次调用(除非我在断点处停止)。我仍然不知道为什么第一篇文章总是失败。

5 个答案:

答案 0 :(得分:2)

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

saveObject1.setUUID(initObj.getUUID())
initObj.setUUID(UUID.randomUUID());
saveObject2.setUUID(initObj.getUUID()) 

答案 1 :(得分:1)

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

如果成功状态为假,并且公告与我刚发送的公告相同,我将再次发布通知。重复进行此过程,直到成功发布为止。

此方法显然存在多个问题,包括必须注销,如果另一个对象设法发布相同的公告会发生什么(这在实践中永远不会发生,但从理论上讲可能会发生),必须跟踪最近发送的公告,等等

代码如下:

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!)
    }
}

问题在于,将始终使用此重试机制,因为始终始终对UIAccessibility.post(notification: .announcement, argument: _errors.first!)进行首次调用(除非我在断点处停止)。我仍然不知道为什么第一篇文章总是失败。

答案 2 :(得分:1)

如果有人使用 RxSwift,以下解决方案可能更合适:

extension UIAccessibility {
    static func announce(_ message: String) -> Completable {
        guard !message.isEmpty else { return .empty() }
        return Completable.create { subscriber in
            let postAnnouncement = {
                DispatchQueue.main.async {
                    UIAccessibility.post(notification: .announcement, argument: message)
                }
            }
            
            postAnnouncement()
            
            let observable = NotificationCenter.default.rx.notification(UIAccessibility.announcementDidFinishNotification)
            return observable.subscribe(onNext: { notification in
                guard let userInfo = notification.userInfo,
                      let announcement = userInfo[UIAccessibility.announcementStringValueUserInfoKey] as? String,
                      announcement == message,
                      let success = userInfo[UIAccessibility.announcementWasSuccessfulUserInfoKey] as? Bool else { return }
                success ? subscriber(.completed) : postAnnouncement()
            })
        }
    }
}

答案 3 :(得分:0)

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

我写了answer,说明有关排队多个VoiceOver通知的问题,这些问题可以帮助您了解当前的情况。

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

一个简单的解决方案可能是在发送通知之前先进行短暂的延迟

您的重试机制很聪明,在有很多系统接管的情况下,可以在很少的重试循环内加以改进。

答案 4 :(得分:0)

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

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