© Daniel Devesa Derksen-Staats 2019
D. D. Derksen-StaatsDeveloping Accessible iOS Appshttps://doi.org/10.1007/978-1-4842-5308-3_3

3. VoiceOver 101 – The fundamentals

Daniel Derksen-Staats1 
(1)
London, UK
 

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.

The following code shows an example of some of the most common accessibility properties in UIAccessibility:
// Do I serve a purpose?
accessibleView.isAccessibilityElement = true
// What is my name?
accessibleView.accessibilityLabel = NSLocalizedString("accessibility.close_button", comment: "")
// What is my personality?
accessibleView.accessibilityTraits = UIAccessibilityTraitButton
// What is my value?
accessibleView.accessibilityValue = NSLocalizedString("accessibility.selector_value", comment: "")
// Where am I?
accessibleView.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibleView.frame, view)
You can see it as five essential questions for defining a UI component as accessible:
  • 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

One case in which it is easy to forget setting an accessibility label is when creating a button that has an icon image but not a title. If we don’t set it, VoiceOver will just read the name of the image you used. If you named the file sensibly, you may be lucky, but chances are the names of your images are full of abbreviations and naming conventions that you have with your designers that wouldn’t be good accessibility labels, like “close_icon_64px_blue@3x”. Furthermore, the name of a file is not localized. Think about that settings cog button item in your navigation bar, the “X” button for closing your modal view, or any playback control buttons, etc. All those examples are good places to go and check that you didn’t forget to add an accessibility label that makes sense.
// Setting an accessibility label with a localized string
button.accessibilityLabel = NSLocalizedString("accessibility.close_button", comment: "")

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.

The following code shows an example of how you could create an accessibility label that reads durations in minutes and seconds in a natural way:
// Formatting the time in a readable format
let dateComponentsFormatter = DateComponentsFormatter()
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = “mm:ss”
dateComponentsFormatter.allowedUnits = [.minute, .second]
dateComponentsFormatter.unitStyle = .full
if let date = timeFormatter.date(from: time) {
        let components = Calendar.current.dateComponents([.minute, .second], from:date)
        durationLabel.accessibilityLabel = dateComponentsFormatter.string(from: components)
}

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

