How it works
AccessibilityRotorContent is the result-builder content type that describes the ordered set of entries inside a custom accessibility rotor. A rotor is a VoiceOver navigation aid that lets people spin to a category of related elements and jump directly between them; AccessibilityRotorContent is how you declare which views belong to that category and in what order they should be visited. Reach for it whenever a screen has a meaningful subset of elements that users would want to traverse on their own — headings, flagged items, search matches — rather than swiping through everything linearly. You rarely name the type directly; it is produced by the @AccessibilityRotorContentBuilder closure you pass to the accessibilityRotor modifier.
Open a rotor with the accessibilityRotor modifier
The
accessibilityRotor(_:entries:)modifier (and its variants) is the entry point: you give it a label that names the rotor in the VoiceOver rotor menu, plus a trailing closure that builds the content. Here.accessibilityRotor("Important")attaches a rotor titled "Important" to the surroundingVStack, and the braces that follow are theAccessibilityRotorContentbody.List the targets as AccessibilityRotorEntry values
Inside the builder, each
AccessibilityRotorEntrynames one destination in the rotor and pairs it with a stable identifier. In the example,AccessibilityRotorEntry(line, id: line, in: namespace)creates one entry per important line — its first argument is the spoken label,id:is the identity VoiceOver uses to locate the element, andin:ties that identity to a namespace.Bind entries to on-screen views through a namespace
A rotor entry only works if its
idresolves to a real, visible view. That link is made with@Namespace: the samenamespacepassed to eachAccessibilityRotorEntrymust also be passed where the element is rendered, via.accessibilityRotorEntry(id: line, in: namespace)on the matchingText(line). Matchingidplus matching namespace is what lets VoiceOver move focus to the correct element.Build the entry list with control flow
Because the closure is an
@AccessibilityRotorContentBuilder, you can compose entries with ordinary SwiftUI building blocks — loops, conditionals, andForEach— instead of listing them by hand. The example drives the rotor from data withForEach(lines.filter { $0.hasPrefix("Important") }, id: \.self), so the rotor automatically contains exactly the flagged lines and updates when that filtered set changes.
lines.filter { $0.hasPrefix("Important") } to lines so every line becomes an AccessibilityRotorEntry, then notice how the "Important" rotor now stops on the "Regular item" too.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct AccessibilityRotorContentDemo: View {
let lines = ["Important: review notes", "Regular item", "Important: ship release"]
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(lines, id: \.self) { line in
Text(line)
.accessibilityRotorEntry(id: line, in: namespace)
}
}
.padding()
.accessibilityRotor("Important") {
ForEach(lines.filter { $0.hasPrefix("Important") }, id: \.self) { line in
AccessibilityRotorEntry(line, id: line, in: namespace)
}
}
}
@Namespace private var namespace
}