Search code examples
swiftiphonesqlitecore-datapersistence

How to pre-load Core Data with a SQLite file that have references to images that were saved using "external storage"?


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.


Solution

  • 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
    }()