I have a UISwitch
inside tableviewcell. The toggle makes a network request. A UIActivityIndicator
replaces the switch until the response completes.
So
Unfortunately Voice Over has A LOT of bugs (at the beginning of its existence it was much better but with changes in subsequent iOS versions it got worse). Forcing it to behave differently is a lot of "hacking" and with new system it may again behave in ways you not wish it to. After saying that I will present solution that can be used at least for now.
At start I must say that pausing VoiceOver isn't possible programmatically because it causes crash (check here). As so
UIAccessibility.post(notification: .pauseAssistiveTechnology, argument: UIAccessibility.AssistiveTechnologyIdentifier.notificationVoiceOver)
won't help.
Best way to handle UITableViewCell with UISwitch is to do it in the same fashion as Apple did in iPhone settings (focus Voice Over on whole cell). So your switch should not be accessibility element.
And now the funniest thing to discover - if switch.isUserInteractionEnabled flag is set to true then double tap on cell won't call
tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath)
or
tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
so system will go in default way and will read new value of slider directly after it changes.
But if you will create method to set up newly dequeued cell (inside your custom cell class) and write there something like that
func setup() {
slider.isUserInteractionEnabled = !UIAccessibility.isVoiceOverRunning //if VO is running you can't normally tap slider so it won't affect user experience
NotificationCenter.default.removeObserver(self)
NotificationCenter.default.addObserver(self, selector: #selector(setSwitchAccessibility), name: UIAccessibility.voiceOverStatusDidChangeNotification, object: nil)
}
@objc func setSwitchAccessibility() {
slider.isUserInteractionEnabled = !UIAccessibility.isVoiceOverRunning //without VO user should be able to interact with switch directly or / and by selecting cell
}
then your table delegate will get called allowing to do something custom. Observing voice over is necessary to allow handling switch when Voice Over is turned off.
By "something custom" I understand showing activity indicator in willSelectRow and moving focus there by
UIAccessibility.post(notification: .layoutChanged, argument: activityIndicatorView)
and setting accessibility value to nil (set it again to slider accessibility value just before restoring Voice Over focus on cell).
Also - after removing accessibility value from cell Voice Over will read accessibility label of this cell if it exists. In this case you need to temporarily overwrite default accessibilityLabel by custom property of this cell (set to nil) to make it silent using something like that:
var labelToReturn: String? = ""
override func accessibilityActivate() -> Bool {
self.accessibilityValue = nil
labelToReturn = nil
return false
}
override var accessibilityLabel: String? {
get {return labelToReturn == nil ? labelToReturn: super.accessibilityLabel}
set {super.accessibilityLabel = newValue}
}
Remember to change labelToReturn
to something else before removing focus from your activity indicator.