Improving Accessibility Support In HotelTonight For iOS

By Justin Williams

I recently joined the HotelTonight development team to help improve and further our existing iOS app as well as building some even cooler stuff down the road. One of the first things I wanted to tackle was ensuring that our iOS app offers proper accessibility support for users that take advantage of Apple’s excellent VoiceOver technology.

VoiceOver is amazing because it enables vision-impaired users to use a device that has just a single physical button like the iPhone or iPad almost as easily as someone with full use of their sight. Tapping a button or label reads aloud just enough information for the user to perform the action intended.

Beyond the noble reasons for improving the accessibility of the HotelTonight iOS app, it will also help us as we start adopting automated testing and continuous integration as part of our build workflow. We’re still evaluating what solution we will adopt, but each one we’ve analyzed makes extensive use of Apple’s accessibility APIs to reference parts of your interface. Bonus!

Apple gives you a lot for free if you use the standard widgets that are included with the UIKit frameworks. If you stray from the standard widgets and build your own controls, they also make it relatively simple to adopt their UIAccessibility informal protocol.

Accessibility Testing and the Almighty Triple-Tap Shortcut

Before you can actually begin working on improving the accessibility of your app, you need to see where it stands in its current incarnation. The easiest way to do this is by enabling VoiceOver on your own device, running through each screen and seeing what works and what needs to be fixed.

If you are going to be working frequently with VoiceOver (and you will be if you plan to do this), you can quickly enable or disable it via a triple-tap of the Home button. Triple-tapping is not enabled by default, but you can turn this feature on by performing the following steps on your device:

  1. Launch the Settings app.
  2. Tap on General.
  3. Tap on Accessibility.
  4. Scroll down to the bottom and set Triple-click Home to Toggle VoiceOver.

Screenshot of triple tap setting

The iOS simulator also has an Accessibility Inspector you can enable that will show you traits, labels and other attributes related to accessibility. I used it a few times, but generally found it easier to do all the testing on a device with VoiceOver enabled. Just make sure you’ve got a set of headphones or your co-workers may try to smash your iPhone to silence the repeated quips of the sweet VoiceOver lady.

Table Cell Improvements

Our accessibility testing showed that the existing version of HotelTonight for iOS had a good head start thanks to how much free stuff Apple gives in terms of VoiceOver support in the frameworks. Our tab bar items each had labels that were properly read aloud. Our hotel and booking table cells each read out most of the relevant information to the user as they were browsing through the app.

Screenshot of hotel cell

In the case of our hotel cells, I wanted to make sure that all of the information that a non-VoiceOver user could see was accessible to anyone. In our case, that meant reading aloud which category the hotel was rather than just displaying an image. I also wanted to make sure that the accessibility label had a good delivery to it rather than being several short, robotic sounding sentences. When you tap on one our the hotel cells, it now reads something along the lines of:

Hotel Rex, a charming hotel. $69 dollars. Regularly $109 dollars. Available for one night. 2 Rooms Left.

Adding this was trivial.

In our UITableViewCell subclass (HTHotelCell) I added two methods that are part of the UIAccessibility informal protocol: isAccessibilityElement and accessibilityLabel. The isAccessibilityElement method just returns YES to let VoiceOver know that our table cell is indeed something that should be supported. The accessibilityLabel is where we define what is spoken by the lovely VoiceOver lady. We adjust that output based on room availability, how many nights are available to book and whether the hotel has sold out for the evening.

Detail Tab Improvements

When a user taps on a specific hotel to check out the details of it, we have a segmented control along the top with separate segments for Book, Info and Map. We use the open source SSSegmentedControl to build our stylized view and what I discovered was that it was completely inaccessible to anyone using VoiceOver.

Screenshot of hotel details

With a standard segmented control such as the one in the YouTube app’s Most Viewed tab, each segment is individually tappable and will have its label read aloud. Unfortunately for us, our custom control didn’t allow any sort selection under VoiceOver. This meant our VoiceOver users could only work within the Book tab and would never be able to see our additional information about the hotel or view it on a map from within the app.

