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,421 @@
|
|
|
1
|
+
# Media Accessibility
|
|
2
|
+
|
|
3
|
+
Covers Captions, Audio Descriptions, Speech synthesis, Chart accessibility, and audio session considerations — required for two App Store Accessibility Nutrition Labels.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
- [Captions and Subtitles](#captions-and-subtitles)
|
|
7
|
+
- [Audio Descriptions](#audio-descriptions)
|
|
8
|
+
- [Speech Synthesis](#speech-synthesis)
|
|
9
|
+
- [Chart Accessibility](#chart-accessibility)
|
|
10
|
+
- [Common Failures](#common-failures)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Captions and Subtitles
|
|
15
|
+
|
|
16
|
+
### Nutrition Label Criteria
|
|
17
|
+
|
|
18
|
+
To declare **Captions** support:
|
|
19
|
+
- Captions are enabled by default when the system caption setting is on
|
|
20
|
+
- All first-party video dialogue and relevant sounds are captioned
|
|
21
|
+
- SDH (Subtitles for Deaf/Hard of Hearing) preferred over plain subtitles
|
|
22
|
+
- Third-party content shows a CC or SDH badge indicator
|
|
23
|
+
- Audio-only content has text transcripts available
|
|
24
|
+
|
|
25
|
+
### AVPlayerViewController — Built-in Support (Automatic)
|
|
26
|
+
|
|
27
|
+
`AVPlayerViewController` handles caption selection, appearance, and the system toggle automatically. When the user has "Closed Captions + SDH" enabled in Settings, captions activate without any code.
|
|
28
|
+
|
|
29
|
+
```swift
|
|
30
|
+
// ✅ Built-in AVPlayerViewController — captions work automatically
|
|
31
|
+
import AVKit
|
|
32
|
+
|
|
33
|
+
let player = AVPlayer(url: videoURL)
|
|
34
|
+
let playerVC = AVPlayerViewController()
|
|
35
|
+
playerVC.player = player
|
|
36
|
+
present(playerVC, animated: true) {
|
|
37
|
+
player.play()
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```swift
|
|
42
|
+
// SwiftUI equivalent
|
|
43
|
+
VideoPlayer(player: AVPlayer(url: videoURL))
|
|
44
|
+
.frame(height: 300)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Checking the System Caption Setting
|
|
48
|
+
|
|
49
|
+
```swift
|
|
50
|
+
import MediaAccessibility
|
|
51
|
+
|
|
52
|
+
// Check if the user has closed captions enabled
|
|
53
|
+
let captionType = MACaptionAppearanceGetDisplayType(.user)
|
|
54
|
+
switch captionType {
|
|
55
|
+
case .alwaysOn:
|
|
56
|
+
// Captions always shown
|
|
57
|
+
break
|
|
58
|
+
case .automatic:
|
|
59
|
+
// System decides based on content and audio route
|
|
60
|
+
break
|
|
61
|
+
case .forcedOnly:
|
|
62
|
+
// Only forced subtitles
|
|
63
|
+
break
|
|
64
|
+
@unknown default:
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Serving Caption Tracks
|
|
70
|
+
|
|
71
|
+
Caption tracks must be embedded in your media asset or served via HLS with `.vtt` or `.srt` subtitle tracks.
|
|
72
|
+
|
|
73
|
+
```swift
|
|
74
|
+
// Programmatically select caption track
|
|
75
|
+
let asset = AVAsset(url: videoURL)
|
|
76
|
+
|
|
77
|
+
Task {
|
|
78
|
+
let characteristics = try await asset.loadMediaSelectionGroup(for: .legible)
|
|
79
|
+
if let group = characteristics {
|
|
80
|
+
// Find SDH (Subtitles for Deaf/Hard of Hearing) track
|
|
81
|
+
let sdhOption = AVMediaSelectionGroup.mediaSelectionOptions(
|
|
82
|
+
from: group.options,
|
|
83
|
+
withMediaCharacteristics: [.describesVideoForAccessibility, .isSDH]
|
|
84
|
+
).first
|
|
85
|
+
|
|
86
|
+
// Find any caption track
|
|
87
|
+
let captionOption = AVMediaSelectionGroup.mediaSelectionOptions(
|
|
88
|
+
from: group.options,
|
|
89
|
+
withMediaCharacteristics: [.legible]
|
|
90
|
+
).first
|
|
91
|
+
|
|
92
|
+
// Activate the preferred option
|
|
93
|
+
await player.currentItem?.select(sdhOption ?? captionOption, in: group)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Caption Appearance Customization
|
|
99
|
+
|
|
100
|
+
```swift
|
|
101
|
+
// Caption styling follows system preferences by default
|
|
102
|
+
// Override only for branded players that need custom styling
|
|
103
|
+
|
|
104
|
+
// Check user-preferred caption style
|
|
105
|
+
let foregroundColor = MACaptionAppearanceCopyForegroundColor(.user, nil)
|
|
106
|
+
let fontSize = MACaptionAppearanceGetRelativeCharacterSize(.user)
|
|
107
|
+
let fontStyle = MACaptionAppearanceGetTextEdgeStyle(.user)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### SDH vs Regular Subtitles
|
|
111
|
+
|
|
112
|
+
| Type | Content | When to Use |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| SDH (Subtitles for Deaf/Hard of Hearing) | Dialogue + sound effects + speaker identification | Preferred for accessibility |
|
|
115
|
+
| Subtitles | Dialogue only (translation) | Foreign language content |
|
|
116
|
+
| Forced Subtitles | Untranslated speech only | When characters speak a foreign language mid-content |
|
|
117
|
+
| Closed Captions (CC) | Dialogue + sound effects | Legacy format, same role as SDH |
|
|
118
|
+
|
|
119
|
+
Mark SDH tracks with `AVMediaCharacteristic.isSDH` when creating custom tracks.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Audio Descriptions
|
|
124
|
+
|
|
125
|
+
### Nutrition Label Criteria
|
|
126
|
+
|
|
127
|
+
To declare **Audio Descriptions** support:
|
|
128
|
+
- Audio Descriptions are enabled by default when the system AD setting is on
|
|
129
|
+
- All first-party video visual content is narrated (actions, scene changes, on-screen text)
|
|
130
|
+
- Game interstitials and cut scenes are covered
|
|
131
|
+
- Third-party content shows an "AD" badge indicator
|
|
132
|
+
- Don't claim support if very little described content exists
|
|
133
|
+
|
|
134
|
+
### AVPlayerViewController — Built-in Support
|
|
135
|
+
|
|
136
|
+
`AVPlayerViewController` automatically selects an Audio Description track when the user has "Audio Descriptions" enabled in Settings.
|
|
137
|
+
|
|
138
|
+
```swift
|
|
139
|
+
// Built-in: no code required when using AVPlayerViewController
|
|
140
|
+
// AD tracks must be included in the media asset or HLS manifest
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Checking for Audio Description Track
|
|
144
|
+
|
|
145
|
+
```swift
|
|
146
|
+
let asset = AVAsset(url: videoURL)
|
|
147
|
+
|
|
148
|
+
Task {
|
|
149
|
+
let group = try? await asset.loadMediaSelectionGroup(for: .audible)
|
|
150
|
+
if let group {
|
|
151
|
+
let adOptions = AVMediaSelectionGroup.mediaSelectionOptions(
|
|
152
|
+
from: group.options,
|
|
153
|
+
withMediaCharacteristics: [.describesVideoForAccessibility]
|
|
154
|
+
)
|
|
155
|
+
let hasAudioDescription = !adOptions.isEmpty
|
|
156
|
+
// Show "AD" badge in UI if hasAudioDescription
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Respecting Spoken Audio Sessions
|
|
162
|
+
|
|
163
|
+
When your app plays audio that competes with VoiceOver or Audio Descriptions, use the `.spokenAudio` mode to duck or pause:
|
|
164
|
+
|
|
165
|
+
```swift
|
|
166
|
+
import AVFoundation
|
|
167
|
+
|
|
168
|
+
// Configure audio session to respect spoken audio (VoiceOver, Audio Descriptions)
|
|
169
|
+
try? AVAudioSession.sharedInstance().setCategory(
|
|
170
|
+
.playback,
|
|
171
|
+
mode: .spokenAudio,
|
|
172
|
+
options: [.duckOthers] // duck, don't interrupt
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
// For apps where spoken word IS the primary content (audiobooks, podcasts)
|
|
176
|
+
try? AVAudioSession.sharedInstance().setCategory(
|
|
177
|
+
.playback,
|
|
178
|
+
mode: .spokenAudio
|
|
179
|
+
// No .duckOthers — this IS the audio that should not be ducked
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Detecting the System Audio Description Setting
|
|
184
|
+
|
|
185
|
+
```swift
|
|
186
|
+
// No direct API equivalent to MACaptionAppearance for Audio Descriptions
|
|
187
|
+
// AVPlayerViewController handles it automatically
|
|
188
|
+
// For custom players, observe AVAudioSession for changes
|
|
189
|
+
NotificationCenter.default.addObserver(
|
|
190
|
+
forName: AVAudioSession.routeChangeNotification,
|
|
191
|
+
object: nil,
|
|
192
|
+
queue: .main
|
|
193
|
+
) { _ in
|
|
194
|
+
// Re-evaluate AD track selection after route change
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Speech Synthesis
|
|
201
|
+
|
|
202
|
+
For apps that generate spoken content (reading apps, navigation, notifications).
|
|
203
|
+
|
|
204
|
+
### Basic AVSpeechSynthesizer
|
|
205
|
+
|
|
206
|
+
```swift
|
|
207
|
+
import AVFoundation
|
|
208
|
+
|
|
209
|
+
let synthesizer = AVSpeechSynthesizer()
|
|
210
|
+
|
|
211
|
+
func speak(_ text: String) {
|
|
212
|
+
let utterance = AVSpeechUtterance(string: text)
|
|
213
|
+
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
|
|
214
|
+
utterance.rate = AVSpeechUtteranceDefaultSpeechRate
|
|
215
|
+
utterance.pitchMultiplier = 1.0 // 0.5 (low) to 2.0 (high)
|
|
216
|
+
utterance.volume = 1.0
|
|
217
|
+
utterance.preUtteranceDelay = 0.1
|
|
218
|
+
|
|
219
|
+
synthesizer.speak(utterance)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Pause and resume
|
|
223
|
+
synthesizer.pauseSpeaking(at: .word)
|
|
224
|
+
synthesizer.continueSpeaking()
|
|
225
|
+
|
|
226
|
+
// Stop
|
|
227
|
+
synthesizer.stopSpeaking(at: .immediate) // or .word, .sentence
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### SSML-Based Utterance (iOS 16+)
|
|
231
|
+
|
|
232
|
+
```swift
|
|
233
|
+
// Use SSML for fine-grained prosody control
|
|
234
|
+
let ssml = """
|
|
235
|
+
<speak>
|
|
236
|
+
<s>Welcome to <emphasis level="strong">My App</emphasis>.</s>
|
|
237
|
+
<break time="500ms"/>
|
|
238
|
+
<s>Your balance is <say-as interpret-as="currency" language="en-US">$1,234.56</say-as>.</s>
|
|
239
|
+
</speak>
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
if let utterance = AVSpeechUtterance(ssmlRepresentation: ssml) {
|
|
243
|
+
synthesizer.speak(utterance)
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Personal Voice (iOS 17+)
|
|
248
|
+
|
|
249
|
+
```swift
|
|
250
|
+
import AVFoundation
|
|
251
|
+
|
|
252
|
+
// Request access to Personal Voice
|
|
253
|
+
AVSpeechSynthesizer.requestPersonalVoiceAuthorization { status in
|
|
254
|
+
if status == .authorized {
|
|
255
|
+
// List available personal voices
|
|
256
|
+
let personalVoices = AVSpeechSynthesisVoice.speechVoices()
|
|
257
|
+
.filter { $0.voiceTraits.contains(.isPersonalVoice) }
|
|
258
|
+
|
|
259
|
+
if let voice = personalVoices.first {
|
|
260
|
+
let utterance = AVSpeechUtterance(string: "Hello!")
|
|
261
|
+
utterance.voice = voice
|
|
262
|
+
synthesizer.speak(utterance)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### AVSpeechSynthesizerDelegate
|
|
269
|
+
|
|
270
|
+
```swift
|
|
271
|
+
class NarratorController: NSObject, AVSpeechSynthesizerDelegate {
|
|
272
|
+
let synthesizer = AVSpeechSynthesizer()
|
|
273
|
+
|
|
274
|
+
override init() {
|
|
275
|
+
super.init()
|
|
276
|
+
synthesizer.delegate = self
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
|
|
280
|
+
didStart utterance: AVSpeechUtterance) {
|
|
281
|
+
// Update UI — speaking started
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
|
|
285
|
+
didFinish utterance: AVSpeechUtterance) {
|
|
286
|
+
// Proceed to next utterance or update UI
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer,
|
|
290
|
+
willSpeakRangeOfSpeechString characterRange: NSRange,
|
|
291
|
+
utterance: AVSpeechUtterance) {
|
|
292
|
+
// Highlight the currently spoken word
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Audio Session for Speech Apps
|
|
298
|
+
|
|
299
|
+
```swift
|
|
300
|
+
// Configure session for spoken word apps (audiobooks, narration)
|
|
301
|
+
try? AVAudioSession.sharedInstance().setCategory(
|
|
302
|
+
.playback,
|
|
303
|
+
mode: .spokenAudio,
|
|
304
|
+
options: [.allowBluetooth, .allowAirPlay]
|
|
305
|
+
)
|
|
306
|
+
try? AVAudioSession.sharedInstance().setActive(true)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Chart Accessibility
|
|
312
|
+
|
|
313
|
+
Charts are visual; users who cannot see them need structured data alternatives.
|
|
314
|
+
|
|
315
|
+
### SwiftUI Charts: `.accessibilityChartDescriptor(_:)`
|
|
316
|
+
|
|
317
|
+
```swift
|
|
318
|
+
import Charts
|
|
319
|
+
|
|
320
|
+
struct SalesChartDescriptor: AXChartDescriptorRepresentable {
|
|
321
|
+
let data: [SalesData]
|
|
322
|
+
|
|
323
|
+
func makeChartDescriptor() -> AXChartDescriptor {
|
|
324
|
+
let months = data.map(\.month)
|
|
325
|
+
let maxSales = data.map(\.sales).max() ?? 0
|
|
326
|
+
|
|
327
|
+
let xAxis = AXCategoricalDataAxisDescriptor(
|
|
328
|
+
title: "Month",
|
|
329
|
+
categoryOrder: months
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
let yAxis = AXNumericDataAxisDescriptor(
|
|
333
|
+
title: "Revenue (USD)",
|
|
334
|
+
range: 0...Double(maxSales),
|
|
335
|
+
gridlinePositions: []
|
|
336
|
+
) { value in
|
|
337
|
+
"$\(Int(value).formatted())" // format for speech
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
let series = AXDataSeriesDescriptor(
|
|
341
|
+
name: "Monthly Sales",
|
|
342
|
+
isContinuous: false,
|
|
343
|
+
dataPoints: data.map { item in
|
|
344
|
+
AXDataPoint(x: item.month, y: Double(item.sales))
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
return AXChartDescriptor(
|
|
349
|
+
title: "Monthly Sales Report",
|
|
350
|
+
summary: "Sales increased 23% year-over-year, peaking in December.",
|
|
351
|
+
xAxis: xAxis,
|
|
352
|
+
yAxis: yAxis,
|
|
353
|
+
additionalAxes: [],
|
|
354
|
+
series: [series]
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Apply to the chart
|
|
360
|
+
Chart(salesData) { item in
|
|
361
|
+
BarMark(
|
|
362
|
+
x: .value("Month", item.month),
|
|
363
|
+
y: .value("Sales", item.sales)
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
.accessibilityChartDescriptor(SalesChartDescriptor(data: salesData))
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Multi-Series Charts
|
|
370
|
+
|
|
371
|
+
```swift
|
|
372
|
+
let descriptor = AXChartDescriptor(
|
|
373
|
+
title: "Revenue by Region",
|
|
374
|
+
summary: "North America leads, followed by Europe and Asia.",
|
|
375
|
+
xAxis: AXCategoricalDataAxisDescriptor(title: "Quarter", categoryOrder: ["Q1", "Q2", "Q3", "Q4"]),
|
|
376
|
+
yAxis: AXNumericDataAxisDescriptor(title: "Revenue (M)", range: 0...500, gridlinePositions: []) { "\($0)M" },
|
|
377
|
+
additionalAxes: [],
|
|
378
|
+
series: [
|
|
379
|
+
AXDataSeriesDescriptor(name: "North America", isContinuous: true,
|
|
380
|
+
dataPoints: naData.map { AXDataPoint(x: $0.quarter, y: $0.revenue) }),
|
|
381
|
+
AXDataSeriesDescriptor(name: "Europe", isContinuous: true,
|
|
382
|
+
dataPoints: euData.map { AXDataPoint(x: $0.quarter, y: $0.revenue) })
|
|
383
|
+
]
|
|
384
|
+
)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Text Alternatives for Custom Charts
|
|
388
|
+
|
|
389
|
+
For non-Swift Charts visualizations (Core Graphics, custom drawing):
|
|
390
|
+
|
|
391
|
+
```swift
|
|
392
|
+
// Provide a data table as an accessibility alternative
|
|
393
|
+
CustomBarChartView(data: chartData)
|
|
394
|
+
.accessibilityElement(children: .ignore)
|
|
395
|
+
.accessibilityLabel("Sales chart")
|
|
396
|
+
.accessibilityValue(chartData.map { "\($0.label): \($0.value)" }.joined(separator: ", "))
|
|
397
|
+
|
|
398
|
+
// Or use accessibilityCustomContent for detailed chunked delivery
|
|
399
|
+
CustomBarChartView(data: chartData)
|
|
400
|
+
.accessibilityLabel("Quarterly Revenue Chart")
|
|
401
|
+
.accessibilityCustomContent("Summary", "Revenue grew 15% this year", importance: .high)
|
|
402
|
+
.accessibilityCustomContent(
|
|
403
|
+
"Data",
|
|
404
|
+
chartData.map { "\($0.quarter): \($0.value)" }.joined(separator: "; ")
|
|
405
|
+
)
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Common Failures
|
|
411
|
+
|
|
412
|
+
| Failure | Category | Fix |
|
|
413
|
+
|---|---|---|
|
|
414
|
+
| Captions don't enable automatically | Captions | Use `AVPlayerViewController`; check system caption setting via `MACaptionAppearanceGetDisplayType` |
|
|
415
|
+
| No caption tracks in video | Captions | Embed `.vtt`/`.srt` tracks or include SDH in HLS manifest |
|
|
416
|
+
| Audio Descriptions never activate | Audio Descriptions | Use `AVPlayerViewController`; embed AD audio tracks with `.describesVideoForAccessibility` characteristic |
|
|
417
|
+
| App audio ducks VoiceOver | Audio Session | Set `.spokenAudio` mode with `.duckOthers` option |
|
|
418
|
+
| Chart data inaccessible to VoiceOver | Charts | Add `.accessibilityChartDescriptor(_:)` with meaningful summary |
|
|
419
|
+
| Speech synthesizer interrupts VoiceOver | Speech | Check `UIAccessibility.isVoiceOverRunning` and pause/queue synthesis |
|
|
420
|
+
| Custom media player ignores caption setting | Captions | Query `MACaptionAppearanceGetDisplayType` and auto-select caption track |
|
|
421
|
+
| No transcript for audio-only content | Captions | Provide static text transcript alongside audio |
|