Simplify SwiftUI accessibility with one modifier to rule them all!

Image for post
Image for post

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 myView.frame.height, because 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).

See more of what our Digital team does by visiting the Twinkl Reality page

A11yModifier

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.

Next steps

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!

About Rob Sturgeon

Rob is a Placement App Developer at Twinkl. He’s currently working on their iOS and Android apps, enabling teachers to view, organize and download resources on the go.

Written by

An iOS developer who writes about gadgets, startups and cybersecurity. Swift programming tutorials and SwiftUI documentation too. robsturgeon.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store