TechnologiesSwiftUI

AccessibilityRotorContent protocol

iOSmacOStvOSwatchOSvisionOSiOS 15.0+✓ renders

Content within an accessibility rotor.

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.

  1. 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 surrounding VStack, and the braces that follow are the AccessibilityRotorContent body.

  2. List the targets as AccessibilityRotorEntry values

    Inside the builder, each AccessibilityRotorEntry names 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, and in: ties that identity to a namespace.

  3. Bind entries to on-screen views through a namespace

    A rotor entry only works if its id resolves to a real, visible view. That link is made with @Namespace: the same namespace passed to each AccessibilityRotorEntry must also be passed where the element is rendered, via .accessibilityRotorEntry(id: line, in: namespace) on the matching Text(line). Matching id plus matching namespace is what lets VoiceOver move focus to the correct element.

  4. Build the entry list with control flow

    Because the closure is an @AccessibilityRotorContentBuilder, you can compose entries with ordinary SwiftUI building blocks — loops, conditionals, and ForEach — instead of listing them by hand. The example drives the rotor from data with ForEach(lines.filter { $0.hasPrefix("Important") }, id: \.self), so the rotor automatically contains exactly the flagged lines and updates when that filtered set changes.

Try it — Loosen the membership test by changing 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.

AccessibilityRotorContent.swift
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
}
Live preview
Important: review notes Regular item Important: ship release
Important: review notes Regular item Important: ship release
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →