VoiceOver is Apple’s technology for allowing blind and partially sighted users to access iOS devices. As they describe it, VoiceOver is "a gesture-based screen reader that lets you enjoy using iPhone even if you don’t see the screen". Apple tries as much as possible to give VoiceOver support to developers ‘for free’ by enabling it on standard system interface elements that are used in apps, like buttons and labels, and by providing sensible default values if you don’t customise them.
From the beginning, I’d been working to build good support for VoiceOver into Intro. As part of that, I created functional UI tests for Intro using the KIF iOS Integration Testing Framework as part of Intro’s suite of automated tests. KIF tests run by controlling an app using the same accessibility frameworks that are used by VoiceOver. This meant that from time to time, I’d go to set up a functional test for a new part of Intro, and I'd discover that it wasn’t possible to complete some step of the task—a button wasn’t clickable, say. I’d figure out why, and add the necessary accessibility label or enable accessibility for that element. This process meant that by having a good test coverage of key functional tasks that users do in Intro, I can be certain that those tasks can also be completed by users using VoiceOver.
Can be completed, yes. But that doesn’t always mean it’s pretty. As I familiarised myself with VoiceOver, I found screens where there were just a heck of a lot of interface elements that were accessible. This means you potentially are swiping through a lot of what seems like noise. As a person deeply familiar with iOS, but completely unfamiliar with VoiceOver, it felt a bit overwhelming. As discussed, Apple enables VoiceOver for standard interface elements ‘for free’, because that’s the best default. But at times it felt like some of what was presented in VoiceOver for Intro was as a result too repetitive. And even as I spent more time digging into it, what I realised was I didn’t know what best practices were for what interface content should be presented via VoiceOver. Purely decorative interface graphics—of course, omit. Core content being presented by the app—of course, include. The grey area was in things like whether to name containers of controls, or finding cases where a label was separate from a button, so the button name is spoken (a name not visible in the visual interface) and then the label text is also spoken. Seems like I should perhaps be customising the defaults so that users weren’t presented with needless repetition? But how to be sure?
Let’s come back to that at the end. Time to look at a specific example.
Auditing the Intro main menu
I’d noticed a little while ago something odd about Intro’s main menu and VoiceOver. There was no question the menu was accessible—every one of those functional tests started from here, after all—but I noticed a few things that weren’t right. There was some duplication that seemed unnecessary, and one button behaved differently to the others. So, as part of my year-long plan to make Intro the most accessible app on the App Store, I started with my focus on this main menu.
The first video here gives you a VoiceOver tour of that main menu in Intro 1.2.1, before any fixes had been made. (47 seconds, watch with either audio or Closed Captions to hear the VoiceOver content when swiping through the menu contents and back.)
There’s a number of things I noticed here that needed thinking about:
- As you swipe through, sections of each main button are targeted three times. First, the target is the entire button, and it says “[Name of button] button”. The next swipe, the target moves to the main button label, and reads the text—which is the same as the name of the button. Finally, the target moves to the subheading label, and reads that out. Three swipes for what is intended to be one button.
- Along the way down this menu, one button is different. For the Learn button, the first target, of the entire button—is skipped. It goes straight to target the label, and then the subheading. This is unintentionally different to the other buttons, so whatever I decide is the right behaviour for these buttons, I need to make sure I fix this one so they all behave the same. As shown at the end of the video, you could still reach the Learn menu that this button is supposed to link to, because that button label was tappable. That label was being targeted by the functional test in this area, and so this discrepancy hadn’t been picked up via that process.
- Somewhat similar to the first issue, after VoiceOver reads the three buttons across the bottom of the screen, “Settings button” and so on—with three more swipes you move across again, while VoiceOver reads out the labels on each of these three buttons.
- Finally, a possible issue right at the start. When VoiceOver is activated it states the name of the app, and then the first item targeted is the logo in the top left corner, where VoiceOver says “Intro logo image”.
This second video walks you through VoiceOver on the main menu in Intro 1.2.2, when the fixes discussed here had been implemented.
One target per main button: It seems pretty clear each button on this main screen should be only a single target for VoiceOver. The reason they are not was obvious pretty quickly when I looked at it. Each button had been designed as a UIButton itself, which filled the full blue stripe, overlaid on which was an icon, a main label, and a subheading label. The label and subheading were both UILabels, used instead of the built-in UIButton label text as this provided the required layout customisation. However, since VoiceOver accessibility is enabled by default for both UIButtons and UILabels, all of them were thus being read out.
Fixing this was pretty simple for the main labels—given the button itself was already appropriately labelled in VoiceOver, reading out the label was redundant. I simply disabled the label as an accessibility element. What to do with the subheadings required slightly more thought. They offer information that is additional to the main button name, so it’s not redundant. But would a VoiceOver user really want this read out every single time? For users of the visual interface, it’s there as a hint as to what the main button is for, but it is also downplayed by putting it in a smaller size. Turns out, there’s an ideal option for this in VoiceOver—the
accessibilityHint string. From Apple’s documentation, this is "A brief description of the result of performing an action on the accessibility element, in a localized string." Sounded perfect. I applied the text of each subheading as an accessibilityHint on the main button, and then like the main label, set
isAccessibilityElement to false for the subheading itself. As you see in the video, initially VoiceOver reads out the main button label. If you keep moving, you don’t hear the hint. In the second half of the video I pause longer between swipes. When you leave an accessibility element as the target after the label is read out, after a slight pause VoiceOver reads out the hint. Perfect. Finally, VoiceOver users can control whether they hear hints or not… so an Intro power user could choose to turn these off. Even better. Ironically, I’ve been toying with whether Intro should offer a feature in Settings for users to hide these written hints, when they are familiar with Intro. So in this case, VoiceOver users have a feature today that might one day also be provided to other users of the app. Nice turn of the usual tables?
That different Learn button: Nothing complex here. At some point, the main Learn button had for unknown reasons had
isAccessibiltyElement set to false. This was toggled back on, and this button treated just like the others above.
One target for Settings, Help and Support buttons: Similar to the above, these buttons are in fact an assemblage of a UIButton, an icon, and a label. As the buttons were already correctly labelled for accessibility, all that was required here was marking the additional label as not an accessibility element, as above.
The logo: My understanding is that graphics that are purely decorative should probably be xcluded from VoiceOver—though I’d be pleased to get advice on this. The Intro logo though I felt was not purely decorative. For people using the visual interface, the logo serves as a marker that Intro is the app they are in, and an anchor that this menu is the top level in the Intro hierarchy, as it doesn’t appear anywhere else in the app. For those reasons, I thought this probably should be an accessibility element.
Actually using the app in VoiceOver, I felt quite differently. Firstly, it seemed redundant to always have to swipe away from the logo—which is not selectable and has no associated action—to get to the first button. Secondly, VoiceOver was reading redundant content, because it already announces the name of the app when you launch it or change to it. That was the clincher. VoiceOver already provides by default the marker purpose that the logo serves in the visual interface, so it isn’t needed as an accessibility element.
VoiceOver comes for free, which is great, but there are some caveats. Most important among these is to watch for times you’re composing multiple UI elements, like a UIButton and one or more UILabels, into a single logical target. Make the item on which the user can take action the accessibility element, set all relevant information in the accessibility label and accessibility hint, and disable accessibility on the other parts of the assemblage. For an even cleaner look, ensure that the tappable item also has the most logical target outline, such as by ensuring your button itself fill the entire part of the view that appears to be shaded as that button in the visual interface. That way, the target outline in VoiceOver will nicely match your visual interface. Remember, many visually impaired users have some vision, and not all VoiceOver users are visually impaired at all—they may be using these accessibility frameworks due to motor difficulties, convenience, or many other reasons.
As for actually implementing fixes like this, once you get your head around VoiceOver, it’s not that difficult to do. A contact of mine at the New Zealand's Blind Foundation previously was a trainer with their clients in using mobile technologies. (She also has expert lived experience in being blind.) She put me on to the LookTel VoiceOver Tutorial for iPhone. This great app walks you through VoiceOver, going beyond the basics into the more sophisticated ways that you interact with an iPhone using VoiceOver. I can’t recommend highly enough the benefits of using this free app, in getting your head around a user’s VoiceOver experience, and to begin to see what a VoiceOver user might expect in your app.
These changes went out in Intro 1.2.2. More changes are coming soon. It took way longer to make these videos and document these changes than it actually took to identify the issues and resolve them. I’m going to keep on writing about this journey to make Intro as accessible as possible, and if you’re a developer, I’d encourage you to do the same—the more examples and resources out there, the better.
My main accessibility challenge to other developers though is to try and ship accessibility improvements to your app every month for a year—ideally shipping them with every release. Changes like this don’t take long, and they make a big difference to many users (and potential users you’d otherwise lose). They also give you something good to write about in those release notes, which is great, since Apple now shockingly require release notes to contain actual content.