When I was writing my tutorial Accessible SwiftUI for easy UI testing, I mentioned that it would be possible to combine the various accessibility attributes you can add to a View into one ViewModifier. Currently, if you want to add an accessibility label, identifier, value, and hint to your Views, you need to add four separate modifiers every time. You have to type the lengthy word ‘accessibility’ every time, which can slow you down.
There is a better way!
One of the great things about SwiftUI is the ability to create custom ViewModifiers that tidy away complex sets of modifiers into one easy call. When you give default values, you also don’t need every parameter to be given every time the modifier is used. Have you ever set only the width or height of a SwiftUI View, without having to give both dimensions? I use some variation of
.frame(height: 50) all the time because I use Form or List to lay my Views out in a scrolling column where every View matches the width of the screen.
In UIKit you may have discovered that you can’t set
height in that case is a get-only property. That means you have to set
myView.frame to something like
CGRect(x: myView.frame.origin.x, y: myView.frame.origin.y, width: myView.frame.width, height: 50). Miss any of these parameters, and you fail to construct a CGRect. Fail to construct a CGRect, and you can’t set the UIView’s frame. SwiftUI has a better way to do most things, and customizing specific aspects of a View is no exception.
The standard ViewModifier constructor requires you to pass in a value for every uninitialized property. This would make it pretty difficult for us to only pass in the accessibility strings we want. In the Hacking With Swift Custom Modifiers tutorial, Paul Hudson shows how we can create View extensions that neatly wrap our ViewModifier in a shorter identifier.
This allows us to decide which parameters we want to be optional (by providing a default value) or required (by not providing a default value).
A11y has been used as a shorthand for accessibility. The 11 stands for the number of letters that usually go between the A and y, and putting it in the middle looks like the word ‘ally’. Any developer who makes accessibility a priority in their apps is an ally of the disabled community, so it works on multiple levels.
Be careful when using a11y elsewhere, however, as there are a few ways in which A11y is not accessible.
Let’s create our custom modifier. It is not possible to put a modifier inside an if statement, so you can’t avoid adding a label if the label string was nil for instance. Instead, I’ve used the nil coalescing operator ?? to give an empty string when the property is nil.
Other than the main strings for identifiers, labels, values, and hints, I’ve also added a Boolean value that determines whether an element is hidden from VoiceOver.
Not everything needs to be accessible if it doesn’t add useful context for what is being displayed
Although the properties are optional that are not initialized, they shouldn’t ever be nil when we use the View extension a11y because all of the parameters have default values. It’s easy to see how we could set these defaults to something we want all Views to have, but that would probably be a mistake. Something generic in the label like ‘Button’ would not help VoiceOver at all and would add unnecessary noise, slowing the user down.
Just like the .frame(height: 50) modifier I mentioned, giving default values for the parameters gives us the choice of adding them or not. In the example above, I add different combinations of accessibility attributes and Xcode doesn’t give an error because I didn’t set them all.
Note that the fact that these values are set to an empty string by default overwrites the defaults.
For instance, a Text sets its accessibility label to the string it displays by default. Remember to always set the label, as this is the most important value for VoiceOver. If in doubt, test your app on a device using VoiceOver, and see what it says.
Forcing accessibility conformance
One way you could make sure you never forget to label a button is to create a custom button that takes a label string in its ViewBuilder. What if you want to prevent an empty string being passed for the label? That’s easy to do if you use an enum. Remember that the best labels start with a capital letter and ideally describe the action in one word.
It’s up to you what accessibility features you want to require so that you never forget these important attributes. I wanted to focus on the most common attributes for the purposes of this tutorial, but there are also Traits, Sort Priority and User Settings to consider. I’m really only scratching the surface of how you can automate the process of adding great accessibility to your SwiftUI apps.
Let me know if you have any other ideas in a response below!