# SwiftData to CloudKit Sync Requirements (Reusable) Primary source of truth lives in Bedrock: `/Users/mattbruce/Documents/Projects/iPhone/Andromida/Bedrock/Sources/Bedrock/Storage/SWIFTDATA_CLOUDKIT_SETUP_GUIDE.md` This Andromida copy is app-facing reference material and should stay aligned with the Bedrock guide. Use this checklist for any iOS app that uses SwiftData with CloudKit and requires near-real-time multi-device sync. ## 1) Capabilities and Entitlements - Enable `iCloud` with `CloudKit` for the app target. - Enable `Push Notifications` for the app target. - Enable `Background Modes > Remote notifications`. - Add App Group if app + widget share local SQLite. Required entitlement keys: - `com.apple.developer.icloud-container-identifiers` - `com.apple.developer.icloud-services` including `CloudKit` - `com.apple.developer.ubiquity-kvstore-identifier` (if KVS is used) - `aps-environment` (must resolve per config) - `com.apple.security.application-groups` (if widget/app group storage is used) ## 2) Build Configuration Use xcconfig variables so environments are explicit and portable: - Debug: `APS_ENVIRONMENT = development` - Release: `APS_ENVIRONMENT = production` Entitlements should reference variables, not hard-coded values, where possible. ## 3) Model and Schema Constraints (SwiftData + CloudKit) - Avoid `@Attribute(.unique)` in CloudKit-mirrored models. - Ensure all stored properties have defaults or are optional. - Keep relationships optional for CloudKit compatibility. - Use additive schema evolution only (add fields/models; do not remove/rename/change types in place). ## 4) Runtime Sync Behavior - Prefer Bedrock `SwiftDataCloudKitSyncManager` as the reusable remote observer/lifecycle component. - Prefer Bedrock `SwiftDataStore` + `SwiftDataCloudKitStore` to avoid app-specific pulse boilerplate. - Observe `.NSPersistentStoreRemoteChange` to detect remote merges. - On remote change, call Bedrock `processObservedRemoteChange(modelContext:modelContainer:)` before refetch. - Rebuild long-lived contexts only when safe (`hasChanges == false`) to avoid dropping unsaved local edits. - Implement protocol reload hook (`reloadData`) to run your store-specific fetch step. - Keep iOS-on-Mac pulsing loop in the root scene lifecycle (`active` only, cancel on `background`). - Keep a foreground fallback refresh as a safety net; gate it with Bedrock `hasReceivedRemoteChange(since:)`. - Emit structured logs for remote sync events (event count + timestamp) for debugging. ## 5) UI Freshness Requirements - UI must re-render on each remote merge, even for batched updates. - Keep an observable refresh version/counter and increment on each successful reload. - Ensure list/detail views do not rely on stale assumptions when models are updated remotely. - Make high-frequency interaction rows fully tappable to reduce missed user actions. ## 6) Verification Matrix Test all cases on two physical devices with the same Apple ID and same app flavor: 1. Single toggle on Device A appears on Device B while both apps are open. 2. Rapid batch toggles on Device A all appear on Device B without manual pull-to-refresh. 3. Device B in background receives updates after foregrounding (without force quit). 4. Airplane mode recovery syncs correctly after reconnection. 5. Simultaneous edits resolve predictably (CloudKit last-writer-wins). ## 7) Observability and Console Checks - Device logs: filter by sync logger category (for example `CloudKitSync`). - CloudKit Console: validate record updates in the app container private database. - If pushes are delivered but UI is stale, investigate context freshness and view invalidation, not transport. - If APNs is unreliable on Mac runtime, validate that pulse logs appear every interval while active. ## 8) Reuse Checklist for New Apps Before shipping any new SwiftData+CloudKit app: - [ ] Capabilities: iCloud/CloudKit + Push + Remote Notifications are enabled - [ ] Entitlements include `aps-environment` and correct container IDs - [ ] xcconfig defines `APS_ENVIRONMENT` per configuration - [ ] Remote change observer uses Bedrock `processObservedRemoteChange(...)` + reloads data - [ ] Store conforms to Bedrock `SwiftDataCloudKitStore` - [ ] Foreground fallback is gated by Bedrock `hasReceivedRemoteChange(since:)` - [ ] UI has deterministic invalidation on remote reload - [ ] Two-device batch-update test passes without manual refresh - [ ] CloudKit Console verification documented in README/PRD