Does coredata save after a change like this? - iphone

I have CoreData in my app, with an Entry class, which contains an NSOrderedSet of Media classes.
I then have this code, for adding a new Media item to the NSOrderedSet:
-(void)addImage:(UIImage *)image isInPhotoLibrary:(BOOL)isInPhotoLibrary {
Media *media = [[Media alloc] init];
media.type = #"Image";
media.originalImage = UIImageJPEGRepresentation(image, 1.0);
media.isInPhotoLibrary = [NSNumber numberWithBool:isInPhotoLibrary];
[self addMediaObject:media];
}
Will this automatically save the changes, or will I have to do it myself. If so, will i then need to pass in a context to do this, or is there another way?

No, this code doesn't have any Core Data references at all.
Is Media an NSManagedObject? If so you need to be creating it like so:
Media *media = [NSEntityDescription insertNewObjectForEntityForName:#"Media" inManagedObjectContext:context];
This will put it in your managed object context.
If you then want to persist it, you will need to call save: on the managed object context.
EDIT ALSO....
In your Entry class, you will probably have a generated method that you use to add objects to the NSSet. It will be in a category (CoreDataGeneratedAccessors) on the Entry header file
- (void)addMediaObject:(Media *)value;

No it won't.. If you want to save changes to Database in Core data you gotta call save function for that.. I assume Media is kind of NSManagedObject class. To save the changes to persistent store you have to call save method . Until then the changes are just temporary present on your scratch board/ ManagedObjectContext.
This is how I save changes:
Worker *worker = (Worker *)[NSEntityDescription insertNewObjectForEntityForName:#"Worker" inManagedObjectContext:self.managedObjectContext];
worker.name=txtContact.text;
worker.address=txtAddress.text;
worker.zipCode=txtZip.text;
worker.city=txtCity.text;
worker.mobile=txtMobile.text;
NSError *error;
if (![managedObjectContext save:&error])
{
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}

Related

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];

Saving an instance of an NSManagedObjectContext does not permanently save a mutable transformable attribute

I'm using Core Data + sqlite as a data cache. The app reads in a file into core data, then uses Core Data to run the app. Saves are done to both the NSManagedObjectContext and to the file. I've noticed, though, if I quit the app and reload it without repopulating the Core Data database, some (but not all) of the data saved using -save: is not being saved to the data store.
Changes to my managed object are all done in a single batch on the main thread, with the -save: message being sent after all changes are completed. The data that isn't being saved is a transformable attribute, the only transformable attribute in the core data object. Here's the code that saves the object:
NSInteger columnIndex = [headers indexOfObject:questionID];
if (NSNotFound != columnIndex) {
// parsedLine is a mutable array already
NSMutableArray *parsedLine = person.dataFromFile.parsedLine;
[parsedLine replaceObjectAtIndex:columnIndex withObject:answer];
person.dataFromFile.parsedLine = parsedLine;
person.questionsAnsweredByPerson = [NSNumber numberWithInt:[FileParser questionsAnsweredInRow:person.dataFromFile.parsedLine withRowHeaders:headers]];
person.address.street.questionsAnsweredByPeopleOnStreet = [NSNumber numberWithInt:[self questionsAnsweredByPeopleOnStreet]];
//NSLog(#"rawLineBefore:\n%#", person.dataFromFile.rawLine);
person.dataFromFile.rawLine = [ReconstructCSV composeCSVLineFromArray:person.dataFromFile.parsedLine];
//NSLog(#"rawLineAfter:\n%#", person.dataFromFile.rawLine);
Voter_SurveyAppDelegate *appDelegate = (Voter_SurveyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSError *error;
NSManagedObjectContext *managedObjectContext = [appDelegate managedObjectContext];
if (![managedObjectContext save:&error]) {
// XXX inform the user there was a fatal error opening the file. Low disk space?
NSLog(#"Unresolved error - could not save managedObjectContext - %#, %#", error, [error userInfo]);
abort();
}
return YES;
}
abort(); is not getting called, so I assume -save; is getting called properly.
I doubt it is related, but after this code is run on the main thread, I perform an NSFetchRequest using a new NSManagedObjectContext on a different thread. Nothing else takes place related to Core Data on other threads.
Why isn't the transformable attribute getting saved?
The problem is that transformable properties don't like Mutable objects. As noted in an answer to this question, an NSMutableDictionary wasn't getting saved. In my case, it was an NSMutableArray.

Save object in CoreData

I am using CoreData with iPhone SDK. I am making a notes app. I have a table with note objects displayed from my model. When a button is pressed I want to save the text in the textview to the object being edited. How do I do this? I've been trying several things but none seem to work.
Thanks
EDIT:
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:detailViewController.textView.text forKey:#"noteText"];
NSError *error;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
The above code saves it correctly but it saves it as a new object. I want it to be saved as the one I have selected in my tableView.
You should check out the Core Data Programming Guide. It's hard to know exactly what you want from the question, but the basic idea is:
-(IBAction)saveNote { //hooked up in Interface Builder (or programmatically)
self.currentNote.text = self.textField.text; //assuming currentNote is an NSManagedObject subclass with a property called text, and textField is the UITextField
}
//later, at a convenient time such as application quit
NSError *error = nil;
[self.managedObjectContext save:&error]; //saves the context to disk
EDIT: If you want to edit a preexisting object, you should get the object from the fetched results controller, e.g. NSManagedObject *currentObject = [fetchedResultsController objectAtIndexPath:[self.tableView indexPathForSelectedRow]], then edit that object. I'd also recommend using a custom subclass of NSManagedObject with property declarations, rather than using setValue:forKey, since it's more flexible.

NSUndoManager undo Not Working With Core Data

I am trying to create an iPhone application where the user can add entries. When he presses a new entry, a box will popup asking him for some information. Then he can either press "Cancel" or "Save" to discard the data or save it to disk.
For saving, I am using the Core Data framework, which works pretty well. However, I cannot get the "Cancel" button to work. When the window pops up, asking for information, I create a new object in the managed object context (MOC). Then when the user presses cancel, I try to use the NSUndoManager belonging to the MOC.
I would also like to do it using nested undo groups, because there might be nested groups.
To test this, I wrote a simple application. The application is just the "Window based application" template with Core Data enabled. For the Core Data model, I create a single entity called "Entity" with integer attribute "x". Then inside the applicationDidFinishLaunching, I add this code:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after app launch
unsigned int x=arc4random()%1000;
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext.undoManager beginUndoGrouping];
NSManagedObject *entity=[NSEntityDescription insertNewObjectForEntityForName:#"Entity"
inManagedObjectContext:self.managedObjectContext];
[entity setValue:[NSNumber numberWithInt:x] forKey:#"x"];
NSLog(#"Insert Value %d",x);
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext.undoManager endUndoGrouping];
[self.managedObjectContext.undoManager undoNestedGroup];
NSFetchRequest *fetchRequest=[[NSFetchRequest alloc] init];
NSEntityDescription *entityEntity=[NSEntityDescription entityForName:#"Entity"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entityEntity];
NSArray *result=[self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
for(entity in result) {
NSLog(#"FETCHED ENTITY %d",[[entity valueForKey:#"x"] intValue]);
}
[window makeKeyAndVisible];
}
The idea is simple. Try to insert a new Entity object, undo it, fetch all Entity objects in the MOC and print them out. If everything worked correctly, there should be no objects at the end.
However, I get this output:
[Session started at 2010-02-20 13:41:49 -0800.]
2010-02-20 13:41:51.695 Untitledundotes[7373:20b] Insert Value 136
2010-02-20 13:41:51.715 Untitledundotes[7373:20b] FETCHED ENTITY 136
As you can see, the object is present in the MOC after I try to undo its creation.
Any suggestions as to what I am doing wrong?
Your problem is caused by the fact that, unlike OS X, the iPhone managed object context does not contain an undo manager by default. You need to explicitly add one.
Change the generated code in the app delegate for the managedObjectContext property to look like this:
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
//add the following 3 lines of code
NSUndoManager *undoManager = [[NSUndoManager alloc] init];
[managedObjectContext setUndoManager:undoManager];
[undoManager release];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
After making that change, the 2nd log message is no longer printed.
Hope that helps...
Dave
I tried Dave approach, but did not work for me.
I finally found the solution in Apple's example CoreDataBooks
The trick is to create a new context that shares the coordinator with you App's context. To discard the changes you dont need to do a thing, just discard the new context object. Since you share the coordinator, saving updates your main context.
Here is my adapted version, where I use a static object for the temp context to create a new ChannelMO object.
//Gets a new ChannelMO that is part of the addingManagedContext
+(ChannelMO*) getNewChannelMO{
// Create a new managed object context for the new channel -- set its persistent store coordinator to the same as that from the fetched results controller's context.
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
addingManagedObjectContext = addingContext;
[addingManagedObjectContext setPersistentStoreCoordinator:[[self getContext] persistentStoreCoordinator]];
ChannelMO* aux = (ChannelMO *)[NSEntityDescription insertNewObjectForEntityForName:#"ChannelMO" inManagedObjectContext:addingManagedObjectContext];
return aux;
}
+(void) saveAddingContext{
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:self selector:#selector(addControllerContextDidSave:)
name:NSManagedObjectContextDidSaveNotification object:addingManagedObjectContext];
NSError *error;
if (![addingManagedObjectContext save:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:addingManagedObjectContext];
// Release the adding managed object context.
addingManagedObjectContext = nil;
}
I hope it helps
Gonso
It should work. Did you correctly assign the undo manager to your managedObjectContext? If you have rightly done that, it by default has undo registration enabled, and you should be good to go. There is a good article on core data here. There is a good tutorial on core data and NSUndoManager here. Hope that helps.