iOS Accessibility: Getting Started
In this iOS accessibility tutorial, learn how to make apps more accessible using VoiceOver and the Accessibility inspector.
Version
- Swift 5, iOS 13, Xcode 11

People of all walks of life, of all ages, and from different backgrounds use smartphone apps, including people with disabilities. Designing your apps with accessibility in mind helps everyone use them, including people with vision, motor, learning or hearing disabilities.
In this iOS accessibility tutorial, you’ll transform an existing app to make it more accessible for people with visual disabilities. In the process, you’ll learn how to:
- Use VoiceOver.
- Check your app with the Accessibility Inspector.
- Implement accessibility elements with UIKit.
- Build a better user experience for people with disabilities.
This tutorial requires Xcode 11.3 and Swift 5.1. It assumes you already know the basics of Swift development. If you’re new to Swift, you should first check out our book, Swift Apprentice.
Getting Started
In this tutorial, you’ll work with an already-completed app called Recipe, which contains a list of recipes and their difficulty levels. It also allows you to rate the quality of the dishes you make.
Download everything you need to get started by using the Download Materials button at the top or bottom of the tutorial. Open Recipe.xcodeproj in the begin folder.
Before you can run the app on a device, you need to configure signing.
To do this, click the Recipe project in the navigator, then select the target with the same name. Select the Signing & Capabilities tab, then make sure you’ve selected Debug at the top. Finally, pick your Team from the drop-down.
Getting to Know the Recipe App
Now, build and run the app to familiarize yourself with its features.
The root controller is a table view of recipes containing an image, description and difficulty rating. Click a recipe for a larger picture with the recipe’s ingredients and instructions.
To make things more exciting, you can also cross off the items on the list to make sure you have all the necessary ingredients. If you love or hate what you made, you can toggle the like/dislike emoji.
Behind the Scenes of the Recipe App
Spend a few minutes familiarizing yourself with the code in the begin project. Here are some highlights:
- Main.storyboard contains all the storyboard scenes for the app. You’ll notice all the UI components are standard UIKit controls and views. They’re already accessible, which makes your job easier.
-
RecipeListViewController.swift manages the root table view, which displays the list of all recipes available. It uses an array of
Recipe
objects as the data source. - Recipe.swift is the model object that represents a recipe. It contains utility methods for loading an array of recipes that you’ll use throughout the app.
-
RecipeCell.swift is the cell for the root controller’s recipe list. It displays the recipe’s difficulty level, name and photo based on the passed
Recipe
model object. -
RecipeInstructionViewController.swift contains the controller code for the detail view, which shows a large image of the dish along with its ingredients and cooking instructions. It features a
UISegmentedControl
to toggle between ingredients and instructions in the table view, which usesInstructionViewModel
. -
InstructionViewModel.swift acts as the data source for
RecipeInstructionsViewController
. It includes descriptions for ingredients and instructions as well as state information for the check boxes. - InstructionCell.swift defines a cell that contains a label and a checkbox for use in instructions and ingredient lists. When you check the box, it crosses out the text.
Now you understand how the app works, it’s time to consider how to make it more accessible.
Why Accessibility?
Before you get started with the code, it’s important to understand the benefits of accessibility.
- Designing your app for accessibility makes it easier to write functional tests, whether you’re using the KIF framework or UI Testing in Xcode.
- You’ll also broaden your market and user base by making your app usable by a larger group.
- If you work for any government agency, you’re required to implement 508 compliance, which states any software or technology must be accessible to all users.
- Implementing accessibility in your app shows you’re willing to go the extra mile for every user, and that’s a good thing.
- It feels good to know you’re making a small but noticeable difference in someone’s life! :]
Convinced? Then it’s time to get to know VoiceOver, an accessibility tool for people with visual disabilities.
Enabling VoiceOver
iOS comes with the VoiceOver screen-reading tool, which helps users interact with software without needing to see the screen. It’s specifically designed for people with vision problems.
VoiceOver lets users who are visually impaired hear and interact with what’s visible on-screen. VoiceOver responds to gestures and audibly communicates to the user what’s on the screen or what the user selects. In essence, VoiceOver is the link between the UI and the user’s touch input.
The quickest way to use VoiceOver is to open the Settings app on your iOS device, select Accessibility ▸ Accessibility Shortcut then select VoiceOver.
This creates a shortcut so you can triple-tap the home button — or the side button, for newer phones — on a physical device to toggle VoiceOver on and off.
Now you’ve enabled VoiceOver, it’s time to try it out.
How to Use VoiceOver
VoiceOver comes with some handy gesture presets that make it easy to navigate an app. Here are some of the more common in-app gestures to use with VoiceOver:
- Single-tap anywhere on the screen and VoiceOver will read identifying information from the item’s accessibility attributes out loud.
- Single-swipe left or right and VoiceOver will select the next visible accessibility item and read it out loud. Right-swipes move forward and down, while left-swipes do the opposite.
- Single-swipe down to spell the focused item letter-by-letter.
- Double-tap to select the focused item.
- Three-finger-swipe left or right to navigate forward or backward in a page view.
For the complete list of VoiceOver gestures, check out Apple’s Learn VoiceOver gestures on iPhone. So now you know how VoiceOver works — but how does your app perform with it? You’ll test it in the next step.
Trying VoiceOver With the Recipe App
Build and run on a physical device and triple click the home button to turn on VoiceOver. Swipe left and right to navigate through the recipe list. VoiceOver reads the elements from top-left to bottom-right. It starts with the header name followed by each recipe’s name and the name of the associated image.
But there are a few problems with the VoiceOver experience:
- Image is not a helpful description of the image views in each cell. You know an image is there, but not what it is.
- VoiceOver says nothing about the difficulty level of each recipe, rendering this feature useless for people with visual disabilities.
Now you’ve identified problem areas, you might want to dive right into fixing them. But before you do, you need to know a bit about how accessibility features work.
Accessibility Attributes
Accessibility attributes are the core components you must implement to support accessibility. These attributes supply VoiceOver with information about elements in your app so VoiceOver can read that information out loud to your users. If they aren’t properly configured, VoiceOver won’t be able to supply the necessary details about your app.
An accessibility attribute has five properties:
- Label: A concise way to identify the control or view. Some examples are Back button and recipe image.
- Traits: These describe the element’s state, behavior or usage. A button trait might be is selected, for example.
- Hint: Describes the action an element completes. For example: Displays the recipe detail.
-
Frame: The frame of the element within the screen, in the format of a
CGRect
. VoiceOver speaks the contents of theCGRect
. - Value: The value of an element. For example, with a progress bar or a slider, the current value might read: 5 out of 100.
Most UIKit components preset these attributes for you; you simply need to supply the details to improve the user experience. If you create custom controls, you’ll have to supply most of the attributes yourself.
So now you know where VoiceOver gets the information it provides to users, it’s time to get to know a new tool that will help you find and fix accessibility weaknesses in your app: the Accessibility Inspector.
Using the Accessibility Inspector
If you’re retrofitting an app to be more accessible, finding weaknesses is time-consuming and error-prone. Fortunately, there’s a tool to help named Accessibility Inspector, which does the following:
- Runs through your app and finds common accessibility issues.
- Lets you check the accessibility attributes of UI elements in Inspection Mode.
- Provides live previews of accessibility elements without leaving your app.
- Supports all platforms including macOS, iOS, watchOS and tvOS.
Before you use the Accessibility Inspector on Recipes, take a look at the tool. First, open it in the Xcode menu by navigating to Xcode ▸ Open Developer Tool ▸ Accessibility Inspector.
The inspector should look something like this:
The target chooser lets you pick which device you’d like to inspect. This could be your MacBook Pro, an iOS device or your simulator.
Live previews of accessibility elements let you test right in the simulator. Since live previews are faster than listening to VoiceOver, this is where you’ll do the bulk of your work during this iOS accessibility tutorial.
Build and run in a simulator, and change the Accessibility Inspector target to your simulator:
Now that you have the tool open, you can look at some of its most helpful features.
Using the Inspection Pointer
Selecting Inspection Pointer, which looks like a reticle sight in the inspector UI, is similar to enabling VoiceOver on your device. When you activate the pointer, you can hover over any UI element to check its attributes. Interacting with the simulator directly via button presses will deactivate the Inspection Pointer.
Inspection Detail pane has everything you need to review and interact with the accessibility attributes in your app:
- Basic: Displays the attribute properties for the currently-highlighted element.
- Actions: Lets you perform actions like tapping a button or scrolling the view. Pressing the Perform button in this pane will perform the action on your target.
- Element: Displays the class, address and controller of the current item. As of this writing, it doesn’t work consistently.
- Hierarchy: Shows the view hierarchy for the element, making it easier to understand complex views.
Using Quicklook to Check Audio in Xcode
Xcode 11 has a new feature in the Inspection Detail pane, within the Quicklook section at the top, that allows you to simulate in Xcode the audio you’d hear on your device. This means you can check what your users hear when using your app without requiring an actual device.
Press the Play button while the app is running in a simulator, let the Accessibility Inspector cycle through the app and listen as it describes each element out loud.
If you prefer to manually step through each element, you can either press the Pause button or press the Audio button inside the Quicklook section. Press the Forward or Back buttons to step through each component at your own pace.
Using this feature is faster than running your app on a device and using VoiceOver, particularly during development. As always, you want to also test your app, along with all its accessibility features, on real devices.
Highlighting Problems With the Inspector Audit
One of the most useful features of the Inspector Audit is its audit capability, which gives you warnings about accessibility problems within your app. To try out this feature, make sure the simulator is still running and you’re on the recipe list. In the inspector, click the Audit icon and then Run audit.
You’ll see the inspector gives several warnings, including that some of your elements lack description.
When you click a warning, Xcode highlights the related element in the simulator as well as at the bottom of the Inspector Audit screen.
In this case, the image views associated with the cells have no description. This means VoiceOver won’t be able to describe them to your readers.
Click the Suggest Fixes icon, which looks like a question mark in a circle, for one of the warnings and the inspector will offer suggestions about how to fix the issue. You’ll act on these suggestions later.
Click the Eye icon to take a snapshot of the app. This is useful for anyone in quality assurance who needs to create accurate bug reports.
There are a few more useful accessibility settings you can find in the inspector. Next, you’ll take a quick look at these features.
Additional Inspector Settings
Although they’re outside the scope of this tutorial, it’s good to know the Accessibility Inspector also lets you test the following accessibility settings:
- Invert colors
- Increase contrast
- Reduce transparency
- Reduce motion
- Change font size
You no longer have to use the Settings app to enable these features. The Accessibility Inspector currently offers only these five options, but Apple plans to add more in the future.
The Accessibility Inspector saves time when testing your app. Remember, however, you should still test VoiceOver manually to try out the actual user experience. This final step helps you catch any problems the inspector misses.
Now you’ve taken a tour of the Accessibility Inspector’s features, it’s time to get to work your app.
Making the Recipe App Accessible
When you tested your app on your device with VoiceOver, you noted the images’ descriptions weren’t very useful. The audit tool showed you the reason why: The image view didn’t have an accessibility label. You’re going to fix this now.
In Xcode, open RecipeCell.swift and add the following code to the bottom of the file:
// MARK: Accessibility
extension RecipeCell {
func applyAccessibility(_ recipe: Recipe) {
// 1
foodImageView.accessibilityTraits = .image
// 2
foodImageView.accessibilityLabel = recipe.photoDescription
}
}
This code fills in the missing accessibility properties based on the Recipe
object for the cell. Here’s how it works:
-
accessibilityTraits
takes a mask of traits that characterize the accessibility element. In this case,.image
indicates it is an image. - You use
accessibilityLabel
to describe the element in VoiceOver. Here, it’s set torecipe.photoDescription
, which is a string that describes the contents of the image.
Now, you want to apply this to future recipes, too. Find configureCell(_:)
in the RecipeCell
class. Add the following line to the end of the method:
applyAccessibility(recipe)
Every time you create a cell, this code will apply the accessibility attributes to the image using properties in the recipe object.
Build and run on your device and enable VoiceOver with three taps on the home button. Test the recipe list to see if the image descriptions are more meaningful.
Much better! Instead of simply hearing “Image,” which provided no specific details, you now hear a full description of the image. The user can now visualize the food instead of being frustrated at not knowing what the image is.
With the app still running in the simulator, run the Accessibility Inspector again and navigate to the recipe list. Make sure you clear all warnings in the inspector and tap Run Audit.
WOOt — no more description warnings! After successfully adding descriptions to the images, the core of this view is now fully accessible.
Now, it’s time to make the difficulty level of a recipe accessible.
Transforming the Difficulty Labels
In the Accessibility Inspector, you see potentially inaccessible text warnings, which tell you the difficulty labels are invisible to a user with visual impairments. To fix these, you need to make the labels accessible and update their properties with a meaningful description.
For your next step, go to RecipeCell.swift and add the following to the end of applyAccessibility(_:)
:
// 1
difficultyLabel.isAccessibilityElement = true
// 2
difficultyLabel.accessibilityTraits = .none
// 3
difficultyLabel.accessibilityLabel = "Difficulty Level"
// 4
switch recipe.difficulty {
case .unknown:
difficultyLabel.accessibilityValue = "Unknown"
case .rating(let value):
difficultyLabel.accessibilityValue = "\(value)"
}
Here’s some more detail about what this code does:
-
isAccessibilityElement
is a flag that makes the item visible to accessibility features whentrue
. For most UIKit classes, the default istrue
, but for UILabel it’sfalse
. -
accessibilityTraits
helps characterize the accessibility element. Since you don’t need any interactions, you set it to have no traits. - Next, you have VoiceOver concisely identify the intent of this label.
Difficulty Level
lets the user know exactly what they’re dealing with. - VoiceOver will read the
accessibilityValue
as part of the label description. Setting the difficulty level here makes this element much more useful.
Build and run your app on a physical device, triple tap the home button to enable VoiceOver and swipe through the recipe list.
As you scroll through the different accessibility elements, VoiceOver reads a full description of each cell, including the difficulty level.
Checking for Warnings
Every time you expose a new accessibility element, as you did here with the difficulty level, you should run the audit again.
Start the Accessibility Inspector, if it isn’t already running. Run the app on your device or the simulator and set the inspector target accordingly. Now, select the audit toggle button and tap Run audit.
Fewer warnings appeared! The remaining ones are about the labels not supporting dynamic text. You’ll fix those next.
Making the Text Dynamic
The auditor is warning you that you are missing an important step to make your app usable by everyone: dynamic text. This is an important feature for accessibility, allowing users with partial vision impairments to increase the font size for readability. The non-dynamic font your app currently uses doesn’t allow this.
Click the Fix Suggestions icon to see what the auditor recommends:
It tells you to use a UIfont preferred font and to set adjustsFontForContentSizeCategory
to true. You’ll do this now.
Within RecipeCell.swift add the following code inside applyAccessibility(_:)
at the very bottom:
dishNameLabel.font = .preferredFont(forTextStyle: .body)
dishNameLabel.adjustsFontForContentSizeCategory = true
difficultyLabel.font = .preferredFont(forTextStyle: .body)
difficultyLabel.adjustsFontForContentSizeCategory = true
This sets the preferredFont
to a body
style, which means iOS will style text as it would the body of a document. The specifics of the size and font depend on the accessibility settings. adjustsFontForContentSizeCategory
indicates the font should update automatically when the user changes the text content size.
Testing how your app handles resizing fonts is easy, thanks to the Accessibility Inspector.
Build and run the recipe app alongside the Accessibility Inspector. Run the audit again and all your warnings should be gone.
Testing Some Other Options
Navigate to the Settings toggle in the inspector and experiment with some of the tools:
- Invert Colors to preview what your interface looks like with this accessibility feature. This is useful for people with light sensitivities, poor vision and in some cases, color blindness.
- You can also test out dynamic font size changes in real-time for the users who prefer larger font sizes.
As you test your app, it probably looks a lot like this:
The inspector makes testing accessibility cases easy. From this, you can tell the recipe list will work well for users with visual impairments.
Transforming the Recipe Detail Screen
Now you’ve taken care of the list of recipe options, you want to see what happens when a user clicks one of the recipes. Run the app on your device, enable VoiceOver and look around the detail view. It sounds something like this:
There are some issues with the VoiceOver interaction in the detail view:
- Left Arrow button isn’t a great description for the navigation. How would the user know what the button does?
- The emoji face states are: heart shape eyes and confounded face. Those explanations would confound any user!
- When the user selects a checkbox, it reads icon empty, which doesn’t explain much.
In each of these cases, it’s important to explain what the state of the control means, rather than how it looks. Back button is clearer than Left Arrow button. Like and Dislike succinctly explain the emojis. You’ll make these two changes next.
To change the navigation, open RecipeInstructionsViewController.swift and add the following to viewDidLoad
, after assert(recipe != nil)
:
backButton.accessibilityLabel = "back"
backButton.accessibilityTraits = .button
Instead of Left Arrow button, VoiceOver now says Back button.
Now, on to the emojis. In the same file, replace the contents of isLikedFood(_:)
with the following:
if liked {
likeButton.setTitle("😍", for: .normal)
likeButton.accessibilityLabel = "Like"
likeButton.accessibilityTraits = .button
didLikeFood = true
} else {
likeButton.setTitle("😖", for: .normal)
likeButton.accessibilityLabel = "Dislike"
likeButton.accessibilityTraits = .button
didLikeFood = false
}
For both Like and Dislike modes, you’ve added an accessibilityLabel
that’s clear about what the button does. You also set accessibilityTraits
to identify it as a button, so the user knows how they can interact with it.
Build and run on a device and enable VoiceOver. Navigate to the detail recipe screen using VoiceOver to test your changes to the buttons at the top of the view.
Now, each of these features has clear, short descriptions that help the user understand their intent. Much better!
Improving the Checkboxes
The final issue is with the checklist. For each checkbox, VoiceOver currently states icon empty followed by the recipe instruction. That’s not clear at all!
To change this, open InstructionCell.swift and look for shouldStrikeThroughText(_:)
. Replace the entire if strikeThrough
statement with the following:
// 1
checkmarkButton.isAccessibilityElement = false
if strikeThrough {
// 2
descriptionLabel.accessibilityLabel = "Completed: \(text)"
attributeString.addAttribute(
NSAttributedString.Key.strikethroughStyle,
value: 2,
range: NSRange(text.startIndex..., in: text))
} else {
// 3
descriptionLabel.accessibilityLabel = "Uncompleted: \(text)"
}
Here’s what this code does:
- Turns off accessibility for checkmark button so VoiceOver reads it as one unit instead of two different accessibility elements.
- The
accessibilityLabel
for the description now uses the hard-coded string"Completed"
followed by the text. This provides all the necessary info with a single visit to the label. - As with the completed code, if a user marks an item as uncompleted, you add
"Uncompleted"
before the label description.
Build and run the app again and see how it sounds. It will be music to the ears of your users. :]
Where to Go From Here?
You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.
In this iOS accessibility tutorial, you learned about VoiceOver. You performed manual auditing by scrolling through every accessible element and testing the user experience for yourself. Then you used the Accessibility Inspector to perform audits, look at accessibility element values and perform live dynamic changes to invert colors or change the font size.
Now, you have the necessary tools to make your app more accessible. Knowing you’ll have a positive impact on someone’s life is rewarding.
There are a ton of possibilities with accessibility features. This tutorial only scratches the surface to get you started. Below are more resources to check out:
- Categories of Accessibility
- Accessibility Development Resources
- Applying Accessibility to Custom Views
- Deliver an Exceptional Accessibility Experience
- Accessibility Inspector
If you have any comments or questions, please join the discussion below!
Comments