$npx -y skills add dpearson2699/swift-ios-skills --skill cloudkitImplement, review, or improve CloudKit and iCloud sync in iOS/macOS apps. Use when working with CKContainer, CKRecord, CKQuery, CKSubscription, CKSyncEngine, CKShare, NSUbiquitousKeyValueStore, or iCloud Drive file coordination; when syncing SwiftData models via ModelConfiguratio
| 1 | # CloudKit |
| 2 | |
| 3 | Sync data across devices using CloudKit, iCloud key-value storage, and iCloud |
| 4 | Drive. Covers container setup, record CRUD, queries, subscriptions, CKSyncEngine, |
| 5 | SwiftData integration, conflict resolution, and error handling. |
| 6 | |
| 7 | ## Contents |
| 8 | |
| 9 | - [Container and Database Setup](#container-and-database-setup) |
| 10 | - [Workflow](#workflow) |
| 11 | - [CKRecord CRUD](#ckrecord-crud) |
| 12 | - [CKQuery](#ckquery) |
| 13 | - [CKSubscription](#cksubscription) |
| 14 | - [CKSyncEngine (iOS 17+)](#cksyncengine-ios-17) |
| 15 | - [SwiftData + CloudKit](#swiftdata--cloudkit) |
| 16 | - [NSUbiquitousKeyValueStore](#nsubiquitouskeyvaluestore) |
| 17 | - [iCloud Drive File Sync](#icloud-drive-file-sync) |
| 18 | - [Account Status and Error Handling](#account-status-and-error-handling) |
| 19 | - [Conflict Resolution](#conflict-resolution) |
| 20 | - [Common Mistakes](#common-mistakes) |
| 21 | - [Review Checklist](#review-checklist) |
| 22 | - [References](#references) |
| 23 | |
| 24 | ## Workflow |
| 25 | |
| 26 | 1. Choose the database scope and sync owner; verify capability, container, account status, schema, and environment before writing records. |
| 27 | 2. Make a local change durable, enqueue it, then let subscriptions or `CKSyncEngine` drive remote work rather than polling. |
| 28 | 3. Persist change tokens or sync-engine state after successful application. |
| 29 | 4. Test offline edits, partial failure, rate limiting, token expiry, conflict, account loss, zone deletion, and relaunch. |
| 30 | 5. On failure, classify the `CKError`, restore the affected fixture or queue item, apply the documented retry/reset/merge action, and rerun the same scenario. Never restart a full sync blindly after partial success. |
| 31 | |
| 32 | Load [references/cloudkit-patterns.md](references/cloudkit-patterns.md) for incremental zone changes, shares, assets, batch operations, and Dashboard procedures. |
| 33 | |
| 34 | ## Container and Database Setup |
| 35 | |
| 36 | Enable iCloud + CloudKit in Signing & Capabilities. A container provides three databases: |
| 37 | |
| 38 | | Database | Scope | Requires iCloud | Storage Quota | |
| 39 | |----------|-------|-----------------|---------------| |
| 40 | | Public | All users | Read: No, Write: Yes | App quota | |
| 41 | | Private | Current user | Yes | User quota | |
| 42 | | Shared | Shared records | Yes | Owner quota | |
| 43 | |
| 44 | ```swift |
| 45 | import CloudKit |
| 46 | |
| 47 | let container = CKContainer.default() |
| 48 | // Or named: CKContainer(identifier: "iCloud.com.example.app") |
| 49 | |
| 50 | let publicDB = container.publicCloudDatabase |
| 51 | let privateDB = container.privateCloudDatabase |
| 52 | let sharedDB = container.sharedCloudDatabase |
| 53 | ``` |
| 54 | |
| 55 | ## CKRecord CRUD |
| 56 | |
| 57 | Records are key-value pairs. Max 1 MB per record (excluding CKAsset data). |
| 58 | |
| 59 | ```swift |
| 60 | // CREATE |
| 61 | let record = CKRecord(recordType: "Note") |
| 62 | record["title"] = "Meeting Notes" as CKRecordValue |
| 63 | record["body"] = "Discussed Q3 roadmap" as CKRecordValue |
| 64 | record["createdAt"] = Date() as CKRecordValue |
| 65 | record["tags"] = ["work", "planning"] as CKRecordValue |
| 66 | let saved = try await privateDB.save(record) |
| 67 | |
| 68 | // FETCH by ID |
| 69 | let recordID = CKRecord.ID(recordName: "unique-id-123") |
| 70 | let fetched = try await privateDB.record(for: recordID) |
| 71 | |
| 72 | // UPDATE -- fetch first, modify, then save |
| 73 | fetched["title"] = "Updated Title" as CKRecordValue |
| 74 | let updated = try await privateDB.save(fetched) |
| 75 | |
| 76 | // DELETE |
| 77 | try await privateDB.deleteRecord(withID: recordID) |
| 78 | ``` |
| 79 | |
| 80 | ### Custom Record Zones |
| 81 | |
| 82 | Apps create custom zones in the private database. Shared databases expose zones |
| 83 | that other users share with the current user. Custom zones support atomic |
| 84 | commits, change tracking, and sharing; public databases do not support custom |
| 85 | zones. |
| 86 | |
| 87 | ```swift |
| 88 | let zoneID = CKRecordZone.ID(zoneName: "NotesZone") |
| 89 | let zone = CKRecordZone(zoneID: zoneID) |
| 90 | try await privateDB.save(zone) |
| 91 | |
| 92 | let recordID = CKRecord.ID(recordName: UUID().uuidString, zoneID: zoneID) |
| 93 | let record = CKRecord(recordType: "Note", recordID: recordID) |
| 94 | ``` |
| 95 | |
| 96 | ## CKQuery |
| 97 | |
| 98 | Query records with NSPredicate. Supported: `==`, `!=`, `<`, `>`, `<=`, `>=`, |
| 99 | `BEGINSWITH`, `CONTAINS`, `IN`, `AND`, `NOT`, `BETWEEN`, |
| 100 | `distanceToLocation:fromLocation:`. |
| 101 | |
| 102 | `CONTAINS` tests list membership except for tokenized full-text search with |
| 103 | `self CONTAINS`. `BEGINSWITH` is the string-prefix operator; unsupported |
| 104 | operators, key paths, or field types fail when the query executes. |
| 105 | For every encryption review, explicitly call out field eligibility: encrypted |
| 106 | values cannot be queried or sorted; `CKAsset` is encrypted by default; and |
| 107 | `CKRecord.Reference` cannot be encrypted because CloudKit needs it server-side. |
| 108 | |
| 109 | ```swift |
| 110 | let predicate = NSPredicate(format: "title BEGINSWITH %@", "Meeting") |
| 111 | let query = CKQuery(recordType: "Note", predicate: predicate) |
| 112 | query.sortDescriptors = [NSSortDescriptor(key: "cre |