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,441 @@
|
|
|
1
|
+
# Before/After: SwiftUI Accessibility
|
|
2
|
+
|
|
3
|
+
Concrete code transformations with priority tier annotations. Each example shows the inaccessible version, the corrected version, and a summary of every change.
|
|
4
|
+
|
|
5
|
+
Priority tiers:
|
|
6
|
+
- **Blocks Assistive Tech** — Element is completely unreachable or unusable
|
|
7
|
+
- **Degrades Experience** — Reachable with significant friction
|
|
8
|
+
- **Incomplete Support** — Gaps that prevent Nutrition Label claims
|
|
9
|
+
|
|
10
|
+
## Contents
|
|
11
|
+
|
|
12
|
+
### Blocks Assistive Tech
|
|
13
|
+
- Icon-only button missing label
|
|
14
|
+
- Tappable view using `onTapGesture`
|
|
15
|
+
- Decorative image announced by VoiceOver
|
|
16
|
+
|
|
17
|
+
### Degrades Experience
|
|
18
|
+
- Label includes control type
|
|
19
|
+
- State embedded in label
|
|
20
|
+
- Touch target too small
|
|
21
|
+
- Long-press menu without VoiceOver equivalent
|
|
22
|
+
|
|
23
|
+
### Incomplete Support
|
|
24
|
+
- Text doesn't scale with Dynamic Type
|
|
25
|
+
- Animation plays with Reduce Motion enabled
|
|
26
|
+
- Color-only status indicator
|
|
27
|
+
- VoiceOver modal allows background access
|
|
28
|
+
- Custom slider missing adjustable support
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## [Blocks Assistive Tech] Icon-only button missing label
|
|
33
|
+
|
|
34
|
+
**Problem:** VoiceOver announces "square.and.arrow.up" (the SF Symbol name). Voice Control cannot identify the button. Neither feature can use this control correctly.
|
|
35
|
+
|
|
36
|
+
```swift
|
|
37
|
+
// ❌ Before
|
|
38
|
+
Button(action: shareDocument) {
|
|
39
|
+
Image(systemName: "square.and.arrow.up")
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```swift
|
|
44
|
+
// ✅ After
|
|
45
|
+
Button(action: shareDocument) {
|
|
46
|
+
Image(systemName: "square.and.arrow.up")
|
|
47
|
+
}
|
|
48
|
+
.accessibilityLabel("Share") // [VERIFY] confirm this matches intent
|
|
49
|
+
.accessibilityInputLabels(["Share", "Share Document"]) // Voice Control alternate names
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Changes:**
|
|
53
|
+
| Change | Why |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Added `.accessibilityLabel("Share")` | VoiceOver reads the action, not the symbol name |
|
|
56
|
+
| Added `.accessibilityInputLabels(["Share", "Share Document"])` | Voice Control: "Tap Share" and "Tap Share Document" both work |
|
|
57
|
+
| Added `[VERIFY]` comment | Label inferred from symbol — developer must confirm it matches the action |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## [Blocks Assistive Tech] Tappable view using `onTapGesture`
|
|
62
|
+
|
|
63
|
+
**Problem:** VoiceOver has no label and treats this as non-interactive. Voice Control's "Show numbers" doesn't include it. Switch Control skips it.
|
|
64
|
+
|
|
65
|
+
```swift
|
|
66
|
+
// ❌ Before
|
|
67
|
+
HStack {
|
|
68
|
+
Image(product.thumbnail)
|
|
69
|
+
VStack(alignment: .leading) {
|
|
70
|
+
Text(product.name)
|
|
71
|
+
Text(product.price, format: .currency(code: "USD"))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
.onTapGesture { openProduct(product) }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```swift
|
|
78
|
+
// ✅ After
|
|
79
|
+
Button(action: { openProduct(product) }) {
|
|
80
|
+
HStack {
|
|
81
|
+
Image(product.thumbnail)
|
|
82
|
+
.accessibilityHidden(true) // decorative — name announced via label below
|
|
83
|
+
VStack(alignment: .leading) {
|
|
84
|
+
Text(product.name)
|
|
85
|
+
Text(product.price, format: .currency(code: "USD"))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
.accessibilityLabel("\(product.name), \(product.formattedPrice)")
|
|
90
|
+
.accessibilityHint("Opens product details")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Changes:**
|
|
94
|
+
| Change | Why |
|
|
95
|
+
|---|---|
|
|
96
|
+
| Replaced `onTapGesture` with `Button` | `Button` is automatically interactive for VoiceOver, Voice Control, and keyboard |
|
|
97
|
+
| Added `.accessibilityLabel(...)` | Combined label prevents VoiceOver from reading child elements separately |
|
|
98
|
+
| Added `.accessibilityHidden(true)` on image | Thumbnail is redundant with label — hide from accessibility tree |
|
|
99
|
+
| Added `.accessibilityHint(...)` | Explains the result without being redundant with the label |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## [Blocks Assistive Tech] Decorative image announced by VoiceOver
|
|
104
|
+
|
|
105
|
+
**Problem:** VoiceOver reads "Image: background-wave" interrupting the reading flow.
|
|
106
|
+
|
|
107
|
+
```swift
|
|
108
|
+
// ❌ Before
|
|
109
|
+
Image("background-wave")
|
|
110
|
+
.frame(height: 200)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```swift
|
|
114
|
+
// ✅ After
|
|
115
|
+
Image("background-wave")
|
|
116
|
+
.frame(height: 200)
|
|
117
|
+
.accessibilityHidden(true)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Changes:**
|
|
121
|
+
| Change | Why |
|
|
122
|
+
|---|---|
|
|
123
|
+
| Added `.accessibilityHidden(true)` | Removes decorative image from accessibility tree entirely |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## [Degrades Experience] Label includes control type
|
|
128
|
+
|
|
129
|
+
**Problem:** VoiceOver announces "Delete button, button" — double-announces the type.
|
|
130
|
+
|
|
131
|
+
```swift
|
|
132
|
+
// ❌ Before
|
|
133
|
+
Button("Delete button") { delete(item) }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```swift
|
|
137
|
+
// ✅ After
|
|
138
|
+
Button("Delete") { delete(item) }
|
|
139
|
+
.accessibilityLabel("Delete \(item.name)") // unique label per item
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Changes:**
|
|
143
|
+
| Change | Why |
|
|
144
|
+
|---|---|
|
|
145
|
+
| Removed "button" from label | VoiceOver adds "button" automatically from the `.isButton` trait |
|
|
146
|
+
| Added item name to label | Prevents disambiguation when multiple Delete buttons appear |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## [Degrades Experience] State embedded in label
|
|
151
|
+
|
|
152
|
+
**Problem:** When the favorite state changes, VoiceOver re-reads the full label. State should be a trait, not a label change.
|
|
153
|
+
|
|
154
|
+
```swift
|
|
155
|
+
// ❌ Before
|
|
156
|
+
Button(action: toggleFavorite) {
|
|
157
|
+
Image(systemName: item.isFavorited ? "star.fill" : "star")
|
|
158
|
+
}
|
|
159
|
+
.accessibilityLabel(item.isFavorited ? "Favorited" : "Not favorited")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```swift
|
|
163
|
+
// ✅ After
|
|
164
|
+
Button(action: toggleFavorite) {
|
|
165
|
+
Image(systemName: item.isFavorited ? "star.fill" : "star")
|
|
166
|
+
}
|
|
167
|
+
.accessibilityLabel("Favorite")
|
|
168
|
+
.accessibilityAddTraits(item.isFavorited ? .isSelected : [])
|
|
169
|
+
.accessibilityHint(item.isFavorited ? "Removes from favorites" : "Adds to favorites")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Changes:**
|
|
173
|
+
| Change | Why |
|
|
174
|
+
|---|---|
|
|
175
|
+
| Label is always "Favorite" | Stable label — VoiceOver doesn't re-read on state change |
|
|
176
|
+
| Added `.accessibilityAddTraits(.isSelected)` when favorited | VoiceOver announces "selected" — correct for iOS 13–16 targets |
|
|
177
|
+
| Added `.accessibilityHint(...)` describing result | Tells user what activating will do based on current state |
|
|
178
|
+
|
|
179
|
+
> **iOS 17+ note:** Prefer `.accessibilityAddTraits(.isToggle)` over `.isSelected` for toggle controls. `.isToggle` identifies the *type* of control (binary on/off), while `.isSelected` conveys *current selection state* (e.g., selected tab). For favorite/bookmark buttons targeting iOS 17+, use `.isToggle` and express the current state via `accessibilityValue("On")` / `accessibilityValue("Off")`.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## [Degrades Experience] Touch target too small
|
|
184
|
+
|
|
185
|
+
**Problem:** The heart icon is 20×20pt. The tap area is too small for many users, especially those with motor impairments.
|
|
186
|
+
|
|
187
|
+
```swift
|
|
188
|
+
// ❌ Before
|
|
189
|
+
Image(systemName: "heart")
|
|
190
|
+
.font(.system(size: 20))
|
|
191
|
+
.onTapGesture { toggleFavorite() }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```swift
|
|
195
|
+
// ✅ After
|
|
196
|
+
Button(action: toggleFavorite) {
|
|
197
|
+
Image(systemName: "heart")
|
|
198
|
+
.font(.system(size: 20))
|
|
199
|
+
}
|
|
200
|
+
.frame(minWidth: 44, minHeight: 44)
|
|
201
|
+
.contentShape(Rectangle())
|
|
202
|
+
.accessibilityLabel("Favorite")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Changes:**
|
|
206
|
+
| Change | Why |
|
|
207
|
+
|---|---|
|
|
208
|
+
| Changed `onTapGesture` to `Button` | Makes element accessible to VoiceOver and Voice Control |
|
|
209
|
+
| Added `.frame(minWidth: 44, minHeight: 44)` | Ensures minimum 44×44pt touch target |
|
|
210
|
+
| Added `.contentShape(Rectangle())` | Ensures the full frame is tappable, not just the icon |
|
|
211
|
+
| Added `.accessibilityLabel("Favorite")` | Icon-only button needs explicit label |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## [Degrades Experience] Long-press menu without VoiceOver equivalent
|
|
216
|
+
|
|
217
|
+
**Problem:** The context menu is only accessible via long press. VoiceOver users cannot discover or trigger these actions.
|
|
218
|
+
|
|
219
|
+
```swift
|
|
220
|
+
// ❌ Before
|
|
221
|
+
MessageRow(message: message)
|
|
222
|
+
.contextMenu {
|
|
223
|
+
Button("Reply") { reply(message) }
|
|
224
|
+
Button("Forward") { forward(message) }
|
|
225
|
+
Button("Delete", role: .destructive) { delete(message) }
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```swift
|
|
230
|
+
// ✅ After
|
|
231
|
+
MessageRow(message: message)
|
|
232
|
+
.contextMenu {
|
|
233
|
+
Button("Reply") { reply(message) }
|
|
234
|
+
Button("Forward") { forward(message) }
|
|
235
|
+
Button("Delete", role: .destructive) { delete(message) }
|
|
236
|
+
}
|
|
237
|
+
// VoiceOver: Actions rotor; Voice Control: ">>" indicator
|
|
238
|
+
.accessibilityAction(named: "Reply") { reply(message) }
|
|
239
|
+
.accessibilityAction(named: "Forward") { forward(message) }
|
|
240
|
+
.accessibilityAction(named: "Delete") { delete(message) }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Changes:**
|
|
244
|
+
| Change | Why |
|
|
245
|
+
|---|---|
|
|
246
|
+
| Added `.accessibilityAction(named:)` for each action | VoiceOver Actions rotor can access them; Voice Control shows ">>" |
|
|
247
|
+
| Context menu kept | Sighted users keep the expected gesture — not removed |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## [Incomplete Support] Text doesn't scale with Dynamic Type
|
|
252
|
+
|
|
253
|
+
**Problem:** Fixed font size means text stays small even when the user has selected a larger accessibility size.
|
|
254
|
+
|
|
255
|
+
```swift
|
|
256
|
+
// ❌ Before
|
|
257
|
+
Text(article.title)
|
|
258
|
+
.font(.system(size: 17))
|
|
259
|
+
Text(article.body)
|
|
260
|
+
.font(.system(size: 14))
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```swift
|
|
264
|
+
// ✅ After
|
|
265
|
+
Text(article.title)
|
|
266
|
+
.font(.headline) // scales with Dynamic Type
|
|
267
|
+
Text(article.body)
|
|
268
|
+
.font(.body) // scales with Dynamic Type
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Changes:**
|
|
272
|
+
| Change | Why |
|
|
273
|
+
|---|---|
|
|
274
|
+
| `.system(size: 17)` → `.headline` | Semantic text style scales with user's preferred size |
|
|
275
|
+
| `.system(size: 14)` → `.body` | Semantic text style scales; reads as body content |
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## [Incomplete Support] Animation plays with Reduce Motion enabled
|
|
280
|
+
|
|
281
|
+
**Problem:** A slide transition plays even when the user has enabled Reduce Motion to avoid vestibular issues.
|
|
282
|
+
|
|
283
|
+
```swift
|
|
284
|
+
// ❌ Before
|
|
285
|
+
if isVisible {
|
|
286
|
+
DetailView()
|
|
287
|
+
.transition(.slide)
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
```swift
|
|
292
|
+
// ✅ After
|
|
293
|
+
@Environment(\.accessibilityReduceMotion) var reduceMotion
|
|
294
|
+
|
|
295
|
+
// In body:
|
|
296
|
+
if isVisible {
|
|
297
|
+
DetailView()
|
|
298
|
+
.transition(reduceMotion ? .opacity : .slide)
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Changes:**
|
|
303
|
+
| Change | Why |
|
|
304
|
+
|---|---|
|
|
305
|
+
| Read `@Environment(\.accessibilityReduceMotion)` | Detect user preference |
|
|
306
|
+
| Switch `.slide` to `.opacity` when reduce motion is on | Fade preserves meaning without vestibular-triggering motion |
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## [Incomplete Support] Color-only status indicator
|
|
311
|
+
|
|
312
|
+
**Problem:** Online status is shown only by color (green = online, red = offline). Fails grayscale test and Differentiate Without Color.
|
|
313
|
+
|
|
314
|
+
```swift
|
|
315
|
+
// ❌ Before
|
|
316
|
+
Circle()
|
|
317
|
+
.fill(user.isOnline ? .green : .red)
|
|
318
|
+
.frame(width: 12, height: 12)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
```swift
|
|
322
|
+
// ✅ After
|
|
323
|
+
Group {
|
|
324
|
+
if user.isOnline {
|
|
325
|
+
Circle()
|
|
326
|
+
.fill(.green)
|
|
327
|
+
.frame(width: 12, height: 12)
|
|
328
|
+
} else {
|
|
329
|
+
Circle()
|
|
330
|
+
.fill(.red)
|
|
331
|
+
.overlay(
|
|
332
|
+
Image(systemName: "xmark")
|
|
333
|
+
.font(.system(size: 7, weight: .bold))
|
|
334
|
+
.foregroundStyle(.white)
|
|
335
|
+
)
|
|
336
|
+
.frame(width: 12, height: 12)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
.accessibilityLabel(user.isOnline ? "Online" : "Offline")
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Changes:**
|
|
343
|
+
| Change | Why |
|
|
344
|
+
|---|---|
|
|
345
|
+
| Added xmark icon on offline circle | Shape distinguishes states in grayscale |
|
|
346
|
+
| Added `.accessibilityLabel(...)` | VoiceOver reads semantic state, not just color |
|
|
347
|
+
| Kept color differentiation | Doesn't break sighted users — shape is additive |
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## [Incomplete Support] VoiceOver modal allows background access
|
|
352
|
+
|
|
353
|
+
**Problem:** When a modal is presented, VoiceOver can still swipe to reach elements behind it.
|
|
354
|
+
|
|
355
|
+
```swift
|
|
356
|
+
// ❌ Before
|
|
357
|
+
struct ConfirmationModal: View {
|
|
358
|
+
@Binding var isPresented: Bool
|
|
359
|
+
|
|
360
|
+
var body: some View {
|
|
361
|
+
VStack {
|
|
362
|
+
Text("Are you sure?")
|
|
363
|
+
Button("Confirm") {
|
|
364
|
+
// ...
|
|
365
|
+
isPresented = false
|
|
366
|
+
}
|
|
367
|
+
Button("Cancel") { isPresented = false }
|
|
368
|
+
}
|
|
369
|
+
.padding()
|
|
370
|
+
.background(Color(.systemBackground))
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
```swift
|
|
376
|
+
// ✅ After
|
|
377
|
+
struct ConfirmationModal: View {
|
|
378
|
+
@Binding var isPresented: Bool
|
|
379
|
+
@AccessibilityFocusState private var isConfirmFocused: Bool
|
|
380
|
+
|
|
381
|
+
var body: some View {
|
|
382
|
+
VStack {
|
|
383
|
+
Text("Are you sure?")
|
|
384
|
+
Button("Confirm") {
|
|
385
|
+
// ...
|
|
386
|
+
isPresented = false
|
|
387
|
+
}
|
|
388
|
+
.accessibilityFocused($isConfirmFocused)
|
|
389
|
+
Button("Cancel") { isPresented = false }
|
|
390
|
+
}
|
|
391
|
+
.padding()
|
|
392
|
+
.background(Color(.systemBackground))
|
|
393
|
+
.accessibilityElement(children: .contain)
|
|
394
|
+
.onAppear { isConfirmFocused = true }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Presenting site — prefer .sheet() which traps focus automatically:
|
|
399
|
+
.sheet(isPresented: $showConfirm) {
|
|
400
|
+
ConfirmationModal(isPresented: $showConfirm)
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Changes:**
|
|
405
|
+
| Change | Why |
|
|
406
|
+
|---|---|
|
|
407
|
+
| Use `.sheet()` for presentation | `.sheet()` traps VoiceOver focus automatically |
|
|
408
|
+
| Added `@AccessibilityFocusState` + `.onAppear` | Focus moves to Confirm button when modal appears |
|
|
409
|
+
| Added `.accessibilityElement(children: .contain)` | Ensures logical grouping within the modal |
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## [Incomplete Support] Custom slider missing adjustable support
|
|
414
|
+
|
|
415
|
+
**Problem:** VoiceOver reaches the slider but cannot change its value (no swipe up/down support).
|
|
416
|
+
|
|
417
|
+
```swift
|
|
418
|
+
// ❌ Before
|
|
419
|
+
CustomSliderView(value: $brightness)
|
|
420
|
+
.accessibilityLabel("Brightness")
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
```swift
|
|
424
|
+
// ✅ After
|
|
425
|
+
CustomSliderView(value: $brightness)
|
|
426
|
+
.accessibilityLabel("Brightness")
|
|
427
|
+
.accessibilityValue("\(Int(brightness * 100)) percent")
|
|
428
|
+
.accessibilityAdjustableAction { direction in
|
|
429
|
+
switch direction {
|
|
430
|
+
case .increment: brightness = min(1, brightness + 0.05)
|
|
431
|
+
case .decrement: brightness = max(0, brightness - 0.05)
|
|
432
|
+
@unknown default: break
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Changes:**
|
|
438
|
+
| Change | Why |
|
|
439
|
+
|---|---|
|
|
440
|
+
| Added `.accessibilityValue(...)` | Announces current value when focused |
|
|
441
|
+
| Added `.accessibilityAdjustableAction(...)` | Enables VoiceOver swipe up/down to change value |
|