Blog

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

Getting Started

I recently wrote a blog post discussing accessibility in iOS apps. That blog discussed how to implement some of the most common and most necessary accessibility features; however it left one very important feature out: VoiceOver. VoiceOver is an incredibly useful feature that will read the content of the screen to anyone who is not able to read it for themselves, even with bolder text and larger font size. This feature enables people who still need help, even people who are completely blind, to use the iPhone and iPad.

Sample code for this can be found on GitHub. There is a branch named “voiceOver” that has all of the features from the previous blog post with the addition of VoiceOver.

To get started, some background information is needed. First, to test VoiceOver, open the Settings app on your device and go to General > Accessibility > VoiceOver and turn on the VoiceOver toggle at the top of the screen. Second, by default VoiceOver is supported by all standard UIKit elements. In the sample app there are not any custom UIKit elements so we can run the app immediately. You will quickly find out that everything we have, which is almost all text, works out of the box, with the exception of the image. If you are having a hard time navigating your device with VoiceOver enabled, here is a handy guide from Apple.

Customizing UI Controls

UISlider

Not everything is quite this simple though. In the “voiceOver” branch there are some new UI elements that are not quite given to us for free.  The first change in this branch is adding the “Age” label, along with a slider that is positioned below the label and represents the age, as well as a button that allows the user to view education information. There are a few problems created by this, however.

First, consider the UISlider element. By default, VoiceOver will say the percentage value of this item but since the slider is being used for age, a percentage value does not make much sense. To fix this a simple subclass of the UISlider class is needed. In that subclass the following method is needed

  func accessibilityValue() -> String { 
    var age = Int(self.value) 
    return "\(age) years old" 
  } 

Run the app again and VoiceOver now says an age that makes sense with this context. There is still one more problem with the UISlider element. If the user swipes up or down to change the value, several years are being skipped. This means that an exact age is not able to be set. The fix for this requires two more methods to be implemented in the UISlider subclass. The first is for incrementing the age when the user swipes up

  override func accessibilityIncrement() { 
    self.value++ 
    sendActionForControlEvents(.ValueChanged) 
  } 

The second is for decrementing when the user swipes down

  override func accessibilityDecrement() { 
    self.value-- 
    sendActionsForControlEvents(.ValueChanged) 
  } 

The slider is now completely ready to go.  The age is given by VoiceOver accurately and the user now has the ability to change the value by one year at a time to give an accurate age.

Ordering Elements

The next issue we have is due to the ordering of the UI elements. VoiceOver is going to naturally read them from left to right then move down and read from left to right again, continuing this process until there are no more elements on the screen. For the sample app this means that the “Age” label will be read, followed by the “View Education” button and finally, returning to the age slider value. Obviously, this does not make much sense. The slider value, which is the person’s age in the sample app, should be read immediately following the “Age” label. To accomplish this goal, in iOS 8, we simply need to set the accessibilityElements property on the view

  view.accessibilityElements = [screenHeader, employerHeader, employerDetail, titleHeader, titleDetail, biographyHeader, biographyDetail, ageHeader, ageSlider, educationButton] 

Notice that the age slider property is immediately following the age header property and that the education button is last. VoiceOver will now read the screen in this order. There are two things to note with this. First, the profile image is not listed in the array. When the image is made accessible, it will need to be added so that VoiceOver knows the full order of accessibility elements. Second, this property is new in iOS 8 but the concept is not new. Prior to iOS 8 the same tasks can be accomplished by implementing the following three methods, which are part of the UIAccessibilityContainer

  /* 
  Returns the number of accessibility elements in the container. 
  */ 
  func accessibilityElementCount() -> Int 
 
  /* 
  Returns the accessibility element in order, based on index. 
  default == nil 
  */ 
  func accessibilityElementAtIndex(index: Int) -> AnyObject! 
 
  /* 
  Returns the ordered index for an accessibility element 
  default == NSNotFound 
  */ 
  func indexOfAccessibilityElement(element: AnyObject!) -> Int

Dealing With Images

The final step on the first screen of this app is to make the image accessible. This may not seem like a necessary step, and in some cases it is not, but there are times when a user might need to know that an image exists. For example, in a social networking app the user would probably want to know that there is a profile picture. Apple has made this easy to accomplish as well and it can be configured in two ways. First, directly in Interface Builder there is a section called “Accessibility”. In that section, the “Enabled” check box should be checked and the text that is written in the “Label” field is the text that will be read by VoiceOver. The following screen shot shows how to make VoiceOver read “Profile Image”.

VoiceOver customization

The second way that this could be set up is through code.

  profileImage.isAccessibilityElement = true
  profileImage.accessibilityLabel = "Profile Image"

Tables and Cells

The first page of the sample app is now completely setup for VoiceOver. The final part of VoiceOver that will be covered here is accessibility support for UITableViewCells. In order to demonstrate this a second page has been created that will be displayed when the “View Education” button is tapped. That new screen contains an educational institution, the type of degree obtained and the date of graduation.

 

education table screen shot

 

VoiceOver now reads the content of a full cell at once, instead of individual elements. Meaning that cell 1, for example will be read as a continual sentence: “Institution 1 Bachelor of Science Grad Date zero six slash fourteen slash zero three”. The first issue with this is the date, which would be more understandable if it were read as “June Fourteenth, 2003”. The second is that “Grad Date” is fine for saving space visually but when being read it would be nice to hear it as “Graduation Date”. To make the flow easy to understand, after all of the cell’s properties are being set, a method called updateAccessibility() is being called on the cell which gives a place for the accessibilityLabel for the cell to be updated to something that works better.

To fix the date a standard NSDateFormatter is used to format the date as previously described

  cellDateFormatter = NSDateFormatter()
  cellDateFormatter?.dateStyle = NSDateFormatterStyle.MediumStyle
  cellDateFormatter?.timeStyle = NSDateFormatterStyle.NoStyle
 
  var dateString = cellDateFormatter?.stringFromDate(date!)

Now the dateString property can be used to put the accessibilityLabel together as desired. This is the complete updateAccessibility() function

  func updateAccessibility() {
    var dateString = dateFormatter?.stringFromDate(date!)
    if let institution = educationInstitutionLabel.text {
      if let degree = degreeTypeLabel.text {
        if let readableDate = dateString {
          self.accessibilityLabel = "\(institution). \(degree). Graduation date, \(readableDate)"
        }
      }
    }
  }

There are a couple of important things to note in this method. First, there are several if-let statements in this code. This is done because if an optional is used as a piece of the data added to the accessibilityLabel, VoiceOver will read the word “optional” before the actual value. By using an if-let statement a value that is not an optional is able to be used thereby eliminating the word “optional” from what is read by VoiceOver. Second, since we added the words “Graduation date” along with the actual date, but did not use the text in the graduation date UILabel, the device still shows “Grad Date: 06/14/03” but VoiceOver now reads that as “Graduation date, June 14 2003”. Lastly, VoiceOver does understand punctuation marks. Looking closely at the code you will see two periods and one comma added to the accessibilityLabel. Without these characters added, VoiceOver would read one long label with no pausing, which would make it difficult to understand that there are three distinct pieces of data in this cell.

Conclusion

In comparison to my previous blog post on accessibility, it is clear that this is quite a bit more complicated to implement and there are many more ways to customize VoiceOver but I hope this will serve as a good starting point and guide you through the basics of the feature as you work to support VoiceOver and truly make your app usable for everyone.