iOS Accessibility Tutorial: Making Custom Controls Accessible
In this iOS accessibility tutorial, you’ll learn to make custom controls accessible using VoiceOver, elements group, custom action, traits, frame and more. By Andrew Tetlaw.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
iOS Accessibility Tutorial: Making Custom Controls Accessible
20 mins
- Getting Started
- Experiencing VoiceOver
- Grouping Accessibility Elements
- Making Screen Updates
- Working with Custom Accessibility Actions
- Adding Your Rating Action
- Making the Dial Control Accessible
- Setting Accessibility Traits For Custom Controls
- Supporting Accessibility Gestures
- Overriding Accessibility Value
- Accessibility Frame in Container Space
- Supporting Reduced Motion
- Taking Accessibility to the Next Level
- Where to Go From Here?
Overriding Accessibility Value
The VoiceOver announcement implementation for the knob rating’s value is simple: You need to override the accessibilityValue
property. You can override this property in your subclass.
Add the following code to RatingKnobAccessibilityElement
:
override var accessibilityValue: String? {
get {
guard let rating = ratingViewController?.rating else {
return super.accessibilityValue
}
return "\(rating)"
}
set {
super.accessibilityValue = newValue
}
}
Build and run. VoiceOver should now announce the rating value as you adjust it.
Accessibility Frame in Container Space
With Screen Curtain disabled, you may notice that as you switch focus to your custom element there’s no frame drawn around it. You need to override accessibilityFrameInContainerSpace
to fix that.
First, in AddRatingViewController
, add the following code:
var frameForAccessibilityElement: CGRect {
return ratingKnobContainer.convert(ratingKnob.frame, to: nil)
}
The code above converts the knob’s frame to the coordinate space of the accessibility container or view controller’s view. You must do this because the knob is nested within another view. This will allow the accessibility focus frame to correctly position in the view controller’s view.
Then, override the following property in RatingKnobAccessibilityElement
:
override var accessibilityFrameInContainerSpace: CGRect {
get {
guard let frame = ratingViewController?.frameForAccessibilityElement
else {
return super.accessibilityFrameInContainerSpace
}
return frame
}
set {
super.accessibilityFrameInContainerSpace = newValue
}
}
Build and run. Then, add a rating. Swipe to select the knob control. The frame should correctly surround the control.
Supporting Reduced Motion
Since the knob control can animate into its next position, you can add support for the accessibility reduce motion feature, too. You’ll normally make use of this feature if your end users have sensitivity to motion effects or screen movement.
First, open Knob.swift.
In the class KnobRenderer
find setPointerAngle(_:animated:)
, then replace:
if animated {
With:
if animated && !UIAccessibility.isReduceMotionEnabled {
In the if
statement, you decide whether to animate the knob rotation based on the device’s reduced motion accessibility setting.
To test this out, you need to go to your device’s accessibility settings and set Reduce Motion to On. You find this setting under Accessibility ▸ Motion.
Build and run. Now, navigate between amps. You should see the knob value change without the knob rotation animation.
Taking Accessibility to the Next Level
Now, add an amp rating and make it an eleven. Seems a bit anticlimactic right?
If an amp deserves the ultimate rating, the app should acknowledge this in some way.
Inside AddRatingViewController.swift, go to RatingKnobAccessibilityElement
and add the following to the end of accessibilityIncrement()
:
if let rating = ratingViewController?.rating, rating == 11 {
let message = NSAttributedString(
string: "Whoa! This one goes to 11!",
attributes: [.accessibilitySpeechPitch: 0.1,
.accessibilitySpeechQueueAnnouncement: true])
UIAccessibility.post(notification: .announcement,
argument: message)
}
UIAccessibility.Notification.announcement
is an accessibility notification you can use to make VoiceOver announce something. I suppose the name makes that obvious. You can pass an NSAttributedString
annotated with special attributes for VoiceOver.
accessibilitySpeechPitch
controls the pitch of the voice. It’s a value between 0.0 and 2.0. 1.0 is normal, 0.0 is the lowest pitch and 2.0 is the highest.
accessibilitySpeechQueueAnnouncement
controls when the string is announced. Specify true
to ensure VoiceOver queues the announcement after it finishes other current announcements.
If this is false
, the notification can interrupt whatever VoiceOver is announcing when the app posts this notification. You don’t want to interrupt the value original announcement, but rather add this as a fun VoiceOver Easter egg when your user selects 11 on the rating knob.
Build and run. Try it out. Hopefully, it’ll be a fun surprise. :)
And that’s it! You now have an app that’s accessible. Navigating the app is a simple and clear experience, and it even includes an Easter egg for those using VoiceOver!
Great job! :]
Where to Go From Here?
You can download the final project using the Download Materials button at the top or bottom of this tutorial.
Of course, this is not the limit of what you can achieve with the iOS accessibility features. Here’s a couple of challenges you may like to try:
Implement accessibilityPerformMagicTap()
:
Magic tap is a special accessibility gesture that should act as a shortcut to the most likely action. For example, if there’s an incoming phone call, the magic tap will answer the call and then hang up if you perform the gesture again. Perhaps for your app, you might use it to trigger adding a rating or view the next amp.
Implement accessibilityPerformEscape()
:
This is another standard gesture for dismissing the current modal view on screen. This could be useful for dismissing AmpDetailsViewController
.
Finally, start exploring. The iOS Accessibility features are extensive. Implementing any number of them will instantly elevate your app above all the apps that fail to consider them.
Apple’s documentation is a gold mine for ideas on improving the accessibility of your app. And, a great place to start is the Apple Accessibility Documentation.
I hope you enjoyed this iOS accessibility tutorial. If you have any questions or comments, please join the forum discussion below.