73

I've been creating a list app and backing it with core data.

I would like to have a default list of say 10 airport's items, so that the user doesn't have to start from scratch.

Is there any way to do this?

Any help is appreciated. Thanks in advance.

Irfan
  • 4,301
  • 6
  • 29
  • 46
Tanner
  • 813
  • 1
  • 7
  • 12
  • 2
    This is a duplicate of the following questions: http://stackoverflow.com/questions/928177/provide-base-data-for-core-data-application , http://stackoverflow.com/questions/978187/default-dataset-for-core-data-based-iphone-application , http://stackoverflow.com/questions/1264382/how-do-i-initialize-a-store-with-default-data-in-a-coredata-application – Brad Larson Feb 09 '10 at 19:02
  • 14
    Ha ha ha, yeah it is a popular question with no really good answer. – Ziggy Jun 16 '12 at 00:39

11 Answers11

60

Here's the best way (and doesn't require SQL knowledge):
Create a quick Core Data iPhone app (Or even Mac app) using the same object model as your List app. Write a few lines of code to save the default managed objects you want to the store. Then, run that app in the simulator. Now, go to ~/Library/Application Support/iPhone Simulator/User/Applications. Find your application among the GUIDs, then just copy the sqlite store out into your List app's project folder.

Then, load that store like they do in the CoreDataBooks example.

Ken Aspeslagh
  • 11,484
  • 2
  • 36
  • 42
  • 7
    Won't this break your app if Apple decides to change the internals of Core Data between iOs versions (and you don't ship an update in time)? – Sjors Provoost Feb 01 '12 at 09:19
  • I honestly doubt Apple would make a change that breaks its ability to read its own databases files. – Ken Aspeslagh Feb 03 '12 at 19:54
  • Apple could migrate all existing core data databases on the device during a system upgrade, so it would still be able to read them. But such a migration might skip pre-packaged database files in new installs. – Sjors Provoost Feb 08 '12 at 10:03
  • That wouldn't work at all Sjors. App data can be backed up on the user's computer in iTunes and restored at any time. – Ken Aspeslagh Feb 10 '12 at 14:33
  • 1
    I did as you suggest, but I still get "The model used to open the store is incompatible with the one used to create the store". I actually copied the model file from one project to the other... so I'm pretty sure they are identical. – Ziggy Jun 17 '12 at 01:25
  • instead of creating a new app with the same model, can't we just duplicate our app or even use it, run it, create whatever data we want and just copy that sqlite store into our Xcode project? – Ace Green May 28 '15 at 21:49
  • If anyone is trying to do this post-iOS 9, Core Data now saves data to a sqlite-shm and sqlite-wal file in addition to the regular sqlite file. You need to copy those files over as well, otherwise you'll get an empty database. – bmueller Apr 11 '17 at 05:27
  • 1
    FWIW, I did exactly that for an app in 2014, the sqlite file I created back then still works flawlessly with 2022 Xcode. – Skoua Dec 08 '22 at 17:28
31

Yes there is in fact the CoreDataBooks example does this, you can download the code here: sample code

What you do is create the internal store (database) using the normal procedure to initialize your store just like you would with any other store, then you simply run your code and let it execute the code as described in the CoreDataBooks example (code snippet below). Once the store has been initialized you will want to create a NSManagedObjectContext and initialize it with the created persistent store, insert all the entities you need, and save the context.

Once the context has been successfully saved, you can stop your application, then go to finder and go to folder: ~/Library/Developer type in the search .sqlite and look under /Developer, sorting by date will give you the most recent .sqlite database which should match the time that the code was executed, you can then take this store and add it as a resource of your project. This file then can be read by a persistent store coordinator.

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

if (persistentStoreCoordinator) {
    return persistentStoreCoordinator;
}


NSString *storePath = [[self applicationDocumentsDirectory]      stringByAppendingPathComponent: @"CoreDataBooks.sqlite"];
 /*
  Set up the store.
 For the sake of illustration, provide a pre-populated default store.
 */
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
  NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"CoreDataBooks"      ofType:@"sqlite"];
 if (defaultStorePath) {
 [fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
 }
}

NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

 NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber   numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; 
  persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];

 NSError *error;
 if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
  // Update to handle the error appropriately.
  NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
 exit(-1);  // Fail
}    

return persistentStoreCoordinator;
}

Hope that helps.

-Oscar

Oscar Gomez
  • 18,436
  • 13
  • 85
  • 118
  • No you don't I created mine just like you would create it using SQL Server 2005 and simply inserting the values, using SQLite Database Browser which you can get here: http://mac.softpedia.com/get/Developer-Tools/SQLite-Database-Browser.shtml – Oscar Gomez Feb 09 '10 at 16:05
  • I believe I would like the sql browser approach better because I could add different list. Ive downloaded it. Do I just add an item, name it passport and add 9 more items and then im done? – Tanner Feb 09 '10 at 16:11
  • Yes pretty much, it is as easy to use as any other Database browser. – Oscar Gomez Feb 09 '10 at 16:14
  • Ive never used sql before. If there all small words (passport, book) for file type would I choose text? Thanks – Tanner Feb 09 '10 at 16:20
  • Ive created a table named AirportTable. I then hit create but it said no fields were there so I made 1 field called passport. Is this all id have to do if I only want 1? – Tanner Feb 09 '10 at 17:41
  • I did the above then added a new record named "passport" saved it, then added it to the project with the code above however nothing happened – Tanner Feb 09 '10 at 17:53
  • Yes that is all you need. How are you displaying your results?, are you using a NSFetchedResultsController?, I assume the code posted and your table name match, it this correct? – Oscar Gomez Feb 09 '10 at 18:16
  • Yes but it made the app crash saying it excited with a sqlite error. – Tanner Feb 09 '10 at 18:41
  • 4
    This answer is misleading. You can't just dump data into any old SQLite database and then load it into Core Data. Core Data has a very specific internal structure to its SQLite databases that is not documented and that you are advised not to manually write to. – Brad Larson Feb 09 '10 at 18:47
  • Right, on opening the coredatabooks .sqlite there are many tables. Do you know of a way to make 10 items or a default list appear? Having the app ship with a blank list doesnt seem very attractive. – Tanner Feb 09 '10 at 19:13
  • If I do create a mac core data model and populate it, drag it over to my iphone project will it hold the list? Is this a way to go? Id rather not learn xml or such if this works. Please advise. – Tanner Feb 09 '10 at 19:25
  • This will work in fact that is how I did it on one of my applications. Although I did actually use the coredatabooks.sqlite example and modified it to what I needed in order to have the needed tables to work with Core Data. Sorry I should have suggested to base yourself on that table just as I did. – Oscar Gomez Feb 09 '10 at 21:05
  • As long as something works im happy. How did you add items to the table? What part of it did you change? – Tanner Feb 09 '10 at 21:19
  • First Delete all Records in the ZBook Table (leave the other 2 tables intact). Then modify the table as follow: leave the first 3 columns: Z_PK (identity key), Z_ENT and Z_OPT, delete the other columns, and add the column(s) you need. Finally add the records you want. – Oscar Gomez Feb 09 '10 at 22:49
  • If I do the desktop and add say 10 items. Will those items still be in it when I transfer? – Tanner Feb 10 '10 at 01:26
  • Also if I do it like that would there be a way to make separate list? Like 1 for the airport, 1 for shopping, 1 for school...? Thanks – Tanner Feb 10 '10 at 01:28
  • 1
    Could anyone translate this to Swift? That would be of big help. – martin Feb 21 '15 at 15:05
  • I think you ought to revise this answer so that it states one should create a Core Data Store using normal procedures and then to distribute the store simply add the sqlite file to the project folder and the Copy Phases List. At that point one can use the process as described in the sample. – Tommie C. Jul 28 '15 at 14:25
  • Edited answer to clear up confusion, I realize it was misleading. – Oscar Gomez Jul 28 '15 at 14:39
11

With this method you don't need to make a separate app or have any SQL knowledge. You only need to be able to make a JSON file for your initial data.

