2

When I include a SQLite file with Objective-C under "Target - Build Phases - Copy Bundle Resources", this file will be completely copied to the target, i.e. device or simulator. On the target, I get the whole file: tables and contents (Records/rows).

Doing the same with Swift, the tables will be copied, but they are empty: no records/rows. :-(

Can I do something additional? Is there any "trick"?

How can I preload my core data with base-records using Swift???

I'm using Xcode v6.1.1, with Beta 6.2 it is the same.

Ulli H
  • 1,748
  • 1
  • 19
  • 32
  • Copying the resource file to the target is done by Xcode, not by Objectice-C or Swift. – Martin R Jan 08 '15 at 13:29
  • Might be due to iOS using a different SQLite pragma, with the result that the db is stored in three files: .sqlite, .sqlite-shm and .sqlite-wal. Either copy the -shm and -wal files as well, or change the pragma when you create the db. – pbasdf Jan 08 '15 at 13:29
  • Ok, Martin i agree. But it doesnt help me – Ulli H Jan 08 '15 at 15:28
  • @ pbasdf: copying .sqlite-shm and .sqlite-wal doesnt change anything. :-( How can i change the pragma??? – Ulli H Jan 08 '15 at 15:41
  • You can change pragmas with the `NSSQLitePragmasOption`. But before doing so, set Core Data to verbose mode and check the used pragmas. – Amin Negm-Awad Jan 08 '15 at 16:21
  • @MartinR UlliHeinelt said that the files are copied, but empty. Ulli, how did you check that? From your app code or with a SQL tool? What about file size? – Amin Negm-Awad Jan 08 '15 at 16:23
  • As per @AminNegm-Awad, use NSSQLitePragmasOption - see [this answer](http://stackoverflow.com/a/19982830/3985749) for further info. – pbasdf Jan 08 '15 at 16:57
  • The -shm and -wal files are optimizations created at runtime and needn't (shouldn't) be copied. – David Berry Jan 08 '15 at 17:04
  • I bet it's something more obvious, like you've mistyped the filename somewhere or you're looking for it in the wrong directory. – Tom Harrington Jan 08 '15 at 17:34
  • @AminNegm-Awad: can u please give me a sample how and where to set NSSQLitePragmasOption in Swift? I cannot find anything about this in Swift, but i'm an absolute Swift-NewBy. :-( I use the Standard-Swift-Template – Ulli H Jan 19 '15 at 16:04
  • @UlliHeinelt No, I cannot, because writing swift code bringt me pimples with pus to my ass. – Amin Negm-Awad Jan 19 '15 at 16:08
  • @MartinR: i checked it from the app code and two SQLite-tools – Ulli H Jan 19 '15 at 16:08
  • @pbasdf and all: can u please give me a sample how and where to set NSSQLitePragmasOption in Swift? – Ulli H Jan 19 '15 at 16:16
  • This is specified when building the CoreData stack, specifically when you add a NSPersistentStore to the NSPersistentStoreCoordinator. If you are using Apple's template, this is usually done in the AppDelegate. Locate the code which lazy-loads the persistent store coordinator. There should be a function call to `addPersistentStoreWithType`. One of the arguments is `options:` - you add the NSSQLitePragmasOption to that argument. (eg. ...options:[NSSQLitePragmasOption : ["journal_mode" : "DELETE"]]) – pbasdf Jan 19 '15 at 16:30
  • @pbasdf: THX, but that doesn't change anything :-( It's driving me crazy... – Ulli H Jan 19 '15 at 17:58
  • @UlliHeinelt How do you create the original sqlite file? In Objective-C, or Swift? – pbasdf Jan 19 '15 at 18:15
  • @pbasdf i created it in Objective-C... – Ulli H Jan 19 '15 at 18:18
  • OK. Thinking aloud now... If it is an issue with the -wal file, you need to specify the NSSQLitePragmasOption in the Objective-C that creates the sqlite database. But if you can read the data OK (without the -wal file) from Objective-C, then the -wal file is not the problem. Have you tried any of the logic from David's answer below - i.e. using NSFileManager to check existence of the file? Can you post the function which lazy-loads the persistent store coordinator? – pbasdf Jan 19 '15 at 18:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/69147/discussion-between-ulliheinelt-and-pbasdf). – Ulli H Jan 19 '15 at 18:42

3 Answers3

4

This is my solution (for sqlite-files). I don't know why it is so "extravagant" in Swift. Perhaps there's an easier way? Many THX to pbasdf!!!

Important: beneath the *.sqlite-file you must add the *.sqlite-shm and the *.sqlite-wal to your project This files will automatically be added to "Target - Build Phases - Copy Bundle Resources"

This is based on the Standard-Template for the "AppDelegate.swift"

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
    // Create the coordinator and store
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

    let dataName = „MyData.sqlite" // must be replaced by the name of your file!
    let modelName = „MyData“ // must be replaced by the name of your model!

    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent(dataName)

    let fileManager = NSFileManager.defaultManager()
    let bundle = NSBundle.mainBundle()
    let fromURL = bundle.URLForResource(modelName, withExtension: "sqlite")

    // check sqlite-file: must it be installed?
    if !fileManager.fileExistsAtPath(url.path!) {
        self.copySqlliteFiles(modelName, databaseName: dataName)
    }

    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."

    if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options:  nil, error: &error) == nil {

...}

   return coordinator
}()

