tkhelm
tkhelm

Reputation: 345

How to pre-load Core Data SQLite files with NSPersistentCloudKitContainer

I have a Core Data+CloudKit configuration where I need to provide the user with some initial data as a starting point. It's only a few records/objects so at first I tried hardcoding the data and writing to Core Data on first launch. Users are then able to add to and change the starting point data.

When I enabled CloudKit syncing, I discovered that when the second device on the same account was launched, the newly written default data for that device was 'newer' from CloudKit's point of view. That meant that the default data on the second device would sync and overwrite the user's own data on the first device. That's bad.

I believe the right solution is to provide pre-populated Core Data file (.sqlite) files in the app, so that on first launch the default data is already older than anything the user would have created on other devices. Most of the tutorials and sample code for doing this are very old, referencing Swift 3, Xcode 7, etc. I'm looking for a solution that works when using NSPersistentCloudKitContainer.

I tried the code in the answer to this SO question, changing the type for persistentContainer to NSPersistentCloudKitContainer. This compiles and runs, but the result is that my app launches with no pre-loaded data, and I'm not sure why. I have included the three .sqlite files in my Xcode project, and added them to the 'Copy Bundle Resources' list in 'Build Phases'.

Here is my code:

lazy var persistentContainer: NSPersistentCloudKitContainer = {

    let appName = Bundle.main.infoDictionary!["CFBundleName"] as! String
    let container = NSPersistentCloudKitContainer(name: appName)
    
    //Pre-load the default Core Data (Category names) on first launch
    var persistentStoreDescriptions: NSPersistentStoreDescription

    let storeUrl = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first!.appendingPathComponent(appName + ".sqlite")
    let storeUrlFolder = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first!

    if !FileManager.default.fileExists(atPath: (storeUrl.path)) {
        let seededDataUrl = Bundle.main.url(forResource: appName, withExtension: "sqlite")
        let seededDataUrl2 = Bundle.main.url(forResource: appName, withExtension: "sqlite-shm")
        let seededDataUrl3 = Bundle.main.url(forResource: appName, withExtension: "sqlite-wal")

        try! FileManager.default.copyItem(at: seededDataUrl!, to: storeUrl)
        try! FileManager.default.copyItem(at: seededDataUrl2!, to: storeUrlFolder.appendingPathComponent(appName + ".sqlite-shm"))
        try! FileManager.default.copyItem(at: seededDataUrl3!, to: storeUrlFolder.appendingPathComponent(appName + ".sqlite-wal"))
    }
    
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
         
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

Upvotes: 2

Views: 348

Answers (1)

Ch Ryder
Ch Ryder

Reputation: 597

I believe you are not seeing any data because your code looks into the .documentDirectory, and PersistentCloudKitContainer looks for databases in the Application Support directory by default.

Upvotes: 1

Related Questions