I use a JSON file that I parse into objects, then insert them in Core Data. I do this when the app initializes. I also make one entity in my core data that indicates if this initial data is already inserted, after I insert the initial data I set this entity so the next time the script runs it sees that the initial data has already been initialized.

To read json file into objects:

NSString *initialDataFile = [[NSBundle mainBundle] pathForResource:@"InitialData" ofType:@"json"];
NSError *readJsonError = nil;
NSArray *initialData = [NSJSONSerialization
                        JSONObjectWithData:[NSData dataWithContentsOfFile:initialDataFile]
                        options:kNilOptions
                        error:&readJsonError];

if(!initialData) {
    NSLog(@"Could not read JSON file: %@", readJsonError);
    abort();
}

Then you can make entity objects for it like this:

[initialData enumerateObjectsUsingBlock:^(id objData, NSUInteger idx, BOOL *stop) {

    MyEntityObject *obj = [NSEntityDescription
                          insertNewObjectForEntityForName:@"MyEntity"
                          inManagedObjectContext:dataController.managedObjectContext];

    obj.name = [objData objectForKey:@"name"];
    obj.description = [objData objectForKey:@"description"];

    // then insert 'obj' into Core Data

}];

If you want a more detailed description on how to do this, check out this tutorial: http://www.raywenderlich.com/12170/core-data-tutorial-how-to-preloadimport-existing-data-updated

gitaarik
  • 42,736
  • 12
  • 98
  • 105
10

For 10 items, you can just do this within applicationDidFinishLaunching: in your app delegate.

Define a method, say insertPredefinedObjects, that creates and populates the instances of the entity in charge of managing your airport items, and save your context. You may either read the attributes from a file or simply hardwire them in your code. Then, call this method inside applicationDidFinishLaunching:.

Massimo Cafaro
  • 25,429
  • 15
  • 79
  • 93
6

Bear in mind, when following the CoreDataBooks example code, that it probably breaks the iOS Data Storage Guidelines:

https://developer.apple.com/icloud/documentation/data-storage/

I've had an app rejected for copying the (read-only) pre-populated database to the documents directory - as it then gets backed up to iCloud - and Apple only want that to happen to user-generated files.

The guidelines above offer some solutions, but they mostly boil down to:

  • store the DB in the caches directory, and gracefully handle situations where the OS purges the caches - you will have to rebuild the DB, which probably rules it out for most of us.

  • set a 'do not cache attribute' on the DB file, which is a little arcane, as it needs to be done differently for different OS versions.

I don't think it is too tricky, but be aware that you have a bit extra to do to make that example code work alongside iCloud...

Adrian Bigland
  • 1,439
  • 1
  • 13
  • 8
5

This answer is only for people who are

  • including a pre-populated database in your app
  • making an app for multiple platforms (iOS, Android, etc.)

I had made a prepopulated SQLite database for an Android app. Then when I was making an iOS version of the app I thought it would be best to use Core Data. So I spent quite a long time learning Core Data and then rewriting the code to prepopulate the database. Learning how to do every single step in both platforms required lots of research and trial and error. There was a lot less overlap than I would have hoped.

In the end I just decided to use the same SQLite database from my Android project. Then I used the FMDB wrapper to directly access the database in iOS. The benefits:

  • Only need to make the prepopulated database once.
  • Doesn't require a paradigm shift. The syntax between Android and FMDB, while different, is still fairly similar.
  • Have a lot more control over how Queries are performed.
  • Allows full text search.

Although I don't regret learning Core Data, if I were to do it over I could have saved a lot of time by just sticking to SQLite.

If you are starting in iOS and then planning to move to Android, I would still use a SQLite wrapper like FMDB or some other software to prepopulate the database. Although you can technically extract the SQLite database that you prepopulate with Core Data, the schema (table and column names, etc.) will be strangely named.

By the way, if you don't need to modify your prepopulated database, then don't copy it to the documents directory after the app is installed. Just access it directly from the bundle.

// get url reference to databaseName.sqlite in the bundle
let databaseURL: NSURL = NSBundle.mainBundle().URLForResource("databaseName", withExtension: "sqlite")!

