iOS Accessibility: Getting Started

In this iOS accessibility tutorial, learn how to make apps more accessible using VoiceOver and the Accessibility inspector. By Fayyazuddin Syed.

Login to leave a rating/review
Download materials
Save for later

In this iOS accessibility tutorial, learn how to make apps more accessible using VoiceOver and the Accessibility inspector.

Update note: Fayyaz Syed updated this tutorial for iOS 13, Xcode 11 and Swift 5. Vincent Ngo wrote the original.

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.

Note: You’ll need a physical device to work with VoiceOver. This accessibility feature is not supported in the simulator at this time.

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.

Configuring signing in Xcode

Getting to Know the Recipe App

Now, build and run the app to familiarize yourself with its features.

Recipe app's 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 uses InstructionViewModel.
  • 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.

VoiceOver shortcut

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.

Note: There are many other accessibility features besides VoiceOver including Invert Colors, Increase Contrast, Color Filters, Reduce White Point, Zoom, Switch Control and a lot more. In this tutorial, you’ll mostly focus on VoiceOver.

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:

  1. Image is not a helpful description of the image views in each cell. You know an image is there, but not what it is.
  2. 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:

  1. Label: A concise way to identify the control or view. Some examples are Back button and recipe image.
  2. Traits: These describe the element’s state, behavior or usage. A button trait might be is selected, for example.
  3. Hint: Describes the action an element completes. For example: Displays the recipe detail.
  4. Frame: The frame of the element within the screen, in the format of a CGRect. VoiceOver speaks the contents of the CGRect.
  5. 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.

Note: The Recipe app uses standard UIKit views and controls, which are already accessible and, at most, require modification to the attribute strings. For projects with custom elements, be sure to read our iOS Accessibility Tutorial: Making Custom Controls Accessible tutorial.

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.

Selecting the Accessibility Inspector

The inspector should look something like this:

Accessibility Inspector layout

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:

Changing your target in the Accessibility Inspector

Now that you have the tool open, you can look at some of its most helpful features.

Using the Inspection Pointer

Note: At the time of this writing, Xcode 11.3, the most current version, has a bug that prevents you from using this tool reliably.

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.

Inspection Pointer in action

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.

Listening to audio in the Inspection Pointer

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.

Warnings in the inspector

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.

Suggestions to fix warnings in the inspector audit

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.

Taking a snapshot of your app to help with 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:

  1. Invert colors
  2. Increase contrast
  3. Reduce transparency
  4. Reduce motion
  5. 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.

Additional accessibility settings in the inspector

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:

  1. accessibilityTraits takes a mask of traits that characterize the accessibility element. In this case, .image indicates it is an image.
  2. You use accessibilityLabel to describe the element in VoiceOver. Here, it’s set to recipe.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:


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.

Accessibility Inspector with no more warnings

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:

  1. isAccessibilityElement is a flag that makes the item visible to accessibility features when true. For most UIKit classes, the default is true, but for UILabel it’s false.
  2. accessibilityTraits helps characterize the accessibility element. Since you don’t need any interactions, you set it to have no traits.
  3. Next, you have VoiceOver concisely identify the intent of this label. Difficulty Level lets the user know exactly what they’re dealing with.
  4. 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.

The audit showing fewer warnings

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:

Suggestions to fix the dynamic font warnings

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.

The auditor displays no more warnings

Testing Some Other Options

Navigate to the Settings toggle in the inspector and experiment with some of the tools:

  1. 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.
  2. 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:

Recipe App with various accessibility changes

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:

  1. Left Arrow button isn’t a great description for the navigation. How would the user know what the button does?
  2. The emoji face states are: heart shape eyes and confounded face. Those explanations would confound any user!
  3. 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)"
    value: 2, 
    range: NSRange(text.startIndex..., in: text))
} else {
  // 3
  descriptionLabel.accessibilityLabel = "Uncompleted: \(text)"

Here’s what this code does:

  1. Turns off accessibility for checkmark button so VoiceOver reads it as one unit instead of two different accessibility elements.
  2. 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.
  3. 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:

If you have any comments or questions, please join the discussion below!