CloudKit and CoreData synchronization

Hello

I am developing an app with SwiftUI using CoreData and iCloudKit to sync data between platforms. The problem is that the iCloud background update is not being triggered when staying in the application. If I make changes on both systems, the changes are being pushed, however not visible on the other device. I need to reload the app, close the app and open again.

I already enabled iCloud capability, background notifications and push notifications.

This is my persistentContainer

var persistentContainer: NSPersistentCloudKitContainer = {
    
    let container = NSPersistentCloudKitContainer(name: "Test7")
    container.loadPersistentStores(completionHandler: {(StoreDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    
    container.viewContext.automaticallyMergesChangesFromParent = true
    container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
    
    return container
}()

func saveContext() {
    
    let context = persistentContainer.viewContext
    
    if context.hasChanges{
        do {
            try context.save()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}

This is my model

class ItemsModel: ObservableObject {
    
    init() {
        
        readData()
    }
    
    @Published var dataInputs: [Item] = []
    
    let context = persistentContainer.viewContext
    
    func readData(){
        let request: NSFetchRequest<Item> = Item.fetchRequest()
        
        do {
            let results = try context.fetch(request)
            self.dataInputs = results
        } catch  {
            print(error.localizedDescription)
        }
    }
    
    func addItem(todo: String, date: Date){
        
        let entity =  NSEntityDescription.insertNewObject(forEntityName: "Item", into: context) as! Item
        entity.todo = todo
        entity.date = date
        
        do {
            try context.save()
            
            self.dataInputs.append(entity)
            
        } catch  {
            print(error.localizedDescription)
        }
    }
    
    func deleteItems(indexSet: IndexSet){
        
        for index in indexSet{
            
            do {
                let obj = dataInputs[index]
                
                context.delete(obj)
                
                try context.save()
                
                let index = dataInputs.firstIndex(of: obj)
                
                dataInputs.remove(at: index!)
                
            } catch  {
                print(error.localizedDescription)
            }
        }
    }
}

and this is my view

struct ContentView: View {
    @EnvironmentObject var items: ItemsModel
    
    var body: some View {
        NavigationView{
            List {
                ForEach(items.dataInputs) { item in
                    Text("Item at \(item.date!)")
                }
                .onDelete(perform: items.deleteItems)
            }
            .toolbar {
                Button {
                    items.addItem(todo: "Hello", date: Date())
                } label: {
                    Image(systemName: "plus")
                }

            }
        }
    }
}

Thank you

Answered by deeje in 679393022

In CoreData, NSFetchRequest is used to perform a single fetch, whereas NSFetchedResultsController provides a continuous view of data matching your predicates. Furthermore, for SwiftUI, you should look at the property wrapper @FetchRequest, which in fact implements a NSFetchResultsController, and can be used instead of your ItemsModel class. Hope this helps.

hi,

it may be as simple as not turning on history tracking. in my code, i usually have these lines right after creating the NSPersistentCloudKitContainer:

		// (1) Enable history tracking.  this seems to be important when you have more than one persistent
		// store in your app (e.g., when using the cloud) and you want to do any sort of cross-store
		// syncing.  See WWDC 2019 Session 209, "Making Apps with Core Data."
		guard let persistentStoreDescription = container.persistentStoreDescriptions.first else {
			fatalError("\(#function): Failed to retrieve a persistent store description.")
		}
		persistentStoreDescription
			.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
		persistentStoreDescription
			.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)

hope that helps, DMG

I don't think it's working. It still syncs the data across devices just if I close and reopen the app. I had also tried using a timer, to call the readData function every second, but it creates several bugs in the app.

Accepted Answer

In CoreData, NSFetchRequest is used to perform a single fetch, whereas NSFetchedResultsController provides a continuous view of data matching your predicates. Furthermore, for SwiftUI, you should look at the property wrapper @FetchRequest, which in fact implements a NSFetchResultsController, and can be used instead of your ItemsModel class. Hope this helps.

CloudKit and CoreData synchronization
 
 
Q