Home > Back-end >  How to pre-load Core Data with a SQLite file that have references to images that were saved using &q
How to pre-load Core Data with a SQLite file that have references to images that were saved using &q

Time:09-16

My goal is pre-loading Core Data, at the first app launch. So far I ran a simulation and filled Core Data with data. (I had checked "allow external Storage").

I went into application_support and copied: MyApp.sqlite-wal, MyApp.sqlite-shm, .MyApp_SUPPORT/_EXTERNAL_DATA/ and MyApp.sqlite.

Then I added the MyApp.sqlite file in my app bundle and added this code in my app delegate:

lazy var persistentContainer: NSPersistentContainer = {
        let modelName = "MyApp"

        var container: NSPersistentContainer!

        container = NSPersistentContainer(name: modelName)
        
        
        // Preloading
        let appName: String = "MyApp"
        var persistentStoreDescriptions: NSPersistentStoreDescription

        let storeUrl = self.getDocumentsDirectory().appendingPathComponent("MyApp.sqlite")

        if !FileManager.default.fileExists(atPath: (storeUrl.path)) {
            let seededDataUrl = Bundle.main.url(forResource: appName, withExtension: "sqlite")
            try! FileManager.default.copyItem(at: seededDataUrl!, to: storeUrl)
        }

        let description = NSPersistentStoreDescription()
        description.shouldInferMappingModelAutomatically = true
        description.shouldMigrateStoreAutomatically = true
        description.url = storeUrl

        container.persistentStoreDescriptions = [description]
        //End Preloading

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {

                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

It works but It looks like it doesn't find the images that were saved in external storage. They're present in .MyApp_SUPPORT/_EXTERNAL_DATA as references. Where should I add the references?

Load everything is my goal.

CodePudding user response:

If you have a file named MyApp.sqlite with external binary storage in some directory (here, the app bundle), Core Data puts those files in a sub-directory named .MyApp_SUPPORT/_EXTERNAL_DATA/. You need to recursively copy that directory and everything in its sub-directories.

Using that path is not a good idea, though, because it's undocumented and could change without warning. Also, this will miss MyApp.sqlite-wal and MyApp.sqlite-shm, if they exist.

A better idea is to put the seed store in a custom directory of its own, and copy everything from that directory. Instead of just having MyApp.sqlite, you'd have a directory named MyAppSeedData which would contain MyApp.sqlite. It would also contain all the other stuff Core Data needs like the external binary files. Use the same FileManager function to copy the MyAppSeedData directory (because it will recursively copy every file) and you should be fine.

The code to copy the folder would be something like this:

if let sourceDirURL = Bundle.main.url(forResource: "Source Folder", withExtension: nil) {
    let destinationDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("TestFolder")
    if !FileManager.default.fileExists(atPath: destinationDirURL.path) {
        do {
            try FileManager.default.copyItem(at: sourceDirURL, to: destinationDirURL)
        } catch {
            print("Error copying directory: \(error)")
        }
    }
}

You could then add the SQLite file name to the end of destinationDirURL and use it for Core Data.

CodePudding user response:

Step 1: Create "MyAppSeedData" dir and paste MyApp.sqlite, the MyApp_SUPPORT, the MyApp.sqilte-smh, MyApp.sqilte-wal files inside.

Step 2: Drag MyAppSeedData to the bundle under AppDelegate and tick the box add target.

Step 3: These functions must be in AppDelegate file:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
    //If first launch condition == true {
    seedData()
    //}
    return true
}

    
func seedData() {
    let fm = FileManager.default
    
    //Destination URL: Application Folder
    let libURL = fm.urls(for: .libraryDirectory, in: .userDomainMask).first!
    let destFolder = libURL.appendingPathComponent("Application Support").path
    //Or
    //let l1 = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).last!
    //
    
    //Starting URL: MyAppSeedData dir
    let folderPath = Bundle.main.resourceURL!.appendingPathComponent("MyAppSeedData").path
    
    let fileManager = FileManager.default
        let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
        if let applicationSupportURL = urls.last {
            do{
                try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil)
            }
            catch{
                print(error)
            }
        }
    copyFiles(pathFromBundle: folderPath, pathDestDocs: destFolder)
}


func copyFiles(pathFromBundle : String, pathDestDocs: String) {
    let fm = FileManager.default
    do {
        let filelist = try fm.contentsOfDirectory(atPath: pathFromBundle)
        let fileDestList = try fm.contentsOfDirectory(atPath: pathDestDocs)

        for filename in fileDestList {
            try FileManager.default.removeItem(atPath: "\(pathDestDocs)/\(filename)")
        }
        
        for filename in filelist {
            try? fm.copyItem(atPath: "\(pathFromBundle)/\(filename)", toPath: "\(pathDestDocs)/\(filename)")
        }
    } catch {
        print("Error info: \(error)")
    }
}




// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    let modelName = "MyApp"

    var container: NSPersistentContainer!

    container = NSPersistentContainer(name: modelName)
            
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()
  • Related