In previous articles in this series, I've talked about how to dynamically create, access, and modify information using Core Data. However that all misses one vital component. I haven't yet talked about how to prefill your persistent storage.
Now, maybe this isn't actually an issue for you. If you're using Core Data to store a high score list or a selection of favorites, then you don't care about prefilling. Your user will supply all the content. However, there are many situations where you want to have something stored away before a user ever touches your program.
Throughout this series I've been using as examples some card-game classes which I've been building. When using these classes, I want to prefill my starting deck of cards, even though a lot of data (such as the order of those cards and later who's them) doesn't occur until the program goes live. It's still important to know what your 52 (or 54 or 104 or whatever) cards are before you get started. Similarly you might want to prefill a database of places, a listing of help topics, or many other things that you could use Core Data to store.
So how do you do so?
It Ain't Easy
Unfortunately, there are no simple answers. Following is discussion of two relatively simple methods that aren't possible using Core Data (but which I looked into before I arrived at a correct solution).
You Can't Use a Nifty UI. I kind of expected a pretty user interface where I could just input data and save it. It'd be a nice match to the Data UI that you use to create your managed object model. Alas, there's nothing like this yet, though perhaps Apple will support it someday.
You Can't Create Your Peristent Store By Hand. When I was still programming with SQLite, I'd typically create a prefilled database by hand, then copy it into my project. If you try to do this when using Core Data everything will break.
The reason is that Core Data puts a lot of extra junk into your persistent store to model internal IDs, joins, and other things. The result is a lot more complicated than the simple managed object model that you see on the surface.
Because you can't create all of the connective tissue that Core Data requires on your own, you have to let Core Data to it on its own. Which means that you must prefill your data programatically.
Prefilling in Objective-C
If you want to prefill your data, you probably have two objectives. First, you want to make it easy to update the prefilled contents of your persistent store. Second, you want take the burden/complexity of parsing your initial data out of the program that your users will be running.
I thus suggest the following five-step process:
- Create your data in a comma-separated file, typically placing each row of data (an entity) in a row of the file and separating different columns (its attributes) by commas.
- Write a standalone program and copy in your .xcdatamodel file from your main project.
- Write code in your new program that parses your comma-separated file and inserts the information into a Core Data persistent store that should be identical to the persistent store in your main project.
- Run the program in the Simulator
- Copy your data from the Simulator's documents directory into your actual project's bundle.
A Prefilling Example
Here's an example of a few comma-separated lines of data for my current card game:
1,A$20,Australian Dollars,20,aussie-20.png
2,R$20,Brazilian Reals,20,brasil-20.png
After creating that complete CSV file, I then made a new Core Data project. Whereas my original program was called "Money", this new one was called "MoneyCreateCards". I made sure to copy in Money.xcdatamodel. I then edited the peristentStoreCoordinator method in my app delegate so that it'd output to a file using the same name as my original program:
NSURL *storeUrl = [NSURL fileURLWithPath:
[[self applicationDocumentsDirectory]
stringByAppendingPathComponent: @"Money.sqlite"]];
I'm pretty sure this isn't strictly necessary, but when you're dealing with data abstractions doing super secret things, it's better to cover all your bases.
Then I wrote some code to read everything from my CSV and output it to my sqlite file:
- (void)setupCards {
NSString *paths = [[NSBundle mainBundle] resourcePath];
NSString *bundlePath = [paths stringByAppendingPathComponent:@"cards.csv"];
NSString *dataFile = [[NSString alloc] initWithContentsOfFile:bundlePath];NSArray *dataRows = [dataFile componentsSeparatedByString:@"\n"];
[dataFile release];Card *card;
for (int i = 0 ; i < [dataRows count] ; i++) {
NSArray *dataElements = [[dataRows objectAtIndex:i]
componentsSeparatedByString:@","];
if ([dataElements count] >= 4) {
card = (Card *)[NSEntityDescription insertNewObjectForEntityForName:@"Card"
inManagedObjectContext:[self managedObjectContext]];[card setId:[NSNumber numberWithInt:i]];
[card setName:[dataElements objectAtIndex:1]];
[card setType:[dataElements objectAtIndex:2]];
[card setWorth:[NSNumber numberWithInt:
[[dataElements objectAtIndex:3] intValue]]];
[card setImages:[NSSet setWithObject:
[self setupCardPic:[dataElements objectAtIndex:4]]]];
[self saveAction:self];
}
}}
The parsing was pretty simple. I just separated rows by \ns and columns by ,s. It's totally non-robust, but since I'm creating the CSV file, that's OK.
Afterward, I followed the methodology that I explained for inserting data in the last article. I created a new entity, set its attributes, and saved the managed object context.
An Aside on Relationship Creation
Note in particular the setImages: message. It's a nice addendum to my previous article, about inserting. If you look back at my article on the data model you'll see that images is a one-to-many relationship between the Card entity and the CardPics entity. To set this relationship attribute, I had to do two things.
First, I created a CardPic (unless the same CardPic had already been created for an earlier card):
- (CardPics *)setupCardPic:(NSString *)fileName {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"CardPics"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];NSPredicate *predicate = [NSPredicate
predicateWithFormat:@"name=%@",fileName];
[fetchRequest setPredicate:predicate];NSError *error;
NSArray *items = [self.managedObjectContext
executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];if ([items count] >= 1) {
return [items objectAtIndex:0];
} else {
CardPics *newPic = (CardPics *)[NSEntityDescription insertNewObjectForEntityForName:@"CardPics" inManagedObjectContext:[self managedObjectContext]];
[newPic setName:fileName];
[newPic setSize:[NSNumber numberWithInt:150]];
[self saveAction:self];
return newPic;
}
}
Note the search I do before the creation, offering another example of Core Data programming.
Second, I set the Card's relationship attribute to be an NSSet containing the CardPic as we saw in the previous example. Note that the use of NSSet in this manner is always required to set a one-to-many relationship's attribute.
Finishing Up
That's pretty much it. When I was all done I copied the resulting Money.sqlite file from my card-creation program to my card-playing program.
Whenever I want to make a change, I just modify my CSV file, delete my MoneyCreateCards program, and rerun it. (If I'd wanted to be fancier I could have had the program empty its database and recreate it every time, but I'm less inclined to spend much effort on a utility program of this sort.) The only thing I have to be careful about is making sure that my MoneyCreateCards program's .xcdatamodel stays synced with that in my Money program. If it doesn't, you can get bizarre errors.
Next Up: This is actually the last of my planned article on Core Data. If you haven't read them all, I suggest you click on the "Core Data" tag, just below. Also, if there are Core Data topics that you'd particularly like to learn more about, feel free to drop me a line in the comments. If it's something I've already done work with, I'm happy to expand on it in some future article.

