@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1
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 +21 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +285 -285
- package/package.json +3 -3
- package/pipeline/lib/redact.mjs +3 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/pre-commit-check.sh +4 -0
- package/pipeline/skills/.skill-manifest.json +69 -69
- 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 +345 -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 +107 -121
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -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 +335 -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 +277 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -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 +274 -384
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
- 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 +228 -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 +302 -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 +236 -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/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -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/skill-creator/template.md +7 -1
- 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 +302 -241
- 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 +304 -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 +183 -162
- 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 +411 -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 +375 -491
- 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 +397 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -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 +201 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
- 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 +160 -315
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,237 +1,204 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: contacts-framework
|
|
3
|
-
description: "
|
|
3
|
+
description: "Contacts and ContactsUI: CNContactStore authorization and limited access, fetch and save requests, creating, updating and deleting contacts, CNContactPickerViewController in SwiftUI, change observation. Use when fetching, saving or picking contacts or handling permissions."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Contacts
|
|
8
|
+
# Contacts and ContactsUI
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
`CNContactStore` is the gateway to the user's address book: it answers
|
|
11
|
+
authorization questions, runs fetches and executes save requests.
|
|
12
|
+
ContactsUI adds the system contact picker and the SwiftUI controls for
|
|
13
|
+
limited access. Code here assumes Swift 6.3 and an iOS 26 deployment target.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- [Setup](#setup)
|
|
17
|
-
- [Authorization](#authorization)
|
|
18
|
-
- [Fetching Contacts](#fetching-contacts)
|
|
19
|
-
- [Key Descriptors](#key-descriptors)
|
|
20
|
-
- [Creating and Updating Contacts](#creating-and-updating-contacts)
|
|
21
|
-
- [Contact Picker](#contact-picker)
|
|
22
|
-
- [Observing Changes](#observing-changes)
|
|
23
|
-
- [Common Mistakes](#common-mistakes)
|
|
24
|
-
- [Review Checklist](#review-checklist)
|
|
25
|
-
- [References](#references)
|
|
15
|
+
Not for Swift change-history enumeration: the Contacts Swift API does not
|
|
16
|
+
offer it.
|
|
26
17
|
|
|
27
18
|
## Setup
|
|
28
19
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
- Info.plist must contain `NSContactsUsageDescription`. Calling the contact
|
|
21
|
+
data APIs without it terminates the app.
|
|
22
|
+
- Ordinary Contacts access needs no capability or entitlement.
|
|
23
|
+
- The note field is special. Reading or writing `CNContactNoteKey`
|
|
24
|
+
(`CNContact.note`) requires the `com.apple.developer.contacts.notes`
|
|
25
|
+
entitlement, and Apple must approve that entitlement before the app can be
|
|
26
|
+
distributed publicly.
|
|
36
27
|
|
|
37
28
|
```swift
|
|
38
|
-
@preconcurrency import Contacts
|
|
39
|
-
import ContactsUI
|
|
29
|
+
@preconcurrency import Contacts // store, contacts, save requests
|
|
30
|
+
import ContactsUI // picker and limited-access UI
|
|
40
31
|
```
|
|
41
32
|
|
|
42
33
|
## Authorization
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
Ask before the first fetch or save. The picker is the exception: it runs
|
|
36
|
+
out of process and hands over only what the user chose, so it needs no
|
|
37
|
+
permission at all.
|
|
47
38
|
|
|
48
39
|
```swift
|
|
49
|
-
let
|
|
50
|
-
|
|
51
|
-
func
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
40
|
+
let book = CNContactStore() // create once, share across the app
|
|
41
|
+
|
|
42
|
+
func ensureAccess() async throws -> Bool {
|
|
43
|
+
switch CNContactStore.authorizationStatus(for: .contacts) {
|
|
44
|
+
case .authorized, .limited:
|
|
45
|
+
return true
|
|
46
|
+
case .notDetermined:
|
|
47
|
+
return try await book.requestAccess(for: .contacts)
|
|
48
|
+
case .denied, .restricted:
|
|
49
|
+
return false // explain and link to Settings
|
|
50
|
+
@unknown default:
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
58
53
|
}
|
|
59
54
|
```
|
|
60
55
|
|
|
61
|
-
|
|
56
|
+
`authorizationStatus(for:)` reads the state without prompting.
|
|
62
57
|
|
|
63
58
|
| Status | Meaning |
|
|
64
59
|
|---|---|
|
|
65
|
-
| `.notDetermined` |
|
|
66
|
-
| `.authorized` | Full read
|
|
67
|
-
| `.
|
|
68
|
-
| `.
|
|
69
|
-
| `.
|
|
60
|
+
| `.notDetermined` | Never asked |
|
|
61
|
+
| `.authorized` | Full read and write access |
|
|
62
|
+
| `.limited` | iOS 18+: only the contacts the user shared with the app |
|
|
63
|
+
| `.denied` | User said no; point to Settings |
|
|
64
|
+
| `.restricted` | Blocked by parental controls or device management |
|
|
70
65
|
|
|
71
|
-
Treat
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`contactAccessPicker(isPresented:completionHandler:)`
|
|
75
|
-
to the app's limited-access set.
|
|
66
|
+
Treat `.authorized` and `.limited` alike as "can proceed". Under `.limited`,
|
|
67
|
+
fetches, edits and deletes only reach contacts the user granted plus contacts
|
|
68
|
+
the app created itself. Let people widen that set with `ContactAccessButton`
|
|
69
|
+
or the `contactAccessPicker(isPresented:completionHandler:)` modifier.
|
|
76
70
|
|
|
77
|
-
## Fetching
|
|
71
|
+
## Fetching
|
|
78
72
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
contacts in batches by identifier.
|
|
73
|
+
- `unifiedContacts(matching:keysToFetch:)` answers a predicate query.
|
|
74
|
+
- `unifiedContact(withIdentifier:keysToFetch:)` loads one known contact.
|
|
75
|
+
- `enumerateContacts(with:usingBlock:)` walks the whole book.
|
|
83
76
|
|
|
84
|
-
|
|
77
|
+
For big address books, collect identifiers first and then load details in
|
|
78
|
+
batches by identifier.
|
|
85
79
|
|
|
86
80
|
```swift
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
CNContactPhoneNumbersKey as CNKeyDescriptor
|
|
93
|
-
]
|
|
94
|
-
return try store.unifiedContacts(matching: predicate, keysToFetch: keys)
|
|
95
|
-
}
|
|
96
|
-
```
|
|
81
|
+
let wanted: [CNKeyDescriptor] = [
|
|
82
|
+
CNContactGivenNameKey as CNKeyDescriptor,
|
|
83
|
+
CNContactFamilyNameKey as CNKeyDescriptor,
|
|
84
|
+
CNContactEmailAddressesKey as CNKeyDescriptor
|
|
85
|
+
]
|
|
97
86
|
|
|
98
|
-
|
|
87
|
+
let byName = try book.unifiedContacts(
|
|
88
|
+
matching: CNContact.predicateForContacts(matchingName: "Okafor"),
|
|
89
|
+
keysToFetch: wanted
|
|
90
|
+
)
|
|
99
91
|
|
|
100
|
-
|
|
101
|
-
func fetchContact(identifier: String) throws -> CNContact {
|
|
102
|
-
let keys: [CNKeyDescriptor] = [
|
|
103
|
-
CNContactGivenNameKey as CNKeyDescriptor,
|
|
104
|
-
CNContactFamilyNameKey as CNKeyDescriptor,
|
|
105
|
-
CNContactEmailAddressesKey as CNKeyDescriptor
|
|
106
|
-
]
|
|
107
|
-
return try store.unifiedContact(withIdentifier: identifier, keysToFetch: keys)
|
|
108
|
-
}
|
|
92
|
+
let single = try book.unifiedContact(withIdentifier: savedID, keysToFetch: wanted)
|
|
109
93
|
```
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Perform I/O-heavy enumeration off the main thread.
|
|
95
|
+
Enumeration hits storage, so keep it off the main actor:
|
|
114
96
|
|
|
115
97
|
```swift
|
|
116
|
-
func
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
return contacts
|
|
98
|
+
func everyone(in book: CNContactStore) async throws -> [CNContact] {
|
|
99
|
+
try await Task.detached {
|
|
100
|
+
let request = CNContactFetchRequest(keysToFetch: [
|
|
101
|
+
CNContactGivenNameKey as CNKeyDescriptor,
|
|
102
|
+
CNContactFamilyNameKey as CNKeyDescriptor
|
|
103
|
+
])
|
|
104
|
+
request.sortOrder = .familyName
|
|
105
|
+
var found: [CNContact] = []
|
|
106
|
+
try book.enumerateContacts(with: request) { person, _ in found.append(person) }
|
|
107
|
+
return found
|
|
108
|
+
}.value
|
|
129
109
|
}
|
|
130
110
|
```
|
|
131
111
|
|
|
132
|
-
##
|
|
133
|
-
|
|
134
|
-
Only fetch the properties you need. Accessing an unfetched property throws
|
|
135
|
-
`CNContactPropertyNotFetchedException`.
|
|
112
|
+
## Choosing keys
|
|
136
113
|
|
|
137
|
-
|
|
114
|
+
Ask only for the properties the screen shows. Reading a property that was
|
|
115
|
+
not fetched raises `CNContactPropertyNotFetchedException`.
|
|
138
116
|
|
|
139
|
-
| Key |
|
|
117
|
+
| Key | Gives you |
|
|
140
118
|
|---|---|
|
|
141
|
-
| `CNContactGivenNameKey` |
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
Use `CNContactFormatter.descriptorForRequiredKeys(for:)` to fetch all keys needed
|
|
154
|
-
for formatting a contact's name.
|
|
119
|
+
| `CNContactGivenNameKey`, `CNContactFamilyNameKey` | Name parts |
|
|
120
|
+
| `CNContactPhoneNumbersKey` | Phone numbers |
|
|
121
|
+
| `CNContactEmailAddressesKey` | Email addresses |
|
|
122
|
+
| `CNContactPostalAddressesKey` | Postal addresses |
|
|
123
|
+
| `CNContactImageDataKey` | Full-resolution photo |
|
|
124
|
+
| `CNContactThumbnailImageDataKey` | Small photo for lists |
|
|
125
|
+
| `CNContactBirthdayKey` | Birthday as date components |
|
|
126
|
+
| `CNContactOrganizationNameKey` | Company |
|
|
127
|
+
|
|
128
|
+
To display a formatted name, let the formatter tell you what it needs and
|
|
129
|
+
mix that descriptor into the key list:
|
|
155
130
|
|
|
156
131
|
```swift
|
|
157
|
-
let
|
|
158
|
-
|
|
132
|
+
let listKeys: [CNKeyDescriptor] = [
|
|
133
|
+
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
|
134
|
+
CNContactThumbnailImageDataKey as CNKeyDescriptor
|
|
135
|
+
]
|
|
159
136
|
```
|
|
160
137
|
|
|
161
|
-
##
|
|
138
|
+
## Writing
|
|
162
139
|
|
|
163
|
-
|
|
140
|
+
`CNContact` is immutable. Build or copy a `CNMutableContact`, describe the
|
|
141
|
+
change in a `CNSaveRequest`, then run it with `execute(_:)`.
|
|
164
142
|
|
|
165
|
-
###
|
|
143
|
+
### Create
|
|
166
144
|
|
|
167
145
|
```swift
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
let saveRequest = CNSaveRequest()
|
|
180
|
-
saveRequest.add(contact, toContainerWithIdentifier: nil) // nil = default container
|
|
181
|
-
try store.execute(saveRequest)
|
|
182
|
-
}
|
|
146
|
+
let fresh = CNMutableContact()
|
|
147
|
+
fresh.givenName = "Imani"
|
|
148
|
+
fresh.familyName = "Okafor"
|
|
149
|
+
fresh.phoneNumbers = [
|
|
150
|
+
CNLabeledValue(label: CNLabelPhoneNumberMobile,
|
|
151
|
+
value: CNPhoneNumber(stringValue: "+44 20 7946 0018"))
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
let create = CNSaveRequest()
|
|
155
|
+
create.add(fresh, toContainerWithIdentifier: nil) // nil means the default container
|
|
156
|
+
try book.execute(create)
|
|
183
157
|
```
|
|
184
158
|
|
|
185
|
-
###
|
|
159
|
+
### Update
|
|
186
160
|
|
|
187
|
-
|
|
188
|
-
mutable copy, change the properties, then save.
|
|
161
|
+
Fetch with every key you intend to change, copy, edit, save:
|
|
189
162
|
|
|
190
163
|
```swift
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let saveRequest = CNSaveRequest()
|
|
203
|
-
saveRequest.update(mutable)
|
|
204
|
-
try store.execute(saveRequest)
|
|
205
|
-
}
|
|
164
|
+
let current = try book.unifiedContact(
|
|
165
|
+
withIdentifier: savedID,
|
|
166
|
+
keysToFetch: [CNContactEmailAddressesKey as CNKeyDescriptor]
|
|
167
|
+
)
|
|
168
|
+
guard let editable = current.mutableCopy() as? CNMutableContact else { return }
|
|
169
|
+
editable.emailAddresses.append(
|
|
170
|
+
CNLabeledValue(label: CNLabelWork, value: "imani@studio.example" as NSString)
|
|
171
|
+
)
|
|
172
|
+
let change = CNSaveRequest()
|
|
173
|
+
change.update(editable)
|
|
174
|
+
try book.execute(change)
|
|
206
175
|
```
|
|
207
176
|
|
|
208
|
-
###
|
|
177
|
+
### Delete
|
|
209
178
|
|
|
210
179
|
```swift
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
let
|
|
217
|
-
|
|
218
|
-
try
|
|
180
|
+
let target = try book.unifiedContact(
|
|
181
|
+
withIdentifier: savedID,
|
|
182
|
+
keysToFetch: [CNContactIdentifierKey as CNKeyDescriptor]
|
|
183
|
+
)
|
|
184
|
+
if let doomed = target.mutableCopy() as? CNMutableContact {
|
|
185
|
+
let removal = CNSaveRequest()
|
|
186
|
+
removal.delete(doomed)
|
|
187
|
+
try book.execute(removal)
|
|
219
188
|
}
|
|
220
189
|
```
|
|
221
190
|
|
|
222
|
-
##
|
|
223
|
-
|
|
224
|
-
`CNContactPickerViewController` lets users pick contacts without granting full
|
|
225
|
-
Contacts access. The app receives only the selected contact data.
|
|
191
|
+
## The contact picker
|
|
226
192
|
|
|
227
|
-
|
|
193
|
+
`CNContactPickerViewController` returns the chosen contact without the app
|
|
194
|
+
ever holding Contacts permission.
|
|
228
195
|
|
|
229
196
|
```swift
|
|
230
|
-
import SwiftUI
|
|
231
197
|
import ContactsUI
|
|
198
|
+
import SwiftUI
|
|
232
199
|
|
|
233
|
-
struct
|
|
234
|
-
|
|
200
|
+
struct ContactChooser: UIViewControllerRepresentable {
|
|
201
|
+
var onPick: (CNContact?) -> Void
|
|
235
202
|
|
|
236
203
|
func makeUIViewController(context: Context) -> CNContactPickerViewController {
|
|
237
204
|
let picker = CNContactPickerViewController()
|
|
@@ -239,209 +206,93 @@ struct ContactPicker: UIViewControllerRepresentable {
|
|
|
239
206
|
return picker
|
|
240
207
|
}
|
|
241
208
|
|
|
242
|
-
func updateUIViewController(_
|
|
209
|
+
func updateUIViewController(_ picker: CNContactPickerViewController, context: Context) { }
|
|
243
210
|
|
|
244
|
-
func makeCoordinator() -> Coordinator {
|
|
245
|
-
Coordinator(self)
|
|
246
|
-
}
|
|
211
|
+
func makeCoordinator() -> Coordinator { Coordinator(onPick: onPick) }
|
|
247
212
|
|
|
248
213
|
final class Coordinator: NSObject, CNContactPickerDelegate {
|
|
249
|
-
let
|
|
250
|
-
|
|
251
|
-
init(_ parent: ContactPicker) {
|
|
252
|
-
self.parent = parent
|
|
253
|
-
}
|
|
214
|
+
let onPick: (CNContact?) -> Void
|
|
215
|
+
init(onPick: @escaping (CNContact?) -> Void) { self.onPick = onPick }
|
|
254
216
|
|
|
255
217
|
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
|
|
256
|
-
|
|
218
|
+
onPick(contact)
|
|
257
219
|
}
|
|
258
220
|
|
|
259
221
|
func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
|
|
260
|
-
|
|
222
|
+
onPick(nil)
|
|
261
223
|
}
|
|
262
224
|
}
|
|
263
225
|
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Using the Picker
|
|
267
226
|
|
|
268
|
-
|
|
269
|
-
struct ContactSelectionView: View {
|
|
270
|
-
@State private var selectedContact: CNContact?
|
|
271
|
-
@State private var showPicker = false
|
|
272
|
-
|
|
273
|
-
var body: some View {
|
|
274
|
-
VStack {
|
|
275
|
-
if let contact = selectedContact {
|
|
276
|
-
Text("\(contact.givenName) \(contact.familyName)")
|
|
277
|
-
}
|
|
278
|
-
Button("Select Contact") {
|
|
279
|
-
showPicker = true
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
.sheet(isPresented: $showPicker) {
|
|
283
|
-
ContactPicker(selectedContact: $selectedContact)
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
227
|
+
// .sheet(isPresented: $choosing) { ContactChooser { chosen = $0 } }
|
|
287
228
|
```
|
|
288
229
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
Use predicates to control which contacts appear and what the user can select.
|
|
230
|
+
Set the picker's predicates before it is presented:
|
|
292
231
|
|
|
293
232
|
```swift
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
picker.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0")
|
|
297
|
-
// Selecting a contact returns it directly (no detail card)
|
|
298
|
-
picker.predicateForSelectionOfContact = NSPredicate(value: true)
|
|
233
|
+
picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0")
|
|
234
|
+
picker.predicateForSelectionOfContact = NSPredicate(value: true) // return at once, no detail card
|
|
299
235
|
```
|
|
300
236
|
|
|
301
|
-
##
|
|
237
|
+
## Watching for changes
|
|
302
238
|
|
|
303
|
-
|
|
239
|
+
Other apps and sync can change the address book at any time. Cached
|
|
240
|
+
`CNContact` values go stale, so listen for `.CNContactStoreDidChange` and
|
|
241
|
+
fetch again:
|
|
304
242
|
|
|
305
243
|
```swift
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
queue: .main
|
|
311
|
-
) { _ in
|
|
312
|
-
// Refetch contacts -- cached CNContact objects are stale
|
|
313
|
-
refreshContacts()
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## Common Mistakes
|
|
319
|
-
|
|
320
|
-
### DON'T: Fetch all keys when you only need a name
|
|
321
|
-
|
|
322
|
-
Over-fetching wastes memory and slows queries, especially for contacts with
|
|
323
|
-
large photos.
|
|
324
|
-
|
|
325
|
-
```swift
|
|
326
|
-
// WRONG: Fetches far more than the UI displays, including full-resolution photos
|
|
327
|
-
let keys: [CNKeyDescriptor] = [
|
|
328
|
-
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
|
329
|
-
CNContactImageDataKey as CNKeyDescriptor,
|
|
330
|
-
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
|
331
|
-
CNContactEmailAddressesKey as CNKeyDescriptor,
|
|
332
|
-
CNContactPostalAddressesKey as CNKeyDescriptor,
|
|
333
|
-
CNContactBirthdayKey as CNKeyDescriptor
|
|
334
|
-
]
|
|
335
|
-
|
|
336
|
-
// CORRECT: Fetch only what you display
|
|
337
|
-
let keys: [CNKeyDescriptor] = [
|
|
338
|
-
CNContactGivenNameKey as CNKeyDescriptor,
|
|
339
|
-
CNContactFamilyNameKey as CNKeyDescriptor
|
|
340
|
-
]
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### DON'T: Access unfetched properties
|
|
344
|
-
|
|
345
|
-
Accessing a property that was not in `keysToFetch` throws
|
|
346
|
-
`CNContactPropertyNotFetchedException` at runtime.
|
|
347
|
-
|
|
348
|
-
```swift
|
|
349
|
-
// WRONG: Only fetched name keys, now accessing phone
|
|
350
|
-
let keys: [CNKeyDescriptor] = [CNContactGivenNameKey as CNKeyDescriptor]
|
|
351
|
-
let contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)
|
|
352
|
-
let phone = contact.phoneNumbers.first // CRASH
|
|
353
|
-
|
|
354
|
-
// CORRECT: Include the key you need
|
|
355
|
-
let keys: [CNKeyDescriptor] = [
|
|
356
|
-
CNContactGivenNameKey as CNKeyDescriptor,
|
|
357
|
-
CNContactPhoneNumbersKey as CNKeyDescriptor
|
|
358
|
-
]
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### DON'T: Mutate a CNContact directly
|
|
362
|
-
|
|
363
|
-
`CNContact` is immutable. You must call `mutableCopy()` to get a `CNMutableContact`.
|
|
364
|
-
|
|
365
|
-
```swift
|
|
366
|
-
// WRONG: CNContact has no setter
|
|
367
|
-
let contact = try store.unifiedContact(withIdentifier: id, keysToFetch: keys)
|
|
368
|
-
contact.givenName = "New Name" // Compile error
|
|
369
|
-
|
|
370
|
-
// CORRECT: Create mutable copy
|
|
371
|
-
guard let mutable = contact.mutableCopy() as? CNMutableContact else { return }
|
|
372
|
-
mutable.givenName = "New Name"
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### DON'T: Skip authorization and assume access
|
|
376
|
-
|
|
377
|
-
Do not let fetch or save calls be the first place the user sees authorization.
|
|
378
|
-
If status is `.notDetermined`, request access; if access was denied, contact
|
|
379
|
-
operations fail with an authorization error.
|
|
380
|
-
|
|
381
|
-
```swift
|
|
382
|
-
// WRONG: Jump straight to fetch
|
|
383
|
-
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)
|
|
384
|
-
|
|
385
|
-
// CORRECT: Check or request access first
|
|
386
|
-
let granted = try await store.requestAccess(for: .contacts)
|
|
387
|
-
guard granted else { return }
|
|
388
|
-
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keys)
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### DON'T: Run heavy fetches on the main thread
|
|
392
|
-
|
|
393
|
-
`enumerateContacts` performs I/O. Running it on the main thread blocks the UI.
|
|
394
|
-
When strict concurrency checks complain about `CNContact` crossing task or actor
|
|
395
|
-
boundaries, use `@preconcurrency import Contacts` in that file or map contacts
|
|
396
|
-
into Sendable view models before returning them.
|
|
397
|
-
|
|
398
|
-
```swift
|
|
399
|
-
// WRONG: Main thread enumeration
|
|
400
|
-
func loadContacts() {
|
|
401
|
-
try store.enumerateContacts(with: request) { contact, _ in ... }
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// CORRECT: Run on a background thread
|
|
405
|
-
func loadContacts() async throws -> [CNContact] {
|
|
406
|
-
try await Task.detached {
|
|
407
|
-
var results: [CNContact] = []
|
|
408
|
-
try store.enumerateContacts(with: request) { contact, _ in
|
|
409
|
-
results.append(contact)
|
|
410
|
-
}
|
|
411
|
-
return results
|
|
412
|
-
}.value
|
|
244
|
+
let token = NotificationCenter.default.addObserver(
|
|
245
|
+
forName: .CNContactStoreDidChange, object: nil, queue: .main
|
|
246
|
+
) { _ in
|
|
247
|
+
Task { @MainActor in await reloadVisibleContacts() }
|
|
413
248
|
}
|
|
414
249
|
```
|
|
415
250
|
|
|
416
|
-
##
|
|
417
|
-
|
|
418
|
-
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
-
|
|
422
|
-
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
-
|
|
428
|
-
|
|
429
|
-
-
|
|
430
|
-
|
|
431
|
-
|
|
251
|
+
## Common mistakes
|
|
252
|
+
|
|
253
|
+
- **Fetching too much.** Loading `CNContactImageDataKey`, postal addresses
|
|
254
|
+
and birthdays for a list that shows names costs memory and time. Fetch the
|
|
255
|
+
keys the screen uses.
|
|
256
|
+
- **Reading a key you did not fetch.** Asking for `phoneNumbers` after a
|
|
257
|
+
name-only fetch crashes at runtime.
|
|
258
|
+
- **Mutating a `CNContact`.** It does not compile; take a `mutableCopy()`.
|
|
259
|
+
- **Letting a fetch be the first permission touchpoint.** Request access when
|
|
260
|
+
the status is `.notDetermined`; otherwise the fetch simply fails with an
|
|
261
|
+
authorization error.
|
|
262
|
+
- **Enumerating on the main actor.** It blocks the UI; use
|
|
263
|
+
`Task.detached { ... }.value` or another background context.
|
|
264
|
+
- **Strict-concurrency errors about `CNContact` crossing actors.** Use
|
|
265
|
+
`@preconcurrency import Contacts`, or map contacts into small `Sendable`
|
|
266
|
+
view models before they leave the background task.
|
|
267
|
+
|
|
268
|
+
## Review checklist
|
|
269
|
+
|
|
270
|
+
- [ ] `NSContactsUsageDescription` present
|
|
271
|
+
- [ ] `requestAccess(for: .contacts)` happens before any fetch or save
|
|
272
|
+
- [ ] `.limited` handled as usable, with its narrower scope in mind
|
|
273
|
+
- [ ] `ContactAccessButton` or `contactAccessPicker` offered to grow limited access
|
|
274
|
+
- [ ] Denial handled with a path to Settings
|
|
275
|
+
- [ ] Only the needed keys fetched
|
|
276
|
+
- [ ] Names formatted with `descriptorForRequiredKeys(for:)` keys
|
|
277
|
+
- [ ] `mutableCopy()` taken before any change
|
|
278
|
+
- [ ] Every create, update and delete goes through `CNSaveRequest`
|
|
279
|
+
- [ ] Heavy fetches run off the main actor
|
|
280
|
+
- [ ] `CNContactStoreDidChange` observed
|
|
281
|
+
- [ ] Picker used whenever full access is not needed
|
|
282
|
+
- [ ] Picker predicates set before presentation
|
|
283
|
+
- [ ] One `CNContactStore` shared by the whole app
|
|
432
284
|
|
|
433
285
|
## References
|
|
434
286
|
|
|
435
|
-
-
|
|
436
|
-
-
|
|
437
|
-
-
|
|
438
|
-
-
|
|
439
|
-
-
|
|
440
|
-
-
|
|
441
|
-
-
|
|
442
|
-
-
|
|
443
|
-
-
|
|
444
|
-
-
|
|
445
|
-
-
|
|
446
|
-
-
|
|
447
|
-
- [Contact Keys](https://sosumi.ai/documentation/contacts/contact-keys)
|
|
287
|
+
- [contacts patterns](references/contacts-patterns.md): observable manager and list with limited access, multi-select and email pickers, predicates, vCard, groups, change-notification limits
|
|
288
|
+
- https://developer.apple.com/documentation/contacts
|
|
289
|
+
- https://developer.apple.com/documentation/contacts/cncontactstore
|
|
290
|
+
- https://developer.apple.com/documentation/contacts/cncontactfetchrequest
|
|
291
|
+
- https://developer.apple.com/documentation/contacts/cnsaverequest
|
|
292
|
+
- https://developer.apple.com/documentation/contacts/cnmutablecontact
|
|
293
|
+
- https://developer.apple.com/documentation/contacts/contact-keys
|
|
294
|
+
- https://developer.apple.com/documentation/contactsui/cncontactpickerviewcontroller
|
|
295
|
+
- https://developer.apple.com/documentation/contactsui/cncontactpickerdelegate
|
|
296
|
+
- https://developer.apple.com/documentation/contactsui/contactaccessbutton
|
|
297
|
+
- https://developer.apple.com/documentation/bundleresources/information-property-list/nscontactsusagedescription
|
|
298
|
+
- Apple article "Accessing the contact store" and the `contactAccessPicker(isPresented:completionHandler:)` modifier page in the ContactsUI documentation
|