CloudKit sync development for Apple platforms
CloudKit is Apple’s cloud database. For most apps, its private database is the simplest way to give users sync across their own iPhone, iPad and Mac: no sign-up screen, no backend to run and no user data on servers you manage. Apple hosts it, and the data belongs to the user’s iCloud.
It fits when:
- Your app is personal or offline-first: notes, libraries, logs, collections, settings.
- You ship on more than one Apple device and users expect their work everywhere.
- You want strong privacy without building authentication.
It is the wrong tool when you need Android clients, server-side business logic or reporting across all users. I will tell you that early.
Why sync is harder than it looks
Choosing the sync layer
There are several ways to sync, each with real trade-offs:
| Approach | Good for | Watch out for |
|---|---|---|
| NSPersistentCloudKitContainer | Existing Core Data apps | Limited control over when and how merges happen |
| SwiftData with CloudKit | New SwiftUI apps with simple models | No unique constraints; properties must be optional or have defaults |
| CKSyncEngine | Custom stores that need control | More code, you own the state machine |
| Direct CloudKit operations | Special cases, shared or public data | You handle batching, retries and change tokens |
Conflicts are a product question
When two devices change the same record, CloudKit rejects the second save with serverRecordChanged and includes the current server record. The app has to merge: last writer wins for some fields, combine for others (lists, favorites, counters), and never silently drop user work. I define these rules per field while designing the model.
The schema is forever
CloudKit has a development and a production environment. Once a schema is deployed to production, it can only grow: you can add record types and fields, but not remove or rename them. Model changes have to be planned for every version still installed on users’ devices.
Account and network reality
Users sign out of iCloud, switch accounts, run out of storage or go offline for days. The app needs to react to account changes, keep working locally, show sync state honestly and catch up later without duplicates. Large binary data belongs in assets, not in record fields.
Testing with real devices
Sync bugs appear between devices, not in unit tests. I test with several devices on the same account, including offline edits, deletions on one device while another edits, and fresh installs that pull down existing data.
How I use iCloud sync in Haptic Forge
Haptic Forge, my Core Haptics editor for iPhone, iPad and Mac, keeps a library of collections, favorites and recent work, synced through the user’s own iCloud across iPhone, iPad and Mac. There is no account and no server, and patterns stay in the user’s iCloud.
That library is shared by very different devices. On Mac and iPad it sits in a full editor with a sidebar, layer tabs and an inspector, while the iPhone both edits patterns and plays them, since only iPhones can play Core Haptics patterns. A pattern started on the Mac and felt on a paired iPhone, as described in testing haptics on iPhone from a Mac, should show up on every device the user owns. Building one data model for all three platforms is covered on my SwiftUI multiplatform page.
iCloud sync work I take on
- iCloud sync added to an existing iOS or Mac app, with a migration path for current local data.
- A sync-ready data model for a new app, designed with conflict rules from the start.
- Moving from a fragile custom sync to CloudKit, or from one sync layer to another. See app rescue and modernization.
- Diagnosis of existing sync issues: duplicates, missing records or data that never arrives on the Mac.
Apple’s CloudKit documentation covers the APIs; the hard part is the data design around them.
Plan your sync
Tell me what data your users create and on which devices, at contact@hobbysoft.pro. I will suggest the sync approach that fits and the pitfalls to plan for.