Let's add accessibility support to this app!
- Go to Settings → General → Accessibility
- Scroll down to the very bottom → Accessibility Shortcut
- Choose VoiceOver
- You can now turn VoiceOver on and off by quickly triple-clicking the Home button (or Side button on iPhone X<)
Single-tap anywhere on the screen and VoiceOver will speak aloud the item that you're tapping on.
Drag your finger over the screen and VoiceOver tells you what’s there.
Flick left and right to move from one element to the next.
Double-tap on an item to select it.
Use 3 fingers to scroll through a page.
Triple-tap with 3 fingers to toggle curtain.
Twist 2 fingers on the screen (like you're turning a dial) to turn on rotor.
[iPhone X<]Slide one finger up from the bottom of the screen until you feel the click & lift your finger to return to the Home screen.
Using VoiceOver, explore the Clock app. Try to add a new Alarm.
- Download / clone the project
- Select a Team in Project settings & run on your iPhone
- Triple-tap Home/Side button to activate VoiceOver.
- Flick through the
TartListViewControllerscreen. - Set focus on one of the cells and double tap to select it.
- Flick through the
TartDetailsViewControllerscreen.
What could work better?
1. Visual hierarchy
After entering a new screen, focus should be set to a Back/Close button.
Remove addImportantButton() from viewDidLoad.
2. Accessibility labels
Short and descriptive name for UI controls.
Add a label to the Back button.
@IBOutletprivate weak varbackButton:UIButton!{
didSet {
backButton.accessibilityLabel ="Back"}}3. Accessibility elements
Navigating through the screen should be logical and should not slow down the user.
Group UI controls so that you create:
- two accessibility elements for
tartDetailsContainerView: adifficultyElementand aauthorElement, - a
recipeElementfor arecipeContainerView.
privatefunc setupAccessibility(){letdifficultyElement=UIAccessibilityElement(accessibilityContainer: tartDetailsContainerView)
difficultyElement.accessibilityLabel ="\(difficultyTitleLabel.text!), \(difficultyLevel!)"
difficultyElement.accessibilityFrameInContainerSpace = difficultyTitleLabel.frame.union(difficultyLabel.frame)letauthorElement=UIAccessibilityElement(accessibilityContainer: tartDetailsContainerView)
authorElement.accessibilityLabel ="\(authorTitleLabel.text!), \(authorLabel.text!)"
authorElement.accessibilityFrameInContainerSpace = authorTitleLabel.frame.union(authorLabel.frame)
tartDetailsContainerView.accessibilityElements =[difficultyElement, authorElement]letrecipeElement=UIAccessibilityElement(accessibilityContainer: recipeContainerView)
recipeElement.accessibilityLabel ="\(recipeHeaderLabel.text!), \(recipeLabel.text!)"
recipeElement.accessibilityFrameInContainerSpace = recipeHeaderLabel.frame.union(recipeLabel.frame)
recipeContainerView.accessibilityElements =[recipeElement]}Call setupAccessibility() from viewDidLoad.
1. Modal views
Using a modal overlay view should be marked so that the elements underneath are not accessible.
Set the overlay preview as a modal view. Go to the zoomTart(_ sender:) function and add:
overlay.accessibilityViewIsModal =trueInform VoiceOver that there was a new screen presented. At the end of the same function, add:
UIAccessibility.post(notification:.screenChanged, argument:nil)2. Navigating cells in a table view
Detailed contents of a custom cell should not slow down navigating through a table view.
Go to the TartCell.swift and set isAccessibilityElement property of a cell to true, so that the whole cell is treated as an accessibility element by VoiceOve. Go to awakeFromNib() and add:
isAccessibilityElement =trueSet the accessibilityLabel and accessibilityHint of the cell. In the configure(withName: level: image: tag:) function
accessibilityLabel = nameLabel.text
accessibilityHint = difficultyLabel.textHints are optional and can be disabled by the user. Keep them short and informative — they slow down the navigation a lot.
3. Custom actions
Don't strip the VoiceOver user of any functionality.
Go to the TartListViewController.swift and add a custom action to the cell that will show a preview. In a tableView(_: cellForRowAt:) -> UITableViewCell function, add:
letshowPreview=UIAccessibilityCustomAction(name:"ShowPreview",
target:self,
selector: #selector(zoomTart(_:)))
cell.accessibilityCustomActions =[showPreview]If we want to let the user add the tart to favorites using VoiceOver, we need to know which cell was the custom action called from. Create a subclass of UIAccessibilityCustomAction that will have an indexPath property.
classTartAccessibilityCustomAction:UIAccessibilityCustomAction{letindexPath:IndexPathinit(name:String, indexPath:IndexPath, target:Any, selector:Selector){self.indexPath = indexPath
super.init(name: name, target: target, selector: selector)}}We will create a favoriteAccessibilityCustomAction, but it can't be static — if the tart is not yet starred, it should be read "Add to favorites". However, if the tart is already starred, it should be: "Remove from favorites". So every time we update the custom action, we will overwrite the accessibilityCustomActions property of the cell. Go to the TartCell.swift and add:
varpreviewAccessibilityCustomAction:UIAccessibilityCustomAction?{
didSet {iflet preview = previewAccessibilityCustomAction,let favorite = favoriteAccessibilityCustomAction {self.accessibilityCustomActions =[preview,
favorite]}}}varfavoriteAccessibilityCustomAction:TartAccessibilityCustomAction?{
didSet {iflet preview = previewAccessibilityCustomAction,let favorite = favoriteAccessibilityCustomAction {self.accessibilityCustomActions =[preview,
favorite]}}}Now go to the TartListViewController and, instead of code from Checkpoint 7 point 3 inside the tableView(_: cellForRowAt:) -> UITableViewCell function, let's do:
letshowPreview=UIAccessibilityCustomAction(
name:"ShowPreview",
target:self,
selector: #selector(zoomTart(_:)))
cell.previewAccessibilityCustomAction = showPreview
letfavorite=TartAccessibilityCustomAction(
name:"Add to favorites",
indexPath: indexPath,
target:self,
selector: #selector(toggleFavorite))
cell.favoriteAccessibilityCustomAction = favoriteand below let's add a function:
@objcprivatefunc toggleFavorite(_ sender:TartAccessibilityCustomAction)->Bool{letcell= tableView.cellForRow(at: sender.indexPath)as!TartCell
cell.toggleFavorite()
sender.name = cell.isFavorite ?"Remove from favorites":"Add to favorites"
cell.favoriteAccessibilityCustomAction = sender
returntrue}Larger text can be set under Settings → General → Accessibility → Larger Text
1. Larger text for system font
- Storyboard: choose Tarts overview label and in the Attributes inspector, set the Font to Headline and tick Automatically Adjusts Font.
- Programmatically: go to TartCell.swift and do:
@IBOutlet weak vardifficultyLabel:UILabel!{ didSet { difficultyLabel.font =.preferredFont(forTextStyle:.caption1) difficultyLabel.adjustsFontForContentSizeCategory =true}}
2. Larger text for custom font
Let's add an UIFont's extension with a method returning a custom font ready to support accessibility larger text:
extensionUIFont{staticfunc getScaledFont(named name:String, textStyle:UIFont.TextStyle)->UIFont{letuserFont=UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle)letpointSize= userFont.pointSize
guardlet customFont =UIFont(name: name, size: pointSize)else{fatalError("Failed to load the \(name) font.")}returnUIFontMetrics.default.scaledFont(for: customFont)}}and in the TartCell.swift, let's change the nameLabel so that it uses our custom font properly:
@IBOutlet weak varnameLabel:UILabel!{
didSet {
nameLabel.font =.getScaledFont(named:"Cochin", textStyle:.body)
nameLabel.adjustsFontForContentSizeCategory =true}}