@mmerterden/multi-agent-pipeline 20.7.0 → 20.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/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,398 +1,452 @@
|
|
|
1
|
-
# Accessibility Patterns
|
|
1
|
+
# Accessibility Code Patterns
|
|
2
|
+
|
|
3
|
+
Back to [SKILL.md](../SKILL.md).
|
|
2
4
|
|
|
3
5
|
## Contents
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
|
|
7
|
+
- [Labels, values, hints](#labels-values-hints)
|
|
8
|
+
- [Traits and grouping](#traits-and-grouping)
|
|
9
|
+
- [Custom and adjustable controls](#custom-and-adjustable-controls)
|
|
10
|
+
- [Focus patterns](#focus-patterns)
|
|
11
|
+
- [Dynamic Type and layout](#dynamic-type-and-layout)
|
|
12
|
+
- [Custom rotors](#custom-rotors)
|
|
13
|
+
- [System preference values](#system-preference-values)
|
|
14
|
+
- [UIKit](#uikit)
|
|
15
|
+
- [AppKit](#appkit)
|
|
16
|
+
- [Mistakes to catch in review](#mistakes-to-catch-in-review)
|
|
17
|
+
- [Voice Control](#voice-control)
|
|
18
|
+
- [Switch Control](#switch-control)
|
|
19
|
+
- [Full Keyboard Access](#full-keyboard-access)
|
|
20
|
+
- [Automated checks with XCTest](#automated-checks-with-xctest)
|
|
21
|
+
|
|
22
|
+
## Labels, values, hints
|
|
20
23
|
|
|
21
24
|
```swift
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
// Icon-only button: the label is what VoiceOver and Voice Control use.
|
|
26
|
+
Button { bookmark(article) } label: {
|
|
27
|
+
Image(systemName: article.isSaved ? "bookmark.fill" : "bookmark")
|
|
24
28
|
}
|
|
25
|
-
.accessibilityLabel("
|
|
29
|
+
.accessibilityLabel("Bookmark")
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
// Value: speak the number people care about, not the raw slider position.
|
|
32
|
+
Slider(value: $brightness) // 0.0 ... 1.0
|
|
33
|
+
.accessibilityLabel("Brightness")
|
|
34
|
+
.accessibilityValue("\(Int(brightness * 100)) percent")
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
// Hint: what happens when activated, read last and only after a pause.
|
|
37
|
+
Button("Archive", action: archiveThread)
|
|
38
|
+
.accessibilityHint("Moves this conversation out of the inbox")
|
|
32
39
|
```
|
|
33
40
|
|
|
34
|
-
## Traits and
|
|
41
|
+
## Traits and grouping
|
|
42
|
+
|
|
43
|
+
`.accessibilityAddTraits` adds to the element's existing traits rather than
|
|
44
|
+
replacing them:
|
|
35
45
|
|
|
36
46
|
```swift
|
|
37
|
-
|
|
38
|
-
Button("Go") { }
|
|
47
|
+
Text(departureCountdown)
|
|
39
48
|
.accessibilityAddTraits(.updatesFrequently)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Merge a row so it is one stop instead of three:
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
```swift
|
|
42
54
|
HStack {
|
|
43
|
-
Image(
|
|
44
|
-
VStack {
|
|
45
|
-
Text(
|
|
46
|
-
Text(
|
|
55
|
+
Image(driver.photoName)
|
|
56
|
+
VStack(alignment: .leading) {
|
|
57
|
+
Text(driver.name)
|
|
58
|
+
Text(driver.vehicle)
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
.accessibilityElement(children: .combine)
|
|
62
|
+
```
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
### Two-state custom control
|
|
65
|
+
|
|
66
|
+
First choice is a real `Toggle`. When the design forces a custom view, expose
|
|
67
|
+
it as a toggle yourself:
|
|
68
|
+
|
|
69
|
+
```swift
|
|
70
|
+
struct PinChip: View {
|
|
71
|
+
let noteTitle: String
|
|
72
|
+
@Binding var isPinned: Bool
|
|
73
|
+
|
|
74
|
+
var body: some View {
|
|
75
|
+
HStack(spacing: 6) {
|
|
76
|
+
Image(systemName: isPinned ? "pin.fill" : "pin")
|
|
77
|
+
Text(noteTitle)
|
|
78
|
+
}
|
|
79
|
+
.onTapGesture { isPinned.toggle() }
|
|
80
|
+
.accessibilityElement()
|
|
81
|
+
.accessibilityLabel("Pin \(noteTitle)")
|
|
82
|
+
.accessibilityValue(isPinned ? "On" : "Off")
|
|
83
|
+
.accessibilityAddTraits(.isToggle) // iOS 17+
|
|
84
|
+
.accessibilityAction { isPinned.toggle() } // default activate action
|
|
85
|
+
}
|
|
55
86
|
}
|
|
56
|
-
.onTapGesture { isFavorite.toggle() }
|
|
57
|
-
.accessibilityElement()
|
|
58
|
-
.accessibilityLabel("Favorite \(product.name)")
|
|
59
|
-
.accessibilityValue(isFavorite ? "On" : "Off")
|
|
60
|
-
.accessibilityAddTraits(.isToggle)
|
|
61
|
-
.accessibilityAction { isFavorite.toggle() }
|
|
62
87
|
```
|
|
63
88
|
|
|
64
|
-
## Custom
|
|
89
|
+
## Custom and adjustable controls
|
|
90
|
+
|
|
91
|
+
For quantities, steppers, ratings, sliders and any other adjustable value,
|
|
92
|
+
start from the native control (`Stepper`, `Slider`, `Picker`). Only a truly
|
|
93
|
+
custom control needs the work below: in SwiftUI
|
|
94
|
+
`.accessibilityAdjustableAction`, in UIKit
|
|
95
|
+
`accessibilityTraits.insert(.adjustable)` plus `accessibilityIncrement()` /
|
|
96
|
+
`accessibilityDecrement()`.
|
|
65
97
|
|
|
66
98
|
```swift
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
99
|
+
struct SpiceLevelPicker: View {
|
|
100
|
+
@Binding var level: Int // 1...4
|
|
101
|
+
private let range = 1...4
|
|
102
|
+
|
|
103
|
+
var body: some View {
|
|
104
|
+
HStack {
|
|
105
|
+
ForEach(range, id: \.self) { value in
|
|
106
|
+
Image(systemName: value <= level ? "flame.fill" : "flame")
|
|
107
|
+
.onTapGesture { level = value }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
.accessibilityElement()
|
|
111
|
+
.accessibilityLabel("Spice level")
|
|
112
|
+
.accessibilityValue("\(level) of \(range.upperBound)")
|
|
113
|
+
.accessibilityAdjustableAction { change in
|
|
114
|
+
switch change {
|
|
115
|
+
case .increment: level = min(level + 1, range.upperBound)
|
|
116
|
+
case .decrement: level = max(level - 1, range.lowerBound)
|
|
117
|
+
@unknown default: break
|
|
118
|
+
}
|
|
76
119
|
}
|
|
77
120
|
}
|
|
121
|
+
}
|
|
78
122
|
```
|
|
79
123
|
|
|
80
|
-
|
|
124
|
+
## Focus patterns
|
|
81
125
|
|
|
82
|
-
|
|
126
|
+
Returning focus after a sheet, with a `Bool`:
|
|
83
127
|
|
|
84
128
|
```swift
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
129
|
+
struct InvoiceHeader: View {
|
|
130
|
+
@State private var editing = false
|
|
131
|
+
@AccessibilityFocusState private var editButtonFocused: Bool
|
|
132
|
+
|
|
133
|
+
var body: some View {
|
|
134
|
+
Button("Edit invoice", systemImage: "pencil") { editing = true }
|
|
135
|
+
.accessibilityFocused($editButtonFocused)
|
|
136
|
+
.sheet(isPresented: $editing) {
|
|
137
|
+
InvoiceEditor()
|
|
138
|
+
.onDisappear {
|
|
139
|
+
Task { @MainActor in
|
|
140
|
+
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
141
|
+
editButtonFocused = true
|
|
142
|
+
}
|
|
143
|
+
}
|
|
96
144
|
}
|
|
97
145
|
}
|
|
146
|
+
}
|
|
98
147
|
```
|
|
99
148
|
|
|
149
|
+
Several targets, with an optional enum:
|
|
150
|
+
|
|
100
151
|
```swift
|
|
101
|
-
enum
|
|
102
|
-
|
|
152
|
+
enum AddressFocus: Hashable { case street, city, postcode }
|
|
153
|
+
|
|
154
|
+
@AccessibilityFocusState private var addressFocus: AddressFocus?
|
|
155
|
+
// TextField("Street", text: $street).accessibilityFocused($addressFocus, equals: .street)
|
|
156
|
+
// On a bad postcode: addressFocus = .postcode
|
|
103
157
|
```
|
|
104
158
|
|
|
105
|
-
## Dynamic Type and
|
|
159
|
+
## Dynamic Type and layout
|
|
106
160
|
|
|
107
161
|
```swift
|
|
108
|
-
|
|
109
|
-
@
|
|
110
|
-
@ScaledMetric(relativeTo: .body) private var
|
|
111
|
-
@
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
162
|
+
struct TransferRow: View {
|
|
163
|
+
@Environment(\.dynamicTypeSize) private var typeSize
|
|
164
|
+
@ScaledMetric(relativeTo: .body) private var rowHeight: CGFloat = 44
|
|
165
|
+
@ScaledMetric(relativeTo: .body) private var gap: CGFloat = 12
|
|
166
|
+
@ScaledMetric(relativeTo: .title) private var glyphSize: CGFloat = 24
|
|
167
|
+
|
|
168
|
+
let transfer: Transfer
|
|
169
|
+
|
|
170
|
+
var body: some View {
|
|
171
|
+
let layout = typeSize.isAccessibilitySize
|
|
172
|
+
? AnyLayout(VStackLayout(alignment: .leading, spacing: gap))
|
|
173
|
+
: AnyLayout(HStackLayout(spacing: gap))
|
|
174
|
+
|
|
175
|
+
layout {
|
|
176
|
+
Image(systemName: "arrow.up.arrow.down")
|
|
177
|
+
.font(.system(size: glyphSize))
|
|
178
|
+
Text(transfer.counterparty).font(.body)
|
|
179
|
+
Text(transfer.amountText).font(.headline)
|
|
119
180
|
}
|
|
181
|
+
.frame(minHeight: rowHeight)
|
|
120
182
|
}
|
|
121
|
-
.frame(minHeight: controlHeight)
|
|
122
183
|
}
|
|
123
184
|
```
|
|
124
185
|
|
|
125
|
-
|
|
186
|
+
- `@ScaledMetric(relativeTo:)` is for custom hit regions, control heights,
|
|
187
|
+
spacing and glyph sizes that should keep pace with the text.
|
|
188
|
+
- `dynamicTypeSize.isAccessibilitySize` is the usual switch point from a
|
|
189
|
+
horizontal row to a leading-aligned vertical stack.
|
|
126
190
|
|
|
127
|
-
## Custom
|
|
191
|
+
## Custom rotors
|
|
128
192
|
|
|
129
193
|
```swift
|
|
130
|
-
List(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
194
|
+
List(inbox) { item in
|
|
195
|
+
MessageRow(message: item)
|
|
196
|
+
}
|
|
197
|
+
.accessibilityRotor("Flagged") {
|
|
198
|
+
ForEach(inbox.filter(\.isFlagged)) { item in
|
|
199
|
+
AccessibilityRotorEntry(item.subject, id: item.id)
|
|
135
200
|
}
|
|
201
|
+
}
|
|
136
202
|
```
|
|
137
203
|
|
|
138
|
-
## System
|
|
204
|
+
## System preference values
|
|
139
205
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
@Environment(\.colorSchemeContrast) var contrast
|
|
144
|
-
@Environment(\.legibilityWeight) var legibilityWeight
|
|
145
|
-
```
|
|
206
|
+
The four environment values to read are `\.accessibilityReduceMotion`,
|
|
207
|
+
`\.accessibilityReduceTransparency`, `\.colorSchemeContrast` and
|
|
208
|
+
`\.legibilityWeight`. Usage is in [SKILL.md](../SKILL.md#system-preferences).
|
|
146
209
|
|
|
147
|
-
## UIKit
|
|
210
|
+
## UIKit
|
|
148
211
|
|
|
149
212
|
```swift
|
|
150
|
-
|
|
151
|
-
|
|
213
|
+
final class StatusPill: UIView {
|
|
214
|
+
func configureAccessibility(title: String) {
|
|
215
|
+
isAccessibilityElement = true
|
|
216
|
+
accessibilityLabel = title
|
|
217
|
+
accessibilityTraits.insert(.button)
|
|
218
|
+
accessibilityTraits.remove(.staticText)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
152
221
|
|
|
153
|
-
|
|
154
|
-
UIAccessibility.post(notification: .
|
|
155
|
-
UIAccessibility.post(notification: .
|
|
222
|
+
// Status, partial update, full screen change:
|
|
223
|
+
UIAccessibility.post(notification: .announcement, argument: "3 files uploaded")
|
|
224
|
+
UIAccessibility.post(notification: .layoutChanged, argument: resultsTable)
|
|
225
|
+
UIAccessibility.post(notification: .screenChanged, argument: onboardingTitle)
|
|
156
226
|
```
|
|
157
227
|
|
|
158
|
-
## AppKit
|
|
228
|
+
## AppKit
|
|
159
229
|
|
|
160
|
-
|
|
230
|
+
Everything flows through `NSAccessibilityProtocol`. Customize only the pieces
|
|
231
|
+
AppKit gets wrong or leaves out.
|
|
161
232
|
|
|
162
|
-
### Custom
|
|
233
|
+
### Custom view: role, label, value, action
|
|
163
234
|
|
|
164
235
|
```swift
|
|
165
|
-
final class
|
|
166
|
-
var
|
|
236
|
+
final class LockToggleView: NSView {
|
|
237
|
+
private(set) var isLocked = false {
|
|
167
238
|
didSet {
|
|
168
239
|
NSAccessibility.post(element: self, notification: .valueChanged)
|
|
240
|
+
needsDisplay = true
|
|
169
241
|
}
|
|
170
242
|
}
|
|
171
243
|
|
|
244
|
+
override func accessibilityLabel() -> String? { "Lock layer" }
|
|
245
|
+
override func accessibilityValue() -> Any? { isLocked ? "Locked" : "Unlocked" }
|
|
172
246
|
override func isAccessibilityElement() -> Bool { true }
|
|
173
|
-
override func accessibilityRole() -> NSAccessibility.Role? { .button }
|
|
174
|
-
override func accessibilityLabel() -> String? { "Favorite" }
|
|
175
|
-
override func accessibilityValue() -> Any? { isFavorite ? "On" : "Off" }
|
|
176
|
-
|
|
177
247
|
override func accessibilityPerformPress() -> Bool {
|
|
178
|
-
|
|
248
|
+
isLocked.toggle()
|
|
179
249
|
return true
|
|
180
250
|
}
|
|
251
|
+
override func accessibilityRole() -> NSAccessibility.Role? { .button }
|
|
181
252
|
}
|
|
182
253
|
```
|
|
183
254
|
|
|
184
|
-
###
|
|
255
|
+
### Elements with no view
|
|
185
256
|
|
|
186
|
-
|
|
257
|
+
For things that are drawn rather than backed by an `NSView`, such as one bar
|
|
258
|
+
in a chart or an annotation painted on a canvas, create an
|
|
259
|
+
`NSAccessibilityElement`:
|
|
187
260
|
|
|
188
261
|
```swift
|
|
189
|
-
let
|
|
262
|
+
let barElement = NSAccessibilityElement.element(
|
|
190
263
|
withRole: .button,
|
|
191
|
-
frame:
|
|
192
|
-
label: "March revenue",
|
|
264
|
+
frame: barFrameInScreenCoordinates,
|
|
265
|
+
label: "March revenue, 42 thousand",
|
|
193
266
|
parent: chartView
|
|
194
267
|
)
|
|
195
268
|
```
|
|
196
269
|
|
|
197
|
-
###
|
|
270
|
+
### Notifications
|
|
198
271
|
|
|
199
272
|
```swift
|
|
200
|
-
|
|
273
|
+
// The accessible value changed.
|
|
274
|
+
NSAccessibility.post(element: sliderView, notification: .valueChanged)
|
|
201
275
|
|
|
276
|
+
// A passing status message.
|
|
202
277
|
NSAccessibility.post(
|
|
203
|
-
element:
|
|
278
|
+
element: exportPanelView,
|
|
204
279
|
notification: .announcementRequested,
|
|
205
|
-
userInfo: [
|
|
206
|
-
.announcement: "Export complete"
|
|
207
|
-
]
|
|
280
|
+
userInfo: [.announcement: "Backup finished"]
|
|
208
281
|
)
|
|
209
282
|
```
|
|
210
283
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
- Direct trait assignment instead of `.accessibilityAddTraits`
|
|
216
|
-
- Missing focus restoration after dismissing sheets
|
|
217
|
-
- Ungrouped list rows creating excessive swipe stops
|
|
218
|
-
- Icon-only buttons missing labels
|
|
219
|
-
- Ignoring Reduce Motion, Reduce Transparency, or Increase Contrast
|
|
220
|
-
- Fixed font sizes that break Dynamic Type
|
|
221
|
-
- Tap targets smaller than 44x44 points
|
|
284
|
+
Pick `.announcementRequested` for transient status and the specific
|
|
285
|
+
notification (such as `.valueChanged`) when the element's accessible value
|
|
286
|
+
actually changed.
|
|
222
287
|
|
|
223
|
-
##
|
|
288
|
+
## Mistakes to catch in review
|
|
224
289
|
|
|
225
|
-
|
|
290
|
+
- Tap targets below 44x44 pt.
|
|
291
|
+
- Traits assigned directly instead of through `.accessibilityAddTraits`.
|
|
292
|
+
- Hard-coded font sizes, so Dynamic Type has no effect.
|
|
293
|
+
- Focus not restored after a sheet closes.
|
|
294
|
+
- Icon-only buttons with no label.
|
|
295
|
+
- Increase Contrast, Reduce Transparency or Reduce Motion settings ignored.
|
|
296
|
+
- List rows left ungrouped, each producing several swipe stops.
|
|
226
297
|
|
|
227
|
-
|
|
298
|
+
## Voice Control
|
|
228
299
|
|
|
229
|
-
|
|
300
|
+
Voice Control builds its tap targets from accessibility labels, so every label
|
|
301
|
+
must be something a person can say, and unique among what is visible.
|
|
302
|
+
`accessibilityInputLabels` (iOS 14+) supplies shorter spoken alternatives when
|
|
303
|
+
the main label is long, clumsy, repeated, localized, acronym-heavy, or
|
|
304
|
+
normally shortened in speech.
|
|
230
305
|
|
|
231
306
|
```swift
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
Image(systemName: "figure.run")
|
|
236
|
-
Text("Start Outdoor Running Workout")
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
.accessibilityLabel("Start Outdoor Running Workout")
|
|
240
|
-
.accessibilityInputLabels(["Start Run", "Run", "Start Workout"])
|
|
241
|
-
```
|
|
307
|
+
Button("Start 45-minute progressive endurance ride") { startRide() }
|
|
308
|
+
.accessibilityLabel("Start 45-minute progressive endurance ride")
|
|
309
|
+
.accessibilityInputLabels(["Start ride", "Endurance ride", "Ride"])
|
|
242
310
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
NavigationLink {
|
|
246
|
-
AccountSettingsView()
|
|
247
|
-
} label: {
|
|
248
|
-
Label("Account and Privacy Settings", systemImage: "person.circle")
|
|
311
|
+
NavigationLink(value: Route.notifications) {
|
|
312
|
+
Label("Notification and alert preferences", systemImage: "bell.badge")
|
|
249
313
|
}
|
|
250
|
-
.accessibilityInputLabels(["
|
|
314
|
+
.accessibilityInputLabels(["Notifications", "Alerts"])
|
|
251
315
|
```
|
|
252
316
|
|
|
253
|
-
Also
|
|
317
|
+
Also worth input labels: labels built from product names, playback controls,
|
|
318
|
+
steppers for quantities, and the same action repeated on every row.
|
|
254
319
|
|
|
255
|
-
### Speakable
|
|
320
|
+
### Speakable labels
|
|
256
321
|
|
|
257
322
|
```swift
|
|
258
|
-
// Bad:
|
|
259
|
-
Button("
|
|
323
|
+
// Bad: nothing to say.
|
|
324
|
+
Button("\u{1F5D1}") { remove(entry) } // a wastebasket emoji
|
|
260
325
|
|
|
261
|
-
// Good:
|
|
262
|
-
Button(
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
.accessibilityLabel("Favorite")
|
|
326
|
+
// Good: symbol on screen, words for speech.
|
|
327
|
+
Button { remove(entry) } label: { Image(systemName: "trash") }
|
|
328
|
+
.accessibilityLabel("Delete")
|
|
266
329
|
|
|
267
|
-
// Bad:
|
|
268
|
-
ForEach(
|
|
269
|
-
Button("
|
|
330
|
+
// Bad: ten identical "Rename" buttons; Voice Control cannot tell them apart.
|
|
331
|
+
ForEach(playlists) { playlist in
|
|
332
|
+
Button("Rename") { rename(playlist) }
|
|
270
333
|
}
|
|
271
334
|
|
|
272
|
-
// Good: unique
|
|
273
|
-
ForEach(
|
|
274
|
-
Button("
|
|
275
|
-
.accessibilityLabel("
|
|
335
|
+
// Good: keep the visible word, make the spoken name unique.
|
|
336
|
+
ForEach(playlists) { playlist in
|
|
337
|
+
Button("Rename") { rename(playlist) }
|
|
338
|
+
.accessibilityLabel("Rename \(playlist.name)")
|
|
276
339
|
}
|
|
277
340
|
```
|
|
278
341
|
|
|
279
|
-
## Switch Control
|
|
280
|
-
|
|
281
|
-
Switch Control scans elements sequentially. Reduce scan stops with grouping and provide custom actions for gesture-based interactions.
|
|
342
|
+
## Switch Control
|
|
282
343
|
|
|
283
|
-
|
|
344
|
+
Switch Control scans element by element, so group to cut stops and add custom
|
|
345
|
+
actions for anything gesture-driven.
|
|
284
346
|
|
|
285
347
|
```swift
|
|
286
|
-
// Swipe
|
|
348
|
+
// Swipe actions also exposed as named actions.
|
|
287
349
|
TaskRow(task: task)
|
|
288
|
-
.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
.
|
|
350
|
+
.swipeActions {
|
|
351
|
+
Button("Delete", role: .destructive) { delete(task) }
|
|
352
|
+
}
|
|
353
|
+
.accessibilityAction(named: "Mark done") { complete(task) }
|
|
354
|
+
.accessibilityAction(named: "Delete") { delete(task) }
|
|
355
|
+
.accessibilityAction(named: "Snooze until tomorrow") { snooze(task) }
|
|
356
|
+
|
|
357
|
+
// Context-menu operations reachable without a long press.
|
|
358
|
+
PhotoTile(photo: photo)
|
|
359
|
+
.contextMenu {
|
|
360
|
+
Button("Share") { share(photo) }
|
|
361
|
+
Button("Add to Album") { addToAlbum(photo) }
|
|
362
|
+
Button("Delete", role: .destructive) { delete(photo) }
|
|
363
|
+
}
|
|
364
|
+
.accessibilityAction(named: "Delete") { delete(photo) }
|
|
365
|
+
.accessibilityAction(named: "Share") { share(photo) }
|
|
298
366
|
.accessibilityAction(named: "Add to Album") { addToAlbum(photo) }
|
|
299
|
-
.accessibilityAction(named: "Delete") { deletePhoto(photo) }
|
|
300
367
|
```
|
|
301
368
|
|
|
302
|
-
|
|
369
|
+
Grouping in numbers: this attachment row is five scan stops as written (icon,
|
|
370
|
+
name, date, size, chevron). With `.combine` it becomes one.
|
|
303
371
|
|
|
304
372
|
```swift
|
|
305
|
-
// Bad: 5 scan stops per row
|
|
306
|
-
HStack {
|
|
307
|
-
Image(systemName: "doc")
|
|
308
|
-
VStack {
|
|
309
|
-
Text(document.title)
|
|
310
|
-
Text(document.date.formatted())
|
|
311
|
-
}
|
|
312
|
-
Spacer()
|
|
313
|
-
Text(document.size)
|
|
314
|
-
Image(systemName: "chevron.right")
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Good: 1 scan stop per row
|
|
318
373
|
HStack {
|
|
319
|
-
Image(systemName: "
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
Spacer()
|
|
325
|
-
Text(document.size)
|
|
374
|
+
Image(systemName: "paperclip")
|
|
375
|
+
Text(file.name)
|
|
376
|
+
Text(file.modified, style: .date)
|
|
377
|
+
Text(file.sizeText)
|
|
326
378
|
Image(systemName: "chevron.right")
|
|
327
379
|
}
|
|
328
380
|
.accessibilityElement(children: .combine)
|
|
329
381
|
```
|
|
330
382
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
Full Keyboard Access review checks whether keyboard users can complete the same common tasks without touch. Keep the implementation mechanics in the `focus-engine` skill when the fix requires Tab-order wiring, skipped custom cards, `.focusable()`, SwiftUI keyboard focus state, focus sections, directional movement, tvOS focus, or `UIFocusGuide`.
|
|
383
|
+
VoiceOver users benefit from the same change: one swipe per attachment instead
|
|
384
|
+
of five.
|
|
334
385
|
|
|
335
|
-
|
|
336
|
-
- Activation works with expected keyboard input.
|
|
337
|
-
- Focus indicators are visible and not hidden by custom styling.
|
|
338
|
-
- Focus traversal is logical and does not trap users in a region.
|
|
339
|
-
- Gesture-only interactions have keyboard-operable alternatives.
|
|
340
|
-
- App shortcuts do not override system shortcuts.
|
|
341
|
-
- Custom controls skipped by Tab should be filed as keyboard focus implementation issues and routed to `focus-engine`; keep the accessibility finding here.
|
|
342
|
-
- Explicitly assess traversal impact: accessibility element order and grouping affect VoiceOver swipe order, Switch Control scan order, Voice Control overlay targeting, and Full Keyboard Access reachability review.
|
|
386
|
+
## Full Keyboard Access
|
|
343
387
|
|
|
344
|
-
|
|
388
|
+
The question a review asks: can someone using only a keyboard complete the
|
|
389
|
+
same common tasks as someone using touch? The mechanics (`UIFocusGuide`, tvOS
|
|
390
|
+
focus, arrow-key movement, focus sections, SwiftUI keyboard focus state,
|
|
391
|
+
`.focusable()`, cards that Tab skips, Tab order wiring) are `hig-inputs`
|
|
392
|
+
work.
|
|
345
393
|
|
|
346
|
-
|
|
394
|
+
Check:
|
|
347
395
|
|
|
348
|
-
|
|
396
|
+
- every interactive control can be reached from the keyboard;
|
|
397
|
+
- it activates with the keys people expect (Space, Return);
|
|
398
|
+
- the focus ring is visible and no custom style hides it;
|
|
399
|
+
- traversal is logical and never gets stuck in one region;
|
|
400
|
+
- gesture-only interactions have a keyboard alternative;
|
|
401
|
+
- the app's shortcuts do not shadow system shortcuts.
|
|
349
402
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// Verify buttons have meaningful labels
|
|
356
|
-
let settingsButton = app.buttons["Settings"]
|
|
357
|
-
XCTAssertTrue(settingsButton.exists, "Settings button must exist")
|
|
358
|
-
XCTAssertTrue(settingsButton.isEnabled, "Settings button must be enabled")
|
|
359
|
-
|
|
360
|
-
// Verify a cell groups content correctly
|
|
361
|
-
let productCell = app.cells.element(boundBy: 0)
|
|
362
|
-
XCTAssertFalse(productCell.label.isEmpty, "Product cell must have a combined label")
|
|
363
|
-
}
|
|
364
|
-
```
|
|
403
|
+
A custom control that Tab skips is filed as a keyboard-focus implementation
|
|
404
|
+
issue for `hig-inputs`, while the accessibility finding is recorded here.
|
|
405
|
+
Whatever the bug, write down what the current ordering and grouping do to the
|
|
406
|
+
four traversal paths: VoiceOver swipes, switch scanning, Voice Control
|
|
407
|
+
overlays and keyboard reach.
|
|
365
408
|
|
|
366
|
-
|
|
409
|
+
## Automated checks with XCTest
|
|
367
410
|
|
|
368
411
|
```swift
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
usernameField.tap()
|
|
377
|
-
XCTAssertTrue(usernameField.hasFocus)
|
|
378
|
-
|
|
379
|
-
// Tab to next field
|
|
380
|
-
usernameField.typeText("\t")
|
|
381
|
-
XCTAssertTrue(passwordField.hasFocus)
|
|
382
|
-
}
|
|
383
|
-
```
|
|
412
|
+
@MainActor
|
|
413
|
+
final class AccountScreenAccessibilityTests: XCTestCase {
|
|
414
|
+
private func launchedApp() -> XCUIApplication {
|
|
415
|
+
let app = XCUIApplication()
|
|
416
|
+
app.launch()
|
|
417
|
+
return app
|
|
418
|
+
}
|
|
384
419
|
|
|
385
|
-
|
|
420
|
+
func testControlsExposeLabels() {
|
|
421
|
+
let app = launchedApp()
|
|
422
|
+
let logout = app.buttons["Sign out"]
|
|
423
|
+
XCTAssertTrue(logout.exists, "Sign out button should be exposed")
|
|
424
|
+
XCTAssertTrue(logout.isEnabled, "Sign out button should be enabled")
|
|
386
425
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
426
|
+
let firstRow = app.cells.element(boundBy: 0)
|
|
427
|
+
XCTAssertTrue(firstRow.exists)
|
|
428
|
+
XCTAssertTrue(firstRow.buttons["Remove device"].exists)
|
|
429
|
+
XCTAssertFalse(firstRow.label.isEmpty, "Grouped row should read as one label")
|
|
430
|
+
}
|
|
391
431
|
|
|
392
|
-
|
|
393
|
-
|
|
432
|
+
func testTabMovesBetweenFields() {
|
|
433
|
+
let app = launchedApp()
|
|
434
|
+
let email = app.textFields["Email"]
|
|
435
|
+
let password = app.secureTextFields["Password"]
|
|
436
|
+
email.tap()
|
|
437
|
+
XCTAssertTrue(email.hasFocus)
|
|
438
|
+
email.typeText("\t")
|
|
439
|
+
XCTAssertTrue(password.hasFocus)
|
|
440
|
+
}
|
|
394
441
|
|
|
395
|
-
|
|
396
|
-
|
|
442
|
+
func testRowKeepsItsIdentifier() {
|
|
443
|
+
let app = launchedApp()
|
|
444
|
+
let row = app.cells["device-row-primary"]
|
|
445
|
+
XCTAssertTrue(row.exists)
|
|
446
|
+
XCTAssertEqual(row.identifier, "device-row-primary")
|
|
447
|
+
}
|
|
397
448
|
}
|
|
398
449
|
```
|
|
450
|
+
|
|
451
|
+
The identifier check matters because UI tests and custom-action tests target
|
|
452
|
+
rows by accessibility identifier.
|