How to support VoiceOver in a SpriteKit game?
So I'm investigating adding VoiceOver support to a solitaire card game app I'm developing, and I'm running into some trouble, and it was suggested that I post on this forum to perhaps get some help.
Are there any developers who have managed to use SpriteKit to make a game that is VoiceOver-capable?
Most of the information I find online refers to connecting standard UIKit controls to VoiceOver. If you don't do that, you need to use an informal protocol to make one of your views a "container" of accessible elements, which means that you can't let Apple do all of the heavy lifting for you.
I've successfully gotten buttons and text boxes working with VoiceOver, but I'm having trouble going any deeper than that. Some of the problems I am running into:
* When a new SKScene is loaded, VoiceOver automatically selects a button and announces it, which interrupts any announcement that I may have issued. Sometimes, a new SKScene is something I want to annotate, such as when you begin a new hand or something.
* UIKit provides a richer set of controls that I can't figure out how to implement in SpriteKit. For instance, I have a card reference section of the game where you can drag through a list of cards, tap one, and get information about it. Normally, that would be a paged UIScrollView or UICollectionView that you swipe through, but I don't see how to make the equivalent control in SpriteKit. So how do I implement this functionality?
* VoiceOver sometimes tries to speak SKLabelNodes, which is not something I coded, so it *seems* like SpriteKit is intended to have some out-of-the-box VoiceOver functionality, but I can't find any information about this. If there's some kind of Apple-supported SpriteKit/VoiceOver support, I don't want to fight against it.
So, if you're a developer who has implemented some of this stuff in SpriteKit, what do you do? Have any advice?
Okay, after lots of digging and experimentation, I have some results I can share, in case anyone else encounters these issues.
The basic model for supporting VoiceOver in SpriteKit turns out to be very simple. SKNodes conform to the same protocols that most UIKit elements do. For any given node, you can set isAccessibleElement to true, set the accessibility label, accessibility hint, and accessibility traits, and VoiceOver will treat them accordingly with no other effort from you. In particular, there's no need to implement any of the informal accessibility protocols described for custom UIView subclasses (although you can if you need that level of control, such as to control the swipe order of elements).
HOWEVER, there is currently an issue which interferes with this system in some cases.
VoiceOver touches are propagated down through your view hierarchy and cancelled if at any point in the path down to your node the touch is outside the LOCAL bounds of the node. This means that the touchable area for any node is the intersection of its own bounding box and the bounding boxes of all of its parents. If this intersection is null, then the item is inaccessible, and any touch registers on the SKView instead.
One of the consequences of this is that anything that is a child of an SKNode will become inaccessible with VoiceOver, because an SKNode has no width or height, and thus, it will block all discoverability of any of its children because the intersection of its bounding box with any other node will always be zero. If you use SKNodes to organize your scene, they will kill all accessibility for any of their descendants.
This can cause all sorts of problems. For instance, if you are implementing the informal accessibility "container" protocols, this can cause bad things to happen, like breaking out of the responder chain and causing swipes to the next item to go to the status bar (carrier bar strength, time, etc.) instead of to what you would expect.
Because of the above, currently the easiest way to support VoiceOver is to ensure that all of your accessible elements are simply direct children of the scene. If you cannot have your elements direct children of the SKScene, you can work around this issue by parenting any accessible element you want to be available to a node that encompasses it in size, such as a transparent SKSpriteNode instead of an SKNode. But depending on what you're doing, this can cause its own problems. In general, you should try to keep your hierarchies as "flat" as possible.
Ideally, VoiceOver would consider the accumulated frame of a node and all its children before deciding to reject a touch because of the above issues. I have filed a radar with this issue: #28355567.