The accessibilityCustomContent view modifier (SwiftUI Release 3, iOS 15+) attaches labelled
data to an element with an importance level, so VoiceOver reads the essentials first and the
rest on demand. Use it when a row holds many fields and reading all of them would drown the
user.
The Problem It Solves
Every Text in a detail row is accessible by default, which VoiceOver reads as a wall of data.
Collapsing the row with accessibilityElement(children: .ignore) and one label fixes the noise
but loses the other fields. Custom content restores them with priorities.
struct UserView: View {
let user: User
var body: some View {
VStack(alignment: .leading) {
Text(user.name).font(.headline)
Text(user.address).foregroundColor(.secondary)
Text(user.email).foregroundColor(.secondary)
Text("Age: \(user.age)").foregroundColor(.secondary)
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(user.name)
.accessibilityCustomContent("Age", "\(user.age)")
.accessibilityCustomContent("Email", user.email, importance: .high)
.accessibilityCustomContent("Address", user.address, importance: .default)
}
}
The modifier takes a localized label VoiceOver announces, the value, and an importance.
high content is spoken immediately with the label; default content is spoken only in verbose
mode, when the user swipes vertically for more detail. This is the assistive equivalent of
visual hierarchy through fonts and colour.
Reusable Keys
A later modifier with the same label overrides the earlier value or importance. To keep labels
consistent across a codebase, define AccessibilityCustomContentKey constants and pass the key
instead of a string.
extension AccessibilityCustomContentKey {
static let age = AccessibilityCustomContentKey("Age")
static let email = AccessibilityCustomContentKey("Email")
static let address = AccessibilityCustomContentKey("Address")
}
// usage
.accessibilityCustomContent(.age, "\(user.age)")
.accessibilityCustomContent(.email, user.email, importance: .high)
.accessibilityCustomContent(.address, user.address, importance: .default)