5

I'm trying to ship my app with Core Data already populated. I found some links where they explain how to do it, but either it doesn't work or the answers are very old. I followed this post but it doesn't work. A solution could be to import .sqlite files to the app folder and then copy them to device's file system, but I can't figure out how to do it. Are there any ways to pre-populate my Core Data with existing entities and records?

Andrea Toso
  • 287
  • 4
  • 12

3 Answers3

4

This is the solution I found:

Step 1
Populate your Core Data in another app and get files' path using this code:

let paths = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
print(documentsDirectory)

Step2
Drag your 3 files with .sqlite extension into your xCode project. (Be sure to select Add to targets option).

Step3
Create function to check app's first run.

func isFirstLaunch() -> Bool {
    let hasBeenLaunchedBeforeFlag = "hasBeenLaunchedBeforeFlag"
    let isFirstLaunch = !UserDefaults.standard.bool(forKey: hasBeenLaunchedBeforeFlag)
    if (isFirstLaunch) {
        UserDefaults.standard.set(true, forKey: hasBeenLaunchedBeforeFlag)
        UserDefaults.standard.synchronize()
    }
    return isFirstLaunch
}

Step4
Copy this in AppDelegate:

func getDocumentsDirectory()-> URL {
    let paths = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
    let documentsDirectory = paths[0]
    return documentsDirectory
}

// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "ProjectName")

    let appName: String = "ProjectName"
    var persistentStoreDescriptions: NSPersistentStoreDescription

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

    if UserDefaults.isFirstLaunch() {
        let seededDataUrl = Bundle.main.url(forResource: "FileName", 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]

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

Step 5
If you want to delete your new Core Data files, use this function:

func deleteFiles() {
    let fileManager = FileManager.default
    let documentsUrl =  FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! as NSURL
    let documentsPath = documentsUrl.path

    do {
        if let documentPath = documentsPath {
            let fileNames = try fileManager.contentsOfDirectory(atPath: "\(documentPath)")
            print("all files in cache: \(fileNames)")
            for fileName in fileNames {
                if (fileName.contains("YourFileName")) {
                    let filePathName = "\(documentPath)/\(fileName)"
                    try fileManager.removeItem(atPath: filePathName)
                }
            }
            let files = try fileManager.contentsOfDirectory(atPath: "\(documentPath)")
            print("all files in cache after deleting images: \(files)")
        }
    } catch {
        print("Could not clear temp folder: \(error)")
    }
}
Andrea Toso
  • 287
  • 4
  • 12
  • 3
    Correct me if I'm wrong, but doesn't this solution duplicate the amount of disk space the app is using for the data? The seeded data is grabbed from the resource bundle and then injected into the core data db. That data now sits in two places on disk. If I'm going to be deploying an app with 250Mb of data, I don't want to duplicate this data on disk. – andrewz Mar 06 '20 at 00:37
0

My step 4 version:

  private func isFirstLaunch() -> Bool {
    let isFirstLaunchKey = "firstLaunch"
    let userDefaults = UserDefaults.standard
    let isFirstLaunch = !userDefaults.bool(forKey: isFirstLaunchKey)
    
    if isFirstLaunch { userDefaults.setValue(true, forKey: isFirstLaunchKey) }
    return isFirstLaunch
  }

  lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "YourProjectCDName")
    
    if isFirstLaunch() {
      if let storeUrl = container.persistentStoreDescriptions.first?.url,
         let seededDataUrl = Bundle.main.url(forResource: "YourProjectCDName", withExtension: "sqlite") {
        do {
          try container.persistentStoreCoordinator.replacePersistentStore(
            at: storeUrl,
            destinationOptions: nil,
            withPersistentStoreFrom: seededDataUrl,
            sourceOptions: nil,
            ofType: NSSQLiteStoreType)
        } catch {
          print(error.localizedDescription)
        }
      } else {
        fatalError("Cannot unwrap URLs!")
      }
    }
    
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
      if let error = error as NSError? {
        fatalError("Unresolved error \(error), \(error.userInfo)")
      }
    })
    return container
  }()
Gil
  • 21
  • 1
-1

I recommend the following tutorial. It explains how it can be done.

https://www.youtube.com/watch?v=xcV8Ow9nWFo

simibac
  • 7,672
  • 3
  • 36
  • 48