// convert the url to a path so that FMDB can use it
let database = FMDatabase(path: databaseURL.path)

This makes it so that you don't have two copies.

Update

I now use SQLite.swift rather than FMDB, because it integrates better with Swift projects.

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • SQLite.swift is magic. Say @Suragch, you may know ... in Android, you have SQLiteOpenHelper which has the handy "OnUpgrade" concept. To deal with when the app is upgraded (ie, new version from the app store). Do you happen to know, how is that issue handled in iOS, does it become a problem? Cheers – Fattie Dec 12 '16 at 09:18
  • BTW what you say above is so true. If you are doing ios-android development, it's so much better to simply stick to SQLite. Even for those who have never used sql before, it's just a matter of learning a few simple sql statements. – Fattie Dec 12 '16 at 09:20
  • @JoeBlow, I haven't done a schema update in Sqlite.swift yet. I think I recall reading something about it in the documentation, but all I could see just now was [this](https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#migrations-and-schema-versioning). It appears to not be as built in as `OnUpgrade` is with Android. – Suragch Dec 12 '16 at 13:28
  • thanks for the reply; right, saw that section "Migrations and Schema" - perhaps that's the size of it. Thanks. BTW I asked a question on the issue, http://stackoverflow.com/questions/41100001/sqlite-swift-regarding-upgrade-of-app Cheers!! – Fattie Dec 12 '16 at 13:39
5

This worked for me. This is a modification of this answer by Andrea Toso and inspired by this blog. The only issue with the answer is that there is a chance of data loss when moving sqlite files with FileManager. I saved around 500 rows of data by using replacePersistentStore instead of FileManager.default.copyItem

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 in AppDelegate.swift

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 function in AppDelegate.swift to get url where sqlite database should be moved:

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

Step 5
Replace declaration of persistentContainer with this one:

// MARK: - Core Data stack

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

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

    if isFirstLaunch() {
        let seededDataUrl = Bundle.main.url(forResource: "FileName", withExtension: "sqlite")
        try! container.persistentStoreCoordinator.replacePersistentStore(at: storeUrl, destinationOptions: nil, withPersistentStoreFrom: seededDataUrl!, sourceOptions: nil, ofType: NSSQLiteStoreType)
    }

    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()
Gaurav Bishnoi
  • 7,963
  • 2
  • 13
  • 12
2

So I have developed a generic method that loads from a dictionary (possibly from JSON) and populates the database. It should be used ONLY with trusted data (from a safe channel), it can't handle circular references and schema migrations can be problematic... But for simple use cases like mine it should be fine

Here it goes

- (void)populateDBWithDict:(NSDictionary*)dict
               withContext:(NSManagedObjectContext*)context
{
    for (NSString* entitieName in dict) {

        for (NSDictionary* objDict in dict[entitieName]) {

            NSManagedObject* obj = [NSEntityDescription insertNewObjectForEntityForName:entitieName inManagedObjectContext:context];
            for (NSString* fieldName in objDict) {

                NSString* attName, *relatedClass, *relatedClassKey;

                if ([fieldName rangeOfString:@">"].location == NSNotFound) {
                    //Normal attribute
                    attName = fieldName; relatedClass=nil; relatedClassKey=nil;
                } else {
                    NSArray* strComponents = [fieldName componentsSeparatedByString:@">"];
                    attName = (NSString*)strComponents[0];
                    relatedClass = (NSString*)strComponents[1];
                    relatedClassKey = (NSString*)strComponents[2];
                }
                SEL selector = NSSelectorFromString([NSString stringWithFormat:@"set%@:", attName ]);
                NSMethodSignature* signature = [obj methodSignatureForSelector:selector];
                NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:signature];
                [invocation setTarget:obj];
                [invocation setSelector:selector];

                //Lets set the argument
                if (relatedClass) {
                    //It is a relationship
                    //Fetch the object
                    NSFetchRequest* query = [NSFetchRequest fetchRequestWithEntityName:relatedClass];
                    query.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:relatedClassKey ascending:YES]];
                    query.predicate = [NSPredicate predicateWithFormat:@"%K = %@", relatedClassKey, objDict[fieldName]];

                    NSError* error = nil;
                    NSArray* matches = [context executeFetchRequest:query error:&error];


                    if ([matches count] == 1) {
                        NSManagedObject* relatedObject = [matches lastObject];
                        [invocation setArgument:&relatedObject atIndex:2];
                    } else {
                        NSLog(@"Error! %@ = %@ (count: %d)", relatedClassKey,objDict[fieldName],[matches count]);
                    }


                } else if ([objDict[fieldName] isKindOfClass:[NSString class]]) {

                    //It is NSString
                    NSString* argument = objDict[fieldName];
                    [invocation setArgument:&argument atIndex:2];
                } else if ([objDict[fieldName] isKindOfClass:[NSNumber class]]) {

                    //It is NSNumber, get the type
                    NSNumber* argument = objDict[fieldName];
                    [invocation setArgument:&argument atIndex:2];

                }
                [invocation invoke];


            }

            NSError *error;
            if (![context save:&error]) {
                NSLog(@"%@",[error description]);
            }
        }
    }   
}

