Core Data on iOS 5 Tutorial: How To Preload and Import Existing Data

Adam Burkepile Adam Burkepile

This post is also available in: Korean

Failed Banks Preloaded Data with Core Data

Failed Banks Preloaded Data with Core Data

Note from Ray: iOS Tutorial Team member Adam Burkepile has kindly updated the Core Data series to iOS 5 – I’ll post an announcement about it in a bit.

This tutorial was completely rewritten in order to show a more elegant way of preloading data by making a Mac command line app that reuses your iPhone app’s Core Data model.

I wanted to repost this specially so everyone is aware of the update/rewrite. Enjoy! :]

This is a blog post by iOS Tutorial Team member Adam Burkepile, a full-time Software Consultant and independent iOS developer. Check out his latest app Pocket No Agenda, or follow him on Twitter.

This is the second part of a three-part series to help get you up to speed with the basics of Core Data quickly.

In the first part of the series, we created a visual data model for our objects, ran a quick and dirty test to make sure it works, and hooked it up to a table view so we could see a list of our objects.

In this part of the series, we’re going to discuss how to import or preload existing data into Core Data so that we have some good default data when our app starts up.

Note that there was an earlier version of this tutorial that showed you a hackish way to preload data by directly manipulating the SQLite database. The method shown in this tutorial is much preferred as it’s more elegant and less likely to break as things change with Core Data in the future.

In the final part of the series, we’re going to discuss how we can optimize our app by using NSFetchedResultsController, to reduce memory overhead and improve response time.

Before we begin, make sure you have the project where we left it off in part 1. If you need a fresh copy, you can download it here.

Preloading / Importing Existing Data

So how do we preload data into a Core Data store, anyway? Well, there are two popular solutions to this problem:

  1. Fill in Core Data on startup from external source. For this the app can start up, notice that the database hasn’t been imported yet, and start reading in data from an external source (such as an SQLite database or XML file) and then start inserting the data into Core Data.
  2. Provide pre-filled in SQLite database. For this we’ll let Core Data create the database structure for us based on the model, and then we populate the database with a utility app. The utility app could be a Mac or iPhone app that uses Core Data to populate the database via Core Data APIs, or some kind of program that fills in the SQLite database directly. Once the database is populated, just include it with the app and make the app use it as the default database if no database already exists.

In this tutorial we are going to go with option #2, and show you how you can make a simple utility app to create a preloaded Core Data database you can use in your app.

Getting Started

The basis for the method we are going to use is that Core Data on iOS is the same Core Data on OS X, and they can both use the same models and classes.

This means we can write a simple console application on OS X to import data from a data source, stick it into a Core Data data store, and move that data store over to our iOS project. Isn’t that great?!

Let’s try this out and make a Mac command line app to preload our data. Open up Xcode and create a new project, and choose the Mac OSX\Application\Command Line Tool template.

Enter “CoreDataTutorial2” as the name fo the project, change the type to “Core Data” and make sure “Use Automatic Reference Counting” is on.

Finish creating the project, then select “CoreDataTutorial2.xcdatamodeld” and delete it. Select “Move to Trash” when it asks.

Next go into the directory for tutorial 1 and find the following files:

  • FailedBankCD.xcdatamodeld
  • FailedBankInfo.h
  • FailedBankInfo.m
  • FailedBankDetails.h
  • FailedBankDetails.m

Copy those files into the directory for this project and then drag them into Xcode.

Make sure the “Copy items into destination group’s folder (if needed)” is NOT checked but be sure to check the “Add to targets” checkbox for “CoreDataTutorial2”.

Select the main.m. You’ll notice that because we selected this as a Core Data type application, we get the same boilerplate Core Data methods we saw in tutorial 1. What we’re gonna do now is make some changes to use our model classes from the iOS project and generate the Core Data entities.

In the managedObjectModel() method replace

NSString *path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0];
path = [path stringByDeletingPathExtension];

with

NSString *path = @"FailedBankCD";

This is pointing the application to the FailedBankCD.xdatamodeld we added instead of the CoreDataTutorial2.xdatamodeld we deleted in the beginning.

Compile and run, and verify that you have no errors.

If you built and ran it previously, you will get the model-mismatch error we discussed in tutorial 1. To delete the sqlite db, hold option(alt) and go to the menu and select “Products” and “Clean Build Folder…”.

