Skip to content

Latest commit

 

History

History
432 lines (307 loc) · 22.7 KB

voiceover-guidelines.md

File metadata and controls

432 lines (307 loc) · 22.7 KB

VoiceOver Guidelines

If you haven't worked with VoiceOver before, we recommend going through the Using VoiceOver guide first.

Table of Contents

Guidelines

Basics

Providing support for VoiceOver is quite straightforward. For most cases, providing only three attributes should be enough:

  • accessibilityLabel: A short, localized word or phrase that succinctly describes the control or view, but does not identify the element’s type.
  • accessibilityTraits: A combination of one or more individual traits, each of which describes a single aspect of an element’s state, behavior, or usage
  • accessibilityHint: A brief, localized phrase that describes the results of an action on an element

As an example, for a Share button, the recommended attributes would be:

button.accessibilityLabel = "Share"
button.accessibilityTraits = .button
button.accessibilityHint = "Opens the sharing sheet."

However, it is important to provide helpful and accurate attributes. The strings used for the attributes should also be localized.

Labels

For a regular control or view, Apple recommends that labels should:

  • Describe the element briefly.
  • Not include the type of control or view.
  • Begin with a capitalized word.
  • Not end with a period.
  • Be localized.

Beginning with a capitalized word and not ending with a period helps VoiceOver read the label with the appropriate inflection.

Traits

The traits attribute contains one or more individual traits that, taken together, describe the behavior of an accessible user interface element. Because some individual traits can be combined to describe a single element, the element’s behavior can be precisely characterized.

Here are some guidelines for using accessibility traits.

  • Use .link instead of .button if a button will show the Safari app. A transition to a separate app may not be immediately obvious to a VoiceOver user. VoiceOver speaking “link” instead of “button” helps with this.
  • Take advantage of opportunities to use .header. Using the rotor, VoiceOver users can opt to navigate by headers. The presence of the .header trait helps the user understand the structure of the screen.

Hints

Apple recommends that hints should:

  • Briefly describe the results. What is going to happen after interacting with this control or view?
  • Begin with a verb and ignore the subject.
  • Begin with a capitalized word.
  • End with a period.
  • Do not include the name or the type of control or view.
  • Be localized.

Always add Labels to Text Fields

By default, text fields with placeholders provide sufficient accessibility. VoiceOver will read the placeholder and describe that it is a text field.

In the following example, VoiceOver will speak “First name, Text field, ...” for the First name field, and a similar message for the Last name field.

However, a problem shows up if you enter “John” as the first name, and “Doe” as the last name.

VoiceOver will no longer read the “First name” and “Last name” placeholders. How will a user, especially someone with short-term memory, know which field is which?

You can improve the experience by making sure that the text fields have accessibility labels.

firstNameTextField.accessibilityLabel = "First name"
lastNameTextField.accessibilityLabel = "Last name"