And some other times you will find that VoiceOver might read something in a way you were not expecting, so it is important to test your app and find those scenarios. For example, sometimes in English a word might be written exactly the same way but read differently depending on the context. These are a couple examples where you might want to set special accessibility labels:
// Live Video
"accessibilitylabel.live" = "Lyve";
// Most Read
"accessibilitylabel.mostread" = "Most Red";
Another case in which VoiceOver may read things in a different way than the one you would expect is with words that are in a different language. Take the word “Paella” (typical rice dish from Valencia, Spain), for example. One solution can be to define accessibility-attributed labels. One of the attributes lets you specify the language VoiceOver should use to read that label.
// Specifying the language for an accessibility label
recipeLabel.accessibilityAttributedLabel = NSAttributedString(string: "Paella", attributes: [.accessibilitySpeechLanguage: "es-ES"])
Or, for further control, if you need to, you could specify the IPA (International Phonetic Alphabet) notation. For that, you just need to configure your text with an attributed string for a defined range and add an .accessibilitySpeechIPANotation as the key and the IPA notation as its value. And if that word is within a phrase like with any other attributed string you can specify the attributes just for a specific range.
// Specifying the language for a word in an accessibility label
let ipaAttributedText = NSMutableAttributedString(string: "Paella is a Valencian rice dish")
let ipaRange = ipaAttributedText.string.range(of: "Paella")!
ipaAttributedText.addAttributes([.accessibilitySpeechIPANotation: "paˈeʎa"], range: NSRange(ipaRange, in: ipaAttributedText.string))
ipaLabel.attributedText = ipaAttributedText
And finally, another accessibility-attributed label property you can use is .accessibilitySpeechPunctuation, which indicates that VoiceOver should vocalize the punctuation marks.
// VoiceOver to vocalize punctuation marks
label.accessibilityAttributedLabel = NSAttributedString(string: "Text within ' and ' or " denote either speech or a quotation", attributes: [.accessibilitySpeechPunctuation: true])

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.

One of the options is “Headings.” So if you select to navigate by headings in the Rotor, you can swipe down to go to the next heading or up to go to the previous one.
../images/473721_1_En_3_Chapter/473721_1_En_3_Fig1_HTML.jpg
Figure 3-1

The Rotor

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.

For VoiceOver to know that your UI element represents a heading, you just need to use the header trait.
// Configuring an accessibility trait as a header
headingLabel.accessibilityTraits = UIAccessibilityTraits.header
This is quite common when you need to group some content in an app in different subsections in one view controller, for example, when you have headers in a table view or collection view. The screenshots (Figure 3-2) are two good examples. The first one is from the Search screen in the BBC News app. Just imagine how many swipes you would need to get to the “More Topics” section if those headings didn’t have the right header trait configured. The second example is from the Settings screen in the BBC+ app. It would be difficult to mentally structure and group all the settings options in the different categories if they weren’t organized in subsections with their corresponding heading.
../images/473721_1_En_3_Chapter/473721_1_En_3_Fig2_HTML.png
Figure 3-2

BBC News (left) and BBC+ (right) examples

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.

You may start creating this component by inheriting a plain UIView. When the user brings the VoiceOver focus to a button, they will expect it to vocalize the label followed by “button”, so you can understand that you can interact with it. That important context will be lost in your custom button. Luckily, as you can see in the code, it is really easy to fix by just setting the button trait.
// Configuring an accessibility trait as a button
animatedButton.accessibilityTraits = UIAccessibilityTraits.button

Adjustable

It might be quite rare that you create adjustable UI components, but it is not strange at all to use UISliders. UISliders are accessible by default, they have the adjustable trait and an accessibility value that gets updated and that can be adjusted by swiping up or down. When you have an adjustable component, you can override the accessibilityIncrement() and accessibilityDecrement() functions to adjust the component to values that make sense when using VoiceOver.
// Configuring an accessibility trait as a button
override func accessibilityIncrement() {
        value += stepSize
        accessibilityValue = readableValue()
        sendActions(for: .valueChanged)
}
override func accessibilityDecrement() {
        value -= stepSize
        accessibilityValue = readableValue()
        sendActions(for: .valueChanged)
}

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.

Some other examples of adjustable components you may have in your app can be carrousels for picking up an item or custom components for rating like the one in the image (Figure 3-3), which is included in the code examples repository of the book.
../images/473721_1_En_3_Chapter/473721_1_En_3_Fig3_HTML.jpg
Figure 3-3

Example of a rating UI component

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.

But as we’ve seen with the native UISlider, despite being accessible by default, it might need some tweaks. The default format for its value is a percentage. As in this example from Apple Music: “Volume, 62%”. But percentages are not always a helpful format. If you are listening to a song, you probably want to know the exact minutes and seconds of the progress instead of knowing the percentage of the song you’ve heard. Or if the slider represents prices like in the previous example, you want VoiceOver to vocalize exactly the selected price. If it goes from 1 to 10 dollars and the slider is right in the middle, “5 dollars” will be much more useful information than “50%”. The accessibility value is a string and as you can see in the code example, number formatters might again help you in this case for setting up a readable value.
// Configuring the accessibility value with a currency
let sliderValueString = NumberFormatter.localizedString(from: NSNumber(value: slider.value), number: .currency)
slider.accessibilityValue = sliderValueString

Accessibility hints

They are optional. It is very useful when a UI component needs further explanation on what it does or how to interact with it. They need to be carefully used as they can make VoiceOver very verbose. The good news is that they will be read after a pause, always after the label and the corresponding trait, value, etc. This way, any experienced user that knows already how to use the app can skip this information and carry on using the app. It is a good practice to start it with a verb, as you are going to describe what it does or how to use it. Some examples taken from the Apple Music app too are:
  • “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.

// Configuring an accessibility hint for a UI control
draggableView.accessibilityHint = NSLocalizedString("accessibility.draggable_hint", comment: "")

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.

Take the price slider as an example again (Figure 3-4). If configured properly and a label and value are set, when VoiceOver has its focus on the slider component, it should vocalize something like: “Price. Four hundred and fifty thousand pounds. Adjustable. Swipe up or down with one finger to adjust the value.” That is all the information the user needs. The labels with the text “Price:” and “£450,000.00” have redundant information for VoiceOver, and it could be a better experience to set them as not accessible elements. Images that are purely decorative and that don’t convey relevant information don’t need to be accessible elements either.
../images/473721_1_En_3_Chapter/473721_1_En_3_Fig4_HTML.jpg
Figure 3-4

Interacting with a slider with VoiceOver

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.205.154