iosswiftuikitaccessibilityvoiceover

Is there a way to detect VoiceOver focus change / user interaction at the `UIApplication` or `UIWindow` level?


One of the features of the app I'm working on is an "idle timeout" - basically if the user doesn't interact with the app for more than five minutes, their session ends for privacy / security reasons.

This is currently implemented by overriding sendEvent(_ event: UIEvent) in the app delegate and resetting a timer.

However, what I've noticed is that sendEvent(_ event: UIEvent) isn't called at all when a user is interacting with the app using VoiceOver.

It is called when the user double-taps to activate a control, but it isn't called for focus changes, interacting with .adjustable controls, and so on.

This means that if a user is swiping through a long list of rows in a table view, for example, sendEvent(_ event: UIEvent) isn't called and a user may be timed-out of the app, even though they were interacting with it.

Is there a way to detect VoiceOver focus changes or interactions at the UIApplication or UIWindow level, or a notification that can be subscribed to for a clean solution?

The closest solution I've found suggests using the UIAccessibilityFocus protocol on individual views, but this feels like it could be quite messy and involve a lot of subclassing (https://stackoverflow.com/a/20712889).

Any different suggestions for an idle timeout that works for VoiceOver and non-VoiceOver users would also be much appreciated - there could well be something I've missed.


Solution

  • So I figured out the solution here and I'm now kicking myself.

    UIAccessibility.elementFocusedNotification is the key (https://developer.apple.com/documentation/uikit/uiaccessibility/1620210-elementfocusednotification).

    So a solution could look something like:

    NotificationCenter.default.addObserver(self,
                                           selector: #selector(self.accessibilityElementFocussed(notification:)),
                                           name: UIAccessibility.elementFocusedNotification,
                                           object: nil)
    
    @objc private func accessibilityElementFocussed(notification: NSNotification) {
        // Reset your idle timer here.
    }
    

    Hopefully this helps someone out - I've seen a lot of solutions for idle timers online that don't take into account VoiceOver users.