If you see an error like this:

NSInvalidArgumentException', reason: 'Cannot create an NSPersistentStoreCoordinator 
with a nil model'

This is because the code is looking for a ‘momd’ file (the file for a versioned Core Data Model), but if your model isn’t versioned it will be saved as plain old ‘mom’. As this article explains, the quickest fix is to replace the line in managedObjectModel() like so:

 NSURL *modelURL = [NSURL fileURLWithPath:[path stringByAppendingPathExtension:@"mom"]];

Now it should compile and run fine (with no output yet).

Importing the Data

Now on to the fun part – preloading our data into a Core Data database!

For our example, we’ll be importing data from a JSON file. In your apps you may want to import data from a different format, but the core idea shown here (using a Mac app to read a file and load it into a Core Data db) will still apply.

Let’s try it out! Right click on the project and select “New File”. Select “Other” category and “Empty”.

Name the file “Banks.json” and make sure to include the file in the “CoreDataTutorial2” target.

Paste the following into the new file:

[{ "name": "Bank1", "city": "City1", "state": "State1", "zip": 11111, "closeDate": "1/1/11" },
 { "name": "Bank2", "city": "City2", "state": "State2", "zip": 22222, "closeDate": "2/2/12" },
 { "name": "Bank3", "city": "City3", "state": "State3", "zip": 33333, "closeDate": "3/3/13" },
 { "name": "Bank4", "city": "City4", "state": "State4", "zip": 44444, "closeDate": "4/4/14" } ]

This is a JSON encoded string containing 4 dictionaries in an array. Each dictionary has a couple properties that correspond to the properties we have in our FailedBankInfo/FailedBankDetails objects.

If you’re a little rusty on how JSON works, check out this tutorial.

Next, we need to tell the application to copy this file into the products dictory when it builds. Select the project file, then the “CoreDataTutorial2” target. Select the “Build Phases” tab, click “Add Build Phase”, and select “Add Copy Files”. Change the destintion to “Products Directory”. Lastly, drag the “Banks.json” file into the “Add files” section.

At this point we have an application that starts up, initializes a Core Data store using our FailedBank model and classes and has a data source with the Banks.json file. Now we are going to:

  • Load the JSON file
  • Deserialize the JSON file into an Objective C array
  • Loop through the array, creating a managed object for each item
  • Save them into Core Data!

Let’s get coding! Open main.m and add the following code to the main function (at the end of the autoreleasepool block):

    NSError* err = nil;
    NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"];
    NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath]
                                                     options:kNilOptions
                                                       error:&err];
    NSLog(@"Imported Banks: %@", Banks);

So your main function should look like this now:

int main(int argc, const char * argv[])
{
 
    @autoreleasepool {
        // Create the managed object context
        NSManagedObjectContext *context = managedObjectContext();
 
        // Custom code here...
        // Save the managed object context
        NSError *error = nil;
        if (![context save:&error]) {
            NSLog(@"Error while saving %@", ([error localizedDescription] != nil) ? [error localizedDescription] : @"Unknown Error");
            exit(1);
        }
 
        NSError* err = nil;
        NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"];
        NSArray* Banks = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:dataPath]
                                                         options:kNilOptions
                                                           error:&err];
        NSLog(@"Imported Banks: %@", Banks);
 
    }
    return 0;
}

This uses the new built-in NSJSONSerialization API to easily convert a JSON document into core Foundation types like NSArray, NSDictionary, etc. To learn more, check out this tutorial.

Give the application a run. You should see this print out:

2012-04-14 22:01:34.995 CoreDataTutorial2[18388:403] Imported Banks: (
        {
        city = City1;
        closeDate = "1/1/11";
        name = Bank1;
        state = State1;
        zip = 11111;
    },
        {
        city = City2;
        closeDate = "2/2/12";
        name = Bank2;
        state = State2;
        zip = 22222;
    },
        {
        city = City3;
        closeDate = "3/3/13";
        name = Bank3;
        state = State3;
        zip = 33333;
    },
        {
        city = City4;
        closeDate = "4/4/14";
        name = Bank4;
        state = State4;
        zip = 44444;
    }
)

So we can see we now have the data in Objective-C objects that we can easily manipulate. Now we can easily import these objects into CoreData just like we did at the end of tutorial 1.