With this, VoiceOver will read the label (e.g. "First name") and the value (e.g. “John").

Grouping Elements

If a group of elements represents a single unit of information, consider grouping them into one accessibility element. This helps reduce clutter and makes your app easier to understand and navigate.

Take the following custom UITableViewCell as an example. It has at least five accessible elements.

Since there are potentially more cells like this in the table, it would be very easy for a VoiceOver user to lose context. To improve this, you can:

  • Group the elements by concatenating the information in the UITableViewCell's accessibilityLabel.
  • And make the child elements inaccessible by setting their isAccessibilityElement to false.
class CustomCell: UITableViewCell {
    override var accessibilityLabel: String? {
        get {
            let format = "Post by %@, from %@. %@. %@. %@. Excerpt. %@."
            return String(format: format,
                          author,
                          blogName,
                          datePublished,
                          isFollowing ? "Following" : "",
                          title,
                          excerpt)
        }
        set { }
    }
}

When the cell is focused, VoiceOver would then speak something like this:

Post by Carolyn Wells, from Discover. 6 days ago. Following. 
Around the World with WordPress: Jamaica. 
Excerpt. Today, we’re highlighting five sites from the island paradise of Jamaica.
  • Prefer to place the most important elements first. The VoiceOver user can prefer to skip if they've already listened to what they need. This is why we placed the excerpt last in the example.
  • Don't forget the periods when concatenating. They make VoiceOver pause, which helps prevent incomprehension.
  • The "Excerpt". static text in the example is used as a separator and signals that a very long text will be read by VoiceOver.

Navigation Order

VoiceOver may not navigate views in the order that you'd naturally expect. For example, this UITableViewCell has nested stack views where:

  • The vertical stack view is the parent, and multiple horizontal stack views are children.
  • Two labels are contained inside of each horizontal stack view. One for the title (e.g. Subtotal) and one for the value (e.g. $999.99).

You would expect VoiceOver to navigate the elements in this order:

  1. “Subtotal”
  2. “$999.99”
  3. “Discount”
  4. “-$601.00”
  5. “Shipping”
  6. “$0.01”
  7. “Taxes”
  8. “$333.33”

In this case, however, VoiceOver defaults to navigating to the first item in each horizontal stack view, followed by the second.

  1. “Subtotal”
  2. “Discount”
  3. “Shipping”
  4. “Taxes”
  5. “$999.99”
  6. “-$601.00”
  7. “$0.01”
  8. “$333.33”

This makes the information difficult to comprehend. To fix this, use accessibilityElements on the parent view to list the desired navigation order of the subviews.

contentView.accessibilityElements = [subtotalTitleLabel, 
			             subtotalValueLabel, 
			             discountTitleLabel, 
			             discountValueLabel,
			             shippingTitleLabel,
			             shippingValueLabel,
			             taxesTitleLabel,
			             taxesValueLabel]

The accessibilityElements can be used for all types of elements, including but not limited to, buttons and images view. You can also use accessibilityElements to make an element inaccessible by not including it in the list.

Appearing and Disappearing Elements

If you have a UI element that is shown after an event happens, consider notifying VoiceOver that a new UI element is visible on the screen.

An example of this is a custom Snackbar.

A blind user may never discover that a Snackbar was shown on the screen unless they accidentally moved their finger over it. To make the user aware of it, you can send a notification to VoiceOver using UIAccessibility.post with .layoutChanged.

let snackBarView = createSnackBarView()
presentSnackBar(snackBarView)

UIAccessibility.post(notification: .layoutChanged, argument: snackBarView)

This notifies VoiceOver that a new view, snackBarView, has appeared and it should move the focus to it. VoiceOver will then read its derived accessibilityLabel.

Once the element disappears, you should send another notification but with a nil argument. This makes VoiceOver immediately move the focus to a different element on the screen.

UIAccessibility.post(notification: .layoutChanged, argument: nil)

Prefer Disabling Instead of Hiding Elements

A common UI pattern is hiding elements (e.g. buttons) until users can use them.

This is not ideal for vision accessibility. VoiceOver users, especially first-time users, may try to understand how to interact with your UI by navigating through all the elements on the screen. If an important button, such as the Insert button example above, is initially hidden, they may never discover it. This can make comprehension difficult.

Consider making the element visible, but disabled, instead.

func viewDidLoad() {
    // Allow VoiceOver users to discover the Insert button. But don't let them use it yet.
    insertButton.isEnabled = false
}

func onImageSelected() {
    // Enable the Insert button when an image was selected.
    insertButton.isEnabled = true
}

If a UIControl's isEnabled property is set to false, UIKit would, by default, automatically add the .notEnabled accessibility trait. This makes VoiceOver read the button as “dimmed”, which sufficiently informs the user that the UIControl is present but cannot be used yet.

Use Static Labels for Toggle Buttons

In general, avoid changing the accessibilityLabel of elements after it's already been set. If an element's accessibilityLabel changes over time, users may think that it is a different element. This can be confusing, especially if the element is at the same position on the screen.

Consider updating the accessibilityValue instead. The accessibilityValue is read by VoiceOver right after the accessibilityLabel. This informs the user that the element is the same but it has a different state.

func viewDidLoad() {
    listStyleToggleButton.accessibilityLabel = "List Style"
    showExpandedView()
}

func showExpandedView() {
    listStyleToggleButton.accessibilityValue = "Expanded"
}

func showCompactView() {
    listStyleToggleButton.accessibilityValue = "Compact"
}

The accessibilityValue should be set to the current state of the element. You might also want to update the accessibilityHint if the activation behavior is not obvious.

Consider Using Custom Actions to Simplify Navigation

If you have a view containing a few buttons, you can simplify your app's navigation and improve the experience by using custom actions. This is especially useful for table view cells where the buttons can become repetitive.

Let's take a blog post's UITableViewCell as an example. It has three buttons, Edit, View, and More. And since it's a cell, it can be repeated multiple times in the table.

We can make the buttons accessible individually. But that would increase the number of elements that the user has to navigate to. Using custom actions, we can decrease the accessible elements, simplifying the app, and still allow the user to perform the buttons' actions.

class PostCell: UITableViewCell {
    override var accessibilityCustomActions: [UIAccessibilityCustomAction]? {
        get {
            let editAction = UIAccessibilityCustomAction(name: "Edit",
                target: self, selector: #selector(showEditor))
            let viewAction = // ...
            let moreAction = // ...

            return [editAction, viewAction, moreAction]
        }
        set { }
    }

    @objc private func showEditor() -> Bool {
        // Return true if the method was successful. Returning false will 
        // trigger a different VoiceOver sound.
        return true
    }
}

Provide Sufficient Context in Labels of Repeating Elements

Repeating elements, like buttons inside table view cells, with the same accessibilityLabel, adds some overhead to users. When focusing on one, a user would have to identify for which item the button is.

Consider the following table with at least three Insight cells. The cells have buttons on the right whose accessibilityLabel values are all the same, "Manage Insight".

You might want to consider making the accessibilityLabel of these buttons to be more specific but still succinct.

todayInsightCell.manageButton.accessibilityLabel = "Manage Today Insight"
allTimeInsightCell.manageButton.accessibilityLabel = "Manage All-Time Insight"
mostPopularInsightCell.manageButton.accessibilityLabel = "Manage Most Popular Insight"

With this, a user focusing a manageButton would have enough context. They wouldn't need to go navigate back to the cell's title to deduce the context themselves.

Support Escape Gesture for Custom Modal Views

VoiceOver has a standard escape gesture that dismisses an alert or returns to the previous screen. You can perform the gesture by moving two fingers back and forth three times quickly, making a “z”.

This is generally supported by all the UIKit components. If you make custom (modal) views, however, VoiceOver may not know how to dismiss it. This can potentially lock your user within that view forever.

You can support the gesture by overriding accessibilityPerformEscape in your custom UIView or UIViewController.

class MyModalView: UIView {
    override func accessibilityPerformEscape() -> Bool {
        dismissView()
        return true
    }
}

Consider Using Adjustables to Consolidate Related Buttons

If you have buttons that operate on a single source, such as Previous and Next buttons, you can opt to group them into a single adjustable element.

Let's take a calendar's month navigation as an example. To allow navigation between months, we expose a Previous and a Next button.

If we consider that there will be more accessible buttons on the screen like the individual days, the two buttons will just add to the clutter. We can improve this by making the parent view an adjustable element and use accessibilityIncrement and accessibilityDecrement for navigation.

class MonthNavigationView: UIView {
    // Make the parent accessible. This will make the subviews 
    // (i.e. the Previous and Next buttons) inaccessible.
    override var isAccessibilityElement: Bool {
        get { true }
        set { }
    }

    // Change to an adjustable. VoiceOver will inform the user that they can 
    // swipe up or down while the element is focused to change the value.
    override var accessibilityTraits: UIAccessibilityTraits {
        get { .adjustable }
        set { }
    }

    override var accessibilityLabel: String? {
        get { "Month" }
        set { }
    }

    override var accessibilityValue: String? {
        get { "RETURN_THE_CURRENT_MONTH_AND_YEAR_VALUE_HERE" }
        set { }
    }

    // Called when the user swipes up while this element is focused.
    override func accessibilityIncrement() {
        moveToNextMonth()
    }

    // Called when the user swipes down while this element is focused.
    override func accessibilityDecrement() {
        moveToPreviousMonth()
    }
}

With this change, users can change the month by just swiping up or down. We eliminated the need to navigate between the buttons, saving a few taps.

Auditing

There are many ways to audit your app for VoiceOver. The following is just something to help get you started.

Questions to ask

As you perform an audit, you can keep these questions in mind.

  • Is the navigation order what you expect?
  • Are there elements on the screen that were not accessed?
  • Are there accessible elements that do not make sense to be accessible?
  • Are there too many accessible elements?
  • Did VoiceOver speak the appropriate trait? For example, "Button" or "Heading".
  • Is there something in the guidelines that I could use to improve the experience?
  • Can you leave the current screen using the escape gesture (make a Z with 2 fingers)?
  • Are the labels and hints localized?

Manual Audit

  • Enable the Caption Panel in iOS Settings → Accessibility → VoiceOver. When enabled, anything that VoiceOver speaks will be displayed at the bottom of the screen. This helps with double-checking what you heard.
  • Navigate to the first element and start moving to the next elements by swiping right.
  • Swipe up with two fingers to make VoiceOver speak the entire screen from the top.
  • Set the rotor to navigate by Headings. Swipe up and down to go through all the page headings.
  • Use Screen Curtain to turn the screen off. This forces you to navigate your app without being able to see anything.

Automated Audit

The Accessibility Inspector can run an automated accessibility audit. It is a great tool to use especially if you're just starting with accessibility. This WWDC video gives a great introduction about it.

The automated audit does not cover everything in the guidelines. We recommend that you use the Accessibility Inspector to complement your auditing practice.

Further Reading