Home iOS & Swift Books SwiftUI by Tutorials

Accessibility Written by Audrey Tam

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Accessibility matters, even if you’re not in the 15-20% of people who live with some form of disability or the 5% who experience short-term disability. Your iOS device can read out loud to you while you’re cooking or driving, or you can use it hands-free if your hands are full or covered in bread dough. Many people prefer Dark Mode, because it’s easier on the eyes and also like larger text, especially when their eyes are tired. And there are growing concerns about smartphone addiction. A popular tip is to set your iPhone to grayscale (bit.ly/3hoVUPm)! To get more people using your app more often, explore all the ways they can adapt their iOS devices to their own needs and preferences, and then think about how you might adapt your app to these situations.

Most app UIs are very visual experiences, so most accessibility work focuses on VoiceOver — a screen reader that lets people with low to no vision use Apple devices without needing to see their screens. VoiceOver reads out information to users about your app’s UI elements. It’s up to you to make sure this information helps users interact efficiently with your app.

In this chapter, you’ll learn how to navigate your app with VoiceOver on an iOS device and use the SwiftUI Accessibility API attributes to improve your app’s accessible UI. You’ll add labels that provide context for UI elements and improve VoiceOver information by reordering, combining or ignoring child elements.

Apple’s investing a lot of effort in helping you improve the accessibility of your apps. With SwiftUI, it’s easier than ever before. The future is accessible (bit.ly/3htnuLe), and you can help make it happen!

Using VoiceOver on a device

Xcode has an Accessibility Inspector you can open with Xcode ▸ Open Developer Tool ▸ Accessibility Inspector. It provides an approximation of VoiceOver, but it’s not similar enough to be really useful. Learn how to use your app with VoiceOver on a device, to find out how it really behaves and sounds for a VoiceOver user.

Setting up VoiceOver shortcut

On your device, open Settings ▸ Accessibility ▸ Accessibility Shortcut, and select VoiceOver. This enables you to switch VoiceOver on and off by triple-clicking the device’s side button.

iPhone Settings: accessibility shortcut includes VoiceOver
aJjeri Melxobwd: izvunsepinufh qfiwbzef urrtaboy LoeboIyez

Using VoiceOver

Still in Settings▸Accessibility, start VoiceOver. Tap the list to make sure you’re not in the navigation bar, then swipe down with three fingers to scroll to the top of the list.

VoiceOver ▸ Speech
NoediOluq ▸ Wpuatn

VoiceOver verbosity settings used for this chapter
ZeihuOqul vencenidj gufzuznr urel rur kvak tzumzub

Accessibility in SwiftUI

With SwiftUI, it’s easy to ensure your apps are accessible because SwiftUI does a lot of the work for you. SwiftUI elements support Dynamic Type and are accessible by default. SwiftUI generates accessibility elements for standard and custom SwiftUI elements. Views automatically get labels and actions and, thanks to declarative layout, their VoiceOver order matches the order they appear on the screen. SwiftUI tracks changes to views and sends notifications to keep VoiceOver up to date on visual changes in your app.

In VoiceOver, double-tap-hold annotation to show context menu
Ih YouteInuc, laitmu-los-sepp ovmabaruur fo vhoj hoqwumq hodo

SwiftUI: Accessibility by default

Look at this typical Toggle code:

Toggle(isOn: $rememberUser) { Text("Remember me") }

Accessibility API

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.


In this section, you’ll cover: Hidden, label, hint, value; change UI for all users; dynamic type, and combining child elements.


The first time Kuchi launches, it displays RegisterView.

Register view
Wiqikmud feuy

Welcome to Kuchi label
Vopkamu hi Nebva mihod

Image(systemName: "table")
Text field and character counter
Sets puugt umw zhunuvrij baozqev

TextField("Type your name", text: $userManager.profile.name)
    Text("name has \(userManager.profile.name.count) letters"))
    Text("name needs 3 or more letters to enable OK button"))
Remember-me toggle and OK button
Howivhaq-lu cesrqe edz EH yavwoh

//Button(action: self.registerUser) {
.accessibilityLabel(Text("OK registers user"))
  Text("name needs 3 or more letters to enable this button"))
//Button(...) {
  userManager.isUserNameValid() ? "enabled" : "disabled")
Keyboard for text field input
Dinyoiyh ric fesd taedj envaw

Braille Screen Input
Wcoepni Zqpuaf Aqgog


Welcome view
Welcome view

//VStack {
.accessibilityElement(children: .combine)
//ZStack {
.accessibilityHint(Text("start playing Kuchi"))

Learn tab

Kuchi starts you in the Learn tab, which involves a lot of fancy gesture recognition. Unfortunately, with VoiceOver on, none of them work.

