Add CloudKit sync for cross-device data persistence

- Switch CoreDataStack from NSPersistentContainer to NSPersistentCloudKitContainer
- Configure CloudKit container: iCloud.com.t-t.PlantGuide
- Remove uniqueness constraints from all Core Data entities (CloudKit incompatible)
- Add CloudKit container identifier to entitlements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-23 14:21:52 -06:00
parent be0d298d9f
commit d125216a95
3 changed files with 12 additions and 29 deletions

View File

@@ -6,6 +6,7 @@
// for plant identification iOS app.
//
import CloudKit
import CoreData
import Foundation
@@ -105,7 +106,7 @@ final class CoreDataStack: CoreDataStackProtocol, @unchecked Sendable {
// - The coreDataQueue serializes any direct container operations
/// The persistent container managing the Core Data stack
private let persistentContainer: NSPersistentContainer
private let persistentContainer: NSPersistentCloudKitContainer
/// Serial queue for thread-safe operations
private let coreDataQueue = DispatchQueue(label: "com.plantguide.coredata", qos: .userInitiated)
@@ -142,8 +143,8 @@ final class CoreDataStack: CoreDataStackProtocol, @unchecked Sendable {
private static func createPersistentContainer(
modelName: String,
migrationConfig: MigrationConfiguration
) -> NSPersistentContainer {
let container = NSPersistentContainer(name: modelName)
) -> NSPersistentCloudKitContainer {
let container = NSPersistentCloudKitContainer(name: modelName)
// Configure store description with migration options
let storeDescription = container.persistentStoreDescriptions.first
@@ -154,6 +155,11 @@ final class CoreDataStack: CoreDataStackProtocol, @unchecked Sendable {
storeDescription?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
storeDescription?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
// Configure CloudKit container
storeDescription?.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
containerIdentifier: "iCloud.com.t-t.PlantGuide"
)
container.loadPersistentStores { storeDescription, error in
if let error = error as NSError? {
// Log the error - in production, consider recovery strategies

View File

@@ -19,11 +19,6 @@
<relationship name="careSchedule" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CareScheduleMO" inverseName="plant" inverseEntity="CareScheduleMO"/>
<relationship name="identifications" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="IdentificationMO" inverseName="plant" inverseEntity="IdentificationMO"/>
<relationship name="plantCareInfo" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="PlantCareInfoMO" inverseName="plant" inverseEntity="PlantCareInfoMO"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="IdentificationMO" representedClassName="IdentificationMO" syncable="YES">
<attribute name="confidenceScore" attributeType="Double" defaultValueString="0.0" usesScalarType="YES"/>
@@ -34,11 +29,6 @@
<attribute name="longitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarType="NO"/>
<attribute name="source" attributeType="String"/>
<relationship name="plant" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PlantMO" inverseName="identifications" inverseEntity="PlantMO"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="CareScheduleMO" representedClassName="CareScheduleMO" syncable="YES">
<attribute name="fertilizerSchedule" attributeType="String"/>
@@ -50,11 +40,6 @@
<attribute name="wateringSchedule" attributeType="String"/>
<relationship name="plant" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PlantMO" inverseName="careSchedule" inverseEntity="PlantMO"/>
<relationship name="tasks" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="CareTaskMO" inverseName="careSchedule" inverseEntity="CareTaskMO"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="CareTaskMO" representedClassName="CareTaskMO" syncable="YES">
<attribute name="completedDate" optional="YES" attributeType="Date" usesScalarType="NO"/>
@@ -64,11 +49,6 @@
<attribute name="scheduledDate" attributeType="Date" usesScalarType="NO"/>
<attribute name="type" attributeType="String"/>
<relationship name="careSchedule" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CareScheduleMO" inverseName="tasks" inverseEntity="CareScheduleMO"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PlantCareInfoMO" representedClassName="PlantCareInfoMO" syncable="YES">
<attribute name="additionalNotes" optional="YES" attributeType="String"/>
@@ -86,10 +66,5 @@
<attribute name="trefleID" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarType="YES"/>
<attribute name="wateringScheduleData" attributeType="Binary"/>
<relationship name="plant" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PlantMO" inverseName="plantCareInfo" inverseEntity="PlantMO"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
</model>

View File

@@ -5,7 +5,9 @@
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<array>
<string>iCloud.com.t-t.PlantGuide</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>