Apple has a second informal protocol called UIAccessibilityContainer to handle situations like this. UIAccessibilityContainer allows developers to group separate UIAccessibilityElements that are part of a single visual control, but have different selectable parts. The UIAccessibilityContainer protocol has three required methods to implement:

  1. accessibilityElementCount returns the number of UIAccessibilityElements.
  2. accessibilityElementAtIndex: returns a specific instance of UIAccessibilityElement.
  3. indexOfAccessibilityElement: returns an NSInteger related to a specific UIAccessibilityElement.

As part of UIAccessibilityContainer you are also required to set the isAccessibilityElement method we discussed earlier to NO. Since each individual element will be selectable, the parent control shouldn’t be otherwise the others wouldn’t be tappable.

To get the tabs selectable in VoiceOver, we sprinkled a few extra lines of code into the drawRect: method of SSSegmentedControl. This snippet creates the UIAccessibilityElement for each tab and adds it to a separate accessibleElements array that we then work with in the protocol methods from above.

NSUInteger numberOfSegments = [self numberOfSegments];
CGFloat segmentWidth = CGRectGetWidth(self.bounds) / numberOfSegments;
CGFloat segmentHeight = CGRectGetHeight(self.bounds);
UIAccessibilityElement *element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element.isAccessibilityElement = YES;
CGRect segmentFrame = CGRectMake(i * segmentWidth, 0, segmentWidth, segmentHeight);
element.accessibilityFrame = [self.window convertRect:segmentFrame fromView:self];
UIAccessibilityTraits traits = UIAccessibilityTraitAllowsDirectInteraction;

if (i == [self selectedSegmentIndex])
{
  traits = traits | UIAccessibilityTraitSelected;
}

if (enabled == NO)
{
  traits = traits | UIAccessibilityTraitNotEnabled;
}

element.accessibilityLabel = [NSString stringWithFormat:NSLocalizedString(@"%@. Tab %d, of %d.", nil), string, i+1, numberOfSegments];
// Add the accessibility element.
element.accessibilityTraits = traits;
[_accessibilityElements addObject:element];
[element release];

You can see our fork of SSToolkit with the accessibility improvements on our Github profile.

Night Selector Improvements

Another piece of our hotel detail view controller needed a bit of improvement in VoiceOver: our HTNightSelectorView control. When deciding to book a specific hotel, we have a custom control that will slide up with the number of nights you are able to book (usually between 1 and 5 nights). Each of those options are rendered as a separate instance of a UIView subclass called HTNightsRow.

Screenshot of night selector

The previous version of the HotelTonight app would allow a VoiceOver user expose the custom night selection UI, but they couldn’t actually change the number during the booking experience. Instead the number of nights and the price were shown as separate labels that were read aloud.

To workaround this we set each instance of HTNightsRow to be an accessible element by setting isAccessibilityElement to YES and adjusting the accessibilityLabel to output the number of nights and the price as a single string. In situtations where a specific number of nights were unavailable, non-VoiceOver users are shown N/A rather than a price. VoiceOver reads that as N Slash A accessibilityLabel will instead read that out as not available.

We also adjusted each HTNightsRow instance to have a custom set of UIAccessibilityTraits. Traits are what help VoiceOver understand what each specific view or control is capable of doing. Apple offers a variety of different traits you can apply to your controls. By default we wanted to treat each HTNightsRow row as a tappable button that contains static text and allows manipulation by the user.

If the night option is unavailable, we also set the UIAccessibilityTraitNotEnabled trait. Here’s the snippet that does that.

- (UIAccessibilityTraits)accessibilityTraits
{
    UIAccessibilityTraits traits = UIAccessibilityTraitButton | UIAccessibilityTraitAllowsDirectInteraction | UIAccessibilityTraitStaticText;

    if (self.disabled == YES)
    {
        traits = traits | UIAccessibilityTraitNotEnabled;
    }

    return traits;
}

Useful Links

These were the links I frequently referenced working on this project.

In all, getting the HotelTonight iOS app up and properly running with VoiceOver enabled was a two-day project. It wasn’t the most difficult problem we have ever tackled, but it could be one of the most rewarding. We want HotelTonight to be usable by anyone with an iPhone and we can happily say that our newest version is.

You can check out our improved VoiceOver support in the upcoming 2.2 release of HotelTonight for iOS.

Written by Justin Williams

Read more posts by Justin, and follow Justin on Twitter.

Interested in building something great?

Join us in building the worlds most loved hotel app.
View our open engineering positions.