Blog

We believe there is something unique at every business that will ignite the fuse of innovation.

In the past I have written about accessibility in iOS (part 1, part 2). With new enhancements released in watchOS 2, it is time to return to accessibility and apply it to the watch. For this tutorial, we will start a new Apple Watch app, which can be found at GitHub. My goal is to demonstrate how easy accessibility for the watch really is and to encourage everyone to use the accessibility features built into watchOS.

Overview

The app is very simple; there is a list of people. Each person has a name, age and weight. When the app loads you will be looking at the details of the first person. Force pressing will bring up a context menu with an option to allow the watch to change the display to a different random person. There is also a button that says “New Person” at the bottom of the screen and selecting that button will display a list of all people. Selecting a person from the list will then return you to the first screen and display the new person’s information.

Accessibility

There are five main accessibility features in watchOS that are controlled by the user. They are: Text Size, Grayscale, Reduced Motion, Reduced Transparency and VoiceOver. We will go through these topics one at a time to see just how quickly they can be handled.

Text Size

This accessibility feature is handled for you automatically as long as you are using a standard font. Since we are not addressing how to create custom fonts we will not address how to implement text size accessibility for those custom fonts. However, just like with system fonts, it is very important for your users to be able to increase text size when needed.

Normal Text Size 

Grayscale

Again, gray scale is handled for us automatically. This is a system wide accessibility feature where everything gets transformed to remove colors.

Reduced Transparency

Surprise! watchOS handles this for us as well. You can see transparency when you pull down the notification center or activate Siri. In your app you can see transparency when you display a WKInterfaceController modally. All of these places become opaque when you turn on the reduced transparency feature. 

 

Reduce Motion

This is one accessibility feature that I am disappointed that we, as developers, do not have (or get) to handle. In iOS we have the option of limiting animation when the Reduced Motion option is on. In watchOS, there is no way for us to determine if this accessibility feature has been turned on. In most watchOS apps there is very limited animation, if any at all, however, I think it would still be good to have an opportunity to remove animation that isn’t absolutely necessary when the user doesn’t want it.

VoiceOver

Before getting into how to deal with VoiceOver programmatically, we need to understand what, exactly, VoiceOver is and what it does. VoiceOver will read the screen to the user. The user can swipe right to move to the next element or swipe left to the previous element. After each swipe, VoiceOver will read the element that is highlighted at that time. The user can also touch the screen and move around the screen with his finger. While doing this, VoiceOver will either read the element directly under the user’s finger, if there is one, or will play a sound to indicate that there is no accessible element under the user’s finger.

Finally, VoiceOver is the one area where we actually have some control. It is very important to implement VoiceOver properly to allow apps to be usable by everyone. Just like on iOS, watchOS handles a large part of this for us. However, it is likely that we will run into some places in our apps where VoiceOver needs some help. The first thing to do whenever you are trying to implement accessibility is to see what is and isn’t working automatically. Lets start by opening the sample app, running it and turning on accessibility.

App Evaluation

We will focus on the first screen of this app since the other screens are supplemental and will not need any help at all to be fully accessible. On the first screen there are a couple of things to take notice of. First, Siri is very intelligent! The app lists the user’s weight at “184 lbs” and Siri properly reads that as “184 pounds”. On the previous line, though, VoiceOver says “18 years” and we would really like it to say “18 years old” since VoiceOver is not limited to screen size. The next thing to notice is that both elements in each row of data are related. It would be nice if we could swipe once and hear both the title and the value together so that we don’t have to swipe twice for each piece of data. Additionally, the row title and the value individually don’t tell the user much, the other piece of data is needed. 

Moving down the screen, VoiceOver completely skips the icon to indicate that the user is a favorite. The last thing that I noticed on this screen is that if I press the screen and pan my finger around to find out what is under my finger, most of the screen is unrecognized by Siri. This is understandable since there is not too much data on the screen. However, it is not helpful for a VoiceOver user. Now that we have identified these issues we can take steps to fix them.

