In this chapter we will start to deep dive into the accessibility APIs offered by Apple, especially UIAccessibility. We will learn that by using native UIKit components, your app can be accessible with very little effort. We will also go through some of the best practices to create a great experience for VoiceOver users, including how to label your UI components properly, what is the Rotor, and a grand tour of the most important accessibility traits and how to implement them appropriately.
Understanding UIAccessibility
It is time to have a look at some code. You’ll find all the code examples from this book in this repo: https://github.com/Apress/developing-accessible-iOS-apps. At the core of accessibility in UIKit, we have UIAccessibility. From Apple’s documentation it can be defined as “a set of methods that provide accessibility information about views and controls in an app’s user interface.” Assistive technologies like VoiceOver, Voice Control, or Switch Control will rely on the correct implementation of these APIs to help users interact with your app with their preferred input/output mechanisms (screen reader, braille keyboard, switches, voice, etc.) depending on their needs.
isAccessibilityElement : Most UI controls have this value set to true by default. UIViews don’t. So if you are defining custom controls, you may find that you have to set this property to true if you want VoiceOver to find it.
accessibilityLabel : For native UI controls, it is inferred from the control title. It is what VoiceOver will announce first. It is a good idea to localize the label so that it works in any language you support.
accessibilityTraits : They can be combined with the “|” operator. They are also combined with the ones in the superclass too. Examples of different traits are Adjustable, Selected, Not Enabled… They are localized for you. Traits are very important, because they define the context around the UI component. We’ll come back to them later.
accessibilityValue : Read by VoiceOver when it differs from the label. Some examples are a slider value or a text field that could have a label like “Name”, but the value would be the text entered.
accessibilityFrame : Already set for any UIView and subclasses. It defines where the UI element is in the screen coordinate system.
Some of these properties can be configured from the Identity Inspector in the Interface Builder when selecting an element, but most of the examples in the book will show how to deal with them in code. There is nothing wrong with configuring things in Interface Builder, but doing it in code has some benefits like the ability to localize the accessibility labels you want to use or tweak the behavior based on the user’s interactions.
And there are many other properties. We’ll explore some of them in the following chapters.
Accessibility labels
Getting your accessibility labels right is probably one of the most important things you have to do to offer a great experience for your VoiceOver users. In principle, the label will be the title of the component, if it has one. If it hasn’t, setting an accessibility label is just a matter of assigning a meaningful string to the UI component’s accessibility label property. It is better if you set a localized string, that way it will work for all the languages your app supports.
Buttons
It is also important to keep it as short as possible; we don’t want to create experiences that are too verbose. But there are some cases in which a bit more context is needed. For example, a good label for a button with a “+” icon could be just “Add.” That would be enough in some situations. If it is placed in the navigation bar, for example, it is probably representing a global action in that section of the app where the context is clear. Could it be adding a new note? Or creating a new photo album? But if it is within the content of the app, it may require further clarification. Consider a music app example: is it clear that you are adding the album to your music library? Or are you adding a song to a playlist? Or are you following an artist in which case “Follow” would be a better label? If it is not clear, you may want to add some more information in the accessibility label, like “Add song.” If it is still not clear, you can also use a hint for further explanation on what actioning the button does. We will talk about hints later in the chapter.
We also want to avoid redundancy. Back to the music app example, if we are in the player, we also know we are dealing with a song. Instead of “Play song,” “Pause song,” “Skip song,” “Repeat song,”… we can just use “Play,” “Pause,” “Skip,” or “Repeat.”
Some buttons also change their state and mean different things. In that case we want to remember to update the label when the state changes. If we have a follow button, when pressed, it may change to an unfollow button. Or a button for adding a song to a playlist may change to delete from the playlist, once added.
And finally, when adding accessibility labels, please never add the type of the element to the label, as VoiceOver will already vocalize the traits of the component. More on accessibility traits later in the chapter too. This is something that happens a lot in the case of buttons. It wouldn’t be a good practice to use the word “button” in the accessibility label. If we added the label “Add button” in the previous example, VoiceOver would read “Add button. Button” and that would just be unnecessary duplicated information.
In short, I would say that, when setting our accessibility labels, we want to give as much context as possible avoiding verbosity and redundancy.
Abbreviations
Sometimes you will also need to set a different accessibility label to the title of the UI component. A classic example is when showing times like 01:30 for minutes and seconds or when using abbreviations like 2d for 2 days or 2 days ago, although sometimes VoiceOver is smart enough to read properly some date abbreviations.
Pro tip
When dealing with units or information that needs formatting (currency, time, and dates, or even addresses, etc.), you will almost always find some formatter in Foundation that will help you deal with them, especially if you need to internationalize or localize them, and you will also have a higher chance of VoiceOver reading them properly.
Remember to test it
But there are many other tweaks and configurations available. So, if when testing VoiceOver anything sounds odd, you may find something in the attributed strings documentation for accessibility attributes that helps you create a more natural experience with VoiceOver: https://developer.apple.com/documentation/uikit/accessibility/uiaccessibility/speech_attributes_for_attributed_strings
And one more thing…, or two
Animations and spinners: it is quite easy to forget to configure a label for your animations that are communicating important information, for example, when “Loading…,” “Updating…,” or “Uploading…” any content in your app. Please give your spinner a label. I know most of times your app loads really fast, but sometimes users might have poor signal and it can take a couple seconds.
The verbosity exception rule: I know I said we must avoid verbosity, but sometimes in order to give enough context, you may need to be verbose. This may happen with information that is very visual. If there are photos, it is great to properly give some details about the images. Or with a stickers pack app, where it is great to explain what each sticker is about. It is all about finding the right balance: avoid unnecessary verbosity while providing information that is useful.
Accessibility traits
Traits give assistive technologies extra information on how to treat a UI component and what the user can do with it. There are many – around 17 at the time of writing this book – and it is better to understand what they are for with some examples:
Header
Some traits make navigation much easier. Imagine having to read this book without any headings at all. Think about how useful it is to be able to quickly visually identify the different chapters and sections in the book to find the information you need straight away or to pick it up where you left it before. There is a way of letting VoiceOver users jump from one section to the next/previous one, and, for that, we’ll need to introduce the concept of the Rotor first.
You can use the Rotor (Figure 3-1) and select one of its options by using two fingers and rotating them on the screen around a center point, as if you were using an invisible knob. And as you rotate, it will speak all the options you can use to navigate the app. Once you select the Headings option, for example, you can navigate using swipes up/down to go to the previous/next header.
Finally, in the VoiceOver settings, you can customize the options in the Rotor. There are tons of them (speaking character by character/word by word..., changing the speaking rate, changing the typing mode, etc.), and it is worth going through the list to check that your app supports them correctly.
Button
You will also find that sometimes you need to configure a specific accessibility trait when you are trying to write a custom component that will mimic a native counterpart. For example, you may want to write a custom button because you want it to have a nice animation when the user taps on it.
Adjustable
For example, if your slider shows prices from $100,000 to $2,000,000, you might want to increment and decrement in chunks of $100,000 - which would be the value of the variable stepSize in the code -, and not of $1 or $10, or it would be almost impossible to adjust it to sensible values.
You may also want to send a value-changed action just in case you are relying on that action for some other logic, like updating the text of a label so that it reflects the value of the slider.
Don’t worry about the accessibility value for the moment, we’ll get to it in the next section, in which we’ll also see an example of a possible implementation for the readableValue() function.
The important thing to remember here is that this is the way you can allow a user to adjust your adjustable UI component and to decide which value to set the component to when it gets incremented/decremented.
And more
The best thing you can do is to go to Apple’s documentation and familiarize yourself with some of the traits available (link, selected, not available, etc.) and come back to them especially when you are creating custom UI components or navigation patterns in your app: https://developer.apple.com/documentation/uikit/accessibility/uiaccessibility/accessibility_traits.
Accessibility value
Imagine that you are developing a UI component that needs to hold a value. The classic examples are a progress bar for playback content, or a volume slider, or a text field whose value depends on the text that the user types. So if you are implementing a UI component instead of using a UISlider or UITextField, or any other custom component that holds a value, you will have to configure this property accordingly.
Accessibility hints
“Double tap and hold, wait for the sound, then drag to rearrange.” This is what VoiceOver vocalizes when moving the focus to the draggable control for rearranging songs in a playlist.
“Double tap to expand the mini player.” When playing a song, you can minimize the fullscreen player, but a mini player will stick to the tab bar at the bottom of the screen, so you can see what is playing at all times. It is not immediately obvious that you can go back to the fullscreen player by double tapping it, so a hint in this case helps to learn how to better use the app.
It is a good idea to localize the hint, so you can easily translate the app into different languages and give VoiceOver a hint in the user’s preferred language.
Accessibility element
It is very tempting to start assigning the isAccessbilityElement property to true for every single view, but that doesn’t always help and there is a reason why it is false by default in views and why you can even change it in any other UIKit component, where it is true by default, to false. Not every single element on the screen needs to be an accessibility element, and the only way to determine which ones do make sense to be set as accessible and which don’t is by using and testing your app.
The joy of using UIKit components
One thing you quickly learn when you start doing some iOS development is that using native UIKit components as much as possible is going to make your life immensely easier. This is due to the fact that when Apple introduces new features or improvements in the SDK, you’ll get most of those for free or with very little implementation effort. Some examples are when Apple introduced split screen in the iPad, drag and drop, peek and pop, devices with new screen shapes like the iPhone X, right-to-left support in iOS…, and many others. All those features were very easy to adopt if you were using size classes, UITableView and UICollectionView, UINavigationController and UITabBarController, etc. But it could easily become weeks or even months of development work if your app was using custom implementations.
That can also be the case for accessibility. By using native UIKit components, your app will be much more accessible than you might be even expecting, because the framework does the heavy-lifting work for you. The controls will have the right traits for VoiceOver and these will be localized, the accessibility labels will be inferred from the title of the component, navigation gestures will work as expected, etc.
But when developers opt for developing custom UI controls, they very often forget about addressing any accessibility issues that these components might have. It is totally fine to develop your own UI component if you need it to support any functionality that shall not be present in its native version. However, it is important to remember to set the appropriate accessibility properties. It is also important to know that users are accustomed to the standard behavior from the apps that come pre-installed in iOS. That is why it can be a good idea to try for it to behave as much as possible as its Apple’s counterpart, so that the user knows what to expect when navigating your app.
For example, when creating custom tab bars, developers sometimes forget to implement the right accessibility properties and functionality. When using VoiceOver on a tab bar, using Apple Music’s example, it will read something like “Selected. Library, tab. One of five.” If you swipe right, it will go to the next tab, and you’ll hear something like “For You. Tab. Two of five.” You’d be surprised how often, when using an app with an in-house developed tab bar component, you get a very poor experience. I’ve seen multiple times apps that, when using VoiceOver, you just hear “Button” for each one of the tabs, especially if the tabs don’t have a label with a title, and even if they do, it doesn’t say important information like if the tab is selected or how many of them there are. Or even worse, sometimes they’re not accessible at all if the developers haven’t even used UIButtons in the implementation.
If I could give any advice to new iOS developers when they start, that would probably be basically to stick as much as possible with UIKit components to build your UI. As I said, not only will it make your app much more maintainable in the future, but it will also help you create more accessible apps.
Summary
Accessibility labels have to give as much context as possible, avoiding verbosity and redundancy.
Accessibility labels can be attributed. These attributes can specify the language of the label and even the IPA notation for further control on how the label should be vocalized by VoiceOver.
The correct use of the Heading accessibility trait makes navigating with VoiceOver much easier and structured.
Accessibility hints give users further explanation on how to interact with a UI component, but they are read after a pause so experienced users can skip them.
We have just learned some of the most important concepts for creating an app that works great with VoiceOver. But there is more; in the next chapter, we will bring the levels of support of your app with VoiceOver up to 11.