I know this is a common question, maybe it is so many time asked before. Also I know how to share a single database with App Extensions using Core Data by enabling App Groups. I am now using the NSPersistentCloudKitContainer to sync the data (across iCloud devices), and works very well.
But I want to share my database with my App Extensions (Today Extension), at this time App Groups won't help anymore, because they are different technologies as Apple described. So I am simply enabled iCloud for both the container app and the app extension from Signing and Capabilities pane. Then I can access my single database from both app container/extension as shown below:
// MARK: - Core Data stack, Shared database
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "MyApp")
// Spotlight search support and history tracking
container.persistentStoreDescriptions.forEach {
$0.setOption(CoreDataSpotlightSearchDelegate.init(forStoreWith: $0, model: container.managedObjectModel), forKey: NSCoreDataCoreSpotlightExporter)
$0.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
}
// Load persistent store
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
However it is only works when the device is online (which is expected behavior), so my question is:
Is it possible to share my database between app and extension while the device is offline using NSPersistentCloudKitContainer? Some workarounds?
EDIT
This is a non-working version, I can access the same database but without cloud-syncing (Not appears on the CloudKit Dashboard):
// MARK: - Core Data stack, Shared database
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "MyApp")
// Shared container support
if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.company.MyApp")?.appendingPathComponent("MyApp.sqlite") {
let persistentStoreDescription = NSPersistentStoreDescription.init(url: url)
container.persistentStoreDescriptions = [persistentStoreDescription]
}
// Spotlight search support and history tracking
container.persistentStoreDescriptions.forEach {
$0.setOption(CoreDataSpotlightSearchDelegate.init(forStoreWith: $0, model: container.managedObjectModel), forKey: NSCoreDataCoreSpotlightExporter)
}
// Load persistent store
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
To share a database between an app and extension you need to fully implement Persistent History Tracking; it is not as simple as enabling the store option. For an introduction to the feature see WWDC 2017 What's New in Core Data at 20:49 and also see the documentation Consuming Relevant Store Changes in particular "Merge Relevant Transactions into a Context".
My theory on why you weren't noticing a problem when using the
NSPersistentCloudKitContaineris when your store was online and syncing with the cloud is you were benefiting of a side effect of the cloud sync running in your app process and editing the store upon resuming which in turn caused your context to get updated.And by the way, the
NSPersistentHistoryTrackingKeyoption is set by default forNSPersistentCloudKitContainer's store descriptions that havecloudKitContainerOptionslike the default first store description has.