Classic CoreData error - iphone

I'm getting this classic error :
The model used to open the store is incompatible with the one used to create the store
This is how it's implemented :
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObject *newShot = [NSEntityDescription insertNewObjectForEntityForName:#"shotName" inManagedObjectContext:context];
NSString *newName= #"test";
[newShot setName:newName];
And this is how it's designed :
No only I'm getting a crash with the message above, I'm also getting this warning :
'NSManagedObject' may not respond to '-setName:'
Obviously something is wrong somewhere, I think I'm using Strings on both side though .
Edit, I'm now using this after Eimantas's comment :
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObject *newShot = [NSEntityDescription insertNewObjectForEntityForName:#"shotName" inManagedObjectContext:context];
NSString *newName= #"test";
[newShot setValue:newName forKey:#"shotNumber"];
[context saveAction];
But I`m still getting :
'NSManagedObjectContext' may not respond to '-saveAction'

Use setValue:forKey:
UPDATE
NSManagedObjectContext has save method, not saveAction. So:
NSError *error = nil;
[context save:&error]
if (error) {
[NSApp presentError:error];
return;
}

insertNewObjectForEntityForName:#"shotName" must be insertNewObjectForEntityForName:#"Shots". Shots is the entity name. shotName is the name of an attribute of the entity Shots. Also, like with Objective-C class names, it's standard to use singular names for your entity objects. So, Shots should be be Shot (recommended, but not required).
Also, if you change around your AppName.xcdatamodel file & generate new NSManagedObject files, you may also get the error: The model used to open the store is incompatible with the one used to create the store upon app launch. It's because it's using the old persistent store file. I call it: AppName.sqlite, but you may have a different name for this file. Search in your project for something like:
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"AppName.sqlite"]];
Then, once you know the name, to find the file, do:
find ~/Library/Application\ Support/ -name AppName.sqlite
Then, remove the file, and build & run again.

Related

How do I assign a related object in Core Data?

Im my iPhone project, I have a data model with 2 objects: Profile and Contact. They are related to each other, with 1 Profile having many Contacts. Here is what I'm not sure about:
What is the right way to assign a Profile to a Contact when I add the Contact to the database?
I know how to add the contact to the database, but I need to know the right way to add the profile.
self.moc = [((AppDelegate *)[[UIApplication sharedApplication] delegate]) managedObjectContext];
Contact *contact = [NSEntityDescription insertNewObjectForEntityForName:#"Contact" inManagedObjectContext:self.moc];
contact.recordID = recID;
contact.profile = ????????
NSError *err = nil;
[self.moc save:&err];
Do I need to fetch the Profile first? Do I use a separate managedObjectContext, or the same one?
I have the first name of the profile, which I know I can use to fetch it like this:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.moc = [appDelegate managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(firstName like %#)", appDelegate.currentProfile];
Profile *fetchedData = (Profile *)[NSEntityDescription entityForName:#"Profile" inManagedObjectContext:self.moc];
I think I could do it like this, but if somebody could give me a code block with the best practice, that would be great. Thanks!
You fetch the profile first, you can use the same MOC. Then just assign it to the profile property on the Contact.
[NSEntityDescription entityForName:#"Profile" inManagedObjectContext:self.moc] is not returning a Profile object though. It returns an entity description.
+ (NSEntityDescription *)entityForName:(NSString *)entityName inManagedObjectContext:(NSManagedObjectContext *)context
You need to use a fetch request on the MOC. Checkout this:
http://cocoawithlove.com/2008/03/core-data-one-line-fetch.html

CoreData DetailTableView BAD_ACCESS Error

Maybe I'm not going about showing a detail for a selected row using CoreData, but I can't figure out why I'm getting a "BAD_ACCESS" error. I've googled around and can't find what I'm looking for.
Basically I use CoreData to populate the data for a Table View. It retrieves all of the title attributes for all of the entities. When the user clicks on a row, I have a Detail View that needs to show the description for that entity. I think I need to make a new NSManagedObjectContext and a new NSEntityDescription for a new NSFetchRequest in my DetailViewController and then use a NSPredicate to say "where title = [user selected title]". I get an error when I select a row. See code:
- (void)viewDidLoad
{
// Do any additional setup after loading the view from its nib.
// Get the objects from Core Data database
Caregiver_Activity_GuideAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Definition"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(title = %#)", self.title];
[request setPredicate:pred];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error!");
// Do whatever error handling is appropriate
}
for (NSManagedObject *oneObject in objects) {
[definitionDescriptionTextView setText:[oneObject valueForKey:#"desc"]];
}
[objects release];
[request release];
[super viewDidLoad];
}
I comment out that code and everything works. But when I try to debug with breakpoints, nothing catches. So I'm more confused.
I know CoreData is probably overkill for what I'm doing but this is a learning app for me.
EDIT: I didn't include that I'm using a sqlite database that is pre-populated with the entities.
You can also download my project on my github page.
Normally, with a Core Data backed Master-Detail interface, you don't fetch for the Detail view.
When you select a row in the Master tableview, you are selecting a particular managed object instance. You then pass that managed object instance to the detail view. There is no need to refetch the object that you selected in the tableview.
A good example of this would be the Contacts app. The Master tableview would be a list of Contact objects (displaying the name.) When you select a row, the Master tableview controller takes the specific Contact object associated with the selected row and then passes it to the Detail view controller which then populates the Detail view using data taking from the properties of the passed Contact object.
So, that entire code block where the error occurs is unnecessary.
However, the immediate error in this code is that you are releasing an object you didn't create. In this line:
NSArray *objects = [context executeFetchRequest:request error:&error];
... you are not creating a NSArray instance with a init, new or create method. Instead, you are merely receiving an autoreleased NSArray instance created and returned by the context NSManagedObjectContext instance. When you release an object you did not create here:
[objects release];
... you cause the crash.
Conversely, you do create a NSFetchRequest here:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
... because you used init so you do have to balance that with:
[request relwase];
BTW, this type of code should not be put in viewDidLoad as the method is only called when the view is read in the first time from the nib file on disk. That is only guaranteed to happen once as the view may remain in memory when the user switches to another view. Instead, put code that needs to run each time the view appears in viewWillAppear.

How to correctly implement a custom initializer for a subclassed NSManagedObject

I am wondering what the correct way is to create my own initializer of a class that is subclassing NSManagedObject.
Currently I am initializing like this:
-(id)initWithXML:(TBXMLElement *)videoXML
{
// Setup the environment for dealing with Core Data and managed objects
HenryHubAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityHubPieceVideo = [NSEntityDescription entityForName:#"HubPieceVideo"
inManagedObjectContext:context];
self = [[HubPieceVideo alloc] initWithEntity:entityHubPieceVideo insertIntoManagedObjectContext:context];
// do stuff and then save
NSError *error;
if(![context save:&error])
{
NSLog(#"HubPiece video save context error: %# %#", error, [error userInfo]);
}
}
Seems like some others also do it this way.
Just found that the NSManagedObject reference says:
If you instantiate a managed object
directly, you must call the designated
initializer
(initWithEntity:insertIntoManagedObjectContext:).

How to Implement Primary Key in Core Data for Iphone

I am working on with Core Data. I need to keep a unique value of bandID within Core Data.
In my data model i am having
bandImagePath
bandID ---------------primary Key
bandName
Code:
-(IBAction)addToFavButtonPressed:(id)sender
{
NSLog(#"add to fav button clicked");
SongRequestAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *addToFav;
addToFav = [NSEntityDescription insertNewObjectForEntityForName:#"AddToFav" inManagedObjectContext:context];
[addToFav setValue:self.imageUrl forKey:#"bandImagePath"];
NSLog(#"band image path url is %#",self.imageUrl);
[addToFav setValue:self.bandName forKey:#"bandName"];
NSLog(#"band name is %#",self.bandName);
[addToFav setValue:self.bandId forKey:#"bandId"];
NSLog(#"band ID is %#",self.bandId);
//NSLog(#"eno is %#",eno.text);
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
Short answer: you can't, automatically.
Long answer: Core Data does not provide a way to specify an attribute as "unique". There are several reasons why. Rest assured that if this were an easy thing to put in, it would have been put in long ago. So how do you work around it? Basically, you have to first check to see if an AddToFav exists with the specified bandId. If it does, then you don't create a new one. If it doesn't, then you can.

Core Data Edit Attributes

So im really new to core data, but i went through a tutorial and pretty much understand it, well at least the idea behind most of the things. But I still have 1 question that i cant find anywhere. It seems really simple but here it is. If I were to have two strings inside one entity lets say:
1.name
2.position
If the name is already entered how might i allow a user to enter text into a textField and assign it to their position at a later time? Even if there were 20 names, considering no duplicates?
I was thinking it might be something like this...But it doesnt seem to work.
UserInfo *userInfo = (UserNumber *)[NSEntityDescription insertNewObjectForEntityForName:#"UserInfo" inManagedObjectContext:managedObjectContext];
if ([userName isEqualToString:"#James"]) {
userInfo.Position = nameField.text;
}
On the code above you are casting (UserNumber*) to an object that you are declaring as (UserInfo*)? Which is what and is there any reason why you are doing that?
If I understand your question correctly, you want to create a record with only the username pre-populated and then allow that record to be updated at a later stage.
I will assume your entity is called UserInfo and that there are 2 NSString properties created for it - userName and position. I also assume you have created the class files for UserInfo and imported the header into the relevant view controllers.
Here's how you would do it:
1) Firstly, assuming you have username typed in a UITextField *userNameField, let's create a new record.
UserInfo *userInfo = (UserInfo*)[NSEntityDescription insertNewObjectForEntityForName:#"UserInfo" inManagedObjectContext:self.managedObjectContext];
[userInfo setValue:userNameField.text forKey:#"userName"];
This will create a new instance of UserInfo in your managed object context and set the value of userName to the value on userNameField.text
Then at a later stage a user will get to a point where they can update their records in your app (you may need to think about authentication somewhere here). You will fetch the record that matches your specified username:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *userNamePredicate = [NSPredicate predicateWithFormat:#"(userName == %#)", userNameField.text];
[fetchRequest setPredicate:userNamePredicate];
NSEntityDescription *userInfo = [NSEntityDescription entityForName:#"UserInfo" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:userInfo];
NSError *error;
NSArray *fetchRequestArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
If the fetchRequest found match(es) to your userNameField.text paramater, they will be saved in the fetchRequestArray. There should only be a maximum of one object there if you take the necessary steps to make the userName property mandatory AND unique.
Access the object by grabbing the objectAtIndex:0 in the array and change it's position property:
UserInfo *userInfoToBeEdited = [fetchRequestArray objectAtIndex:0];
[userInfoToBeEdit setValue:positionTextField.text forKey:#"position"];
In both cases above, remember to invoke CoreData's save method when you are ready to commit your changes. Before save is invoked your changes are only kept in your managed object context which is basically a scratch pad for your persistent data.
[EDIT TO ADD SAVE METHOD]
As per your comment, I usually have the save method below in my AppDelegate (copy/paste directly from Apple template)
- (void)saveContext
{
error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
[self seriousErrorAlert];
}
}
}
And then whenever I need to save changes, from any view controller I simply grab a reference to my AppDelegate and fire it off:
AppDelegate *theDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[theDelegate saveContext];