// MARK: - copy sqllite-files (c) by Ray Wenderlich & Team in „“Core Data by Tutorials“

// check sqlite-files: they must be installed...
func copySqlliteFiles(modelName: String, databaseName: String)
{
    let bundle = NSBundle.mainBundle()

    let baseDatabaseURL = bundle.URLForResource(modelName, withExtension: "sqlite")
    let documentsURL = self.applicationDocumentsDirectory
    let storeURL = documentsURL.URLByAppendingPathComponent(databaseName)
    NSFileManager.defaultManager().copyItemAtURL(baseDatabaseURL!, toURL: storeURL,error: nil)

    let baseSHMURL = bundle.URLForResource(modelName,withExtension: "sqlite-shm")
    let shmURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent(databaseName + "-shm")
    NSFileManager.defaultManager().copyItemAtURL(baseSHMURL!, toURL: shmURL, error: nil)

    let walURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent(databaseName + "-wal")
    let baseWALURL = bundle.URLForResource(modelName, withExtension: "sqlite-wal")
    NSFileManager.defaultManager().copyItemAtURL(baseWALURL!, toURL: walURL, error: nil)
}
Dave
  • 498
  • 2
  • 7
  • 22
Ulli H
  • 1,748
  • 1
  • 19
  • 32
0

I think this probably has to do with how files are laid out on iOS. Specifically:

  1. when you copy the source file (by Xcode or at install time) it gets placed into the application bundle/container directory (which is read-only)
  2. The standard CoreData initialization code looks for the data store in the Documents directory.
  3. Not finding your source data (because it's in the wrong place), CoreData creates a new empty store.

To fix this, you need to copy the store from the Application directory to the Documents directory if it doesn't already exist.

This link gives an explanation and example code to do that. But in Objective-C it looks like:

NSString *storePath = [[self applicationDocumentsDirectory] 
    stringByAppendingPathComponent: @"MyDB.sqlite"];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

// Copy the default db if it doesn't already exist
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storePath]) {
    NSString *defaultStorePath = [[NSBundle mainBundle] 
        pathForResource:@"MyDB" ofType:@"sqlite"];
    if (defaultStorePath) {
        [fileManager copyItemAtPath:defaultStorePath 
toPath:storePath error:NULL];
    }
}
David Berry
  • 40,941
  • 12
  • 84
  • 95
  • Sorry David, i don't need an Objetive-C-Suppert, cause with Objetive-C all is fine... And the sqlite-file is written in the Documents directory – Ulli H Jan 08 '15 at 18:46
0

I had a related issue related to this. I started working on some tutorials which used this line:

documentsURL.URLByAppendingPathComponent("StoreName")

At some point this stopped working. Data was not being written to the store anymore. To see why data was not being written, I tried opening the sqlite file but could not find it on my mac at the URL being used. The app folder was there, but there was no sqlite file. I looked back at older core data projects and found they used .sqlite extension. So I modified the line to:

documentsURL.URLByAppendingPathComponent("StoreName.sqlite")

This seemed to work so give it a try.

marciokoko
  • 4,988
  • 8
  • 51
  • 91