2

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
}()
tkhelm
  • 345
  • 3
  • 14
  • I have made some progress and learned that it's very easy for the .sqlite file(s) to become corrupted during the process of saving them from an instance of the app and then installing the in Xcode. When that happens, the app can't read the data on launch but also doesn't report an error. Once I got uncorrupted files in, it started working fine. So now I can get the default data installed successfully but that has uncovered other issues. I'll update this page when I figure out what's going on, or when I can articulate a sensible question. – tkhelm Jul 04 '20 at 03:17
  • I find it unlikely that a sqlite file is easily corruptible. Save the data, close the app and copy it another folder should work just fine. – user965972 Aug 18 '20 at 16:28

1 Answers1

1

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.

Ch Ryder
  • 360
  • 3
  • 10
  • That may be part of the problem. I changed .documentDirectory to .applicationSupportDirectory but the data is still not getting picked up by the app. I also confirmed that the .sqlite files are not corrupted. It's still not clear to me why Core Data doesn't seem to be connecting to these files. – tkhelm Jul 03 '20 at 18:34
  • Just eyeballing your code, are you sure you are initialising `NSPersistentCloudKitContainer` correctly? I would have expected an `init` with the name of your database model, not the name of the app. Beyond that, I'm not sure I can contribute much more. – Ch Ryder Jul 03 '20 at 19:26
  • For Core Data, the name of the database is the same as the name of the app, at least by default. I'm not sure if it can be changed or not. – tkhelm Jul 04 '20 at 03:13
  • I think it should be the name of the `xcdatamodeld` file. This is not necessarily the app name. – Ch Ryder Jul 04 '20 at 05:38