iOS Accessibility in SwiftUI Tutorial Part 1: Getting Started

In this article, you’ll fix the accessibility of a SwiftUI master-detail app with various types of images that need more informative labels. By Audrey Tam.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Accessibility API

Really Testing Your App’s Accessibility

Where to Go From Here?

What About the Context Menu?

Accessibility Inspector Audit?

Adding an Accessibility Trait and Hint

A few instructions for basic navigation in VoiceOver:

There are many more gestures, and links to more information about VoiceOver, at Learn VoiceOver Gestures on iPhone.

So now, swipe right until you reach the second artwork, double-tap anywhere to view its detail view, and keep swiping right to hear VoiceOver read your new labels for the image and the map pin icon.

Tap the Back button to select it, then split-tap it or double-tap anywhere to return to the list view.

Stay on your device to experience the context menu.

Context menus replace 3D Touch peek and pop. UIKit has UIContextMenuInteraction, but SwiftUI has a wonderful contextMenu modifier for easily creating a context menu and attaching it to a view. Like other SwiftUI controls, context menus are accessible by default … most of the time.

Here’s how it works when it’s working:

On your device, tap the Giraffe artwork: VoiceOver says Giraffe. Button. Actions available. Wait a little, and it might say Swipe up or down to select a custom action, then double-tap to activate. It’s hard to predict when it will say hints, so don’t wait more than a couple of seconds.

Swipe up or down to hear Show context menu, then double-tap anywhere to see the context menu.

Using the Context Menu in VoiceOver

With the context menu open, swipe right to navigate the emoji buttons. Double-tap anywhere to select one. This dismisses the context menu and returns to the list view:

List view with new emoji reaction for Giraffe item

Tap Giraffe to hear VoiceOver read your reaction emoji, followed by Giraffe.

Triple-click the side button to turn off VoiceOver on your device.

Accessibility Inspector has an Audit function: You may have seen it demonstrated in WWDC sessions and in our UIKit accessibility article. First, you use it to find accessibility issues in your app, and later you use it to verify that you’ve fixed them.

Build and run the app in Simulator, and check it’s the selected target for Accessibility Inspector. Tap an item to show its detail view.

In Accessibility Inspector, click the Audit button — the exclamation mark in a triangle — then click Run Audit. Wait a short time, and several warnings appear. Most of them say:

Potentially inaccessible text

Open the disclosure arrow to see:

This element appears to display text that should be represented using the accessibility API.

When you select one of the warnings, the element it refers to is highlighted in the simulator and also appears below the list of warnings:

Accessibility audit warning highlights problem element

Every one of these elements has an accessibility label, so what’s going on here? I suspect at least parts of the accessibility inspector don’t really know about SwiftUI, and doesn’t recognize when you use its Accessibility API. So although this audit function is useful for UIKit apps, I don’t use it in this article.

Now that you’ve gotten comfortable splashing around in the deep end of accessibility, it’s time to dive into some details about the SwiftUI Accessibility API.

Swift mascot handling gears

When a user of your app turns on an iOS assistive technology like VoiceOver, they’re actually interacting with an accessible user interface that iOS creates for your app. This accessible UI tells a VoiceOver user about the accessible elements of your UI — what they are and how to use them.

Every accessible UI element has these two attributes:

Depending on its nature, a UI element might have one or more of these three attributes:

The accessible UI doesn’t change anything in your app’s visible UI, so you can add more information, in a different order, than what your other users see. In Part 2, you’ll change the order and amount of information for your VoiceOver users.

Now take a closer look at traits and hints.

The map pin button on the detail view is small and easy to miss. Why not make the image a button? When the user taps it, the map appears.

In DetailView.swift, add this modifier to the Image element after the resizable() modifier:

Build and run in Simulator to test your new button. Navigate to a detail view, then tap the image to show the map.

Tap Done to return to the detail view, then highlight the image in Accessibility Inspector:

Artwork image doesn't have Button trait

Its Traits lists only Image, so VoiceOver users won’t know it’s a button. You need to use the Accessibility API to add this trait.

Add two more modifiers to the Image in DetailView.swift:

Now VoiceOver will tell the user this element is a button; after a pause with no action from the user, VoiceOver will tell them the button opens the map.

Notice the hint is Opens Map not Open Map: you’re telling the user what this button does, not instructing them to open the map.

Build and run, then tap an item to open its detail view, and highlight the image:

Artwork image now has Button trait

Now its traits are Button and Image.

