Core Data Store included in App Bundle
Asked Answered
H

4

10

I can't find a clear description of these steps in Apple docs...

  1. I have a xcdatamodeld in my xcode project
  2. At launch time, my app parses a XML (project resource) to fill the Core Data Store (SQLLite)
  3. During lifetime of my app, I add, remove, update data of that Store

Now, I want to stop doing that heavy XML parsing process on device and directly include a Store containing the required data.

I have some questions regarding this :

  • Can I fill a store with a OS X app and then include this store in my XCode-iOs project ?
  • My store does not appear in Xcode. In fact it is created at run time. How can I add a store in the project and link it to my xcdatamodeld ?
  • I have read that doing so will prevent my store from being writable... I guess I have to copy it in the right place at launch time (the Core Data utility Tutorial is a great help for that). Am I right ?

Thanks for your hints. URL or other SO questions would be really appreciate !

Kheraud

Haploid answered 12/4, 2011 at 9:28 Comment(0)
L
16

You can include the store file (sqlite db most of the time) in your app. Then in your app delegate edit the persistentStoreCoordinator getter merhod :

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

    if (persistentStoreCoordinator_ != nil) {
        return persistentStoreCoordinator_;
    }

    NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"CoreDataStore.sqlite"];

    // Check if the store exists in. 
    if (![[NSFileManager defaultManager] fileExistsAtPath:storePath]) {
        // copy the payload to the store location.
        NSString *bundleStore = [[NSBundle mainBundle] pathForResource:@"YourPayload" ofType:@"sqlite"];

        NSError *error = nil;
        [[NSFileManager defaultManager] copyItemAtPath:bundleStore toPath:storePath error:&error];

        if (error){
            NSLog(@"Error copying payload: %@", error);
        }
    }

    NSError *error = nil;
    NSURL *storeURL = [NSURL fileURLWithPath:storePath];
    persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    

    return persistentStoreCoordinator_;
}
Landmass answered 12/4, 2011 at 9:46 Comment(3)
Thanks for the piece of code. Don't you think there could be compatibility issues between device / Core Data / copied DB ?Haploid
No, I mostly create the db with the simulator then use it on the device, there is no problem.Landmass
In the second sentence of your answer, what is persantstore ? (typo?)Guardsman
E
11
  1. Write a utility app using the data model and classes for the app. Use the utility app to generate a persistent store from the data provided by the XML.
  2. Add the store file to the app bundle like any other resource
  3. Pick a location in the app directory where you want the active store to reside e.g. the Library directory.
  4. Upon launch have the app check if the store is present in directory. If it isn't, the app should copy the store from the app bundle to the directory using standard NSFileManger methods just like any other file. (Usually, you only need to do this the first time the store is created. )

That's all there really is to it.

Eicher answered 12/4, 2011 at 20:33 Comment(2)
is it OK if I keep the sqlite file in Bundle, because it is read-only ??Deliladelilah
Core Data will want to find the sqlite file(s) in the app's application support directory: try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) is one way to get this. It is OK, though, to keep a COPY of the file(s) in the bundle to copy over to application support if you're preloading the database. -- oops, this is Swift of course, since it's a couple of years later ;-)Kentkenta
H
8

What you're currently doing (populating at first launch) is the "recommended" way of populating a Core Data store. Although its a bit hackish, you could however seed the on-device database as follows:

  1. Launch your app in the simulator
  2. Do whatever you need to do for the simulator app to populate the Core Data store
  3. Stop the simulator app
  4. Navigate to the simulated Documents folder (something like ~/Library/Application Support/iPhone Simulator/4.3/Applications/335567A0-760D-48AF-BC05-7F0D9BD085B6/<app-name>.app/)
  5. Find the sqlite database (it has the name you gave it when initializing Core Data)
  6. Copy this database to your project, and add it to be copied as a resource
  7. Add some code to your application:didFinishLaunchingWithOptions: method so that on first launch, it copies the database from the read-only resources directory to the app's documents directory. Of course you need to do this before initializing Core Data.

Depending on exactly what you're storing in your database, you may however discover big- vs. little-endianness issues, or other incompatibilities. To make the approach a bit safer, you could dump the simulator database (splite3 databasefile .dump >dumpfile) on your Mac, then include the dumpfile in your project (as above), and slurp the dump in your app on first launch (reading it line-by-line, and handing the sql statements to the sqlite API).

Heliochrome answered 12/4, 2011 at 9:48 Comment(1)
Ok. It seems using SQL dump and parse+fill is a good way. But dealing with XML is not so much more complicated (I think). Furthermore, I receive XML data through network and I need to parse it. I will keep my XML mechanic as is... Thanks for the help, I could use it later :)Haploid
M
0

For an updated Swift 5 answer, following the same theme as above (ie)

  1. Populate data into core data sqlite DB from another place.
  2. Include that created DB in your app bundle.
  3. At app launch check to see if that file is in the Application Support folder, and if not, move the seed database there.

Here is the pertinent swift code, I put mine in the static initializer for the shared instance of my Persistence Controller, but do with it as you will.

   static func seedDataIfRequired() {
    let dbname = "your file name here"
    if let appSupportDirURL : URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
        let appSupportDir = appSupportDirURL.path(percentEncoded:false)
        if FileManager.default.fileExists(atPath: "\(appSupportDir)\(dbname).sqlite") {
            debugPrint("Core data database exists, no action needed.")
        }else {
            if let seedDatabasePath = Bundle.main.path(forResource: "\(dbname)", ofType: "sqlite") {
                debugPrint("Seed database is available.")
                // move it there
                try? FileManager.default.createDirectory(atPath: appSupportDir, withIntermediateDirectories: true)
                let status = FileManager.default.secureCopyItem(at:URL(filePath: seedDatabasePath) , to: URL(filePath: "\(appSupportDir)\(dbname)"))
                if status == true {
                    debugPrint("Seed database COPIED.")
                }else{
                    debugPrint("Seed database copy FAILURE.")
                }
            }
        }
    }
}
Majewski answered 18/4, 2023 at 14:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.