// VStack {
//   ZStack { ... }
//   ...
// }
HStack {
  Button { discardCard(to: .left) } label: {
    Image(systemName: "arrowshape.turn.up.left.circle")
      .accessibilityLabel(Text("Swipes left"))
  Button { discardCard(to: .right) } label: {
    Image(systemName: "arrowshape.turn.up.right.circle")
      .accessibilityLabel(Text("Swipes right"))
Buttons to manually invoke drag gestures
Wemvuyh me zacuapwj ezbexi ftax lojdeyex

Challenge tab

The Challenge tab is a much better user experience. VoiceOver speaks the Japanese phrase when you tap it. If you understand some spoken Japanese, this is a huge help to get the right answers!


In this section, you’ll cover: label, value, hidden; sort priority; and change UI for all users.

Reducing jargon

A big part of making your app accessible means ensuring your labels give context and meaning to the UI elements in your app. You can usually fix any problems by replacing the default label with a custom label.

// BevelText(text: "R ??? G ??? B ???", ...)
  Text("Target red, green, blue values you must guess"))
var rInt: Int {
  Int(red * 255.0)
var gInt: Int {
  Int(green * 255.0)
var bInt: Int {
  Int(blue * 255.0)

/// A String representing the integer values of an RGB instance.
var intString: String {
  "R \(rInt) G \(gInt) B \(bInt)"

var accString: String {
  "Red \(rInt), Green \(gInt), Blue \(bInt)."
//BevelText(text: guess.intString, ...)
.accessibilityLabel(Text("Your guess: " + guess.accString))
Slider(value: $value)
      String(describing: trackColor) +
      String(Int(value * 255))))

Reordering navigation

When the app launches, VoiceOver starts reading from the top of the screen. This is just the message about having to guess the target values, which the user probably already knows. A user who relies on swiping to navigate must swipe right twice to reach the red slider, which is where the action is.

  text: guess.intString,
  width: geometry.size.width * labelWidth,
  height: geometry.size.height * labelHeight)
  .accessibilityLabel(Text("Your guess: " + guess.accString))
  ColorSlider(value: $guess.red, trackColor: .red)
  ColorSlider(value: $guess.green, trackColor: .green)
  ColorSlider(value: $guess.blue, trackColor: .blue)
Button("Hit Me!") {
  self.showScore = true
  self.game.check(guess: guess)

Organizing information

Now, tap the Hit Me! button, then double-tap to show the alert. Swipe right twice to hear all three parts:

Modify the alert for all users.

In ContentView.swift, in the body of ContentView, replace the first two arguments of Alert with these:

title: Text("You scored \(game.scoreRound)"),
message: Text("Target values: " + game.target.accString),
Success view modal sheet
Bejbinj yief cunuy mhuav

Refactor to use SuccessView modal sheet.

In ContentView.swift, replace .alert(...) { ... } with the following:

.sheet(isPresented: $showScore) {
    game: $game,
    score: game.scoreRound,
    target: game.target,
    guess: $guess)

Adapting to user settings

Your users have a multitude of options for customizing their iOS devices. Most of the ones that could affect their experiences with your app are Vision settings:

Vision accessibility settings
Zejael aqpactadoyapp hubropbb

Debug toolbar: Environment Overrides
Pafuq siebcos: Udtohibruhb Iramqosok

Use preview inspector to check your app supports dynamic type.
Aya bdoloen utgcezlaq pu hdard lees asy xaqreltr rxzehun ytje.

struct AdaptingStack<Content>: View where Content: View {
  init(@ViewBuilder content: @escaping () -> Content) {
    self.content = content

  var content: () -> Content
  @Environment(\.sizeCategory) var sizeCategory

    var body: some View {
      switch sizeCategory {
      case .accessibilityLarge,
        return AnyView(VStack(
          content: self.content)
            .padding(.top, 10))
        return AnyView(
          HStack(alignment: .top,
            content: self.content))
@Environment(\.accessibilityReduceMotion) var reduceMotion
  if animated && !reduceMotion { /* animate at will! */ }
Display & Text Size▸Color Filters▸Grayscale
Womghos & Siyv Xira▸Gugej Beywodf▸Vpivkqusu

accessibilityEnabled == true
  && !UIAccessibility.isVoiceOverRunning
  && !UIAccessibility.isSwitchControlRunning


In this section, you’ll cover: Motion, cross-fade, hidden, and increase button’s tappable area


Flight Status

This view has a list of arrivals and departures.

  .accessibilityHint(Text("Use the tab bar to show only arrivals or departures."))


First, activate a list item to see its detail view, then activate Show Terminal Map.

Terminal map animation
Jamvawuc rur izubewoon


Activate the Search Flights button. Select the Departures filter then navigate down the list to a canceled flight and activate it.

Button Shapes in action
Nohhor Wgitam ib ulwaon

Truly testing your app’s accessibility

First, get back to the welcome view of MountainAirport.

Key points

  • Use VoiceOver on a device to hear how your app really sounds.
  • Accessibility is built into SwiftUI. This reduces the amount of work you need to do, but you can still make improvements.
  • Use semantic font sizes to support Dynamic Type. Use semantic colors and Dark/Light appearance color assets to adapt to Dark Mode. Use standard SwiftUI control and layout views to take advantage of SwiftUI-generated accessibility elements.
  • The most commonly used accessibility attributes are Label, Value and Hint. Use Hidden to hide UI elements from VoiceOver. Combine child elements or specify their sort priority to reorganize what VoiceOver reads.
  • Sometimes you need to change your app’s UI for all users, to make it accessible.
  • Check how your app looks with accessibility settings and adjust as necessary.
  • Use the screen curtain on your device to really experience how a VoiceOver user interacts with your app.

Where to go from here?

There are lots of resources to help you make your apps accessible. Here are just a few:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.