Improving Accessibility in VoiceOver using SwiftUI
By Gabriel — November 30, 2023
Recently, we have released version 6.2 of our flash card vocabulary learning app Wokabulary for Mac, iPhone, and iPad. But this update has no visible changes. In fact, Wokabulary 6.2 is all about accessibility and improving VoiceOver support to enhance it for people who are blind or have impaired vision.
Wokabulary has always had support for VoiceOver and Dynamic Text. But just like with visual interfaces, being usable is not enough. We want our apps to provide an equally great user experience on both the visible UI and the invisible VoiceOver interface. We want it to be joyful to use for everybody.
Luckily, we have a blind user who gave us a lot of feedback on where Wokabulary’s accessibility needed to be improved.
And she was very patient explaining to us how blind people use apps and what common difficulties they face.
Here is what we learned:
Accessibility is not about programming but about design
There are some minimum technical requirements you need to meet to make your app accessible. Apple provides a great overview their Human Interface Guidelines. Actually, if you just use standard SwiftUI components, your app is probably already technically accessible.
But there is more to it. When you design your visual interface, you will pay close attention to how you group elements, how users can easily access important features, and how you visually separate the most important elements from the details. You need to do the same thing for VoiceOver users.
For example, just because two elements are close together does not mean they will also be read out after each other by VoiceOver. With VoiceOver you navigate from element to element. There is no visual structure, so the order of the elements is crucial. Also, some elements might need a more explicit description. Maybe you have two edit buttons on a view. One next to an image and one next to a text field. To a sighted person, it is obvious which button edits which element. But in VoiceOver your buttons should be labeled “Edit image” and “Edit text”.
Don’t trust the Accessibility Inspector
The Accessibility Inspector is a great tool to quickly check your accessibility labels and values. But it provides little information on how the actual navigation feels to a user. It is essential that you test your app with VoiceOver on a real device.
To get started, go to System Settings → Accessibility → VoiceOver.
Activate VoiceOver and then start the VoiceOver practice offered there.
It will feel very awkward at first, and you will need some time to learn the commands.
But actually using your app like a blind person would, is the only way to see if the user experience is any good.
In VoiceOver mode on the iPhone, tapping an element makes the system read a description of it. To perform an action like tapping a button, you need to double-tap. So, to deactivate VoiceOver again, you need to double-tap the VoiceOver toggle. On the Mac, you can toggle VoiceOver by pressing ⌘+F5 on your keyboard.
Also, it is tempting to just tap on the elements you see to activate them. Of course, this is not how a blind person would use it. Some blind people scan the interface by moving their finger over the screen, which also provides some hints on the visual layout. But many people use the swipe navigation, where you move back and forth between elements by swiping left or right. On the Mac, there are equivalents with mouse scanning and keyboard navigation. Your app should work well with both modes!
Once you got a hold on these, you should learn the basics of the Rotor, which is an essential tool for Accessibility navigation.
SwiftUI Accessibility Tips
When you use standard SwiftUI elements, accessibility is built-in. Just make sure to add labels to all non-text elements like images.
The harder part is making the navigation convenient. Here are some tips:
When you have a container view like an HStack
, VStack
, or a composed view, it sometimes makes sense to expose it as a single element to VoiceOver instead of making any subview navigable.
You can easily do that with the .accessibilityElement(children: .combine)
modifier.
If you have a pair of elements that belong together (like a label and a value) but they are not in the correct order in the VoiceOver tree, you can manually pair them with .accessibilityLabeledPair(role:id:in:)
.
You can influence the order of the elements in the accessibility tree to divert from the visual order using the .accessibilitySortPriority(_:)
modifier.
In views with dense content like settings or object details, it can be useful to mark headlines explicitly.
You can do that with the .accessibilityHeading(_:)
modifier
Accessibility is neither a niche nor a luxury
Unfortunately, there are people who think that it is not worth the effort to add good accessibility to your apps. But apart from making the world a better and more inclusive place, improving the accessibility of your app also is an investment in your product.
In the US, 2.4% of the adult population are legally blind. Increasing your (potential) user base by 2.4 percent points is significant. Probably, you can already improve the UX significantly in just one sprint. And this will not only make your app better for blind people, but also for everybody else with any kind of disability.
Accessibility is very important to Apple, too. When you apply to be featured on the App Store or happen to talk to somebody from Apple, having good accessibility UX is a big plus and can be what sets you apart from your competitors.
Finally, as a developer or designer, making your app (or website) accessible is simply part of building a professional product. As Chris Ferdinandi puts it on his blog: “This is your job”. Yes, you can do the bare minimum to comply with legal requirements. But would you do the bare minimum on the visual interface?
A good product must have good usability — for everybody.
Keep in mind that people with disabilities have tightly-knit communities. If your app has excellent accessibility, you can be sure that your users will recommend it to their friends.