First, add some imports you need to the top of the file:

#import "FailedBankInfo.h" 
#import "FailedBankDetails.h"

Then add the following below the code you just added earlier in main:

[Banks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    FailedBankInfo *failedBankInfo = [NSEntityDescription
                                      insertNewObjectForEntityForName:@"FailedBankInfo"
                                      inManagedObjectContext:context];
    failedBankInfo.name = [obj objectForKey:@"name"];
    failedBankInfo.city = [obj objectForKey:@"city"];
    failedBankInfo.state = [obj objectForKey:@"state"];
    FailedBankDetails *failedBankDetails = [NSEntityDescription
                                            insertNewObjectForEntityForName:@"FailedBankDetails"
                                            inManagedObjectContext:context];
    failedBankDetails.closeDate = [NSDate dateWithString:[obj objectForKey:@"closeDate"]];
    failedBankDetails.updateDate = [NSDate date];
    failedBankDetails.zip = [obj objectForKey:@"zip"];
    failedBankDetails.info = failedBankInfo;
    failedBankInfo.details = failedBankDetails;
    NSError *error;
    if (![context save:&error]) {
        NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
    }
}];
 
// Test listing all FailedBankInfos from the store
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"FailedBankInfo"
                                          inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (FailedBankInfo *info in fetchedObjects) {
    NSLog(@"Name: %@", info.name);
    FailedBankDetails *details = info.details;
    NSLog(@"Zip: %@", details.zip);
}

This is essentially the same code we used in tutorial 1 except for the fact we are using the enumerateObjectsUsingBlock: method to loop through the array of banks and insert one bank and save the context. Then we issue a fetch request and list all the banks.

Give it a run. It should now output the array you saw earier along with this:

2012-04-14 22:15:44.149 CoreDataTutorial2[18484:403] Name: Bank1
2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Zip: 11111
2012-04-14 22:15:44.150 CoreDataTutorial2[18484:403] Name: Bank2
2012-04-14 22:15:44.151 CoreDataTutorial2[18484:403] Zip: 22222
2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Name: Bank3
2012-04-14 22:15:44.152 CoreDataTutorial2[18484:403] Zip: 33333
2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Name: Bank4
2012-04-14 22:15:44.153 CoreDataTutorial2[18484:403] Zip: 44444

Ta-da! That’s your data in Core Data. The exciting part is you can totally customize the way that data comes in. Instead of the small static json file we used, you could use a much larger/a couple json files, a xml file, export a spreadsheet file as a csv file and use that, or even pipe in web service! The possibilities really are endless.

“Doctor, you’re needed in Xcode”

Now we do brain surgery. We need to take the sqlite database we just generated in you OS X console applicaiton and transplant it into the iPhone app. The easiest way to find the sqlite db is the right click on the CoreDataTutorial2 product and select “Show in Finder”.

 

This will open a new Finder window to location that the project got built. Here, you can see four files:

  • Banks.json – The json file with the bank information in it (remember we added the copy file build phase)
  • CoreDataTutorial2 – This is the application
  • FailedBankCD.momd (or just .mom) – this is the compiled version of the Managed Object Model (FailedBankCD.xdatamodeld)
  • CoreDataTutorial2.sqlite – the sqlite db that is generated by the application and where Core Data stores its infomration according to the model that FailedBankCD.momd specifies. If you have a viewer like this one you can even open it and view the raw data.

That “CoreDataTutorial2.sqlite” file is the juicey bit we need. Copy that file into the directory for the project from tutorial 1 then open the tutorial 1 project in Xcode.

Drag the “CoreDataTutorial2.sqlite” file from the Finder into the Xcode project (again make sure that “Copy items into destination group’s folder (if needed)” is NOT checked but “Add to targets” is checked for “FailedBanksCD”).

Lastly open up “FBCDAppDelegate.m”. Scroll down to the persistentStoreCoordinator method. Right under the NSURL *storeURL = [[self app... line, add:

if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
    NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"CoreDataTutorial2" ofType:@"sqlite"]];
    NSError* err = nil;
 
    if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
        NSLog(@"Oops, could copy preloaded data");
    }
}

This chunk of code checks to see if a sqlite db already exists for this app. If it doesn’t exist, it finds the path for the preloaded sqlite db we loaded and tries to copy the db to the path for the normal app db. IT’S THAT SIMPLE! Give it a run. You should see something like this:

 