The Accessibility Inspector VoiceOver simulator doesn’t read hints, so build and run the app on your device. Navigate to a detail view image, then triple-click your device’s side button to play VoiceOver:

Prince Jonah Kuhio Kalanianaole, Button, Image [pause] Opens map

If you’re lucky, VoiceOver also tells you what the machine-learning Vision model detects in the image:

grass, path, shrub, statue

To really test whether a VoiceOver user can use your app, build and run the app on your device, turn on VoiceOver, then turn on the screen curtain: Triple-tap with three fingers.

This turns off the display while keeping the screen contents active. VoiceOver users can use this for privacy.

Now navigate to the Giraffe artwork, set a reaction, listen to its description, then return to the list. You can’t see the items and buttons, so you must rely entirely on VoiceOver information and gestures. Click Reveal if you need some hints.

[spoiler title=”Solution”]

[/spoiler]

Did you succeed? Good for you!

Triple-tap with three fingers to show the display, then triple-click the side button to turn off VoiceOver.

Congratulations, you’re well on your way to becoming an accessibility ninja!

Swift mascot with black belt

You can download the final end project using the link at the top or bottom of this article.

You’ve learned a lot about SwiftUI accessibility in this article. Here’s what you’ve done:

Here are some links for further exploration:

Continue to Part 2, to learn how to fine-tune accessibility information by modifying your app’s accessibility tree.

We hope you enjoyed this article, and if you have any questions or comments, please join the forum discussion below!

  • No-vision user: Swipe right to next element, left to previous element. Double-tap anywhere to execute.
  • Low-vision user: Tap to select an element, double-tap to execute. Alternative: Instead of tapping an item then double-tapping, use a split-tap gesture: Touch and hold an item with one finger, then tap the screen with another finger.
  • Frame: This is the element’s location and size, as given in its CGRect structure.
  • Label: The default value of an element’s label is the label, name or text used to create the element. You’ve already changed many default values in PublicArt with accessibility(label:). Apple’s programming guide provides guidelines for creating labels and hints.
  • Traits: An element can have one or more traits, describing its type or state. The list of traits includes isButton, isModal, isSelected and updatesFrequently. SwiftUI views have default traits. For example, a Trait of Toggle is Button. You can add or remove traits with accessibility(addTraits:) and accessibility(removeTraits:). VoiceOver reads out an element’s traits, so never include them in its label.
  • Value: A UI element has a value if its content can change. If the default value isn’t meaningful to VoiceOver users, use accessibility(value:) to create a more useful value. For example, the Hide Visited Toggle has values 0 or 1 — too generic to mean anything to your users. Set up values like Not Hidden and Hidden to communicate their context in your app.
  • Hint: This attribute is optional. If the user doesn’t do anything after VoiceOver reads a label, VoiceOver reads the hint. You can set it with accessibility(hint:) to describe what happens if the user interacts with the element.
  • Swipe right/left to move to the next/previous item in the list or in the context menu.
  • When you’ve selected a list item, Double-tap-and-hold anywhere to open that item’s context menu.
  • When you’ve selected a list item, Double-tap anywhere to open that item’s detail view.
  • Swipe right/left to move to the next/previous element in the detail view.
  • When you’ve selected the back button, Double-tap anywhere to return to the list.
  • Created labels that provide context for images, emoji, system images and map view annotations.
  • Saved time by using Xcode’s Accessibility Inspector and its VoiceOver simulator.
  • Used VoiceOver on an iOS device to navigate a master-detail app.
Bug Alert: At the time of writing this article with Xcode 11.3 and iOS 13.3, VoiceOver no longer offers a custom actions menu (Actions available), not even in Apple apps like Maps. Until this is fixed, you must use VoiceOver’s gesture to use a standard iOS gesture: Double-tap and hold your finger anywhere on the screen until you hear three rising tones: The context menu for the selected item appears.
Note: You can attach your own custom accessibility action to a SwiftUI element with .accessibilityAction(named:_:). When the custom actions feature is working, VoiceOver will read it out as a custom action, and your user can activate it by double-tapping.
Note: Accessibility Inspector Audit works for UIKit apps: Check out our article iOS Accessibility: Getting Started.
Note: There is one more accessibility attribute: identifier. This is only used in UITests. You would set an identifier for an element that doesn’t have an accessibility label, or if an element’s accessibility label is too long or ambiguous.
Note: If you have the Zoom accessibility feature enabled, you’ll need to quadruple-tap with three fingers.
.onTapGesture { self.showMap = true }
.accessibility(addTraits: .isButton)
.accessibility(hint: Text("Opens Map."))