Let's say we have a working NSPersistentCloudKitContainer and just one CoreData entity named Item. Let's assume we want to sync between iOS, iPad and Mac app.
I watched this WWDC session about syncing CoreData and CloudKit and implemented basic sync in my SwiftUI app. It works and data comes to all 3 devices and syncs automatically.
But on a Mac the data does not appear without reloading the view (as well as in the simulators (because they don't receive push-notifications). It comes, but there is no automatic refresh. I've also tested simulator to a real device and sometimes it syncs automatically, sometimes I need to reopen the app to see the changes.
I'm curious is there a force fetch method that can be used along with NSPersistentCloudKitContainer. Or maybe anyone knows a workaround to Refetch data manually when using NSPersistentCloudKitContainer with SwiftUI?
I also want to show an activity indicator when the new data starts to appear, but not sure where to find this starting getting fetches point in code?
I've used this UIKit sample code provided by Apple and adapted it to work in SwiftUI. I also read all the documentation here.
Here is the persistentContainer code I use:
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "AppName")
container.persistentStoreDescriptions.first?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
If you see, I didn't include container.viewContext.transactionAuthor = appTransactionAuthorName
and setQueryGenerationFrom(.current)
and NotificationCenter.default.addObserver
in the above code. But I also tried with them and get the same results. Not sure If I need to use them at all, because syncing works without these calls as well.
In my Item class I've added this function to get the items:
static func getAllItems() -> NSFetchRequest<Item> {
let request: NSFetchRequest<Item> = Item.fetchRequest() as! NSFetchRequest<Item>
let sortDescriptor = NSSortDescriptor(key: "name", ascending: false)
request.sortDescriptors = [sortDescriptor]
return request
}
In my ContentView I have this MasterView view:
struct MasterView: View {
@FetchRequest(fetchRequest: Item.getAllItems()) var items: FetchedResults<Item>
@Environment(\.managedObjectContext) var viewContext
var body: some View {
List {
ForEach(items, id: \.self) { item in
NavigationLink(
destination: DetailView(item: item)
) {
Text("\(item.name ?? "")")
}
}.onDelete { indices in
self.items.delete(at: indices, from: self.viewContext)
}
}
}
}
P.S. I also noticed that sync works slowly (maybe this is only my case, not sure). I have to wait from 5 to 10 seconds between syncs with this code. Maybe anyone knows if this waiting time normal or not?
.NSPersistentStoreRemoteChange
notification and force refresh SwiftUI List, because context is usually merged silently in background queue, and UI not handle this automatically. The following topics might be helpful [How to update @FetchRequest, when a related Entity changes in SwiftUI? ](https://mcmap.net/q/274652/-how-to-update-fetchrequest-when-a-related-entity-changes-in-swiftui) and SwiftUI: List does not update automatically after deleting all Core Data Entity entries. Pay attention to redirect UI refresh from notification handler to main queue. – Neolamarckism