Aw geez! I screwed something up!
I'm a Core Data noob, working on my first iOS app. After much Stack Overflowing I'm using this code:
NSString *path = [[NSBundle mainBundle] pathForResource:#"CoreData" ofType:#"momd"];
if (!path) {
path = [[NSBundle mainBundle] pathForResource:#"CoreData" ofType:#"mom"];
}
NSAssert(path != nil, #"Unable to find Resource in main bundle");
CoreData is the name of my app.
I've tried to put in initial data into the app by finding the path to the sqlite file in my iPhone simulator, and then going and inserting into that sqlite file. But at some point, I moved the sqlite (thinking it would create a fresh copy), deleted the app from the simulator, and the sqlite file is gone. I'm not sure if I'm leaving out some part of the process (this was a few hours ago) but the end result is that everything is screwed up.
How do I resubstantiate this sqlite / momd file? "Clean" and "Clean all targets" are grayed out.
I'm happy to post the relevant code from my app that would help shed some light on this problem but there's tons of code relating to Core Data which I don't understand, so I'm not sure what part to post! Any help is greatly appreciated.
Here are a few recommendations:
The code you posted to get the .mom(d) file is not exactly the recommended way. Use mergedModelFromBundles instead, as in
self.managedObjectContent= [NSManagedObjectModel mergedModelFromBundles:nil];
It takes care of getting the path, choosing/merging the correct mom or momd, and initializing of the MOC all by one step. You should use this. But note that you need to clean the build process once in a while, as discussed in this SO question/answer.
This shows that, although crawling through StackOverflow is often good, that's not the best approach when you deal with a big framework like CoreData.
Honestly, take a day and read the documentation from the start to the end. You might want to google bits of code and to start coding immediately, but reading through the documentation definitely saves the development time considerably in the long run.
Also, Marcus Zarra's CoreData book (see here) helped me a lot. It might look expensive, but it was totally worth while.
On a different topic, I don't think it a good strategy to put the pre-cooked sqlite file into the simulator's directory (inside ~/Library/Application Support/iPhone Simulator/) even for development purpose... because it doesn't work on the real device. If you need to do so, put the sqlite file as a resource of the app, and copy it at the launch time to either Documents or Caches, and use that afterwards.
If the clean targets menu items are grey, it could be that you have the app running.
The way you're getting the path to the mom/momd looks like you've tried to integrate CoreData after the fact (rather than opting to have it included when you created the project). I had the same issues as you probably do. The default code that looks for momd assumes it exists, and you should be able to, as well, if you add a version of your data model to the project.
To do that, select the xcdatamodel file, and in the Design menu select Data Model > Add Model Version. Then, you'll have CoreData.xcdatamodeld with a subtree containing Coredata.xcdatamodel and a new version. You can just delete the extra version and you'll have the hierarchy you need. If you build and look in the App bundle, you'll see the CoreData.momd directory with the mom inside.
Those steps aren't included in any of the CoreData tutorials I've found so far. I hope it's helpful!
Practical answer:
You might try the following code if you're having path issues. You'll also want to check that your xcdatamodel file is included as a resource for your target.
[[NSBundle bundleForClass:[self class]] pathForResource:#"CoreData" ofType:#"momd"]
Better answer: There's a distinction between a momd file, which represents your NSManagedObjectModel, and a sqlite file, which is used by your NSPersistentStore object. The best approach to working with CoreData is to let the SDK handle all the interaction with sqlite. The only thing you should need to set up regarding sqlite is telling your NSPersistantStore where to stash your sqlite file. The managed objects that you define in your managed object model will handle all the data insertion and updates for you.
If you're not familiar with the objects I've referenced, check out the apple docs for a very helpful overview. You'll definitely want to take the time to grasp the full CoreData stack, as the time investment will pay off in far less headaches if you work with Core Data the way Apple expects. It is a more abstracted design than many web developers are accustomed to, but trying to circumvent that abstraction by working directly with SQLite creates its own set of complexity.
http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdBasics.html#//apple_ref/doc/uid/TP40001650-TP1
I found it very valuable to go look at my executable in the simulator "disk image".
If you head into ~/Library/Application Support/iPhone Simulator//Applications, you can see the apps on the phone. Figure out which one is yours (use find, or look at the date), and then browse into your target.app and look for the .momd file.
In my case, my momd name was different from my .xcdatamodeld file name, which was why core data couldn't load it. Once I corrected the name in the AppDelegate things started working correctly.
Note that the momd "file" seems to actually be a directory (like the .app setup).
--
In terms of adding the code to initialise core data; I did this too. I created a new blank project with core data enabled, and copied over all the initialisation steps for core data in the AppDelegate file. There's a whole bunch of stuff related to initialising the whole stack, so if that is not present, you need to put it there.
If you added a version to your model, change .mom to .momd
NSString *path = [[NSBundle mainBundle] pathForResource:#"CoreData" ofType:#"momd"];
if (!path) {
path = [[NSBundle mainBundle] pathForResource:#"CoreData" ofType:#"momd"];
Related
I'm creating one iphone application that uses and xml to get some data from and a few images. Lets say that originally these files will be in the application bundle. But then I want to make my application to get updates from a web service. So lets say I download a new xml and new Images.
Where do I save them? I think I will be able to save them in the application "cache" right?
But then How do I make my application check if this resource exists in the cache, then load that one...else load the one in the application bundle?
iOS to be used 4.3 but if it makes it easier we can go to 5.
You will have to use NSFileManager to save the file to and load it from the document directory. Check out the following link, which actually contains all the info you need:
http://www.friendlydeveloper.com/2010/02/using-nsfilemanager-to-save-an-image-to-or-loadremove-an-image-from-documents-directory-coding/
If [UIImage imageWithContentsOfFile:fullPath]; (fullPath being the NSString with contains the path to your image in your document directory) returns nil, then the image is not present, so you'll have to load the default image from your bundle.
You can access your default bundle files using [[NSBundle mainbundle] pathForResource:#"yourFileNameHere" ofType:#"yourTypeHere"] This will return the path to your file in your bundle and you can just pass it to [UIImage imageWithContentsOfFile:]
This example only applies to UIImage, but you can easily adapt it to use other classes. I think the UIImage example is enough for illustrative purposes though.
You'll probably want to create a versioning scheme. After the app starts (or periodically) call the web service for a very cheap call to ask what version it has. You'll know what version you have (write in a plist or have the version number in the folder structure). Another options is pushing a notification.
The next thing you'll have to think about is how the app reacts to getting a new data set. You could do it only on start up but that might block the start up experience if you're bounding it to a web call. The other option is to allow the dataset to change while the app is open. You could have a model that uses NSNotificationCenter to notify your views and controllers that the data has changed. I would probably version the storage as well (folder per) to help with the transition.
As for how, you can make web requests with something like ASIHttpRequest, NSFileManager to write to the documents directory, and plists to save settings like version.
I have an app that when first launched reads in its data from a local XML file and is then archived for subsequent loads.
In future app updates its envisioned that this XML file might be updated with more data. To determine if I should re-load the XML data I have placed a custom key/value (a version number for the XML file) in the info.plist.
Now when I update the version number from 1.0 to 1.1 in the plist file, the app still reads it as 1.0. I'm guessing there is some kind of caching going on. Is there a way I can get the updated version on each load?
NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary];
NSString* version = [infoDict objectForKey:#"IdeasVersionNumber"];
NSLog(#"version = %#",version); // always 1.0?!!
Is there a better method to do this? I didn't want to create a separate plist/XML file just to hold the version number as it seems like a waste of resources to load/parse these each time when the info.plist is always read.
Many thanks for any advice.
I am surprised by this behavior, but as it looks like the Info.plist is not always read, you might as well make a separate file for it. Or use NSUserDefaults.
A simple clean build did the trick! Thanks to #MCannon for the tip. I should have copped onto it.
i'd like to have the user tap a cell in the tableview and hear audio. the information for the cell is loaded from core data, so i'm assuming i put the path to the audio file in core data as an attribute with type 'string'.
i am completely lost as to where to go from here. how do i call the path? after a lengthy search, i haven't found much on the topic so any help is greatly appreciated.
You should store just the file name in the managed object and then reconstruct the file path each time.
In iOS the name of the app's directory is just a UUID and it is different on every install. Further, the system may change the directories UUID without warning. This is part of the security system.
Suppose you wanted to put the audio files in a directory called AudioFiles in the Library directory. You would do something like this:
NSString *fileName=//... file name from the managed object
NSArray *libraryPaths=NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libPath=[libraryPaths objectAtIndex:0];
NSArray *components=[NSArray arrayWithObjects:libPath,#"AudioFiles",fileName,nil];
NSString *theAudioFilePath=[NSString pathWithComponents:components];
See Low-Level File Management Programming Topics:Standard System Directories
Depending on how you play the audio files, you may need to convert the file path to a file URL.
Your question is quite wide. The best way for you to get started is to work through Apple's avTouch example. It will show you how to load and play audio files.
I'm working on an iPhone app that uses Core Data. Most times, I just test in the simulator, but occasionally pump the app down to the iPad to make sure.
I've recently changed my Core Data model, and now when I send the app to the iPad, I get a SIGABRT exception telling me:
Can't merge models with two different entities named 'foo'
OK, that I understand. Old version of the database exists on the device. So, I (try to) kill the old version by press/holding the application's icon until it starts wiggling, and then tap its "X". The iPad asks me if I want to delete the application and all of its data. I say yes.
I rebuild the app, targetting the iPad, and get the same error.
Is there a trick to getting the old database to really go away?
For those who come across this question after trying to use core data lightweight migrations:
I was having this issue even after following the instructions for creating a new version of my data model. I noticed that there were two ".mom" files in my application bundle, one ".mom" and one ".momd" directory that contained ".mom" files.
The key is to replace the implementation of - (NSManagedObjectModel *)managedObjectModel that is generated for you with this implementation:
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"Foo" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel; }
where 'Foo' is the name of your data model.
Hopefully this is useful to someone - I spent WAY too many hours beating my head against the wall on this. Thanks again, Apple! :)
The persistent store will remain until you delete the app off of your device just like in the simulator. If you really want to start over, then delete the app off of your iPad and it will use the new model.
However as everyone else has pointed out, that is not the error you are getting, Do a clean build of your application (meaning select Build -> Clean from the menu in Xcode) and do a fresh build. If the error still remains then you have more than one xcdatamodel file being compiled in your project.
Your error message doesn't mean you have two databases i.e. persistent stores. It means you have two overlapping model files (which are source files) in the Xcode project itself. Deleting the build does not good because the project just recreates the error upon the next build.
Model files cannot overlap. You need to remove the duplication. The duplication comes from either (1) having two versions of the same file or (2) having two model files with the same entity in both.
For (1), you need to remove the older duplicate. The model files have an extension of modelName.xcdatamodel. To find the duplicate, open the target and check under Compile Sources. All the model files included in the target will be listed there. Remove the duplicate.
For (2), you can have as many model files as you like but you can't have two model files with the same entity in both. Sometimes, Core Data novices try to link to model files together by having them share one or more entities. If you've done that, you will need to remove the duplicate entity from one of the models.
Delete the app from your iPad, then restart it. After the iPad has rebooted, do a clean build.
I had the similar problem after renaming database model. After cleaning, rebuilding and reinstalling app -- the same consequences: two different named .momd bundles with the same content appears in the application bundle (oldname.momd and newname.momd).
I solved this problem by changing "Tools version" from 4.1 to 3.2 in "Core Data Model" (File inspector of Data Model) panel. After this I completely cleared up and re-builded the project, then changed back this property to 4.1 and again re-cleared and re-builded the app.
In this case Xcode makes two files "layout" and "elements" (for older version compatibility) against "contents" in *.xcdatamodeld bundle, this cause that Xcode's data model compiler rechecks name of the model and there all will be ok with .momd bundles in the app's sandbox -- only one appears after reinstalling the app.
"Can't merge models with two
different entities named 'foo'"
This sounds more like two datamodels beeing merged. Try a clean rebuild of your app.
Check if there really is only one datamodel in your project.
The default core data stack loads all data models in your bundle
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
If old models are present all of them are merged.
for me only the "Reset content and settings" of the simulator worked. otherwise I could not find any problems with my project.
You're eventually going to want to read about model migration so you can automatically update the core data database on the device when users upgrade.
But if you're in dev and you don't have any installed users, you can wipe out the core data database by going into iTunes with your device connected, deleting the app from your device, and syncing. Then drag the new version of your app into iTunes and re-sync. You should have a clean model.
I had the same problem.
Try to clean Shift + Cmnd + K and cleaning build folder ALT + Shift + Cmnd + K in Xcode. This worked for me fine.
I want to ship static read-only data for use in my Core Data model. The problem is that there are obviously different persistent store types and I don't know if the format of those types is supposed to be opaque or if I'm supposed to be able to construct them by hand.
Right now I just have a plist and it's very small (maybe 30 entries total).
Should I just write code to import the plist into my data store when the app is first installed, or is there some way I can ship a hand-constructed initial version of the data store file?
(I'm using the default sqlite persistent store.)
I would not try to hand-construct it, but you certainly should execute an import and save a final Core Data SQLite file to ship with your app.
I plan to write a small mac utility (using the same data model) to generate the Core Data SQLite file for my iPhone app (the import is actually from a web server). Then, I will add the file that was persisted by the utility into my iPhone app's project.
To add a bit to the answer to my own question, I noticed that the Recipes sample code application comes with a default sqlite backing store:
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"Recipes" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
But then again, for another purpose it comes with some static read-only data in a plist file! (TemperatureData.plist) So go figure....
Not many people know this, but you can actually use core data on OSX and use that store file then on the iOS. So essentially write some code that uses exactly the same schema and your model objects (they should all compile and work on OSX).
OSX development isn't really that hard to get your handle on, if you know iOS SDK :-)
HTH