Recently one of our major clients decided to make all their apps WCAG compliant in a push to improve the usability for people with disabilities. This meant a lot of work for our iOS and Android engineers, because the app already had a quite complex UI and flows, and accessibility features were not extensively planned from the beginning.
General Questions about Voice Over
What is the bare minimum of the elements I need to add to VO?
– You absolutely need VO support for your app’s navigation and the main functionality (which you’ll have to define yourself).
How long does it take to add accessibility to a ready app (i.e. what should I plan for accessibility from the beginning)?
– In our case it took almost 2 month of developer time to add a fleshed-out VO support to a ready app (including the QA department learning curve for testing VO). This includes work that could be avoided, had accessibility been required from the start.
How to best test my VO work?
– Enable it in the settings and start working with the app as the intended user would. Swipe to move focus, instead of tapping to move focus. This helps to evaluate the layout of elements - not only should every element be declared in comprehensible fashion, but they should also be in a sensible order - default top-to-bottom, left-to-right doesn’t always work as you would expect it to.
Should I spend time double-checking my simple top-to-bottom, left-to-right layouts? Should I expect some unobvious behavior?
– Yes! For example, in this picture, VO will Read ”Wall Moderator”, then ”…”, then ”3mon” - since it considers the coordinates of the top left corner of the view to determine the order.
Does VO understand punctuation marks?
– Yes. Commas, semicolons and full stops add suitable pauses. Lowercase/capital letters at the start of the sentence make no difference. However, if the word consists of capital letters, VO will spell it out, “LOG” will be read as “L-O-G”. Also long numbers “6000” and “6,000” will be read as “six thousand”, but “6.000” - as “six point zero zero zero”.
What are accessibility groups and when are they useful?
– They are not groups per se, but a handy concept. For example, if you have two labels such as this - “Points: 4”, you want the Voice Over to read them together. This is the main use of accessibility groups. You may remove accessibility from one of them and set the whole phrase as accessibilityString for another.
What item should be read aloud when you first come to the page?
– The default option one is usually a good choice. But there are a couple of alternatives you want to consider:
- Say you select a question icon, which opens an auxiliary screen with some explanation. If you navigate back from that screen, it would be a good idea to mode focus back to that question button instead of the top of the screen.
- If your first item is a “close view” button, you probably should move the focus to something else, and announce the close button at the very end.
- If the screen does not have a visible title, you might want to announce one for it.
What can I do if the view is visually complicated with overlapping elements’ frames?
– You can split it into multiple accessible elements, and some of them may be invisible. For example, this view with leaves is tappable. VO should announce info about each leaf number separately, and then announce the whole area as tappable. Our solution was moving focus for the tap action to an invisible narrow view to the right (highlighted with a black border on the image).
Voice Over Implementation Questions
How do I plan for VO from the very beginning of development?
– Here’s a general set of rules that will prove useful:
- Actually hide elements instead of moving them behind other elements.
- Don’t use gesture recognizers on
UIViews
instead ofUIButtons
. - Set
accessibilityLabels
for icon-only buttons to prevent them being announced as “button”. - Group elements with and assign a single
attributedStrin
g to the group when possible (for example, to complex table view cells). - Don’t forget that you’ll have to explicitly announce pop-up elements and alerts
Will the view be announced by VO if it is not visible?
– It will be if you don’t hide it explicitly with isHidden = true
, or isAccessibilityElement = false
.
Why would I need accessibilityElementsHidden?
– Sometimes you want to disallow using elements visible on the screen for the sake of navigation simplification, for example:
- Side drawer menus. When you open a side menu you can still see some of the items on the main screen, but they should not be announced if you don’t want to confuse the user.
- Alerts. You still see a dimmed main screen below them, so the argument from the previous point holds.
- Keyboard. If you let the user move the focus from the keyboard while it is displayed there is no evident way to let them move it back.
What is the difference between accessibilityLabel, accessibilityHint and accessibilityTraits?
– accessibilityLabel
is read as soon as you select an item, accessibilityTraits
are read after a small pause announcing what type of elements that is, and accessibilityHint
is read after a longer pause - explaining in more detail how to use the item, e.g.: “Save” for accessibilityLabel
, “Save (button)” for accessibilityTrait
, and “Double tap to save you changes” in case of accessibilityHint
. You can set accessibilityTrait
to .none and not use accessibilityHint
at all if everything is trivial without further explanation.
If I can set accessibilityTraits however I want, can I make any UIView mimic any other?
– Sometimes. For example, you can add a “.button” trait to a whole table cell, to let users know it’s tappable. But you can’t change an uneditable textField to be read as a button. Even if you do something like this:
It will say: “Email. Button. Text field. Double tap to edit".
How do I announce an item of my choosing, instead of the default one?
– Use UIAccessibility.post(notification: .layoutChanged, argument: <item_you_need>)
. You may alternatively use UIAccessibility.post(notification: .announcement, argument: “You are entering my app now”)
to announce whatever you want.
Also, if you need to do both at once you can do this:
UIAccessibility.post(notification: .layoutChanged, argument: ["Posted successfully.", <item_you_need>])
. VO will read your announcement and then move focus to the place you define.
How do I change the focus order of the items?
– We found a couple of solutions, and we don’t really like any of them. It’s usually better to read them out as is, but if you absolutely must change the order:
- Set
accessibilityElements
for containing view, listing all the items in correct order (keep in mind what will happen to the ones not listed). - Forbid accessibility focus for the actual visible element, and add the invisible one where you want it.
- Change the constraints to create the necessary frames pattern.
How do gesture recognizers interact with VO?
– They don’t work, just as you suspected. Setting accessibleLabels
for them achieves nothing. Pinch gestures don’t work, while rotation, swipe and pan have other functions in the VO mode. You should use actual buttons for taps, and invisible buttons with explanations for all the other gestures.
Why are table view cells read so strangely?
– Default behavior for cells is as follows: VO sets focus on the whole cell and reads all the labels in one go. The focus is then moved in turn to every button.
- What if I don’t want the cell to be read as a whole at all, just all the items in turn?
– You should specify isAccessibilityElement
values for all subviews explicitly. Every label left out of this will be read as part of the whole cell’s accessibiltiyLabel
. If all the elements are accessible on their own, there’s no need for the whole cell’s accessibiltiyLabel
at all, and it isn’t read. Or you could specify all the elements you want to be read separately in accessibilityElements
of the cell itself for the same result.
- What if I want my own accessibility label for the whole cell?
– Set it on cell itself:
- What if I like the default behaviour but want to exclude just a couple of elements?
– Just set isAccessibilityElement = false
to items you don’t want.
Can I customize VO behaviour further?
– Of course.
- You can override the default implementation of
UIAccessibilityContainer
protocol if you want to specify all the elements yourself, and settingaccessibilityElements
just doesn’t cover it. Keep in mind you should do so for all 4 methods at once or it won’t work. - You can specify the trait
.adjustable
for your view, for example for such a view:
The default behaviour would be to read all 7 of them in turn left-to-right. But what if we want VO to read the currently selected option aloud, and proceed to explain how to change the selection (“Monday. Swipe up and down to adjust”)?
For this, you set the accessible label to “Monday”. The phrase “Swipe up and down to adjust” is a default accessibleHint
for .adjustable trait
. And finally you implement accessibilityIncrement()
and accessibilityDecrement()
, to determine what happens on swiping up/down. In the end, the whole control is just one accessibility element, and if you swipe right once you will go to the next view immediately without having to swipe through all 7 days. But you may still select a day if you need to.
Is there a way to continue the VO announcement when I navigate to another screen?
– While the default behavior is to interrupt the VO upon navigation, you can subscribe on UIAccessibility.announcementDidFinishNotification
to check when VO is finished announcing, and navigate then.
Subscribe
Declare the method to call when announcement is finished
Use
How to prevent a button repeating the VO text upon tap?
– Here’s what you do:
- Use
.startsMediaSession
trait on your button, you may then play your custom sound if you have any (UIAccessibility
’s notifications will be silenced too however). - For a more hacky approach you can change the button’s accessibility label to what you need instead of its name (you will have to change it back if you still need this button).