Accessibility from the View Controller’s Perspective

Aside from managing a view’s behavior, a view controller can also help control an app’s accessibility. An accessible app is one that can be used by everyone, regardless of disability or physical impairment, while retaining its functionality and usability as a helpful tool.

To be accessible, an iOS app must supply information about its user interface elements to VoiceOver users. VoiceOver, a screen-reading technology designed to assist the visually impaired, speaks aloud text, images, and UI controls displayed the screen, so that people who cannot see can still interact with these elements. UIKit objects are accessible by default, but there are still things you can do from the view controller’s perspective to address accessibility. At a high level, this means you should make sure that:

In addition to these fundamentals, a view controller can enhance the VoiceOver user’s experience by setting the position of the VoiceOver focus ring programmatically, responding to special VoiceOver gestures, and observing accessibility notifications.

Moving the VoiceOver Cursor to a Specific Element

When the layout of a screen changes, the VoiceOver focus ring, also known as the VoiceOver cursor, resets its position to the first element displayed on the screen from left to right and top to bottom. You might decide to change the first element the VoiceOver cursor lands on when views are presented onscreen.

For example, when a navigation controller pushes a view controller onto the navigation stack, the VoiceOver cursor falls on the Back button of the navigation bar. Depending on your app, it might make more sense to move it to the heading of the navigation bar instead, or to any other element.

To do so, call UIAccessibilityPostNotification using both the notification UIAccessibilityScreenChangedNotification (which tells VoiceOver that the contents of the screen has changed) and the element you’d like to give focus to, as shown in Listing 9-1.

Listing 9-1  Posting an accessibility notification can change the first element read aloud

@implementation MyViewController
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
 
    UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
                                    self.myFirstElement);
}
@end

If only the layout changes rather than the contents of the screen, such as when switching from portrait to landscape mode, use the notification UIAccessibilityLayoutChangedNotification instead of UIAccessibilityScreenChangedNotification.

Responding to Special VoiceOver Gestures

There are special gestures that VoiceOver users can perform to trigger custom actions. These gestures are special because you are allowed to define their behavior, unlike standard VoiceOver gestures. You can detect the gestures by overriding certain methods in your views or view controllers.

A gesture first checks the view that has VoiceOver focus for instruction and continues up the responder chain until it finds an implementation of the corresponding VoiceOver gesture method. If no implementation is found, the system default action for that gesture is triggered. For example, the Magic Tap gesture plays and pauses music playback from the Music app if no Magic Tap implementation is found from the current view to the app delegate.

Although you can provide any custom logic you want, VoiceOver users expect the actions of these special gestures to follow certain guidelines. Like any gesture, your implementation of a VoiceOver gesture should follow a pattern so that interaction with an accessible app remains intuitive.

There are five special VoiceOver gestures:

Escape

If you present a view that overlays content—such as a modal dialog or an alert—you should override the accessibilityPerformEscape method to dismiss the overlay. The function of the Escape gesture is like the function of the Esc key on a computer keyboard; it cancels a temporary dialog or sheet to reveal the main content.

Another use case to override the Escape gesture would be to go back up one level in a navigation hierarchy. UINavigationController implements this functionality by default. If you’re designing your own kind of navigation controller, you should set the Escape gesture to traverse up one level of your navigation stack, because that is the functionality VoiceOver users expect.

Magic Tap

The purpose of the Magic Tap gesture is to quickly perform an often-used or most-intended action. For example, in the Phone app, it picks up or hangs up a phone call. In the Clock app, it starts and stops the stopwatch. If you want an action to fire from a gesture regardless of the view the VoiceOver cursor is on, you should implement the accessibilityPerformMagicTap method in your view controller.

Three-Finger Scroll

The accessibilityScroll: method fires when a VoiceOver user performs a three-finger scroll. It accepts a UIAccessibilityScrollDirection parameter from which you can determine the direction of the scroll. If you have a custom scrolling view, it may be more appropriate to implement this on the view itself.

Increment and Decrement

The accessibilityIncrement and accessibilityDecrement methods are required for elements with the adjustable trait and should be implemented on the views themselves.

Observing Accessibility Notifications

You can listen for accessibility notifications to trigger callback methods. Under certain circumstances, UIKit fires accessibility notifications which your app can observe to extend its accessible functionality.

For example, if you listen for the notification UIAccessibilityAnnouncementDidFinishNotification, you can trigger a method to follow up the completion of VoiceOver’s speech. Apple does this in the iBooks app. iBooks fires a notification when VoiceOver finishes speaking a line in a book that triggers the next line to be spoken. If it is the last line on the page, the logic in the callback tells iBooks to turn the page and continue reading as soon as the last line ends speaking. This allows for a line-by-line degree of granularity for navigating text while providing a seamless, uninterrupted reading experience.

To register as an observer for accessibility notifications, use the default notification center. Then create a method with the same name that you provide for the selector argument, as shown in Listing 9-2.

Listing 9-2  Registering as an observer for accessibility notifications

@implementation MyViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
 
    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(didFinishAnnouncement:)
               name:UIAccessibilityAnnouncementDidFinishNotification
             object:nil];
}
 
- (void)didFinishAnnouncement:(NSNotification *)dict
{
    NSString *valueSpoken = [[dict userInfo] objectForKey:UIAccessibilityAnnouncementKeyStringValue];
    NSString *wasSuccessful = [[dict userInfo] objectForKey:UIAccessibilityAnnouncementKeyWasSuccessful];
    // ...
}
@end

UIAccessibilityAnnouncementDidFinishNotification expects an NSNotification dictionary as a parameter from which you can determine the value spoken and whether or not the speaking has completed uninterrupted. Speaking may become interrupted if the VoiceOver user performs the stop speech gesture or swipes to another element before the announcement finishes.

Another helpful notification to subscribe to is UIAccessibilityVoiceOverStatusChanged. It can detect when VoiceOver becomes toggled on or off. If VoiceOver is toggled outside of your app, you receive the notification when your app is brought back into the foreground. Because UIAccessibilityVoiceOverStatusChanged doesn’t expect any parameters, the method in your selector doesn’t need to append a trailing colon (:).

For a full list of possible notifications you can observe, consult “Notifications” in UIAccessibility Protocol Reference. Remember that you may only observe the notifications that can be posted by UIKit, which are NSString objects, and not notifications that can be posted by your app, which are of type int.