I’ve been following along with a Hacking with Swift tutorial where he makes an app that syncs data across iPhone, iPad and Mac versions of his app. All the syncing is handled by Core Data and Cloud Kit.
I’ve been trying to adapt what he did to my own app which has a companion watchOS app. I’m using NSPersistentCloudKitContainer
I’ve put the iOS app and the watchOS app in the same app group and I’m loading the shared app container with the correct app group identifier (my widget can see the data just fine), but the watch app can never see the data. The watch app doesn’t crash, it just doesn’t see anything to load, I get back an empty array of entities.
Can watch apps not use this method? Am I missing some key step?
Here’s the DataController class:
import CoreData
import SwiftUI
class DataController: ObservableObject {
let container: NSPersistentCloudKitContainer
private var saveTask: Task<Void, Error>?
static var preview: DataController = {
let dataController = DataController(inMemory: true)
return dataController
}()
private var sharedStoreURL: URL {
let container = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.MyCo.MyApp1")!
return container.appendingPathComponent("Model.sqlite")
}
static let model: NSManagedObjectModel = {
guard let url = Bundle.main.url(forResource: "Model", withExtension: "momd") else {
fatalError("Failed to locate model file.")
}
guard let managedObjectModel = NSManagedObjectModel(contentsOf: url) else {
fatalError("Failed to load model file.")
}
return managedObjectModel
}()
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Model", managedObjectModel: Self.model)
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(filePath: "/dev/null")
} else {
container.persistentStoreDescriptions.first?.url = sharedStoreURL
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
container.persistentStoreDescriptions.first?.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
NotificationCenter.default.addObserver(forName: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator, queue: .main, using: remoteStoreChanged)
container.loadPersistentStores { _, error in
if let error {
fatalError("Fatal error loading store: \(error.localizedDescription)")
}
}
}
func remoteStoreChanged(_ notification: Notification) {
objectWillChange.send()
}
func save() {
saveTask?.cancel()
if container.viewContext.hasChanges {
try? container.viewContext.save()
}
}
func queueSave() {
saveTask?.cancel()
saveTask = Task { @MainActor in
try await Task.sleep(for: .seconds(3))
save()
}
}
func delete(_ object: NSManagedObject) {
objectWillChange.send()
container.viewContext.delete(object)
save()
}
private func delete(_ fetchRequest: NSFetchRequest<NSFetchRequestResult>) {
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
batchDeleteRequest.resultType = .resultTypeObjectIDs
if let delete = try? container.viewContext.execute(batchDeleteRequest) as? NSBatchDeleteResult {
let changes = [NSDeletedObjectsKey: delete.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [container.viewContext])
}
}
func getMyEntities() throws -> [MyEntity] {
let request: NSFetchRequest<MyEntity> = MyEntity.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \MyEntity.name, ascending: true)]
let entities = try container.viewContext.fetch(request)
return entities
}
}




