@mmerterden/multi-agent-pipeline 20.6.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 +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- 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/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- 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/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- 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/ci-cd-pipelines/SKILL.md +1 -0
- 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/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- 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/council/SKILL.md +1 -0
- 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/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- 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/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- 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/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- 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-coding-standard/SKILL.md +1 -0
- 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-module-structure/SKILL.md +1 -0
- 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-security/SKILL.md +2 -0
- 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 +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- 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/nextjs-app-router/SKILL.md +1 -0
- 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/play-store-review/SKILL.md +2 -0
- 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/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- 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/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- 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/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- 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/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- 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/typescript-patterns/SKILL.md +1 -0
- 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/vue-composition/SKILL.md +1 -0
- 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/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- 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,464 +1,336 @@
|
|
|
1
1
|
# CloudKit Patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Lower-level operations and features behind the [skill overview](../SKILL.md):
|
|
4
|
+
incremental fetching, tokens, sharing, zones, files, batching, scheduling,
|
|
5
|
+
encryption and the Dashboard.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
9
|
-
- [
|
|
10
|
-
- [Server
|
|
11
|
-
- [
|
|
12
|
-
- [UICloudSharingController](#uicloudsharingcontroller)
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [Encrypted
|
|
9
|
+
- [Incremental zone fetches](#incremental-zone-fetches)
|
|
10
|
+
- [Server change tokens](#server-change-tokens)
|
|
11
|
+
- [Sharing with `CKShare`](#sharing-with-ckshare)
|
|
12
|
+
- [`UICloudSharingController`](#uicloudsharingcontroller)
|
|
13
|
+
- [Zones](#zones)
|
|
14
|
+
- [Files with `CKAsset`](#files-with-ckasset)
|
|
15
|
+
- [Batches](#batches)
|
|
16
|
+
- [Quality of service and groups](#quality-of-service-and-groups)
|
|
17
|
+
- [Encrypted fields](#encrypted-fields)
|
|
18
18
|
- [CloudKit Dashboard](#cloudkit-dashboard)
|
|
19
|
-
- [
|
|
19
|
+
- [Discovering changed zones](#discovering-changed-zones)
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Incremental zone fetches
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
the
|
|
23
|
+
`CKFetchRecordZoneChangesOperation` returns only what changed since the last
|
|
24
|
+
token, per zone, in the private or shared database. Pass `nil` as the token for
|
|
25
|
+
the first full fetch.
|
|
26
26
|
|
|
27
27
|
```swift
|
|
28
28
|
import CloudKit
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
private let tokenCacheURL: URL
|
|
30
|
+
func pullZone(_ zone: CKRecordZone.ID, from db: CKDatabase, tokens: TokenStore) {
|
|
31
|
+
let settings = CKFetchRecordZoneChangesOperation.ZoneConfiguration()
|
|
32
|
+
settings.previousServerChangeToken = tokens.token(for: zone)
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
loadTokens()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
func fetchChanges(in zoneIDs: [CKRecordZone.ID]) async throws {
|
|
42
|
-
var configs: [CKRecordZone.ID: CKFetchRecordZoneChangesOperation.ZoneConfiguration] = [:]
|
|
43
|
-
for zoneID in zoneIDs {
|
|
44
|
-
let config = CKFetchRecordZoneChangesOperation.ZoneConfiguration()
|
|
45
|
-
config.previousServerChangeToken = changeTokens[zoneID]
|
|
46
|
-
configs[zoneID] = config
|
|
47
|
-
}
|
|
34
|
+
let op = CKFetchRecordZoneChangesOperation(recordZoneIDs: [zone],
|
|
35
|
+
configurationsByRecordZoneID: [zone: settings])
|
|
36
|
+
op.fetchAllChanges = true
|
|
48
37
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Process deleted records
|
|
66
|
-
operation.recordWithIDWasDeletedBlock = { recordID, recordType in
|
|
67
|
-
self.processDeletedRecord(recordID, type: recordType)
|
|
38
|
+
op.recordWasChangedBlock = { id, result in
|
|
39
|
+
if case .success(let record) = result { Inbox.upsert(record) }
|
|
40
|
+
}
|
|
41
|
+
op.recordWithIDWasDeletedBlock = { id, recordType in
|
|
42
|
+
Inbox.delete(id, type: recordType)
|
|
43
|
+
}
|
|
44
|
+
op.recordZoneFetchResultBlock = { zoneID, result in
|
|
45
|
+
switch result {
|
|
46
|
+
case .success(let outcome):
|
|
47
|
+
tokens.set(outcome.serverChangeToken, for: zoneID) // cache right away
|
|
48
|
+
case .failure(let error as CKError) where error.code == .changeTokenExpired:
|
|
49
|
+
tokens.set(nil, for: zoneID) // start over next time
|
|
50
|
+
case .failure:
|
|
51
|
+
break
|
|
68
52
|
}
|
|
53
|
+
}
|
|
54
|
+
op.fetchRecordZoneChangesResultBlock = { overall in
|
|
55
|
+
if case .failure(let error) = overall { SyncLog.record(error) }
|
|
56
|
+
}
|
|
57
|
+
op.qualityOfService = .utility
|
|
58
|
+
db.add(op)
|
|
59
|
+
}
|
|
60
|
+
```
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
operation.recordZoneFetchResultBlock = { zoneID, result in
|
|
72
|
-
switch result {
|
|
73
|
-
case .success(let (serverChangeToken, _, _)):
|
|
74
|
-
self.changeTokens[zoneID] = serverChangeToken
|
|
75
|
-
self.saveTokens()
|
|
76
|
-
case .failure(let error):
|
|
77
|
-
if let ckError = error as? CKError,
|
|
78
|
-
ckError.code == .changeTokenExpired {
|
|
79
|
-
// Clear token and refetch from scratch
|
|
80
|
-
self.changeTokens[zoneID] = nil
|
|
81
|
-
self.saveTokens()
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
62
|
+
### Writing tokens to disk
|
|
85
63
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
64
|
+
```swift
|
|
65
|
+
final class TokenStore: @unchecked Sendable {
|
|
66
|
+
private let file: URL
|
|
67
|
+
private var tokens: [CKRecordZone.ID: CKServerChangeToken] = [:]
|
|
68
|
+
private let lock = NSLock()
|
|
69
|
+
|
|
70
|
+
init(file: URL) {
|
|
71
|
+
self.file = file
|
|
72
|
+
if let archived = try? Data(contentsOf: file),
|
|
73
|
+
let dict = try? NSKeyedUnarchiver.unarchivedObject(
|
|
74
|
+
ofClasses: [NSDictionary.self, CKRecordZone.ID.self, CKServerChangeToken.self],
|
|
75
|
+
from: archived) as? [CKRecordZone.ID: CKServerChangeToken] {
|
|
76
|
+
tokens = dict
|
|
90
77
|
}
|
|
91
|
-
|
|
92
|
-
operation.qualityOfService = .utility
|
|
93
|
-
database.add(operation)
|
|
94
78
|
}
|
|
95
79
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
private func loadTokens() {
|
|
99
|
-
guard let data = try? Data(contentsOf: tokenCacheURL),
|
|
100
|
-
let tokens = try? NSKeyedUnarchiver.unarchivedObject(
|
|
101
|
-
ofClasses: [NSDictionary.self, CKRecordZone.ID.self,
|
|
102
|
-
CKServerChangeToken.self],
|
|
103
|
-
from: data) as? [CKRecordZone.ID: CKServerChangeToken]
|
|
104
|
-
else { return }
|
|
105
|
-
changeTokens = tokens
|
|
80
|
+
func token(for zone: CKRecordZone.ID) -> CKServerChangeToken? {
|
|
81
|
+
lock.withLock { tokens[zone] }
|
|
106
82
|
}
|
|
107
83
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
84
|
+
func set(_ token: CKServerChangeToken?, for zone: CKRecordZone.ID) {
|
|
85
|
+
lock.withLock {
|
|
86
|
+
tokens[zone] = token
|
|
87
|
+
let snapshot = tokens as NSDictionary
|
|
88
|
+
if let encoded = try? NSKeyedArchiver.archivedData(withRootObject: snapshot, requiringSecureCoding: true) {
|
|
89
|
+
try? encoded.write(to: file, options: .atomic)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
113
92
|
}
|
|
114
93
|
}
|
|
115
94
|
```
|
|
116
95
|
|
|
117
|
-
## Server
|
|
96
|
+
## Server change tokens
|
|
118
97
|
|
|
119
|
-
|
|
98
|
+
- A token is an opaque bookmark into one zone's (or one database's) change history.
|
|
99
|
+
- Tokens adopt `NSSecureCoding`, so they can be archived and stored.
|
|
100
|
+
- A zone token and a database token are different things; never swap them.
|
|
101
|
+
- `.changeTokenExpired` means the bookmark is too old. Set it to `nil` and refetch everything.
|
|
102
|
+
- Save the new token as soon as `recordZoneFetchResultBlock` delivers it.
|
|
120
103
|
|
|
121
|
-
|
|
122
|
-
- Zone change tokens are NOT interchangeable with database change tokens.
|
|
123
|
-
- A `.changeTokenExpired` error means the token is too old; reset to `nil` and
|
|
124
|
-
refetch all changes.
|
|
125
|
-
- On `recordZoneFetchResultBlock`, cache the returned token immediately.
|
|
104
|
+
For a handful of zones, `UserDefaults` works too:
|
|
126
105
|
|
|
127
106
|
```swift
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
guard let token
|
|
131
|
-
|
|
107
|
+
func remember(_ token: CKServerChangeToken?, zone: String) {
|
|
108
|
+
let key = "ck.token.\(zone)"
|
|
109
|
+
guard let token,
|
|
110
|
+
let blob = try? NSKeyedArchiver.archivedData(withRootObject: token as NSSecureCoding, requiringSecureCoding: true)
|
|
111
|
+
else {
|
|
112
|
+
UserDefaults.standard.removeObject(forKey: key)
|
|
132
113
|
return
|
|
133
114
|
}
|
|
134
|
-
|
|
135
|
-
withRootObject: token, requiringSecureCoding: true)
|
|
136
|
-
UserDefaults.standard.set(data, forKey: "token-\(zoneID.zoneName)")
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
func cachedToken(for zoneID: CKRecordZone.ID) -> CKServerChangeToken? {
|
|
140
|
-
guard let data = UserDefaults.standard.data(forKey: "token-\(zoneID.zoneName)")
|
|
141
|
-
else { return nil }
|
|
142
|
-
return try? NSKeyedUnarchiver.unarchivedObject(
|
|
143
|
-
ofClass: CKServerChangeToken.self, from: data)
|
|
115
|
+
UserDefaults.standard.set(blob, forKey: key)
|
|
144
116
|
}
|
|
145
117
|
```
|
|
146
118
|
|
|
147
|
-
##
|
|
148
|
-
|
|
149
|
-
CKShare manages shared access to records or entire record zones. Limit: 100
|
|
150
|
-
participants per share. Available iOS 10+.
|
|
119
|
+
## Sharing with `CKShare`
|
|
151
120
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
Share a root record and its children (linked via `parent` references).
|
|
121
|
+
`CKShare` (iOS 10+) shares one record hierarchy or an entire zone, with up to
|
|
122
|
+
100 participants per share.
|
|
155
123
|
|
|
156
124
|
```swift
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
)
|
|
173
|
-
operation.modifyRecordsResultBlock = { result in
|
|
174
|
-
switch result {
|
|
175
|
-
case .success:
|
|
176
|
-
print("Share URL: \(share.url?.absoluteString ?? "nil")")
|
|
177
|
-
case .failure(let error):
|
|
178
|
-
print("Sharing failed: \(error)")
|
|
125
|
+
func shareGarden(_ root: CKRecord, in db: CKDatabase) async throws -> URL? {
|
|
126
|
+
let share = CKShare(rootRecord: root) // children follow via their `parent` reference
|
|
127
|
+
share[CKShare.SystemFieldKey.title] = "Community Garden" as CKRecordValue
|
|
128
|
+
share[CKShare.SystemFieldKey.shareType] = "app.gardenlog.garden" as CKRecordValue
|
|
129
|
+
share.publicPermission = .readOnly
|
|
130
|
+
|
|
131
|
+
return try await withCheckedThrowingContinuation { (done: CheckedContinuation<URL?, Error>) in
|
|
132
|
+
let op = CKModifyRecordsOperation(recordsToSave: [root, share], recordIDsToDelete: nil)
|
|
133
|
+
op.modifyRecordsResultBlock = { result in
|
|
134
|
+
switch result {
|
|
135
|
+
case .success: done.resume(returning: share.url)
|
|
136
|
+
case .failure(let problem): done.resume(throwing: problem)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
db.add(op)
|
|
179
140
|
}
|
|
180
141
|
}
|
|
181
|
-
privateDB.add(operation)
|
|
182
142
|
```
|
|
183
143
|
|
|
184
|
-
|
|
144
|
+
Save the root and the share **in the same operation**; `share.url` is set only
|
|
145
|
+
after that save succeeds. To share a whole zone, use
|
|
146
|
+
`CKShare(recordZoneID:)` instead.
|
|
185
147
|
|
|
186
|
-
|
|
148
|
+
### Adding people
|
|
187
149
|
|
|
188
150
|
```swift
|
|
189
|
-
|
|
190
|
-
let
|
|
191
|
-
|
|
151
|
+
func invite(_ emails: [String], to share: CKShare, container: CKContainer) async throws {
|
|
152
|
+
let people = try await container.shareParticipants(
|
|
153
|
+
for: emails.map { CKUserIdentity.LookupInfo(emailAddress: $0) })
|
|
154
|
+
for (_, result) in people {
|
|
155
|
+
let person = try result.get()
|
|
156
|
+
person.permission = .readWrite
|
|
157
|
+
share.addParticipant(person)
|
|
158
|
+
}
|
|
159
|
+
_ = try await container.privateCloudDatabase.save(share)
|
|
160
|
+
}
|
|
192
161
|
```
|
|
193
162
|
|
|
194
|
-
|
|
163
|
+
`container.shareParticipants(forEmailAddresses:)` is the shorter variant when
|
|
164
|
+
you only have addresses.
|
|
195
165
|
|
|
196
|
-
|
|
197
|
-
// Look up participants by email
|
|
198
|
-
let lookupInfo = CKUserIdentity.LookupInfo(emailAddress: "friend@example.com")
|
|
199
|
-
let participants = try await container.shareParticipants(
|
|
200
|
-
forEmailAddresses: ["friend@example.com"])
|
|
201
|
-
|
|
202
|
-
for participant in participants {
|
|
203
|
-
participant.permission = .readWrite
|
|
204
|
-
share.addParticipant(participant)
|
|
205
|
-
}
|
|
166
|
+
### Accepting a share
|
|
206
167
|
|
|
207
|
-
|
|
208
|
-
try await privateDB.save(share)
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Accepting a Share
|
|
168
|
+
Share links open the app only when Info.plist sets `CKSharingSupported` to `YES`.
|
|
212
169
|
|
|
213
170
|
```swift
|
|
214
|
-
|
|
215
|
-
func userDidAcceptCloudKitShare(with metadata: CKShare.Metadata) {
|
|
216
|
-
let container = CKContainer(identifier: metadata.containerIdentifier)
|
|
171
|
+
func windowScene(_ scene: UIWindowScene, userDidAcceptCloudKitShareWith metadata: CKShare.Metadata) {
|
|
217
172
|
Task {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} catch {
|
|
222
|
-
print("Accept failed: \(error)")
|
|
223
|
-
}
|
|
173
|
+
let owner = CKContainer(identifier: metadata.containerIdentifier)
|
|
174
|
+
_ = try await owner.accept([metadata])
|
|
175
|
+
try await GardenSync.shared.pullNow() // records now appear in sharedCloudDatabase
|
|
224
176
|
}
|
|
225
177
|
}
|
|
226
178
|
```
|
|
227
179
|
|
|
228
|
-
|
|
229
|
-
launch your app from share URLs.
|
|
180
|
+
The app delegate variant is `userDidAcceptCloudKitShare(with:)`.
|
|
230
181
|
|
|
231
|
-
## UICloudSharingController
|
|
182
|
+
## `UICloudSharingController`
|
|
232
183
|
|
|
233
|
-
|
|
184
|
+
The system sharing sheet, iOS only:
|
|
234
185
|
|
|
235
186
|
```swift
|
|
236
187
|
import UIKit
|
|
237
188
|
import CloudKit
|
|
238
189
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// UICloudSharingControllerDelegate
|
|
248
|
-
extension MyClass: UICloudSharingControllerDelegate {
|
|
249
|
-
func cloudSharingController(
|
|
250
|
-
_ csc: UICloudSharingController,
|
|
251
|
-
failedToSaveShareWithError error: Error
|
|
252
|
-
) {
|
|
253
|
-
print("Save share error: \(error)")
|
|
190
|
+
final class GardenDetailViewController: UIViewController, UICloudSharingControllerDelegate {
|
|
191
|
+
func presentSharing(_ share: CKShare, in container: CKContainer) {
|
|
192
|
+
let sheet = UICloudSharingController(share: share, container: container)
|
|
193
|
+
sheet.availablePermissions = [.allowReadOnly, .allowReadWrite, .allowPrivate]
|
|
194
|
+
sheet.delegate = self
|
|
195
|
+
present(sheet, animated: true)
|
|
254
196
|
}
|
|
255
197
|
|
|
256
|
-
func
|
|
257
|
-
|
|
198
|
+
func cloudSharingController(_ controller: UICloudSharingController, failedToSaveShareWithError saveError: Error) {
|
|
199
|
+
SyncLog.record(saveError)
|
|
258
200
|
}
|
|
201
|
+
|
|
202
|
+
func itemTitle(for controller: UICloudSharingController) -> String? { "Community Garden" }
|
|
259
203
|
}
|
|
260
204
|
```
|
|
261
205
|
|
|
262
|
-
##
|
|
206
|
+
## Zones
|
|
263
207
|
|
|
264
|
-
|
|
265
|
-
|
|
208
|
+
Only zones you create yourself in the private store give you atomic commits,
|
|
209
|
+
token-based change tracking and sharing. The default zone has neither custom
|
|
210
|
+
change tokens nor atomic operations, so sync code should always use custom zones.
|
|
266
211
|
|
|
267
212
|
```swift
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
let
|
|
271
|
-
try await privateDB.
|
|
272
|
-
|
|
273
|
-
// Fetch all zones
|
|
274
|
-
let zones = try await privateDB.allRecordZones()
|
|
275
|
-
|
|
276
|
-
// Delete
|
|
277
|
-
try await privateDB.deleteRecordZone(withID: zoneID)
|
|
213
|
+
let zoneID = CKRecordZone.ID(zoneName: "Harvests", ownerName: CKCurrentUserDefaultName)
|
|
214
|
+
_ = try await privateDB.save(CKRecordZone(zoneID: zoneID))
|
|
215
|
+
let everyZone = try await privateDB.allRecordZones() // list
|
|
216
|
+
_ = try await privateDB.deleteRecordZone(withID: zoneID) // remove
|
|
278
217
|
```
|
|
279
218
|
|
|
280
|
-
|
|
281
|
-
operations. Always use custom zones for sync.
|
|
282
|
-
|
|
283
|
-
## CKAsset File Storage
|
|
219
|
+
## Files with `CKAsset`
|
|
284
220
|
|
|
285
|
-
|
|
286
|
-
|
|
221
|
+
Anything bigger than a few kilobytes (photos, audio, blobs) belongs in a
|
|
222
|
+
`CKAsset`; asset bytes are stored apart from the record and are exempt from its 1 MB cap.
|
|
287
223
|
|
|
288
224
|
```swift
|
|
289
|
-
|
|
290
|
-
let
|
|
291
|
-
try
|
|
292
|
-
|
|
293
|
-
let record = CKRecord(recordType: "Photo")
|
|
294
|
-
record["image"] = CKAsset(fileURL: tempURL)
|
|
295
|
-
record["caption"] = "Sunset at the beach" as CKRecordValue
|
|
296
|
-
try await privateDB.save(record)
|
|
297
|
-
|
|
298
|
-
// Fetch and read the asset
|
|
299
|
-
let fetched = try await privateDB.record(for: record.recordID)
|
|
300
|
-
if let asset = fetched["image"] as? CKAsset,
|
|
301
|
-
let fileURL = asset.fileURL {
|
|
302
|
-
let data = try Data(contentsOf: fileURL)
|
|
303
|
-
// Move to app container immediately -- staging area is temporary
|
|
304
|
-
let permanentURL = documentsDir.appendingPathComponent("photo.jpg")
|
|
305
|
-
try FileManager.default.moveItem(at: fileURL, to: permanentURL)
|
|
225
|
+
func attachPhoto(_ jpeg: Data, to record: CKRecord) throws {
|
|
226
|
+
let tmp = FileManager.default.temporaryDirectory.appending(path: UUID().uuidString + ".jpg")
|
|
227
|
+
try jpeg.write(to: tmp)
|
|
228
|
+
record["photo"] = CKAsset(fileURL: tmp)
|
|
306
229
|
}
|
|
307
230
|
|
|
308
|
-
|
|
309
|
-
record["
|
|
310
|
-
|
|
311
|
-
|
|
231
|
+
func keepPhoto(from record: CKRecord, into folder: URL) throws -> URL? {
|
|
232
|
+
guard let asset = record["photo"] as? CKAsset, let staged = asset.fileURL else { return nil }
|
|
233
|
+
let destination = folder.appending(path: record.recordID.recordName + ".jpg")
|
|
234
|
+
try FileManager.default.moveItem(at: staged, to: destination) // staging copy is temporary
|
|
235
|
+
return destination
|
|
236
|
+
}
|
|
312
237
|
```
|
|
313
238
|
|
|
314
|
-
|
|
315
|
-
|
|
239
|
+
To delete an asset, set the field to `nil` and save; CloudKit cleans up
|
|
240
|
+
orphaned asset files on its own schedule. Use `desiredKeys` on fetch
|
|
241
|
+
operations to skip asset fields you do not need.
|
|
316
242
|
|
|
317
|
-
##
|
|
243
|
+
## Batches
|
|
318
244
|
|
|
319
|
-
|
|
245
|
+
`CKModifyRecordsOperation` saves and deletes in bulk:
|
|
320
246
|
|
|
321
247
|
```swift
|
|
322
|
-
let
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
329
|
-
operation.savePolicy = .changedKeys // Only upload modified fields
|
|
330
|
-
operation.isAtomic = true // All or nothing (custom zones only)
|
|
331
|
-
|
|
332
|
-
operation.perRecordSaveBlock = { recordID, result in
|
|
333
|
-
switch result {
|
|
334
|
-
case .success(let record): print("Saved: \(recordID)")
|
|
335
|
-
case .failure(let error): print("Failed: \(recordID) \(error)")
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
operation.perRecordDeleteBlock = { recordID, result in
|
|
340
|
-
switch result {
|
|
341
|
-
case .success: print("Deleted: \(recordID)")
|
|
342
|
-
case .failure(let error): print("Delete failed: \(recordID) \(error)")
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
operation.modifyRecordsResultBlock = { result in
|
|
347
|
-
if case .failure(let error) = result {
|
|
348
|
-
print("Batch failed: \(error)")
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// CloudKit limits: 400 records per operation
|
|
353
|
-
operation.qualityOfService = .userInitiated
|
|
354
|
-
privateDB.add(operation)
|
|
248
|
+
let op = CKModifyRecordsOperation(recordsToSave: changed, recordIDsToDelete: removed)
|
|
249
|
+
op.savePolicy = .changedKeys // upload only modified fields
|
|
250
|
+
op.isAtomic = true // every change lands or none does (custom zones)
|
|
251
|
+
op.perRecordSaveBlock = { id, result in Outbox.note(id, result) }
|
|
252
|
+
op.perRecordDeleteBlock = { id, result in Outbox.noteDeletion(id, result) }
|
|
253
|
+
op.modifyRecordsResultBlock = { result in Outbox.finish(result) }
|
|
254
|
+
privateDB.add(op)
|
|
355
255
|
```
|
|
356
256
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
## Operation Queues and QoS
|
|
257
|
+
One operation takes at most 400 records. Split larger sets into chunks.
|
|
360
258
|
|
|
361
|
-
|
|
259
|
+
## Quality of service and groups
|
|
362
260
|
|
|
363
|
-
|
|
|
364
|
-
|
|
365
|
-
|
|
|
366
|
-
|
|
|
367
|
-
|
|
|
261
|
+
| Work | QoS |
|
|
262
|
+
|---|---|
|
|
263
|
+
| The user is waiting on it | `.userInitiated` |
|
|
264
|
+
| Routine background sync | `.utility` |
|
|
265
|
+
| Prefetching, cleanup | `.background` |
|
|
368
266
|
|
|
369
267
|
```swift
|
|
370
|
-
|
|
371
|
-
let group = CKOperationGroup()
|
|
372
|
-
group.expectedSendSize = .kilobytes
|
|
373
|
-
group.expectedReceiveSize = .megabytes
|
|
374
|
-
|
|
375
|
-
let
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
operation.configuration = config
|
|
381
|
-
database.add(operation)
|
|
268
|
+
func tagAsBackgroundSync(_ op: CKOperation) {
|
|
269
|
+
let group = CKOperationGroup()
|
|
270
|
+
group.expectedSendSize = .kilobytes
|
|
271
|
+
group.expectedReceiveSize = .megabytes
|
|
272
|
+
|
|
273
|
+
let configuration = CKOperation.Configuration()
|
|
274
|
+
configuration.qualityOfService = .utility
|
|
275
|
+
op.configuration = configuration
|
|
276
|
+
op.group = group // the group is set on the operation itself
|
|
277
|
+
}
|
|
382
278
|
```
|
|
383
279
|
|
|
384
|
-
## Encrypted
|
|
385
|
-
|
|
386
|
-
Use `encryptedValues` (iOS 15+) for sensitive private or shared data that does
|
|
387
|
-
not need query or sort indexes. CloudKit can encrypt only new schema fields;
|
|
388
|
-
you cannot convert an existing field to encrypted storage, and encrypted values
|
|
389
|
-
are unavailable for public database records. `CKAsset` data is encrypted by
|
|
390
|
-
default, while `CKRecord.Reference` is not encrypted because CloudKit needs it
|
|
391
|
-
for server-side relationship handling.
|
|
280
|
+
## Encrypted fields
|
|
392
281
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
record.encryptedValues["heartRate"] = 72 as CKRecordValue
|
|
396
|
-
record.encryptedValues["notes"] = "Resting" as CKRecordValue
|
|
397
|
-
// Non-sensitive fields remain in plain text
|
|
398
|
-
record["date"] = Date() as CKRecordValue
|
|
282
|
+
`encryptedValues` (iOS 15+) encrypts sensitive fields in private and shared
|
|
283
|
+
databases, as long as you never need to query or sort on them.
|
|
399
284
|
|
|
400
|
-
|
|
285
|
+
- Only a **new** schema field can be encrypted; an existing field cannot be converted.
|
|
286
|
+
- Public database records cannot use encrypted values.
|
|
287
|
+
- `CKAsset` data is encrypted already.
|
|
288
|
+
- `CKRecord.Reference` fields stay unencrypted because the server needs them for relationships.
|
|
401
289
|
|
|
402
|
-
|
|
403
|
-
let
|
|
404
|
-
|
|
290
|
+
```swift
|
|
291
|
+
let journal = CKRecord(recordType: "Journal")
|
|
292
|
+
journal["title"] = "Tomato notes" as CKRecordValue // plain, can be queried
|
|
293
|
+
journal.encryptedValues["body"] = "Blight on bed 3" as NSString // encrypted
|
|
294
|
+
let body = fetched.encryptedValues["body"] as? String
|
|
405
295
|
```
|
|
406
296
|
|
|
407
297
|
## CloudKit Dashboard
|
|
408
298
|
|
|
409
|
-
|
|
410
|
-
Key capabilities:
|
|
299
|
+
Open it at `icloud.developer.apple.com` (the `/dashboard` path).
|
|
411
300
|
|
|
412
|
-
- **Schema**:
|
|
413
|
-
|
|
414
|
-
- **
|
|
415
|
-
- **
|
|
416
|
-
- **
|
|
417
|
-
- **
|
|
418
|
-
- **
|
|
419
|
-
only works with Development.
|
|
420
|
-
- **Deploy to Production**: migrate schema changes from dev to prod. Production
|
|
421
|
-
does not allow adding new record types or fields programmatically.
|
|
301
|
+
- **Schema**: record types, fields and indexes. In production, add indexes only for fields you actually query.
|
|
302
|
+
- **Records**: browse and edit data.
|
|
303
|
+
- **Subscriptions**: see what is registered.
|
|
304
|
+
- **Logs**: API calls, errors, latency.
|
|
305
|
+
- **Telemetry**: how many requests, how often they fail, and latency percentiles.
|
|
306
|
+
- **Environment switch**: Development or Production. The Simulator only talks to Development.
|
|
307
|
+
- **Deploy to Production** copies the schema. Production refuses to create record types or fields from code.
|
|
422
308
|
|
|
423
|
-
##
|
|
309
|
+
## Discovering changed zones
|
|
424
310
|
|
|
425
|
-
|
|
426
|
-
do not know
|
|
311
|
+
`CKFetchDatabaseChangesOperation` reports the zones whose contents moved since a database token.
|
|
312
|
+
It is how you find zones in the shared database when you do not know their IDs.
|
|
427
313
|
|
|
428
314
|
```swift
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
previousServerChangeToken:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
operation.fetchDatabaseChangesResultBlock = { result in
|
|
448
|
-
switch result {
|
|
449
|
-
case .success(let (token, _)):
|
|
450
|
-
dbChangeToken = token
|
|
451
|
-
saveDatabaseChangeToken(token)
|
|
452
|
-
// Now fetch zone changes for changedZoneIDs
|
|
453
|
-
case .failure(let error):
|
|
454
|
-
if let ckError = error as? CKError,
|
|
455
|
-
ckError.code == .changeTokenExpired {
|
|
456
|
-
dbChangeToken = nil
|
|
457
|
-
// Refetch from scratch
|
|
315
|
+
func changedZones(in db: CKDatabase, since token: CKServerChangeToken?,
|
|
316
|
+
store: @escaping @Sendable (CKServerChangeToken?) -> Void,
|
|
317
|
+
then fetchZones: @escaping @Sendable ([CKRecordZone.ID]) -> Void) {
|
|
318
|
+
let op = CKFetchDatabaseChangesOperation(previousServerChangeToken: token)
|
|
319
|
+
op.fetchAllChanges = true
|
|
320
|
+
let collected = ZoneCollector()
|
|
321
|
+
op.recordZoneWithIDChangedBlock = { collected.add($0) }
|
|
322
|
+
op.recordZoneWithIDWasDeletedBlock = { LocalStore.dropZone($0) }
|
|
323
|
+
op.fetchDatabaseChangesResultBlock = { result in
|
|
324
|
+
switch result {
|
|
325
|
+
case .success(let outcome):
|
|
326
|
+
store(outcome.serverChangeToken)
|
|
327
|
+
fetchZones(collected.all) // then run the zone-changes operation
|
|
328
|
+
case .failure(let error as CKError) where error.code == .changeTokenExpired:
|
|
329
|
+
store(nil)
|
|
330
|
+
case .failure:
|
|
331
|
+
break
|
|
458
332
|
}
|
|
459
333
|
}
|
|
334
|
+
db.add(op)
|
|
460
335
|
}
|
|
461
|
-
|
|
462
|
-
operation.qualityOfService = .utility
|
|
463
|
-
sharedDB.add(operation)
|
|
464
336
|
```
|