UIKit Lab running on iPhone and iPad.A VoiceOver bubble explaining the grid style on this page.A VoiceOver bubble explaining the column style selector on this page.A VoiceOver bubble explaining a slider on this page.

Build a Great VoiceOver Experience in UIKit Lab

When UIKit Lab v1.0 came out, thanks to UIKit's built-in accessibility features, it was already able to address the need of users facing certain disabilities pretty well. However, v1.0 was a bit lacking in one crucial aspect of accessibility feature—VoiceOver. To make sure UIKit Lab can deliver a great VoiceOver experience, after the release of v1.0, I started working on enhancing the VoiceOver experience in UIKit Lab by redesigning/refactoring certain custom UI components and following industry-recommended principles. After countless late nights, UIKit Lab started "properly" supporting VoiceOver since v1.1.

The problem

Since v1.0, UIKit Lab has been using native iOS UI components provided by UIKit throughout most parts of the app. Thanks to UIKit's built-in accessibility features such as Dynamic Type, Button Shapes, and Reduce Transparency, v1.0 was already able to address the need of users facing certain disabilities pretty well. However, VoiceOver support in v1.0 was not as great. For example, some custom UI components were lack of proper accessibility labeling or grouping logic, making them not as accessible as native iOS UI components.

A closer look at issues

To test UIKit Lab's support for VoiceOver thoroughly, I tapped almost everywhere tappable in UIKit Lab with VoiceOver enabled to understand how VoiceOver identified and read out onscreen UI elements, then record the issues I found. To summarize, there are X types of issues:

  • 1
    Custom UI components and certain symbols didn't have proper accessibility labels.
    This symbol button is to toggle light/dark mode, but its accessibility label reads "do not disturb/brightness higher".
    This custom widget is supposed to work as a button, but VoiceOver reads it as plain text label rather than a button.
  • 2
    Helpful accessibility hints were missing in some places of the app, failing to provide necessary context to users.
    In this example, user can tap the first table cell to change the glyph used for the button, but when VoiceOver focuses on the first table cell, it only reads "star, missing necessary context about what action to take.
  • 3
    Custom UI components didn't have proper grouping logic, making navigation lengthy and relevant information separate and harder to understand.
    This slider table cell contains three separate elements: minimum value, slider, and maximum value. However, VoiceOver also reads them one by one, requiring more navigation and mental effort for users to understand what this cell does.

Build a great VoiceOver experience

To build a great VoiceOver experience, there are many aspects of

Contextual labeling of UI elements

There are many system-provided and custom SF Symbols used in UIKit Lab. However, they were identified by VoiceOver with their default accessibility labels. This caused issues like the one when a "moon" symbol was supposed to toggle light/dark mode of the UI, but VoiceOver read it as "do not disturb", which is its default accessibility label.

To fix such issues, I carefully audited accessibility labels of all out-of-the-box and custom SF Symbols in UIKit Lab to make sure they convey accurate and concise description of their action.

navigationItem.rightBarButtonItem?.accessibilityLabel = traitCollection.userInterfaceStyle == .light ? "Toggle to dark mode" : "Toggle to light mode"
All SF Symbols used in UIKIt Lab have accessibility labels that are carefully adapted to reflect their actual functions.

In addition to SF Symbols, some custom UI components had similar issues of having unclear accessibility labels. For example, some custom buttons were read by VoiceOver as plain text label rather than a button so users might have trouble figuring out what they actually are. In comparison, a standard button will be read as "XXX (button's label), button".

To fix such issues, I added proper suffixes to the accessibility labels of all interactive custom UI components and made sure they accurately describe what these custom components are and how they work.

cell.selector1.accessibilityLabel = "Regular navigation bar title, button" + (cell.selector1.isSelected ? ", selected" : "")
cell.selector2.accessibilityLabel = "large navigation bar title, button" + (cell.selector2.isSelected ? ", selected" : "")
All custom UI components now have dedicated accessibility labels that clearly speak out their identity.

Proper semantic hints of UI elements

Accessibility hints can be flexible. The accessibility hint for a component in a certain UI might be totally different when it is used in another UI. Regardless of where a component is placed, the point of accessibility hint is to provide proper and helpful semantic information of a component.

Therefore, for all UI components, system-provided or not, their accessibility hints need to provide context relevant to the specific UI. This is exactly what I did to enhance accessibility hints in UIKit Lab.

cell.accessibilityHint = "Double tap to change glyph"
To better assist users, many UI components' accessibility hints have been carefully rephrased to provide context relevant to the specific UI.

Efficient grouping of accessibility elements

Previously, some custom UI components didn't have proper grouping logic among their child elements. As a result, user would have to swipe multiple times to get through each label and control that should have been treated as a whole entity. This made navigation lengthy and harder for users with vision impairment to understand the connection between child elements.

To fix such issues, I audited all custom UI components and tried to group relevant child elements into one large group. On top of that, I gave such group meaningful accessibility label so that user can easily understand what it does as a whole.

cell.slider.accessibilityLabel = "Spacing slider with minimum value of 0 and maximum value of 100"
By grouping labels and controls within custom UI components, navigation between components become much more efficient and easy to understand.

What I learned

Developing an iOS app (in fact, any product) is not easy, but that should not be an excuse of missing/average accessibility experience because any individual can potentially benefit from a great accessibility experience. When it comes to actual development, it is better to consider the accessibility experience as an integral part of the app since the very beginning. That way, a more consistent accessibility experience can be available from the first version of the app, even though sometimes it means more development time, it is still better than releasing a version that provided an average accessibility experience then enhancing it afterward. I learned my lesson and I will always try my best to follow this principle. 😝😝

Read more