WWDC 2017: Helping You Get Things Done
iOSAt this year's Worldwide Developers Conference (WWDC), we saw the continued evolution of Apple’s offerings. Pundits may say these changes aren't revolutionary enough, but...
We’ve been talking about the high value that Apple has placed on Accessibility (AX) in iOS. We wanted to write a few posts to offer some tips and suggestions for covering a good majority of AX issues that creep up and suggest that this is something that should be done as you are going along. In the last post, we talked about a few questions you should always be keeping in the back of your mind that will flag common areas that can break accessibility.
One of those tips was to watch for any time you change the default behavior of an object. Say, when you want to turn an image into effectively a button. It needs to change state, and maybe even play a sound. That’s going to involve playing into UIAccessibilityTraits
from your code. Which gets us into bitmasks. It’s certainly one of the rougher spots that still exists in Apple’s AX implementation. We’ll first talk you through a straight forward way of handling it, and then another more creative solution to make it more Swifty.
So we have our image, that acts like a toggling button. Visually there is a clear design affordance when the image button changes colors. Somehow, this information needs to be conveyed through accessibility options to VoiceOver for people that are not looking at or can not see the screen.
No problem, you can set those UIAccessibilityTraits
with code right? Sure can. But isn’t it a bitmask or some such C relic? Right again. Under the hood of UIAccessibilityTraits
is a UInt64
bitmask. Each on or off bit represents a different trait. So if you’ve been looking for a practical application of bitmasks since CS 101, you’re in luck. For the rest of us, this code can look a little crusty. Even seasoned pros can get confused on how to set and clear bits in Swift. It all starts off pretty okay. Just a simple |
pipe to OR
the options together.
@IBOutlet var imageButton: UIImageView!
// setting in my code somewhere
imageButton.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitImage | UIAccessibilityTraitPlaysSound
That’s all fine and good, if a bit longish. It is certaintly wrapping multiple lines. Now let’s handle clicking the image. That means we need to use the bitwise OR
operator again to toggle the right bits on without clearing any of the other bits that are already set.
imageButton.accessibilityTraits = UIAccessibilityTraitButton | UIAccessibilityTraitImage | UIAccessibilityTraitPlaysSound | UIAccessibilityTraitSelected
// or more simply
imageButton.accessibilityTraits |= UIAccessibilityTraitSelected
Now to turn it back off. This is where it can really get cryptic looking. The most simple way is to use the bitwise AND
along with the bitwise NOT
to unset that tricky little bit. (Remember all that fun logic from your CS classes?)
imageButton.accessibilityTraits &= ~UIAccessibilityTraitSelected
What if you want to see if a trait is already set?
let isSelected = imageButton.accessibilityTraits & UIAccessibilityTraitSelected
Problem solved? Yeah, mostly. It’s fine. Except when you come back to it in a few months. Or the next guy has to figure it what’s going on. Or when you have to explain it. Or when you have to push it up on a PR. Or even worse, when you have to check someone else’s bit logic on their PR.
It really is not the most readable thing write. Writing code is easy; reading it is hard. How can we make this more readable? Do you see this type of code anywhere else in your Swift code? Of course not! In Swift, we have this wonderful thing called an OptionSet. An OptionSet is a type that presents a mathematical set interface to a bitmask. Which is to say, it could make UIAccessibilityTraits
actually pretty easy. Take a look.
struct AccessibilityTraits: OptionSet {
let rawValue: UIAccessibilityTraits
static let button = AccessibilityTraits(rawValue: UIAccessibilityTraitButton)
static let link = AccessibilityTraits(rawValue: UIAccessibilityTraitLink)
static let image = AccessibilityTraits(rawValue: UIAccessibilityTraitImage)
static let selected = AccessibilityTraits(rawValue: UIAccessibilityTraitSelected)
static let playsSound = AccessibilityTraits(rawValue: UIAccessibilityTraitPlaysSound)
static let keyboardKey = AccessibilityTraits(rawValue: UIAccessibilityTraitKeyboardKey)
static let staticText = AccessibilityTraits(rawValue: UIAccessibilityTraitStaticText)
static let summaryElement = AccessibilityTraits(rawValue: UIAccessibilityTraitSummaryElement)
static let notEnabled = AccessibilityTraits(rawValue: UIAccessibilityTraitNotEnabled)
static let updatesFrequently = AccessibilityTraits(rawValue: UIAccessibilityTraitUpdatesFrequently)
static let searchField = AccessibilityTraits(rawValue: UIAccessibilityTraitSearchField)
static let startsMediaSession = AccessibilityTraits(rawValue: UIAccessibilityTraitStartsMediaSession)
static let adjustable = AccessibilityTraits(rawValue: UIAccessibilityTraitAdjustable)
static let directInteraction = AccessibilityTraits(rawValue: UIAccessibilityTraitAllowsDirectInteraction)
static let causesPageTurn = AccessibilityTraits(rawValue: UIAccessibilityTraitCausesPageTurn)
static let header = AccessibilityTraits(rawValue: UIAccessibilityTraitHeader)
static func == (lhs: UIAccessibilityTraits, rhs: AccessibilityTraits) -> Bool {
return lhs == rhs.rawValue
}
static func == (lhs: AccessibilityTraits, rhs: UIAccessibilityTraits) -> Bool {
return lhs.rawValue == rhs
}
}
Add this struct into your code and you’ll get all the readability of the OptionSet
for use in setting up your .accessibilityTraits
. Here’s what using it could look like.
@IBOutlet var imageButton: UIImageView!
@IBOutlet var stateLabel: UILabel!
var imageButtonIsOn = false {
didSet {
updateUI() // change the UI
imageButton.accessibilityTraits = imageButtonTraits.rawValue
}
}
var imageButtonTraits: AccessibilityTraits {
if imageButtonIsOn {
return [.button, .image, .selected, .playsSound]
} else {
return [.button, .image, .playsSound]
}
}
That’s incredibily readable code now! Now when the image is tapped (remember to turn User Interaction Enabled to ON and to make sure Accessibilty is Enabled, both from the Storyboard), you toggle the state of imageButtonIsOn
and that will both update the UI and change the accessibilityTraits
with the button’s didSet
method. This is a great pattern to use in your code.
You also get the benefit of simple readable checks to see whether or not a trait or a collection of traits is present.
imageButtonTraits.contains(.selected)
imageButtonTraits.isEmpty
If you neeed to add or remove traits from code, it is as simple as normal set functions and assigning the new rawValue
.
anotherButtonTraits.insert(.selected)
anotherButtonTraits.remove(.selected)
imageButton.accessibilityTraits = anotherButtonTraits.rawValue
Now UIAccessibilityTraits
feels more Swifty and less like a logic puzzle from a coding interview.
Hopefully that helps with one part of supporting AX in your app, but there are few other areas that can be tricky… we’ll get into those in the next post. What else gives you problems? What rough spots have you run across and how have you addressed them?
At this year's Worldwide Developers Conference (WWDC), we saw the continued evolution of Apple’s offerings. Pundits may say these changes aren't revolutionary enough, but...
Technology should be accessible to everyone. Apple has worked hard to make it easier to support Accessibility. Here's some tips on how to support...