Making a view accessible using the Accessibility Representation modifier
Learn how to ensure the accessibility of your custom views by replacing their accessible representation.
In app development, it's not uncommon for two distinct views to have the same appearance and functionalities, despite being implemented differently. This could pose a challenge for assistive technologies because it can generate a discrepancy between the visual hierarchy and the hierarchy perceived through such technologies.
For example, an Image
view with an onTapGesture
modifier may appear and function like a Button
component, but the VoiceOver experience can be completely different.
In the case of the Image
, VoiceOver might announce it differently because of the inferred label, and it will not inform the user of the possible actions that can be performed on it.
However, this can also benefit developers, as they can quickly make views more accessible by replacing them in the Accessible User Interface (the accessible representation of the app UI) with a representation that offers the same functionalities but a better assistive technology experience.
In this article, we will explore how to use a modifier provided by SwiftUI to achieve this goal.
Accessibility Representation
The accessibilityRepresentation(representation:)
modifier in SwiftUI is designed to transform how an app's elements are perceived by assistive technologies, such as VoiceOver.
This modifier allows developers to provide alternative representations of their components that are more accessible for users using assistive technologies, ensuring that the app's functionality is accessible to everyone.
Consider the implementation of the following RatingView
, which sets a rating from 0 to 5.
struct RatingView: View {
@Binding var rating: Int
private let maxRating: Int = 5
var body: some View {
HStack {
ForEach(0..<maxRating, id: \.self) { index in
Button {
rating = index + 1
} label: {
Image(systemName: "star.fill")
.foregroundStyle(index >= rating ? .gray : .yellow)
}
}
}
}
}
To a sighted user, the view’s visual elements might convey the information at a glance. However, for users relying on VoiceOver, the same view could be incomprehensible without proper enhancements for accessibility.
The experience of using VoiceOver would require a lot of swiping to navigate it. Additionally, there is no context for the rating values and no indication of the actions that can be performed and their results.
To make this view accessible, it is recommended to add labels, traits, values, and add a custom action, or even more than one as we have seen in our series of articles “How to prepare your app for VoiceOver”.
In essence, using most of the accessibility view modifiers provided by SwiftUI will enhance the accessibility of the view.
struct RatingView: View {
@Binding var rating: Int
private let maxRating: Int = 5
var body: some View {
HStack {
ForEach(0..<maxRating, id: \.self) { index in
Button {
rating = index + 1
} label: {
Image(systemName: "star.fill")
.foregroundStyle(index >= rating ? .gray : .yellow)
}
}
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(Text("Rating"))
.accessibilityValue(Text("\(rating) stars rating"))
.accessibilityAdjustableAction { direction in
switch direction {
case .increment:
guard rating < maxRating else { break }
rating += 1
case .decrement:
guard rating > 1 else { break }
rating -= 1
@unknown default:
break
}
}
}
}
Alternatively, you could use a standard component that already supports accessibility instead of creating a custom one, dropping the RatingView
itself.
How to provide an accessibility representation
We can consider the behavior of our RatingView
similar to a Stepper
or a Slider
. This similarity can be used to improve the accessibility of the user experience by replacing the RatingView
's accessible representation with that of these standard SwiftUI components, which are easily accessible by default and with a great assistive technologies experience.
struct RatingView: View {
@Binding var rating: Int
private let maxRating: Int = 5
var body: some View {
HStack {
ForEach(0..<maxRating, id: \.self) { index in
Button {
rating = index + 1
} label: {
Image(systemName: "star.fill")
.foregroundStyle(index >= rating ? .gray : .yellow)
}
}
}
.accessibilityRepresentation {
Stepper("\(rating) stars rating", value: $rating, in: 0...maxRating, step: 1)
}
}
}
SwiftUI hides the view provided in the representation closure and makes it non-interactive. It won't be rendered in the app but used only when the Accessibility Tree is created, replacing the original view.
Conclusion
We've learned about how SwiftUI's accessibilityRepresentation(representation:)
enables developers to customize a view by replacing its associated accessibility element with a standard SwiftUI component, which inherently supports accessibility.
In the case of the RatingView
, substituting its accessible representation with a Stepper
control simplifies the development process while ensuring the component remains fully accessible, providing an optimal experience for everyone.
It is important to highlight that even though replacing the accessibility representation of a custom view with an existing native component is a quick way to guarantee that your view is accessible, to ensure the best user experience with assistive technologies you should use the the accessibility modifiers available.