Making a view accessible using the Accessibility Representation modifier

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.

0:00
/0:20

Example of a bad user experience using VoiceOver on the RatingView

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”.

Accessibility - Create with Swift
We are the coders and designers of imaginary institute and want to empower anyone to create and share their ingenuity through code. #WeLoveSwift

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.

0:00
/0:21

Good user experience using VoiceOver on the RatingView

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.