The accessibilityRepresentation view modifier (SwiftUI Release 3, iOS 15+) swaps a view's
accessibility element for that of another view. Use it when a custom control behaves like a
standard one: borrow the standard control's VoiceOver behaviour instead of rebuilding it.
The Manual Way It Replaces
A custom checkmark image with a long-press gesture reads to VoiceOver as a plain image. Fixing that by hand takes more accessibility modifiers than control logic.
Image(systemName: isSelected ? "checkmark.rectangle" : "rectangle")
.onLongPressGesture { isSelected.toggle() }
.accessibilityRemoveTraits(.isImage)
.accessibilityAddTraits(.isButton)
.accessibilityAddTraits(isSelected ? .isSelected : [])
.accessibilityLabel(Text("Checkmark"))
.accessibilityHint("You can toggle the checkmark")
.accessibilityAction { isSelected.toggle() }
Borrow a Standard Control
Provide the equivalent standard view in the closure. SwiftUI never renders it; it only copies its accessibility information onto your view, including label, traits, value, and actions.
struct LongPressCheckmark: View {
@Binding var isSelected: Bool
var body: some View {
Image(systemName: isSelected ? "checkmark.rectangle" : "rectangle")
.onLongPressGesture { isSelected.toggle() }
.accessibilityRepresentation {
Toggle(isOn: $isSelected) {
Text("Checkmark")
}
}
}
}
Pass real bindings into the representation, so VoiceOver actions actually change the state.
Whole Hierarchies, Not Just Controls
The closure can hold a full view tree. A Canvas has no accessibility at all, so represent a
custom chart as a stack of labelled elements.
struct BarChartView: View {
let bars: [Bar]
var body: some View {
Canvas {
// custom drawing here
}
.accessibilityRepresentation {
HStack {
ForEach(bars) { bar in
Rectangle()
.accessibilityLabel(bar.label)
.accessibilityValue("\(bar.value)")
}
}
}
}
}
VoiceOver then walks the bars one by one, reading each label and value, while the screen shows the drawn chart.