buildanything 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +55 -0
- package/README.md +71 -61
- package/agents/ios-app-review-guardian.md +49 -0
- package/agents/ios-foundation-models-specialist.md +46 -0
- package/agents/ios-storekit-specialist.md +52 -0
- package/agents/ios-swift-architect.md +102 -0
- package/agents/ios-swift-search.md +130 -0
- package/agents/ios-swift-ui-design.md +104 -0
- package/commands/build.md +80 -176
- package/commands/fix.md +65 -0
- package/commands/setup.md +73 -0
- package/commands/ux-review.md +63 -0
- package/commands/verify.md +72 -0
- package/hooks/session-start +18 -1
- package/package.json +5 -2
- package/protocols/brainstorm.md +99 -0
- package/protocols/build-fix.md +52 -0
- package/protocols/cleanup.md +54 -0
- package/protocols/design.md +269 -0
- package/protocols/eval-harness.md +61 -0
- package/protocols/fake-data-detector.md +64 -0
- package/protocols/ios-context.md +235 -0
- package/protocols/ios-frameworks-map.md +323 -0
- package/protocols/ios-phase-branches.md +162 -0
- package/protocols/ios-preflight.md +27 -0
- package/protocols/metric-loop.md +93 -0
- package/protocols/planning.md +87 -0
- package/protocols/smoke-test.md +110 -0
- package/protocols/verify.md +67 -0
- package/protocols/web-phase-branches.md +201 -0
- package/skills/ios/_VENDORED.md +60 -0
- package/skills/ios/activitykit/LICENSE +131 -0
- package/skills/ios/activitykit/SKILL.md +505 -0
- package/skills/ios/activitykit/references/activitykit-patterns.md +868 -0
- package/skills/ios/app-intents/LICENSE +131 -0
- package/skills/ios/app-intents/SKILL.md +494 -0
- package/skills/ios/app-intents/references/appintents-advanced.md +1076 -0
- package/skills/ios/apple-on-device-ai/LICENSE +131 -0
- package/skills/ios/apple-on-device-ai/SKILL.md +505 -0
- package/skills/ios/apple-on-device-ai/references/coreml-conversion.md +425 -0
- package/skills/ios/apple-on-device-ai/references/coreml-optimization.md +344 -0
- package/skills/ios/apple-on-device-ai/references/foundation-models.md +508 -0
- package/skills/ios/apple-on-device-ai/references/mlx-swift.md +285 -0
- package/skills/ios/ios-26-platform/SKILL.md +53 -0
- package/skills/ios/ios-26-platform/references/automatic-adoption.md +161 -0
- package/skills/ios/ios-26-platform/references/backward-compat.md +238 -0
- package/skills/ios/ios-26-platform/references/liquid-glass.md +255 -0
- package/skills/ios/ios-26-platform/references/swiftui-apis.md +277 -0
- package/skills/ios/ios-26-platform/references/toolbar-navigation.md +250 -0
- package/skills/ios/ios-bootstrap/SKILL.md +98 -0
- package/skills/ios/ios-bootstrap/references/apple-docs-mcp-config.md +28 -0
- package/skills/ios/ios-bootstrap/references/new-project-dialog.md +41 -0
- package/skills/ios/ios-bootstrap/references/xcode-mcp-config.md +29 -0
- package/skills/ios/ios-debugger-agent/LICENSE +21 -0
- package/skills/ios/ios-debugger-agent/SKILL.md +58 -0
- package/skills/ios/ios-debugger-agent/agents/openai.yaml +4 -0
- package/skills/ios/ios-entitlements-generator/SKILL.md +47 -0
- package/skills/ios/ios-hig/SKILL.md +41 -0
- package/skills/ios/ios-hig/references/accessibility.md +81 -0
- package/skills/ios/ios-hig/references/content.md +142 -0
- package/skills/ios/ios-hig/references/feedback.md +123 -0
- package/skills/ios/ios-hig/references/interaction.md +199 -0
- package/skills/ios/ios-hig/references/performance-platform.md +129 -0
- package/skills/ios/ios-hig/references/privacy-permissions.md +181 -0
- package/skills/ios/ios-hig/references/visual-design.md +84 -0
- package/skills/ios/ios-info-plist-hardening/SKILL.md +130 -0
- package/skills/ios/ios-maestro-flow-author/SKILL.md +68 -0
- package/skills/ios/ios-maestro-flow-author/references/input-and-scroll.yaml +17 -0
- package/skills/ios/ios-maestro-flow-author/references/modal-and-dismiss.yaml +14 -0
- package/skills/ios/ios-maestro-flow-author/references/onboarding-flow.yaml +16 -0
- package/skills/ios/ios-maestro-flow-author/references/tab-navigation.yaml +13 -0
- package/skills/ios/ios-maestro-flow-author/references/tap-and-assert.yaml +9 -0
- package/skills/ios/swift-accessibility/LICENSE +21 -0
- package/skills/ios/swift-accessibility/SKILL.md +371 -0
- package/skills/ios/swift-accessibility/examples/before-after-appkit.md +446 -0
- package/skills/ios/swift-accessibility/examples/before-after-swiftui.md +441 -0
- package/skills/ios/swift-accessibility/examples/before-after-uikit.md +464 -0
- package/skills/ios/swift-accessibility/references/assistive-access.md +441 -0
- package/skills/ios/swift-accessibility/references/display-settings.md +491 -0
- package/skills/ios/swift-accessibility/references/dynamic-type.md +420 -0
- package/skills/ios/swift-accessibility/references/media-accessibility.md +421 -0
- package/skills/ios/swift-accessibility/references/motor-input.md +393 -0
- package/skills/ios/swift-accessibility/references/nutrition-labels.md +362 -0
- package/skills/ios/swift-accessibility/references/platform-specifics.md +515 -0
- package/skills/ios/swift-accessibility/references/semantic-structure.md +585 -0
- package/skills/ios/swift-accessibility/references/testing-auditing.md +507 -0
- package/skills/ios/swift-accessibility/references/voice-control.md +317 -0
- package/skills/ios/swift-accessibility/references/voiceover-swiftui.md +584 -0
- package/skills/ios/swift-accessibility/references/voiceover-uikit.md +519 -0
- package/skills/ios/swift-accessibility/references/wcag-mapping.md +167 -0
- package/skills/ios/swift-accessibility/resources/audit-template.swift +128 -0
- package/skills/ios/swift-accessibility/resources/qa-checklist.md +258 -0
- package/skills/ios/swift-concurrency/LICENSE +21 -0
- package/skills/ios/swift-concurrency/SKILL.md +171 -0
- package/skills/ios/swift-concurrency/references/_index.md +50 -0
- package/skills/ios/swift-concurrency/references/actors.md +660 -0
- package/skills/ios/swift-concurrency/references/async-algorithms.md +847 -0
- package/skills/ios/swift-concurrency/references/async-await-basics.md +266 -0
- package/skills/ios/swift-concurrency/references/async-sequences.md +710 -0
- package/skills/ios/swift-concurrency/references/core-data.md +560 -0
- package/skills/ios/swift-concurrency/references/glossary.md +135 -0
- package/skills/ios/swift-concurrency/references/linting.md +155 -0
- package/skills/ios/swift-concurrency/references/memory-management.md +569 -0
- package/skills/ios/swift-concurrency/references/migration.md +1104 -0
- package/skills/ios/swift-concurrency/references/performance.md +593 -0
- package/skills/ios/swift-concurrency/references/sendable.md +598 -0
- package/skills/ios/swift-concurrency/references/tasks.md +636 -0
- package/skills/ios/swift-concurrency/references/testing.md +592 -0
- package/skills/ios/swift-concurrency/references/threading.md +495 -0
- package/skills/ios/swift-security-expert/LICENSE +21 -0
- package/skills/ios/swift-security-expert/SKILL.md +470 -0
- package/skills/ios/swift-security-expert/references/biometric-authentication.md +565 -0
- package/skills/ios/swift-security-expert/references/certificate-trust.md +592 -0
- package/skills/ios/swift-security-expert/references/common-anti-patterns.md +690 -0
- package/skills/ios/swift-security-expert/references/compliance-owasp-mapping.md +537 -0
- package/skills/ios/swift-security-expert/references/credential-storage-patterns.md +721 -0
- package/skills/ios/swift-security-expert/references/cryptokit-public-key.md +505 -0
- package/skills/ios/swift-security-expert/references/cryptokit-symmetric.md +497 -0
- package/skills/ios/swift-security-expert/references/keychain-access-control.md +508 -0
- package/skills/ios/swift-security-expert/references/keychain-fundamentals.md +596 -0
- package/skills/ios/swift-security-expert/references/keychain-item-classes.md +476 -0
- package/skills/ios/swift-security-expert/references/keychain-sharing.md +458 -0
- package/skills/ios/swift-security-expert/references/migration-legacy-stores.md +727 -0
- package/skills/ios/swift-security-expert/references/secure-enclave.md +539 -0
- package/skills/ios/swift-security-expert/references/testing-security-code.md +781 -0
- package/skills/ios/swift-testing-expert/LICENSE +21 -0
- package/skills/ios/swift-testing-expert/SKILL.md +79 -0
- package/skills/ios/swift-testing-expert/references/_index.md +12 -0
- package/skills/ios/swift-testing-expert/references/async-testing-and-waiting.md +127 -0
- package/skills/ios/swift-testing-expert/references/expectations.md +145 -0
- package/skills/ios/swift-testing-expert/references/fundamentals.md +141 -0
- package/skills/ios/swift-testing-expert/references/migration-from-xctest.md +127 -0
- package/skills/ios/swift-testing-expert/references/parallelization-and-isolation.md +95 -0
- package/skills/ios/swift-testing-expert/references/parameterized-testing.md +284 -0
- package/skills/ios/swift-testing-expert/references/performance-and-best-practices.md +187 -0
- package/skills/ios/swift-testing-expert/references/traits-and-tags.md +114 -0
- package/skills/ios/swift-testing-expert/references/xcode-workflows.md +70 -0
- package/skills/ios/swiftdata-pro/LICENSE +21 -0
- package/skills/ios/swiftdata-pro/SKILL.md +102 -0
- package/skills/ios/swiftdata-pro/agents/openai.yaml +10 -0
- package/skills/ios/swiftdata-pro/assets/swiftdata-pro-icon.png +0 -0
- package/skills/ios/swiftdata-pro/assets/swiftdata-pro-icon.svg +29 -0
- package/skills/ios/swiftdata-pro/references/class-inheritance.md +104 -0
- package/skills/ios/swiftdata-pro/references/cloudkit.md +10 -0
- package/skills/ios/swiftdata-pro/references/core-rules.md +20 -0
- package/skills/ios/swiftdata-pro/references/indexing.md +27 -0
- package/skills/ios/swiftdata-pro/references/predicates.md +73 -0
- package/skills/ios/swiftui-design-principles/AGENTS.md +21 -0
- package/skills/ios/swiftui-design-principles/LICENSE +21 -0
- package/skills/ios/swiftui-design-principles/README.md +41 -0
- package/skills/ios/swiftui-design-principles/SKILL.md +605 -0
- package/skills/ios/swiftui-design-principles/metadata.json +10 -0
- package/skills/ios/swiftui-liquid-glass/LICENSE +21 -0
- package/skills/ios/swiftui-liquid-glass/SKILL.md +95 -0
- package/skills/ios/swiftui-liquid-glass/agents/openai.yaml +4 -0
- package/skills/ios/swiftui-liquid-glass/references/liquid-glass.md +280 -0
- package/skills/ios/swiftui-performance-audit/LICENSE +21 -0
- package/skills/ios/swiftui-performance-audit/SKILL.md +111 -0
- package/skills/ios/swiftui-performance-audit/agents/openai.yaml +4 -0
- package/skills/ios/swiftui-performance-audit/references/code-smells.md +150 -0
- package/skills/ios/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
- package/skills/ios/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
- package/skills/ios/swiftui-performance-audit/references/profiling-intake.md +44 -0
- package/skills/ios/swiftui-performance-audit/references/report-template.md +47 -0
- package/skills/ios/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
- package/skills/ios/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
- package/skills/ios/swiftui-pro/LICENSE +21 -0
- package/skills/ios/swiftui-pro/SKILL.md +108 -0
- package/skills/ios/swiftui-pro/agents/openai.yaml +10 -0
- package/skills/ios/swiftui-pro/assets/swiftui-pro-icon.png +0 -0
- package/skills/ios/swiftui-pro/assets/swiftui-pro-icon.svg +29 -0
- package/skills/ios/swiftui-pro/references/accessibility.md +13 -0
- package/skills/ios/swiftui-pro/references/api.md +39 -0
- package/skills/ios/swiftui-pro/references/data.md +43 -0
- package/skills/ios/swiftui-pro/references/design.md +31 -0
- package/skills/ios/swiftui-pro/references/hygiene.md +9 -0
- package/skills/ios/swiftui-pro/references/navigation.md +14 -0
- package/skills/ios/swiftui-pro/references/performance.md +46 -0
- package/skills/ios/swiftui-pro/references/swift.md +56 -0
- package/skills/ios/swiftui-pro/references/views.md +35 -0
- package/skills/ios/swiftui-ui-patterns/LICENSE +21 -0
- package/skills/ios/swiftui-ui-patterns/SKILL.md +100 -0
- package/skills/ios/swiftui-ui-patterns/agents/openai.yaml +4 -0
- package/skills/ios/swiftui-ui-patterns/references/app-wiring.md +201 -0
- package/skills/ios/swiftui-ui-patterns/references/async-state.md +96 -0
- package/skills/ios/swiftui-ui-patterns/references/components-index.md +50 -0
- package/skills/ios/swiftui-ui-patterns/references/controls.md +57 -0
- package/skills/ios/swiftui-ui-patterns/references/deeplinks.md +66 -0
- package/skills/ios/swiftui-ui-patterns/references/focus.md +90 -0
- package/skills/ios/swiftui-ui-patterns/references/form.md +97 -0
- package/skills/ios/swiftui-ui-patterns/references/grids.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/haptics.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/input-toolbar.md +51 -0
- package/skills/ios/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
- package/skills/ios/swiftui-ui-patterns/references/list.md +86 -0
- package/skills/ios/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
- package/skills/ios/swiftui-ui-patterns/references/macos-settings.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/matched-transitions.md +59 -0
- package/skills/ios/swiftui-ui-patterns/references/media.md +73 -0
- package/skills/ios/swiftui-ui-patterns/references/menu-bar.md +101 -0
- package/skills/ios/swiftui-ui-patterns/references/navigationstack.md +159 -0
- package/skills/ios/swiftui-ui-patterns/references/overlay.md +45 -0
- package/skills/ios/swiftui-ui-patterns/references/performance.md +62 -0
- package/skills/ios/swiftui-ui-patterns/references/previews.md +48 -0
- package/skills/ios/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
- package/skills/ios/swiftui-ui-patterns/references/scrollview.md +87 -0
- package/skills/ios/swiftui-ui-patterns/references/searchable.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/sheets.md +155 -0
- package/skills/ios/swiftui-ui-patterns/references/split-views.md +72 -0
- package/skills/ios/swiftui-ui-patterns/references/tabview.md +114 -0
- package/skills/ios/swiftui-ui-patterns/references/theming.md +71 -0
- package/skills/ios/swiftui-ui-patterns/references/title-menus.md +93 -0
- package/skills/ios/swiftui-ui-patterns/references/top-bar.md +49 -0
- package/skills/ios/swiftui-view-refactor/LICENSE +21 -0
- package/skills/ios/swiftui-view-refactor/SKILL.md +207 -0
- package/skills/ios/swiftui-view-refactor/agents/openai.yaml +4 -0
- package/skills/ios/swiftui-view-refactor/references/mv-patterns.md +161 -0
- package/skills/ios/widgetkit/LICENSE +131 -0
- package/skills/ios/widgetkit/SKILL.md +502 -0
- package/skills/ios/widgetkit/references/widgetkit-advanced.md +871 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# VoiceOver — SwiftUI
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- [Labels, Hints, Values](#labels-hints-values)
|
|
5
|
+
- [Traits](#traits)
|
|
6
|
+
- [Actions](#actions)
|
|
7
|
+
- [Grouping and Structure](#grouping-and-structure)
|
|
8
|
+
- [Focus Management](#focus-management)
|
|
9
|
+
- [Custom Rotors](#custom-rotors)
|
|
10
|
+
- [Announcements and Live Regions](#announcements-and-live-regions)
|
|
11
|
+
- [Speech Modifiers](#speech-modifiers)
|
|
12
|
+
- [Advanced Modifiers](#advanced-modifiers)
|
|
13
|
+
- [Common Mistakes](#common-mistakes)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Labels, Hints, Values
|
|
18
|
+
|
|
19
|
+
### `.accessibilityLabel(_:)`
|
|
20
|
+
The text VoiceOver announces for any non-text element. Required for icon-only buttons and images.
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
// ✅ Good — concise, context-independent
|
|
24
|
+
Button(action: share) {
|
|
25
|
+
Image(systemName: "square.and.arrow.up")
|
|
26
|
+
}
|
|
27
|
+
.accessibilityLabel("Share")
|
|
28
|
+
|
|
29
|
+
// ❌ Bad — includes control type (VoiceOver adds "button" automatically)
|
|
30
|
+
.accessibilityLabel("Share button")
|
|
31
|
+
|
|
32
|
+
// ❌ Bad — context-dependent, unintelligible alone
|
|
33
|
+
.accessibilityLabel("More")
|
|
34
|
+
|
|
35
|
+
// ❌ Bad — action description belongs in hint
|
|
36
|
+
.accessibilityLabel("Tap to share this post")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**`[VERIFY]` rule:** When inferring a label from an SF Symbol name or action method, add a comment:
|
|
40
|
+
```swift
|
|
41
|
+
Button { deleteItem() } label: { Image(systemName: "trash") }
|
|
42
|
+
.accessibilityLabel("Delete item") // [VERIFY] confirm label matches intent
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `.accessibilityHint(_:)`
|
|
46
|
+
Briefly describes the **result** of activating the element (not the action itself). VoiceOver reads it after a short pause.
|
|
47
|
+
|
|
48
|
+
```swift
|
|
49
|
+
Button("Save") { save() }
|
|
50
|
+
.accessibilityHint("Saves your changes and closes the editor")
|
|
51
|
+
|
|
52
|
+
// ❌ Bad — describes the action, not the result
|
|
53
|
+
.accessibilityHint("Tap to save")
|
|
54
|
+
|
|
55
|
+
// ❌ Bad — redundant with the label
|
|
56
|
+
Button("Delete") { delete() }
|
|
57
|
+
.accessibilityHint("Deletes") // pointless
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Omit hints when the result is obvious from the label alone.
|
|
61
|
+
|
|
62
|
+
### `.accessibilityValue(_:)`
|
|
63
|
+
The current value of controls that change over time: sliders, steppers, progress indicators, toggles with non-standard states.
|
|
64
|
+
|
|
65
|
+
```swift
|
|
66
|
+
Slider(value: $volume, in: 0...1)
|
|
67
|
+
.accessibilityLabel("Volume")
|
|
68
|
+
.accessibilityValue("\(Int(volume * 100)) percent")
|
|
69
|
+
|
|
70
|
+
// Custom progress indicator
|
|
71
|
+
Circle()
|
|
72
|
+
.trim(from: 0, to: progress)
|
|
73
|
+
.accessibilityLabel("Upload progress")
|
|
74
|
+
.accessibilityValue("\(Int(progress * 100)) percent complete")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Do not use `.accessibilityValue` to repeat the label or append static text.
|
|
78
|
+
|
|
79
|
+
### `.accessibilityIdentifier(_:)`
|
|
80
|
+
A stable string for use in UI tests. **Not announced by VoiceOver.** Use for `XCUITest` element queries.
|
|
81
|
+
|
|
82
|
+
```swift
|
|
83
|
+
TextField("Search", text: $query)
|
|
84
|
+
.accessibilityIdentifier("searchField")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `.accessibilityLabeledPair(role:id:in:)`
|
|
88
|
+
Pairs a label with its corresponding control (e.g., a `Text` label next to a `TextField`).
|
|
89
|
+
|
|
90
|
+
```swift
|
|
91
|
+
@Namespace var formNamespace
|
|
92
|
+
|
|
93
|
+
Text("Full name")
|
|
94
|
+
.accessibilityLabeledPair(role: .label, id: "fullName", in: formNamespace)
|
|
95
|
+
|
|
96
|
+
TextField("", text: $name)
|
|
97
|
+
.accessibilityLabeledPair(role: .content, id: "fullName", in: formNamespace)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Traits
|
|
103
|
+
|
|
104
|
+
Traits describe the **semantic role** and **state** of an element. VoiceOver announces them automatically (e.g., "button", "selected", "header").
|
|
105
|
+
|
|
106
|
+
### Adding and Removing Traits
|
|
107
|
+
|
|
108
|
+
```swift
|
|
109
|
+
.accessibilityAddTraits(.isButton)
|
|
110
|
+
.accessibilityAddTraits([.isButton, .isSelected])
|
|
111
|
+
.accessibilityRemoveTraits(.isButton)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Full Trait Reference
|
|
115
|
+
|
|
116
|
+
| Trait | When to Use |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `.isButton` | Any tappable element that isn't a native `Button` |
|
|
119
|
+
| `.isLink` | Opens a URL or navigates outside the app |
|
|
120
|
+
| `.isHeader` | Section headers (h1–h6 equivalent) |
|
|
121
|
+
| `.isSelected` | Currently selected item in a list or tab |
|
|
122
|
+
| `.isToggle` | Boolean on/off control |
|
|
123
|
+
| `.isImage` | Decorative or informational image |
|
|
124
|
+
| `.isSearchField` | Search input field |
|
|
125
|
+
| `.isStaticText` | Non-interactive text |
|
|
126
|
+
| `.playsSound` | Activating this element plays a sound |
|
|
127
|
+
| `.isKeyboardKey` | Custom keyboard key |
|
|
128
|
+
| `.updatesFrequently` | Announces updates as a live region |
|
|
129
|
+
| `.causesPageTurn` | Triggers a page turn (e.g., in a book reader) |
|
|
130
|
+
| `.allowsDirectInteraction` | Passes raw touch events to the view |
|
|
131
|
+
| `.isSummaryElement` | Read when the app launches (system summary) |
|
|
132
|
+
|
|
133
|
+
### State via Traits — Not Labels
|
|
134
|
+
|
|
135
|
+
```swift
|
|
136
|
+
// ✅ Good — state as trait
|
|
137
|
+
Image(systemName: item.isStarred ? "star.fill" : "star")
|
|
138
|
+
.accessibilityLabel("Favorite")
|
|
139
|
+
.accessibilityAddTraits(item.isStarred ? .isSelected : [])
|
|
140
|
+
|
|
141
|
+
// ❌ Bad — state embedded in label (changes require re-announcing the whole label)
|
|
142
|
+
.accessibilityLabel(item.isStarred ? "Favorited" : "Not favorited")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Actions
|
|
148
|
+
|
|
149
|
+
### `.accessibilityAction(_:_:)` — Named Custom Action
|
|
150
|
+
|
|
151
|
+
Adds entries to VoiceOver's Actions rotor. Used for operations available via long-press, swipe, or context menu.
|
|
152
|
+
|
|
153
|
+
```swift
|
|
154
|
+
MessageRow(message: message)
|
|
155
|
+
.accessibilityAction(named: "Reply") { replyTo(message) }
|
|
156
|
+
.accessibilityAction(named: "Forward") { forward(message) }
|
|
157
|
+
.accessibilityAction(named: "Delete") { delete(message) }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `.accessibilityActions(_:)` — Multiple Actions via ViewBuilder
|
|
161
|
+
|
|
162
|
+
```swift
|
|
163
|
+
.accessibilityActions {
|
|
164
|
+
Button("Archive") { archive(item) }
|
|
165
|
+
Button("Share") { share(item) }
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `.accessibilityAdjustableAction(_:)` — Increment / Decrement
|
|
170
|
+
|
|
171
|
+
For custom sliders, steppers, or any value that increases/decreases.
|
|
172
|
+
|
|
173
|
+
```swift
|
|
174
|
+
CustomRatingView(rating: $rating)
|
|
175
|
+
.accessibilityLabel("Rating")
|
|
176
|
+
.accessibilityValue("\(rating) out of 5 stars")
|
|
177
|
+
.accessibilityAdjustableAction { direction in
|
|
178
|
+
switch direction {
|
|
179
|
+
case .increment: rating = min(5, rating + 1)
|
|
180
|
+
case .decrement: rating = max(0, rating - 1)
|
|
181
|
+
@unknown default: break
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### `.accessibilityScrollAction(_:)` — Scroll Direction
|
|
187
|
+
|
|
188
|
+
For custom scrollable content that doesn't use native `ScrollView`.
|
|
189
|
+
|
|
190
|
+
```swift
|
|
191
|
+
.accessibilityScrollAction { edge in
|
|
192
|
+
switch edge {
|
|
193
|
+
case .top: scrollToTop()
|
|
194
|
+
case .bottom: scrollToBottom()
|
|
195
|
+
case .leading: scrollLeft()
|
|
196
|
+
case .trailing: scrollRight()
|
|
197
|
+
@unknown default: break
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### `.accessibilityZoomAction(_:)` — Zoom Gestures
|
|
203
|
+
|
|
204
|
+
For custom maps, image viewers, or zoom-capable content.
|
|
205
|
+
|
|
206
|
+
```swift
|
|
207
|
+
.accessibilityZoomAction { action in
|
|
208
|
+
switch action.direction {
|
|
209
|
+
case .zoomIn: scale *= 1.2
|
|
210
|
+
case .zoomOut: scale /= 1.2
|
|
211
|
+
@unknown default: break
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### `.accessibilityActivationPoint(_:)` — Custom Tap Target
|
|
217
|
+
|
|
218
|
+
When the accessible tap point differs from the visual center.
|
|
219
|
+
|
|
220
|
+
```swift
|
|
221
|
+
// Tap the bottom-center of a custom shape
|
|
222
|
+
.accessibilityActivationPoint(CGPoint(x: frame.midX, y: frame.maxY - 8))
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Drag and Drop
|
|
226
|
+
|
|
227
|
+
```swift
|
|
228
|
+
.accessibilityDragPoint(UnitPoint.center, description: "Drag to reorder")
|
|
229
|
+
.accessibilityDropPoint(UnitPoint.center, description: "Drop here to add")
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Grouping and Structure
|
|
235
|
+
|
|
236
|
+
### `.accessibilityElement(children:)`
|
|
237
|
+
|
|
238
|
+
**`.combine`** — Merges all child elements into one, reading their labels in order. Use for related UI that makes more sense as a single unit.
|
|
239
|
+
|
|
240
|
+
```swift
|
|
241
|
+
// ✅ Rating row read as "4.5 stars, 2,304 reviews"
|
|
242
|
+
HStack {
|
|
243
|
+
Image(systemName: "star.fill")
|
|
244
|
+
Text("4.5")
|
|
245
|
+
Text("(2,304 reviews)")
|
|
246
|
+
}
|
|
247
|
+
.accessibilityElement(children: .combine)
|
|
248
|
+
.accessibilityLabel("4.5 stars, 2,304 reviews")
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**`.contain`** — Groups elements but still exposes each child individually. Used for containers that need a group label while preserving child navigability.
|
|
252
|
+
|
|
253
|
+
**`.ignore`** — Hides all children from VoiceOver. Use for decorative containers.
|
|
254
|
+
|
|
255
|
+
```swift
|
|
256
|
+
// Decorative divider container
|
|
257
|
+
HStack {
|
|
258
|
+
Divider()
|
|
259
|
+
Text("OR")
|
|
260
|
+
Divider()
|
|
261
|
+
}
|
|
262
|
+
.accessibilityElement(children: .ignore)
|
|
263
|
+
.accessibilityLabel("Or")
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `.accessibilityChildren(_:)` — Explicit Child List
|
|
267
|
+
|
|
268
|
+
Provides a custom child list, overriding the default tree.
|
|
269
|
+
|
|
270
|
+
```swift
|
|
271
|
+
.accessibilityChildren {
|
|
272
|
+
ForEach(items) { item in
|
|
273
|
+
Text(item.title)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### `.accessibilityHidden(_:)`
|
|
279
|
+
|
|
280
|
+
```swift
|
|
281
|
+
Image("decorative-background")
|
|
282
|
+
.accessibilityHidden(true)
|
|
283
|
+
|
|
284
|
+
// Conditionally hide
|
|
285
|
+
Text(status)
|
|
286
|
+
.accessibilityHidden(!isVisible)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### `.accessibilitySortPriority(_:)`
|
|
290
|
+
|
|
291
|
+
Higher values are read first. Default is 0.
|
|
292
|
+
|
|
293
|
+
```swift
|
|
294
|
+
VStack {
|
|
295
|
+
Text("Summary").accessibilitySortPriority(2) // read first
|
|
296
|
+
Text("Details").accessibilitySortPriority(1) // read second
|
|
297
|
+
DismissButton().accessibilitySortPriority(-1) // read last
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Focus Management
|
|
302
|
+
|
|
303
|
+
### `@AccessibilityFocusState` + `.accessibilityFocused(_:)`
|
|
304
|
+
|
|
305
|
+
Programmatically move VoiceOver focus to a specific element.
|
|
306
|
+
|
|
307
|
+
```swift
|
|
308
|
+
@AccessibilityFocusState private var isConfirmFocused: Bool
|
|
309
|
+
|
|
310
|
+
Button("Delete") { showConfirm = true }
|
|
311
|
+
|
|
312
|
+
if showConfirm {
|
|
313
|
+
ConfirmationView()
|
|
314
|
+
.accessibilityFocused($isConfirmFocused)
|
|
315
|
+
.onAppear { isConfirmFocused = true }
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### With Enum (Multiple Elements)
|
|
320
|
+
|
|
321
|
+
```swift
|
|
322
|
+
enum FormField { case name, email, password }
|
|
323
|
+
|
|
324
|
+
@AccessibilityFocusState private var focusedField: FormField?
|
|
325
|
+
|
|
326
|
+
TextField("Name", text: $name)
|
|
327
|
+
.accessibilityFocused($focusedField, equals: .name)
|
|
328
|
+
|
|
329
|
+
// Move focus programmatically
|
|
330
|
+
Button("Next") { focusedField = .email }
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### `.accessibilityDefaultFocus(_:_:)`
|
|
334
|
+
|
|
335
|
+
Sets which element receives focus by default when a view appears (iOS 17+).
|
|
336
|
+
|
|
337
|
+
```swift
|
|
338
|
+
VStack {
|
|
339
|
+
HeaderView()
|
|
340
|
+
PrimaryButton().accessibilityDefaultFocus($isDefault, true)
|
|
341
|
+
SecondaryButton()
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### `.accessibilityChildrenInNavigationOrder(_:)` — Explicit Order
|
|
346
|
+
|
|
347
|
+
Overrides default navigation order with an explicit sequence.
|
|
348
|
+
|
|
349
|
+
```swift
|
|
350
|
+
.accessibilityChildrenInNavigationOrder([heading, body, footer])
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Custom Rotors
|
|
356
|
+
|
|
357
|
+
The VoiceOver rotor lets users jump between elements of a specific type. Custom rotors add app-specific navigation.
|
|
358
|
+
|
|
359
|
+
### Basic Custom Rotor
|
|
360
|
+
|
|
361
|
+
```swift
|
|
362
|
+
.accessibilityRotor("Unread Messages") {
|
|
363
|
+
ForEach(messages.filter(\.isUnread)) { message in
|
|
364
|
+
AccessibilityRotorEntry(message.preview, id: message.id)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Text-Range Rotor
|
|
370
|
+
|
|
371
|
+
Navigates through ranges within a `Text` element.
|
|
372
|
+
|
|
373
|
+
```swift
|
|
374
|
+
Text(articleBody)
|
|
375
|
+
.accessibilityRotor("Links") {
|
|
376
|
+
ForEach(links) { link in
|
|
377
|
+
AccessibilityRotorEntry(link.text, textRange: link.range)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### `accessibilityRotorEntry(id:in:)` — Stand-alone Entry
|
|
383
|
+
|
|
384
|
+
```swift
|
|
385
|
+
ForEach(headings) { heading in
|
|
386
|
+
Text(heading.text)
|
|
387
|
+
.font(.headline)
|
|
388
|
+
.accessibilityAddTraits(.isHeader)
|
|
389
|
+
.accessibilityRotorEntry(id: heading.id, in: headingNamespace)
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Announcements and Live Regions
|
|
396
|
+
|
|
397
|
+
### Post an Announcement
|
|
398
|
+
|
|
399
|
+
Use when a change happens that isn't in the view hierarchy (e.g., background upload completes).
|
|
400
|
+
|
|
401
|
+
```swift
|
|
402
|
+
// iOS 17+ (preferred)
|
|
403
|
+
AccessibilityNotification.Announcement("Upload complete").post()
|
|
404
|
+
|
|
405
|
+
// Older syntax
|
|
406
|
+
UIAccessibility.post(notification: .announcement, argument: "Upload complete")
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Screen Changed (Full Navigation Reset)
|
|
410
|
+
|
|
411
|
+
Post when the entire screen content changes (e.g., pushing a new view manually).
|
|
412
|
+
|
|
413
|
+
```swift
|
|
414
|
+
AccessibilityNotification.ScreenChanged().post()
|
|
415
|
+
// Or with a specific element to focus:
|
|
416
|
+
AccessibilityNotification.ScreenChanged(nil).post() // system default focus
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Layout Changed (Partial Update)
|
|
420
|
+
|
|
421
|
+
Post when part of the screen changes (e.g., a section expands, items load).
|
|
422
|
+
|
|
423
|
+
```swift
|
|
424
|
+
AccessibilityNotification.LayoutChanged().post()
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Live Region — `.updatesFrequently`
|
|
428
|
+
|
|
429
|
+
For labels that update continuously (timers, stock prices, status indicators). VoiceOver re-reads when value changes.
|
|
430
|
+
|
|
431
|
+
```swift
|
|
432
|
+
Text(timerLabel)
|
|
433
|
+
.accessibilityAddTraits(.updatesFrequently)
|
|
434
|
+
.accessibilityLabel("Time remaining: \(timerLabel)")
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Speech Modifiers
|
|
440
|
+
|
|
441
|
+
Control how VoiceOver speaks a specific element's text.
|
|
442
|
+
|
|
443
|
+
```swift
|
|
444
|
+
Text("Chapter 1: The Beginning")
|
|
445
|
+
.speechAlwaysIncludesPunctuation() // always read punctuation marks
|
|
446
|
+
|
|
447
|
+
Text("A.I.")
|
|
448
|
+
.speechSpellsOutCharacters() // spell out: "A dot I dot"
|
|
449
|
+
|
|
450
|
+
Text("Error: invalid input")
|
|
451
|
+
.speechAdjustedPitch(0.5) // lower pitch for errors (0.0–2.0)
|
|
452
|
+
|
|
453
|
+
// Queue announcements instead of interrupting
|
|
454
|
+
Text(statusMessage)
|
|
455
|
+
.speechAnnouncementsQueued()
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Advanced Modifiers
|
|
461
|
+
|
|
462
|
+
### `.accessibilityCustomContent(_:_:importance:)` — Chunked Information
|
|
463
|
+
|
|
464
|
+
Delivers additional content through the VoiceOver "More Content" rotor. Useful for complex items (contacts, emails) where not all info should be read at once.
|
|
465
|
+
|
|
466
|
+
```swift
|
|
467
|
+
ContactRow(contact: contact)
|
|
468
|
+
.accessibilityLabel(contact.fullName)
|
|
469
|
+
.accessibilityCustomContent("Phone", contact.phoneNumber, importance: .high)
|
|
470
|
+
.accessibilityCustomContent("Email", contact.email)
|
|
471
|
+
.accessibilityCustomContent("Company", contact.company, importance: .default)
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### `.accessibilityRepresentation(representation:)` — Replace AX Tree
|
|
475
|
+
|
|
476
|
+
Replaces the entire VoiceOver subtree with a different view's tree. Use for complex custom controls.
|
|
477
|
+
|
|
478
|
+
```swift
|
|
479
|
+
CustomSlider(value: $value, range: 0...100)
|
|
480
|
+
.accessibilityRepresentation {
|
|
481
|
+
Slider(value: $value, in: 0...100)
|
|
482
|
+
.accessibilityLabel("Brightness")
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### `.accessibilityTextContentType(_:)` — Reading Style
|
|
487
|
+
|
|
488
|
+
Hints to VoiceOver how to read text (speech rate, pausing).
|
|
489
|
+
|
|
490
|
+
```swift
|
|
491
|
+
Text(poemBody)
|
|
492
|
+
.accessibilityTextContentType(.poetry)
|
|
493
|
+
|
|
494
|
+
// Available types: plain, fileSystem, messaging, narrative,
|
|
495
|
+
// poetry, reading, sourceCode, spreadsheet, wordProcessing
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### `.accessibilityHeading(_:)` — Heading Level
|
|
499
|
+
|
|
500
|
+
```swift
|
|
501
|
+
Text("Section Title")
|
|
502
|
+
.accessibilityAddTraits(.isHeader)
|
|
503
|
+
.accessibilityHeading(.h2)
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### `.accessibilityIgnoresInvertColors(_:)` — Smart Invert Protection
|
|
507
|
+
|
|
508
|
+
Prevents the view from inverting colors when Smart Invert is enabled. Always apply to images, videos, and maps.
|
|
509
|
+
|
|
510
|
+
```swift
|
|
511
|
+
AsyncImage(url: url) { image in
|
|
512
|
+
image.resizable()
|
|
513
|
+
}
|
|
514
|
+
.accessibilityIgnoresInvertColors()
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### `.accessibilityShowsLargeContentViewer()` — Large Content Viewer
|
|
518
|
+
|
|
519
|
+
For UI elements that cannot scale with Dynamic Type (tab bars, toolbars). Shows a large version when long-pressed.
|
|
520
|
+
|
|
521
|
+
```swift
|
|
522
|
+
// Tab bar item that can't grow
|
|
523
|
+
Label("Library", systemImage: "books.vertical")
|
|
524
|
+
.accessibilityShowsLargeContentViewer()
|
|
525
|
+
|
|
526
|
+
// Custom version with explicit content
|
|
527
|
+
TabItem()
|
|
528
|
+
.accessibilityShowsLargeContentViewer {
|
|
529
|
+
Label("Library", systemImage: "books.vertical")
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### `.accessibilityDirectTouch(_:options:)` — Pass-Through Gestures
|
|
534
|
+
|
|
535
|
+
For views that need raw touch input (drawing canvas, piano keys) even when VoiceOver is active.
|
|
536
|
+
|
|
537
|
+
```swift
|
|
538
|
+
DrawingCanvas()
|
|
539
|
+
.accessibilityLabel("Drawing canvas")
|
|
540
|
+
.accessibilityDirectTouch(.automatic, options: .silenceOnTouch)
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### `.accessibilityChartDescriptor(_:)` — Chart Accessibility
|
|
544
|
+
|
|
545
|
+
Provides a full data description for charts (Swift Charts and custom charts).
|
|
546
|
+
|
|
547
|
+
```swift
|
|
548
|
+
Chart(data) { item in
|
|
549
|
+
BarMark(x: .value("Month", item.month), y: .value("Sales", item.sales))
|
|
550
|
+
}
|
|
551
|
+
.accessibilityChartDescriptor(SalesChartDescriptor(data: data))
|
|
552
|
+
|
|
553
|
+
// Descriptor conformance:
|
|
554
|
+
struct SalesChartDescriptor: AXChartDescriptorRepresentable {
|
|
555
|
+
let data: [SalesData]
|
|
556
|
+
func makeChartDescriptor() -> AXChartDescriptor {
|
|
557
|
+
AXChartDescriptor(
|
|
558
|
+
title: "Monthly Sales",
|
|
559
|
+
summary: "Sales increased 23% year-over-year",
|
|
560
|
+
xAxis: AXCategoricalDataAxisDescriptor(title: "Month", categoryOrder: months),
|
|
561
|
+
yAxis: AXNumericDataAxisDescriptor(title: "Revenue", range: 0...maxValue, gridlinePositions: []),
|
|
562
|
+
series: [AXDataSeriesDescriptor(name: "Sales", isContinuous: false, dataPoints: points)]
|
|
563
|
+
)
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Common Mistakes
|
|
571
|
+
|
|
572
|
+
| Mistake | Fix |
|
|
573
|
+
|---|---|
|
|
574
|
+
| Missing label on icon button | Add `.accessibilityLabel("Share")` |
|
|
575
|
+
| Label includes control type: "Save button" | Just "Save" — VoiceOver adds type |
|
|
576
|
+
| Label describes action: "Tap to delete" | Just "Delete" — hint describes result |
|
|
577
|
+
| Decorative image announced | Add `.accessibilityHidden(true)` |
|
|
578
|
+
| State in label: "Selected item" | Use `.accessibilityAddTraits(.isSelected)` |
|
|
579
|
+
| Nested `accessibilityElement(children: .combine)` | Only one level; flatten the structure |
|
|
580
|
+
| No trait on custom tappable view | Add `.accessibilityAddTraits(.isButton)` |
|
|
581
|
+
| Long-press menu with no VoiceOver equivalent | Add `.accessibilityAction(named:)` for each item |
|
|
582
|
+
| `accessibilityValue` duplicates label | Value is for dynamic data only |
|
|
583
|
+
| Hardcoded string in `accessibilityLabel` | Use `LocalizedStringKey` or `Text` for localization |
|
|
584
|
+
| Announcement in `body` on every render | Post announcements in response to events, not on render |
|