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:
@@ -6,6 +6,7 @@
|
|||||||
// for plant identification iOS app.
|
// for plant identification iOS app.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import CloudKit
|
||||||
import CoreData
|
import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ final class CoreDataStack: CoreDataStackProtocol, @unchecked Sendable {
|
|||||||
// - The coreDataQueue serializes any direct container operations
|
// - The coreDataQueue serializes any direct container operations
|
||||||
|
|
||||||
/// The persistent container managing the Core Data stack
|
/// The persistent container managing the Core Data stack
|
||||||
private let persistentContainer: NSPersistentContainer
|
private let persistentContainer: NSPersistentCloudKitContainer
|
||||||
|
|
||||||
/// Serial queue for thread-safe operations
|
/// Serial queue for thread-safe operations
|
||||||
private let coreDataQueue = DispatchQueue(label: "com.plantguide.coredata", qos: .userInitiated)
|
private let coreDataQueue = DispatchQueue(label: "com.plantguide.coredata", qos: .userInitiated)
|
||||||
@@ -142,8 +143,8 @@ final class CoreDataStack: CoreDataStackProtocol, @unchecked Sendable {
|
|||||||
private static func createPersistentContainer(
|
private static func createPersistentContainer(
|
||||||
modelName: String,
|
modelName: String,
|
||||||
migrationConfig: MigrationConfiguration
|
migrationConfig: MigrationConfiguration
|
||||||
) -> NSPersistentContainer {
|
) -> NSPersistentCloudKitContainer {
|
||||||
let container = NSPersistentContainer(name: modelName)
|
let container = NSPersistentCloudKitContainer(name: modelName)
|
||||||
|
|
||||||
// Configure store description with migration options
|
// Configure store description with migration options
|
||||||
let storeDescription = container.persistentStoreDescriptions.first
|
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: NSPersistentHistoryTrackingKey)
|
||||||
storeDescription?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
|
storeDescription?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
|
||||||
|
|
||||||
|
// Configure CloudKit container
|
||||||
|
storeDescription?.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
|
||||||
|
containerIdentifier: "iCloud.com.t-t.PlantGuide"
|
||||||
|
)
|
||||||
|
|
||||||
container.loadPersistentStores { storeDescription, error in
|
container.loadPersistentStores { storeDescription, error in
|
||||||
if let error = error as NSError? {
|
if let error = error as NSError? {
|
||||||
// Log the error - in production, consider recovery strategies
|
// Log the error - in production, consider recovery strategies
|
||||||
|
|||||||
@@ -19,11 +19,6 @@
|
|||||||
<relationship name="careSchedule" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CareScheduleMO" inverseName="plant" inverseEntity="CareScheduleMO"/>
|
<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="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"/>
|
<relationship name="plantCareInfo" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="PlantCareInfoMO" inverseName="plant" inverseEntity="PlantCareInfoMO"/>
|
||||||
<uniquenessConstraints>
|
|
||||||
<uniquenessConstraint>
|
|
||||||
<constraint value="id"/>
|
|
||||||
</uniquenessConstraint>
|
|
||||||
</uniquenessConstraints>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="IdentificationMO" representedClassName="IdentificationMO" syncable="YES">
|
<entity name="IdentificationMO" representedClassName="IdentificationMO" syncable="YES">
|
||||||
<attribute name="confidenceScore" attributeType="Double" defaultValueString="0.0" usesScalarType="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="longitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarType="NO"/>
|
||||||
<attribute name="source" attributeType="String"/>
|
<attribute name="source" attributeType="String"/>
|
||||||
<relationship name="plant" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PlantMO" inverseName="identifications" inverseEntity="PlantMO"/>
|
<relationship name="plant" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PlantMO" inverseName="identifications" inverseEntity="PlantMO"/>
|
||||||
<uniquenessConstraints>
|
|
||||||
<uniquenessConstraint>
|
|
||||||
<constraint value="id"/>
|
|
||||||
</uniquenessConstraint>
|
|
||||||
</uniquenessConstraints>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="CareScheduleMO" representedClassName="CareScheduleMO" syncable="YES">
|
<entity name="CareScheduleMO" representedClassName="CareScheduleMO" syncable="YES">
|
||||||
<attribute name="fertilizerSchedule" attributeType="String"/>
|
<attribute name="fertilizerSchedule" attributeType="String"/>
|
||||||
@@ -50,11 +40,6 @@
|
|||||||
<attribute name="wateringSchedule" attributeType="String"/>
|
<attribute name="wateringSchedule" attributeType="String"/>
|
||||||
<relationship name="plant" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PlantMO" inverseName="careSchedule" inverseEntity="PlantMO"/>
|
<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"/>
|
<relationship name="tasks" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="CareTaskMO" inverseName="careSchedule" inverseEntity="CareTaskMO"/>
|
||||||
<uniquenessConstraints>
|
|
||||||
<uniquenessConstraint>
|
|
||||||
<constraint value="id"/>
|
|
||||||
</uniquenessConstraint>
|
|
||||||
</uniquenessConstraints>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="CareTaskMO" representedClassName="CareTaskMO" syncable="YES">
|
<entity name="CareTaskMO" representedClassName="CareTaskMO" syncable="YES">
|
||||||
<attribute name="completedDate" optional="YES" attributeType="Date" usesScalarType="NO"/>
|
<attribute name="completedDate" optional="YES" attributeType="Date" usesScalarType="NO"/>
|
||||||
@@ -64,11 +49,6 @@
|
|||||||
<attribute name="scheduledDate" attributeType="Date" usesScalarType="NO"/>
|
<attribute name="scheduledDate" attributeType="Date" usesScalarType="NO"/>
|
||||||
<attribute name="type" attributeType="String"/>
|
<attribute name="type" attributeType="String"/>
|
||||||
<relationship name="careSchedule" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CareScheduleMO" inverseName="tasks" inverseEntity="CareScheduleMO"/>
|
<relationship name="careSchedule" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CareScheduleMO" inverseName="tasks" inverseEntity="CareScheduleMO"/>
|
||||||
<uniquenessConstraints>
|
|
||||||
<uniquenessConstraint>
|
|
||||||
<constraint value="id"/>
|
|
||||||
</uniquenessConstraint>
|
|
||||||
</uniquenessConstraints>
|
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="PlantCareInfoMO" representedClassName="PlantCareInfoMO" syncable="YES">
|
<entity name="PlantCareInfoMO" representedClassName="PlantCareInfoMO" syncable="YES">
|
||||||
<attribute name="additionalNotes" optional="YES" attributeType="String"/>
|
<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="trefleID" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarType="YES"/>
|
||||||
<attribute name="wateringScheduleData" attributeType="Binary"/>
|
<attribute name="wateringScheduleData" attributeType="Binary"/>
|
||||||
<relationship name="plant" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PlantMO" inverseName="plantCareInfo" inverseEntity="PlantMO"/>
|
<relationship name="plant" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PlantMO" inverseName="plantCareInfo" inverseEntity="PlantMO"/>
|
||||||
<uniquenessConstraints>
|
|
||||||
<uniquenessConstraint>
|
|
||||||
<constraint value="id"/>
|
|
||||||
</uniquenessConstraint>
|
|
||||||
</uniquenessConstraints>
|
|
||||||
</entity>
|
</entity>
|
||||||
</model>
|
</model>
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
<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>
|
<key>com.apple.developer.icloud-services</key>
|
||||||
<array>
|
<array>
|
||||||
<string>CloudKit</string>
|
<string>CloudKit</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user