UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Data Inconsistency when Syncing Core Data to CloudKit

Forums > Swift

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()
        }
    }
}

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.