We see our four banks and then we see that extra bank than get added from the code we wrote in tutorial 1. If you don’t see this, it’s probably because a sqlite db was already there so it’s not overwriting it. Just delete it like we did in tutorial 1.

Where to Go From Here?

You can download the code for the project here (direct download).

At this point, we have a detail view that works pretty much as efficiently as the way it did in our SQLite example. However, with Core Data, with just a couple more steps we can make it even more efficient. So in the next tutorial we cover how to do that by using NSFetchedResultsController!

If you have any questions about this tutorial or Core Data in general, please join the forum discussion below!


This is a blog post by iOS Tutorial Team member Adam Burkepile, a full-time Software Consultant and independent iOS developer. Check out his latest app Pocket No Agenda, or follow him on Twitter.

Adam Burkepile
Adam Burkepile

Adam Burkepile is currently a full-time Software Consultant and independent iOS developer. If he isn’t at the computer, he’s probably getting punched in the face at Krav Maga. Check out his latest app – Pocket No Agenda. You can reach him for work or just to chat at Twitter, Github, his website, or email.

User Comments

102 Comments

[ 1 , 2 , 3 , 4 , 5 , 6 , 7 ]
  • Once you've selected the "Build Phases" tab, click on "Copy Bundle Resources" to expand that list. At the bottom of the list, click on the plus sign. That will open a list of the files in your application where you can select "Banks.json" and then click on the Add button to add it to the list.

    FYI - This is with Xcode 5.0.2

    Have fun!
    Richard Caseyrcasey
  • For those that have the following issue when loading Banks.json:

    Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'data parameter is nil'

    Be sure to, in the build phases menu, Uncheck "Copy only when installing" and empty the field "Subpath" (if you chose Products Directory as Destination)
    rayyenn
  • Hi Adam.
    These are my log results.

    2014-02-28 14:26:27.752 CoreDataPreLoad[13702:303] Name: C
    2014-02-28 14:26:27.754 CoreDataPreLoad[13702:303] string Info: 64,60,55,52,48,-1
    2014-02-28 14:26:27.754 CoreDataPreLoad[13702:303] Name: C
    2014-02-28 14:26:27.755 CoreDataPreLoad[13702:303] string Info: 64,60,55,52,48,43
    2014-02-28 14:26:27.755 CoreDataPreLoad[13702:303] Name: C
    2014-02-28 14:26:27.756 CoreDataPreLoad[13702:303] string Info: 67,64,60,55,48,43
    2014-02-28 14:26:27.756 CoreDataPreLoad[13702:303] Name: C
    2014-02-28 14:26:27.757 CoreDataPreLoad[13702:303] string Info: 74,67,64,60,55,48
    2014-02-28 14:26:27.757 CoreDataPreLoad[13702:303] Name: C
    2014-02-28 14:26:27.757 CoreDataPreLoad[13702:303] string Info: 65,62,58,53,-1,-1
    2014-02-28 14:26:27.758 CoreDataPreLoad[13702:303] Name: C11
    2014-02-28 14:26:27.758 CoreDataPreLoad[13702:303] string Info: 65,62,58,50,48,-1
    2014-02-28 14:26:27.759 CoreDataPreLoad[13702:303] Name: C11
    2014-02-28 14:26:27.759 CoreDataPreLoad[13702:303] string Info: 67,62,58,53,48,43
    2014-02-28 14:26:27.759 CoreDataPreLoad[13702:303] Name: C11
    2014-02-28 14:26:27.760 CoreDataPreLoad[13702:303] string Info: 70,65,60,55,50,-1

    and my codes like this
    :
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"ChordInfo"
    inManagedObjectContext:context];
    [fetchRequest setEntity:entity];
    NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
    for (ChordInfo *info in fetchedObjects) {
    NSLog(@"Name: %@", info.name);
    NSLog(@"string Info: %@,%@,%@,%@,%@,%@", info.s1, info.s2, info.s3, info.s4, info.s5, info.s6);
    }
    }

    It's all OK.
    But when I run the SQLite Database Browser my sqlite file is empty.
    Even the database structure is empty too.
    What should I do?
    silverbell
  • Hi Adam!

    Can we use SQL Cast in Core data ?

    ex: We have '12345' value with interger type. How to search with value '123' or "45" or "23"?
    nvnguyenit
  • I have seen this question posted but not seen an answer. If preloaded data contents change but not the model, how does updated app copy new core data over. If file does not exist it then copies sql lite data but on update the file will exist and not get copied over. How is best way to handle this situation? Sorry for long description.
    Thanks
    Scott
    ECSScott
  • I tried using the CoreDataTutorial2.sqlite I created. I also tried the one provided in the link above. Neither work.

    I only get the TestBank in my rows, I don't see the 4 rows from the JSON.

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"FailedBankCD3.sqlite"];

    if (![[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {
    //IT NEVER ENTERS HERE - WHY?
    NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"CoreDataTutorial2" ofType:@"sqlite"]];
    NSError* err = nil;

    if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&err]) {
    NSLog(@"Oops, could copy preloaded data");
    }
    }

    Any ideas?

    So I did the first tutorial and it worked perfectly. I did the second tutorial, but when I run at the end I only get the TestBank rows in my TVC. I don't see the 4 banks from the JSON file. I've tried my sqlite file from tutorial 2 and I've also tried downloading the one from the tutorial. Neither seem to work.

    Any idea why these rows aren't showing up in the TVC?

    EDIT: It appears it never enter that IF statement above (the one with the ALL CAPS //COMMENT). Why does it not enter there. Should that not be created?
    chris_284
  • I discovered that the console application is not actually writing anything to the sqlite database. This is why Chris_284 does not see any new records in the iPhone simulator after following the second half of this tutorial. The problem is that Apple has changed the default journal mode to WAL for iOS7 and XCode 5. This results in journalling files being created in the product directory...

    CoreDataTutorial2.sqlite
    CoreDataTutorial2.sqlite-shm
    CoreDataTutorial2.sqlite-wal

    One solution is to disable the journal mode in the console application so that the records will be written to the .sqlite file immediately.

    In main.m, change the managedObjectContext method to call addPersistentStoreWithType with options...

    NSError *error;
    Dictionary *options = @{ NSSQLitePragmasOption : @{@"journal_mode" : @"DELETE"} };
    NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE configuration:nil URL:url options:options error:&error];

    Clean out the Build/Debug directory (right-click on product and show in finder) and then re-build and run the console application. You should only see the CoreDataTutorial2.sqlite database file in the directory. You should not see the -shm or -wal files
    bobm
  • Please provide a tutorial for making an e-book using epub.
    iosdeveloper
  • iOS7 and XCode 5 the sqlite file is empty ...
    maybe you should update the situation with xcode 5?

    in this method:
    static NSManagedObjectContext *managedObjectContext(){

    NSError *error;
    //NOTE NSSQLitePragmasOption
    NSDictionary *options = @{ NSSQLitePragmasOption : @{@"journal_mode" : @"DELETE"} };
    NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE configuration:nil URL:url options:options error:&error];
    }
    boog
  • Hello, I'm having trouble finding the Banks.json file NSString* dataPath = [[NSBundle mainBundle] pathForResource:@"Banks" ofType:@"json"]; always is nil..

    I have copied the Banks.json file via the build phases, any help would be great. Thanks, Rich
    rich7886
  • When you are replacing a database with a new one, Say you had 2 Entities, one with preloaded data and one which saves Users input, is it possible to replace just one entity - the preloaded data entity and leave the other Entity alone - the one with the user data in it?
    JonMillar
  • Hi Adam,

    Thank you for a very great article. I followed your instruction to the final step, after dragged new .sql database file into the project I did in tutorial 1, added new line under NSURL *storeURL = [[self app... ] as you said. However, when I ran the app, even thought I tried to delete the app many times to load the new database but it still met the error of "The model used to open the store is incompatible with the one used to create the store". I have no idea what's going wrong. Is there any reason else can make my app be like that?

    Thanks a lot!
    Lucy Nguyen
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: Best iOS Animations in 2014. [Read Now]!

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Toby Stephens

... 49 total!

Update Team

  • Riccardo D'Antoni
  • Ray Fix

... 15 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Translation Team

  • Jesus Guerra
  • David Xie
  • Takeichi Kanzaki Cabrera

... 32 total!

Subject Matter Experts

  • Richard Casey

... 4 total!