Accessiblity Label

To customize the accessibility text so that VoiceOver reads something different than the screen says, we will assign an accessibility label. VoiceOver will read this label instead of the regular label. Inside of the updateUI() method add the following line:

ageLabel.setAccessibilityLabel("\(person.age) years old")

Group Accessibility

To group rows so that VoiceOver reads them together, since they are very much related, we simply have to specify the WKInterfaceGroup objects as being accessible. By doing this, VoiceOver will recognize the entire group together instead of each individual label. Open the Interface storyboard and select each of the three groups on the main screen. For each one, look on the right and change the accessibility option to “Enabled”.

Now, run the app again and notice that swiping will read an entire row at once instead of requiring the label and value to be read after separate swipes. For example, the first swipe to the right will read “Name Steve” instead of “Name” and the next swipe will go to the next row of data. Also, if you touch the screen at this point and pan around the screen you will notice that VoiceOver has already improved since the user can touch anywhere in the group to hear the value read. Previously, a value was only read if the user’s finger was over a specific UI element. We can improve this a little more, though. 

Layout Changes

The groups are still very small in the vertical direction so it would be easy for a user to skip over an entire group if they do not move their finger slow enough. We can fix this issue as well. First, we need to change the height of each group so that it is a fixed height. Do this by opening the Interface storyboard and selecting a group. In the right panel we can change height from “Size To Fit Content” to “Fixed”. Next, change the height to 20. And, the final step in the storyboard is to make each item inside of the group vertically centered.

If we stop here then we will still have rows that are about the same height as they were before, which does not help a VoiceOver user. So, now we will add a small piece of code to InterfaceController to adjust the height of each group depending on the state of VoiceOver.

func resizeGroupsIfNeeded() {
    let size: CGFloat
    if (WKAccessibilityIsVoiceOverRunning()) {
        size = 30
    } else {
        size = 20
    }
    nameGroup.setHeight(size)
    ageGroup.setHeight(size)
    weightGroup.setHeight(size)
}

We need to call this method in willActivate() and we also want to put the following line of code In the init() method so that the system will tell us if VoiceOver is turned on or off while the app is running.

NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("resizeGroupsIfNeeded"), name: WKAccessibilityVoiceOverStatusChanged, object: nil)

Running the app now, with VoiceOver on feels much better. The user has plenty of room to move their finger around the screen without leaving the important regions of the screen.

Image Accessibility

The only issue left to address is the favorite icon since it is not accessible at all. A VoiceOver user will not even know this icon is on screen and it is important information. There are two ways to fix this. The first is by adding an accessibility label to the image just like we did to change the text of the age for VoiceOver. Since we have already looked at how to do that, we will fix this issue using a WKAccessibilityImageRegion. WKAccessibilityImageRegion objects are typically used with more complex images, but they do work just as well for simpler images as well. It is also important to note that WKAccessibilityImageRegion objects have a frame that is in the context of the device’s screen instead of the image’s frame. In the code below you will notice that we are using the screen’s width to determine the location of the image. To add a WKAccessibilityImageRegion add the following code to the willActivate() method.

let imageRegion = WKAccessibilityImageRegion()
imageRegion.frame = CGRectMake(0, 0, 30, 30)
imageRegion.label = "Favorite Person"
favoriteIcon.setAccessibilityImageRegions([imageRegion])

If you had an image that represented several different pieces of data, you could create several image regions and add them to the image. In that case, as the user touched different parts of the image, VoiceOver would give them different pieces of data. In our example, we only need one image region.

At last, we can run the app and the entire screen is accessible for VoiceOver users! As I’m sure you’ve noticed, there are a few new things to learn and get familiar with here, but implementing accessibility on your watchOS 2 app really does not require much work. Just like iOS, watchOS takes care of a large portion of the heavy lifting for us. A final version of this sample code is available on GitHub on the branch named “AccessibleVersion”.