The following code how I'm Syncing Core Data with CloudKit in SwiftUI. I have a CoreDataManager
class to setup Core Data
, a CarViewModel
class to manage all Core Data transactions, a SettingsView
to turn ON and OFF CloudKit sync and a CarsView
to show all cars and to listen for changes from CloudKit.
The issue I have is that syncing between devices is not consistent, it starts syncing in device 1 and then in the second device I only see partials of the data synced in device 1, obviously, I'm doing something wrong but I cannot figure out what it is.
Can someone please try to identify the possible cause for the syncing inconsistencies I'm seeing? Any suggestion would be greatly appreciated it.
Core Data Manager Class
import SwiftUI
import CoreData
class CoreDataManager{
static let instance = CoreDataManager()
@AppStorage(UserDefaults.Keys.iCloudSyncKey) private var iCloudSync = false
lazy var context: NSManagedObjectContext = {
return container.viewContext
}()
lazy var container: NSPersistentContainer = {
return setupContainer()
}()
init(inMemory: Bool = false){
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
}
func setupContainer()->NSPersistentContainer{
let container = NSPersistentCloudKitContainer(name: "CoreDataContainer")
guard let description = container.persistentStoreDescriptions.first else{
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
if iCloudSync{
print("Turning iCloud Sync ON... ")
let cloudKitContainerIdentifier = "iCloud.com.myDomain.myApp"
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: cloudKitContainerIdentifier)
description.cloudKitContainerOptions = options
}else{
print("Turning iCloud Sync OFF... ")
description.cloudKitContainerOptions = nil
}
container.loadPersistentStores { (description, error) in
if let error = error{
print("Error loading Core Data. \(error)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}
func save(){
do{
try context.save()
}catch let error{
print("Error saving Core Data. \(error.localizedDescription)")
}
}
}
Car View Model Class
This is the class that handles all of the Core Data transactions.
import Foundation
import CoreData
class CarViewModel: ObservableObject{
let manager: CoreDataManager
@Published var cars: [Car] = []
init(coreDataManager: CoreDataManager = .instance){
self.manager = coreDataManager
loadCars()
}
func updateCloudKitContainer(){
manager.container = manager.setupContainer()
}
// Adds, Deletes, Updates etc.
func addCar(name:String){
save()
loadCars()
}
func loadCars(){}
func save(){
self.manager.save()
}
}
Settings View:
Here is where the user can start or stop CloudKit by calling carViewModel.updateCloudKitContainer()
struct SettingsView: View {
var body: some View {
VStack{
Toggle(isOn: $iCloudSync){
Text(" iCloud Sync")
}
.onChange(of: iCloudSync){ isSwitchOn in
if isSwitchOn{
if FileManager.default.ubiquityIdentityToken != nil {
print("iCloud is available!")
iCloudSync = true
carViewModel.updateCloudKitContainer()
} else {
print("iCloud is NOT available")
iCloudSync = false
carViewModel.updateCloudKitContainer()
}
}else{
iCloudSync = false // turn Off iCloud Sync
carViewModel.updateCloudKitContainer()
}
}
}
}
}
SwiftUI View
This is the view that shows all cars and listens for changes in CloudKit
struct CarsView: View {
@ObservedObject var carViewModel: CarViewModel
var didRemoteChange = NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange).receive(on: RunLoop.main)
var body: some View {
VStack{
List {
}
}
.onReceive(self.didRemoteChange){ _ in
carViewModel.loadCars()
}
}
}