On iOS, VoiceOver users can navigate a screen through a swipe right gesture.
Doing so causes iOS to change accessibility focus to the next accessible element
in your views’ accessibility hierarchy. By default, many UIKit
elements declare
themselves as accessibility elements, e.g. UILabel
, UIButton
, &c.
The by-default accessibility of many iOS components is wonderful, but sometimes
we need to do a bit more work. As a concrete example, let’s say that we have a
UICollectionView
composed of cells like the following:
A straightforward implementation of the above view might look like this:
class ProductCard: UICollectionViewCell {
@IBOutlet var image: UIImageView!
@IBOutlet var brandName: UILabel!
@IBOutlet var productName: UILabel!
@IBOutlet var salePrice: UILabel!
@IBOutlet var originalPrice: UILabel!
@IBOutlet var discountLabel: UILabel!
@IBOutlet var starView: UIImageView!
@IBOutlet var reviewCount: UILabel!
@IBOutlet var addToCartBtn: UIButton!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
By default each UI element is its own accessibility element, which means this card would take 9 swipes to navigate through! That can be exhausting, particularly in a collection with many cells (especially if you have motor control concerns). Moreover, it makes more sense for some of these elements to be grouped and read together; e.g. instead of VoiceOver reading the sale price -> swipe right -> reading the original price, grouping the price labels into a single accessibility element better explains the intention and reduces the number of gestures required to navigate the card.
We can make this a bit more sensible by grouping the brand name & product name; the sale price, original price, & discount label; and the star view & review count. I might do so as follows:
class ProductCard: UICollectionViewCell {
/* ... */
func setupAccessibility() {
accessibilityElements = [image, brandName, salePrice, starView, addToCartBtn]
let brandAndProductLabel = "\(productName.text) by \(brandName.text)"
brandName.accessibilityLabel = brandAndProductLabel
let priceLabel = "Sale price: \(salePrice.text); originally \(originalPrice.text); \(discountLabel.text)"
salePrice.accessibilityLabel = priceLabel
let starCount = /* code to find current rating */
let numRatings = /* code to find the number of ratings */
let ratingsLabel = "Rating: \(starCount) out of 5; based on \(numRatings) reviews"
starView.accessibilityLabel = ratingsLabel
}
}
In doing so we’ve done two things: one, reduce the number of navigation swipes per cell from 9 to 5; two, logically group related labels to give our VoiceOver users more context.
Before our changes, VoiceOver would read our card like this (a semicolon indicates a swipe gesture):
Image; thoughtbot; Product design and development; Five dollars; Ten dollars; You save fifty percent; Image; Nine thousand seven hundred eighty four; Add to cart button
After our changes:
Image; Product design and development by thoughtbot; Five dollars, originally ten dollars, you save fifty percent; Rating: five out of five, based on nine thousand seven hundred eighty four reviews; Add to cart button
Apple has done a great job of building accessibility into the UIKit
framework,
which allows many apps to be reasonably accessible by default. As we’ve seen,
with just a small amount of attention and code, we as developers can provide an
even better experience to app users with accessibility needs.