iOS Accessibility Tutorial: Making Custom Controls Accessible

In this iOS accessibility tutorial, you’ll learn to make custom controls accessible using VoiceOver, elements group, custom action, traits, frame and more. By Andrew Tetlaw.

Leave a rating/review
Download materials
Save for later
Share

Apple’s accessibility credentials are among the best around. iOS is full of accessibility features that can elevate your app above the rest. So, why settle for a ten out of ten when you can take it to eleven?

In this iOS accessibility tutorial, you’ll learn to:

  • Apply accessibility enhancements to custom controls.
  • Organize accessibility elements for greater clarity and usability.
  • Notify the user of major changes in the user interface.

To get the most out of this tutorial, you’ll need an iOS device to make use of VoiceOver.

This tutorial uses the reusable knob control from How To Make a Custom Control Tutorial: A Reusable Knob.

Note: This intermediate-level tutorial assumes that you’re comfortable building an iOS app using Xcode, UIKit, writing Swift and have a basic understanding of how iOS Accessibility features function, especially VoiceOver. If you need a primer on accessibility, I recommend reading iOS Accessibility Tutorial: Getting Started before starting getting started.

Getting Started

Your app, Goes to 11, is nearly ready to launch to an eager audience of classic guitar amp lovers, all poised to share their ratings of classic guitar amps. Each amp fan can submit a rating from zero to eleven. However, you’re aiming to reach a wide audience and for that your app ought to be accessible.

To get started, download the starter project using the Download Materials button at the top or bottom of this tutorial.

Then, open the starter project. Build and run.

Starter app

You’ll see the app is fully functional. You can navigate forward and backward between each amp. You can see the brand, model, description and the average rating for each amp. Additionally, you can tap the Add your rating button to rate an amp. When you tap Add your rating, the screen simply unwinds to the previous screen.

The sample project seems fairly simple and straightforward, right?

Experiencing VoiceOver

To test VoiceOver, you’ll need an actual iOS device. With the Simulator you could use the Accessibility Inspector, but that does not speak accessibility information, so you can’t hear how your element descriptions will sound. Rather inconvenient!

You can configure your device’s Accessibility Shortcut to conveniently toggle VoiceOver on or off.

Do the following to set your Accessibility Shortcut:

  • First, open the Settings app.
  • Then, navigate to Accessibility ▸ Accessibility Shortcut.
  • Finally, select VoiceOver.

Now, you can activate or deactivate VoiceOver by triple-clicking the Home or Side button.

Before you start, with VoiceOver activated, triple-tap the screen with three fingers to enable Screen Curtain. With the screen off, you have to rely on VoiceOver’s announcements to navigate the app. If you need to, triple-tap the screen again to disable Screen Curtain.

Note: If you have configured your device so the three-finger triple tap does something other than control Screen Curtain, you must use a three-finger quadruple tap instead.

Build and run.

Now, activate VoiceOver and Screen Curtain on your device.

Using VoiceOver, try to navigate between amps, adjust the rating value and submit your amp rating.

When you swipe between elements on the screen, what do you hear?

It’ll be something close to this, with the hyphens represent a swipe:

Amp details, heading – Brand – Vox – Model – AC30 – 9 – 0 – 11 – Add your rating – description…

At the moment, getting to the Add your rating button requires eight swipes from the heading. Also, you have to swipe back and forth across all the elements to get the gist the current screen. The “9 – 0 – 11”, users may never work out what it is.

Your app can do much better than that!

Grouping Accessibility Elements

There are many labels that add no information when using VoiceOver and make it harder to get real information. First, open Main.storyboard. Then, under Amp Details Scene, select the following labels:

  • Brand.
  • Model.
  • Description.
  • Highest Value.
  • Lowest Value.

Selected labels

With each label selection, uncheck the Accessibility Enabled checkbox in the Identity inspector. This will hide those labels from VoiceOver’s announcements.

Accessibility field in Identity Inspector

Next, open AmpDetailsViewController.swift.

Then, right below the following code in onSelectedIndexUpdate():

ratingLabel.text = "\(amp.rating)"

Add this:

ratingLabel.accessibilityLabel = "Average rating is \(amp.rating) out of 11"

And, right below:

brandLabel.text = amp.brand

Add this:

brandLabel.accessibilityLabel = "\(amp.brand) \(amp.model)"

With the code added above, VoiceOver will read according to accessibilityLabel.

Now, open Main.storyboard.

Then, uncheck Model Label‘s Accessibility Enabled checkbox. This change ensures that VoiceOver will not read the brand out twice.

Now, build and run the project. Keep VoiceOver activated and Screen Curtain on. Hear the improvement:

Amp details, heading – Vox AC30 – Average rating is 9/11 – Add your rating …

In three swipes you can hear all the relevant information in between swipes and find the Add your rating button. That’s a tremendous improvement for very little work, wouldn’t you agree? Keep going though!

You can set the view controller’s accessibilityElements array to set the order for VoiceOver to read out the view elements.

First, open AmpDetailsViewController.swift. Then, add this line to the beginning of viewDidLoad():

accessibilityElements = [
    brandLabel, 
    ratingLabel, 
    descriptionLabel, 
    ratingButton, 
    nextButton, 
    previousButton
  ].compactMap { $0 }

accessibilityElements is an optional array of Any. Because you’re setting it to an array of implicitly unwrapped optional UIViews they can’t be implicitly cast to Any.

You deal with this by doing safe and concise unwrapping of all the elements by calling .compactMap { $0 }.

Now, build and run. Try out VoiceOver and notice the order it reads out the elements. It reads the content first followed by interactive elements. You’re really finding your rhythm now.

Now, select the Next and Previous buttons with VoiceOver. Pay attention to what happens. Even though the screen updates with new amp details, the button remains focused.

This is not ideal.

Making Screen Updates

Still in AmpDetailsViewController.swift, find onSelectedIndexUpdate() and add this line at the end:

UIAccessibility.post(notification: .screenChanged, argument: brandLabel)

screenChanged is an accessibility notification you can post when a major part of the screen has changed. It makes that little boop sound.

The argument you pass can be an accessibility element, a String, or nil. Pass an element and that element gains the focus after the notification and VoiceOver reads out its label.

If you pass nil, the first accessibility element within the container gains focus. When you pass a string, VoiceOver reads out that text, followed by the first accessibility element in the container.

Build and run. Hear the good work you’ve achieved. You still have Screen Curtain enabled, right?

Smooth! It flows much nicer now. You can easily navigate through the whole collection of amps.