Here is a complete working example that updates the UI when something changes in CloudKit
using CoreData
+ CloudKit
+ MVVM
. The code related to the notifications is marked with comments, see CoreDataManager
and SwiftUI
files. Don't forget to add the proper Capabilities in Xcode, see the image below.
Persistence/Data Manager
import CoreData
import SwiftUI
class CoreDataManager{
static let instance = CoreDataManager()
let container: NSPersistentCloudKitContainer
let context: NSManagedObjectContext
init(){
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)
// Generate NOTIFICATIONS on remote changes
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores { (description, error) in
if let error = error{
print("Error loading Core Data. \(error)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context = container.viewContext
}
func save(){
do{
try context.save()
print("Saved successfully!")
}catch let error{
print("Error saving Core Data. \(error.localizedDescription)")
}
}
}
View Model
import CoreData
class CarViewModel: ObservableObject{
let manager = CoreDataManager.instance
@Published var cars: [Car] = []
init(){
getCars()
}
func addCar(model:String, make:String?){
let car = Car(context: manager.context)
car.make = make
car.model = model
save()
getCars()
}
func getCars(){
let request = NSFetchRequest<Car>(entityName: "Car")
let sort = NSSortDescriptor(keyPath: \Car.model, ascending: true)
request.sortDescriptors = [sort]
do{
cars = try manager.context.fetch(request)
}catch let error{
print("Error fetching cars. \(error.localizedDescription)")
}
}
func deleteCar(car: Car){
manager.context.delete(car)
save()
getCars()
}
func save(){
self.manager.save()
}
}
SwiftUI
import SwiftUI
import CoreData
struct ContentView: View {
@StateObject var carViewModel = CarViewModel()
@State private var makeInput:String = ""
@State private var modelInput:String = ""
// Capture NOTIFICATION changes
var didRemoteChange = NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange).receive(on: RunLoop.main)
@State private var deleteCar: Car?
var body: some View {
NavigationView {
VStack{
List {
if carViewModel.cars.isEmpty {
Text("No cars")
.foregroundColor(.gray)
.fontWeight(.light)
}
ForEach(carViewModel.cars) { car in
HStack{
Text(car.model ?? "Model")
Text(car.make ?? "Make")
.foregroundColor(Color(UIColor.systemGray2))
}
.swipeActions{
Button( role: .destructive){
carViewModel.deleteCar(car: car)
}label:{
Label("Delete", systemImage: "trash.fill")
}
}
}
}
// Do something on NOTIFICATION
.onReceive(self.didRemoteChange){ _ in
carViewModel.getCars()
}
Spacer()
Form {
TextField("Make", text:$makeInput)
TextField("Model", text:$modelInput)
}
.frame( height: 200)
Button{
saveNewCar()
makeInput = ""
modelInput = ""
}label: {
Image(systemName: "car")
Text("Add Car")
}
.padding(.bottom)
}
}
}
func saveNewCar(){
if !modelInput.isEmpty{
carViewModel.addCar(model: modelInput, make: makeInput.isEmpty ? nil : makeInput)
}
}
}
Core Data Container
ENTITIES
Car
Attributes
make String
model String
Xcode/CloudKit setup

Thanks to Didier B.
from this thread.
Deploy the Core Data Schema to CloudKit Production
Please note that there is one final step that needs to be done to make syncing work in a production app that uses CloudKit. Once you're satisfied with the Core Data models and your app is working as expected in the development environment, you need to initialize the schema in CloudKit by running the following code.
do {
try container.initializeCloudKitSchema()
} catch {
print(error)
}
Please note that you only need to run the above code when you make changes to the Data Model Container, in other words, you would need to run it before you release the app to the app store for the first time and after that you will only run it if you add or remove Entities or Attributes. Comment out the code after running it.
Please note that after deploying the schema to CloudKit, you will not be able to delete or rename entities or attributes so, make sure your app is working fine and has all of the features you want before deploying the schema to production.