En mi entrada anterior agregué Core Data a mi App MultiPlaforma ahora veamos si podemos agregar CloudKit para que se sincronice entre nuestros diferentes dispositivos.
Paso 1
En primer lugar tenemos que añadir CloudKit a nuestro proyecto
Para eso nos dirigimos al archivo de nuestro proyecto donde podemos ver nuestros targets
Seleccionamos nuestro target y luego singning & Capabilities

Ahora seleccionamos el botón + capability y elegimos iCloud

Una vez añadido seleccionamos la casilla de CloudKit y en Containers elegimos alguno que ya tengamos o creamos uno nuevo.

Lo mismo para iOS y nuestros demás targets (Exceptuando los test )
También es necesario que en iOS añadamos BackGround Modes

y marcamos Remote Notifications
Paso 2
Así que ahora en nuestra clase PersistentCloudKitContainer.swift vamos cambiar nuestro container

let container = NSPersistentContainer(name: "ruleOfThree")
Por este que funciona con CloudKit
let container = NSPersistentCloudKitContainer(name: "ruleOfThree")
y lo mismo en el valor que retorna
...
public static var persistentContainer: NSPersistentContainer = {
...
por este otro
...
public static var persistentContainer: NSPersistentCloudKitContainer = {
...
De forma que nuestra clase queda de la siguiente forma
import CoreData
public class PersistentCloudKitContainer {
// MARK: - Define Constants / Variables
public static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
// MARK: - Initializer
private init() {}
// MARK: - Core Data stack
public static var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "ruleOfThree")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No descriptions found")
}
description.setOption(true as NSObject, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
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 = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
// MARK: - Core Data Saving support
public static 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)")
}
}
}
}
Paso 3
Ahora el siguiente paso es dirigirnos a nuestro modelo de datos y marcar bajo Configurations > Default y en la vista del inspector marcamos used with CloudKit

Con eso ya podemos correr nuestros simuladores y probar sí están sincronizando. Para esto es necesario loggearnos en nuestros dos simuladores

Paso 4 (Extra)
Bien en este punto CloudKit y la sincronización ya funciona de manera correcta pero quiero añadir algunos ajustes extras para optimizar mis registros, evitar duplicados y poder reordenar los elementos.
así que regresamos a nuestra clase y añadimos el siguiente bloque de código
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No descriptions found")
}
description.setOption(true as NSObject, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
Con este código podemos recibir notificaciones cuando algo cambie en nuestro modelo de datos
NotificationCenter.default.addObserver(self, selector: #selector(self.processUpdate), name: .NSPersistentStoreRemoteChange, object: nil)
Ahora gracias a la recomendación de un profesional en twitter encontré una mejor forma de implementar la sincronización
Bien esta nueva implementación tiene muchas mejoras la principal es que ahora utilizamos @StateObject para manejar nuestro modelo de datos
Para eso es necesario hacer que nuestra clase se ajuste a ObservableObject
...
public class PersistentCloudKitContainer: ObservableObject {
...
Así que borramos
...
// MARK: - Define Constants / Variables
public static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
// MARK: - Initializer
private init() {}
...
y ahora nuestra clase queda de esta forma
import CoreData
public class PersistentCloudKitContainer: ObservableObject {
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "ruleOfThree")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No descriptions found")
}
description.setOption(true as NSObject, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
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 = NSMergeByPropertyObjectTrumpMergePolicy
NotificationCenter.default.addObserver(self , selector: #selector(processUpdate), name: .NSPersistentStoreRemoteChange, object: nil)
return container
}()
}
Ahora lo que sigue es modificar en nuestro @main
import SwiftUI
import CoreData
@main
struct ruleOfThreeApp: App {
@StateObject var coreData = PersistentCloudKitContainer()
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, coreData.persistentContainer.viewContext)
}
}
}
y con eso podemos agregar funciones extras a nuestra clase por ejemplo en mi caso para poder reorder mis listas.
Referencias
En este caso base mi código en el siguiente tutorial