And loads from json...

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"initialDB" ofType:@"json"];
NSData *jsonData = [NSData dataWithContentsOfFile:filePath];

NSError* error;
NSDictionary *initialDBDict = [NSJSONSerialization JSONObjectWithData:jsonData
                                                           options:NSJSONReadingMutableContainers error:&error];

[ self populateDBWithDict:initialDBDict withContext: [self managedObjectContext]];

JSON examples

    {
    "EntitieA": [ {"Att1": 1 }, {"Att1": 2} ],
    "EntitieB": [ {"Easy":"AS ABC", "Aref>EntitieA>Att1": 1} ]
    }

and

{
    "Country": [{"Code": 55, "Name": "Brasil","Acronym": "BR"}],
    "Region": [{"Country>Country>code": 55, "Code": 11, "Name": "Sao Paulo"},
               {"Country>Country>code": 55, "Code": 31, "Name": "Belo Horizonte"}]
}
Felizardo
  • 298
  • 1
  • 8
2

How about check if any objects exist and if not, create one with some data?

NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Settings"];
_managedObjectSettings = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];

if ([_managedObjectSettings count] == 0) {
    // first time, create some defaults
    NSManagedObject *newDevice = [NSEntityDescription insertNewObjectForEntityForName:@"Settings" inManagedObjectContext:managedObjectContext];

    [newDevice setValue:[NSNumber numberWithBool: YES ] forKey:@"speed"];
    [newDevice setValue:[NSNumber numberWithBool: YES ] forKey:@"sound"];
    [newDevice setValue:[NSNumber numberWithBool: NO ] forKey:@"aspect"];
    [newDevice setValue:[NSNumber numberWithBool: NO  ] forKey: @"useH264"];
    [newDevice setValue:[NSNumber numberWithBool: NO ] forKey: @"useThumbnail"];

    NSError *error = nil;
    // Save the object to persistent store
    if (![managedObjectContext save:&error]) {
        NSLog(@"Can't Save! %@ %@", error, [error localizedDescription]);
    }
}
Md1079
  • 1,360
  • 1
  • 10
  • 28
0

Another method for storing defaults is found by way of NSUserDefaults. (surprise!) And its easy.

Suggested by some, put that into the applicationDidFinishLaunching

In the given case of 10 defaults, Airport0 thru 9

Setting

NSUserDefaults *nud = [NSUserDefaults standardUserDefaults];
[nud setString:@"MACADDRESSORWHY" forKey:@"Airport0"];
    ...
[nud setString:@"MACADDRESSORWHY" forKey:@"Airport9"];
[nud synchronize];

or

[[NSUserDefaults standardUserDefaults] setString:@"MACADDRESSORWHY" forKey:@"Airport9"]];
     ...
[[NSUserDefaults standardUserDefaults] synchronize];

And then, getting the defaults.

NSString *air0 = [[NSUserDefaults standardUserDefaults] stringForKey:@"Airport0"];
Gabe Rainbow
  • 3,658
  • 4
  • 32
  • 42
-1

As most answers are quite old, 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