Great series on Core Data! These have helped out on a project that I've been working on.
May I request that you do a post on using NSFetchedResultsController?
Posted by: David Pedigo | September 18, 2009 at 08:06 PM
Wonderful series! Thanks so much for compiling it -- it makes a great addition to iPhone in Action, which I also own and love.
Another vote for an NSFetchedResultsController example. Would also be great to learn more about Core Data Migration; I understand that changing the model in a point release renders previous versions of an app's data unreadable by default.
Would be happy to pay for tutorials on both of these. Your writing is so clear and easy to follow. Thanks again!
Posted by: Nick | September 21, 2009 at 04:31 PM
Thank you for the kind words. I do plan to write something on NSFetchedResultsController based on your requests. Probably next week, if my programming schedule allows.
Posted by: Shannon Appelcline | September 21, 2009 at 04:36 PM
Thanks for the great series on this under-documented subject.
I would like to know more about updating the pre-filled data in a point release. For instance, say you have an app for bird-watchers which stores details of bird species in a core data database. How would you add more birds in the next release? Also, if you have a 'favourite birds' part of the interface, does that require a separate persistent store?
Posted by: jerry | September 22, 2009 at 04:32 AM
This is great tutorial. I am pretty new to app development and this series was written like I wanted to learn. It has great analogies to mitigate the apple tech speak. I am working on a database of cities and states and was able to completely finish the database from prefilling to random access with this tutorial. Thanks again.
Posted by: Rick | October 07, 2009 at 07:08 AM
This has been a great series.
I've been able to follow everything and have it work in the simulator.
However, in having it work on the phone it seems I'm doing something wrong on the last step.
"Copy your data from the Simulator's documents directory into your actual project's bundle."
Could you elaborate on exactly which files need to be moved?
Posted by: Simon | October 12, 2009 at 09:39 AM
Just wondering if anyone knows why the saveAction has been removed from the CoreData template in the latest SDK 3.1 and SDK 3.1.2?
Posted by: Jason | October 13, 2009 at 08:47 AM
I'm wondering the same thing, Jason. There is no saveAction: method in my files either. I tried adding one but it's giving me the "may not respond" warning. Anyone know anything about this?
Posted by: Jeff | December 30, 2009 at 06:54 AM
You can add saveAction back in to the app delegate, then change the appWillTerminate method to call that. It probably says "may not respond" because you did not declare saveAction: in the app delegate header (.h) file.
Posted by: mohrt | January 24, 2010 at 02:37 PM
Your Core Data posts have helped me tremendously.
I've gotten much more out of them than a Core Data book I bought.
Thank you.
Posted by: DrStrangecraft | March 12, 2010 at 11:51 PM
(part II of comment)
I've now successfully pre-filled data using exactly the technique you describe. That was cool, and very useful.
I have a related question. The data I've pre-filled requires one of the entities to be pre-selected as the "default". Typically that would mean using an object ID string in an NSUserDefault, and then providing that ID string in a dictionary to be registered upon the first launch of the app. This method works, but something in me gets a little worried about the object ID string and the entity getting out of sync somehow.
A different method would be to create a new "isDefault" attribute in the entity itself, and search through them upon launch.
I don't suppose either of these is superior, but I wonder if you have run into a similar situation, and if so how you handled it.
Thanks.
Posted by: DrStrangecraft | March 13, 2010 at 11:36 AM