CoreData save/revert unsaved changes - iphone

I have a list of objects in a UITableView, managed by a NSFetchedResultsController. When the user selects an object, an edit view is displayed, allowing the user to edit the selected NSManagedObject instance. The properties of the object are updated in the object as the user makes changes.
The thing is, there is, as usual, a Save button and a Cancel button. If the user taps save, the NSManagedObjectContext is saved, and the change is reflected. However, if the user taps cancel, I need the object to revert to the state it was in before. Calling [managedObjectContext rollback] doesn't work for this purpose.
Does anyone know how to properly implement this? I can not store the object's properties as temporary separate ivars during editing, because there are multiple objects that could be edited, all of which have different properties.
Update
What I'm currently doing is storing the NSManagedObject in an instance variable, and calling save: to save and rollback to cancel. Instance variables are modified using either object.property = something or [object setValue:something forKey:#"property"]. This does not work as expected, instead producing this behaviour:
If you edit and save, changes update as expected. However, if you edit again, and cancel without making changes, the state of the object reverts back as it was before the save.
If you edit and cancel, the changes are still updated, as if saved. Editing and cancelling again (without making changes) will revert it back to the state it was in before.
Also:
Could this have anything to do with the way I initialise the NSFetchedResultsController that is used to populate the table view? Code:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSString *entityName = self.entityName;
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[fetchRequest setIncludesPendingChanges:NO];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_context sectionNameKeyPath:nil cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;

Look http://www.section42.de/coredata-how-to-revert-a-managed-object-to-its-original-state. This will help you for sure.

You need to make sure you're setting an NSUndoManager instance on your managed object context as it is nil by default on iOS. Related solution here:
https://stackoverflow.com/a/2214982/318456

Having given up on all the mechanisms in place in CoreData to do this (none of them worked), I ended up storing all the properties of the object as separate variables in the edit view controller, and rolling back to them on cancel.

I had the same problem. It happened because the textField was in modification process when user click back. If user click done on keyboard before click back then everything works fine.

I would store the NSManagedObject as a property of the edit view controller. Then you can edit all of its properties, and either save or rollback when dismissing the view.

Related

iOS init UIWebView to clear navigation history

I am setting uiWebviewSocial as desired on IB and is loading requests as normal. However, later I need also to init this uiwebview in order to clear goBack history. I am using another uiwebview object for this purpose, webViewBridge
- (void)viewDidLoad
{
[super viewDidLoad];
self.webviewBridge = [[UIWebView alloc] init];
self.webviewBridge = self.webViewSocial;
when I need to init UIWebViewSocial, I use following
self.webViewSocial = nil;
self.webViewSocial = [[UIWebView alloc] init];
self.webViewSocial = self.webviewBridge;
however, if I then make goBack action, uiwebview loads an old request when should have its history empty. What I am missing? thank you
Well, I've been going aroundUIWebView in Stack Overflow these days and is a mess, for me at least.
It seemsUIWebView cache works at its own, I mean we cannot change neither access some info that is exclusively managed by system...
About your problem, it seems although yourUIWebView is sent to nil, it keeps getting info from cache... so, you should clear cache firstable
You can check it here and there
The problem is that after you are init'ing the new webviewBridge or webviewSocial, you are immediately overwriting the new object with the assignment on the next line. Remove the third line in each method.
If I am not misunderstanding, when you load the view you get an "initial" webviewSocial (with history and contents??), then after some loadings you make in the UIWebView, these are stored as history in webviewBridge, isn't it?
When you send webviewSocial to nil, that shall be empty... but NOT webviewBridge... and then if you are reloading webviewSocial with webviewBridge (which in fact is previous whole history), that's why it happens... I think
Saludos

+entityForName: could not locate an NSManagedObjectModel for entity name

I was chugging along just fine with a project that uses core data for months now since I wired in the core data stack and migrated the database and last night just started getting an error on the Managed Object Model.
I know that what it is doing is trying to establish a managed object context in this line
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Question" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
The program is crashing in the first view controller when it tries to get the Question entity. I declare/create the AppDelegate
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"PSQ" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
//managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return managedObjectModel;
}
When I set a breakpoint in the app delegate, it never gets there before the app crashes. It goes to the view controller first.
The weird thing is, I did not change my core data functionality for about 6 weeks since it was finished and working.
Any ideas what I might be doing?
This began happening while i was adding NSUSerdefaults for an NSSwitch to toggle the option to display an into screen or not.
Update: The app crashes at the point mentioned only in the iPhone version and goes through the managed object issues just fine in iPad. What would be in the .xib files that would be different causing the flow to error at the managedObjectContext?
Ok got it fixed.
Turns out that somehow the app delegate got rewired in my iphone nib. I guess that is what Xcode was trying to tell me when it would hit up the mainViewController before the app delegate for the managed object context.
I set the class back to UIApplication, then wired the delegate to the app delegate in IB and all is well.

Sort List Based On Custom User Settings

I'm working on an app where a user can upload and download information. The information is downloaded in such a way that when it's downloaded, it's ordered according to when it was submitted.
What I'd like is for the user to be able to reorganize the table view, save the order to a preference file (all ready working) and from now on, whenever data is request, the order now conforms to the users re-organized list.
Maybe my brains just fried, but is there an array function that can accomplish this, or am I on my own? Any suggestions how to go about writing something like this?
Thanks
What about NSSortDescriptor:
NSArray *sortedArray = [originalArray sortedArrayUsingDescriptors:
[NSArray arrayWithObject:
[[[NSSortDescriptor alloc] initWithKey:#"keyToBeSorted"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)] autorelease]]];

Why doesn't the undo/redo panel appear when I start a shake gesture in iPhone Simulator?

I've created an NSUndoManager for the Managed Object Context of Core Data, like this:
NSUndoManager *undoManager = [[NSUndoManager alloc] init];
[undoManager setLevelsOfUndo:10];
[managedObjectContext setUndoManager:undoManager];
[undoManager release];
In the app delegate where the didFinishLaunching method is called, I did this:
application.applicationSupportsShakeToEdit = YES;
For some reason, I never get that undo/redo panel when I make a shake gesture in iPhone Simulator (from the menu). Must I enable undo/redo somewhere else, maybe in the Info.plist file?
As lukya pointed out, you already asked this question back in March.

CoreData is saving model without the save: method being called

I have a list of items with a plus button in the navigation bar that opens up a modal window containing a table of the models attributes, that displays a form when the table items are clicked (pretty standard form style). For some reason if I click the plus button to open the form to create a new model, then immediately click the done button, the person model is saved. The action linked to the done button does nothing but call on a delegate method notifying the personListViewController to close the window.
The apple docs do state that the model is not saved after calling the insertNewObjectForEntityName: ...
Simply creating a managed object does not cause it to be saved to a persistent store. The managed object context acts as a scratchpad.
I am at a loss to why this is happening, but every time I click the done button I have a new blank item in the original tableView.
I am using SDK v3.1.3
// PersonListViewController - open modal window
- (void)addPerson {
// Load the new form
PersonNewViewController *newController = [[PersonNewViewController alloc] init];
newController.modalFormDelegate = self;
[self presentModalViewController:newController animated:YES];
[newController release];
}
// PersonFormViewController
- (void)viewDidLoad {
[super viewDidLoad];
if ( person == nil ) {
MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
self.person = [NSEntityDescription insertNewObjectForEntityForName:#"Person"
inManagedObjectContext:appDelegate.managedObjectContext];
}
...
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self action:#selector(done)];
self.navigationItem.rightBarButtonItem = doneButton;
[doneButton release];
}
- (IBAction) done {
[self.modalFormDelegate didCancel];
}
// delegate method in the original listViewController
- (void) didCancel {
[self dismissModalViewControllerAnimated:YES];
}
If you are using a fetched result controller, it updates itself based on the state of the context. The store is not updated until you call save: on the context, but the context itself absolutely is (it would make no sense otherwise).
Also, by default, when you close your application (if you used the CoreData template), it will save the current context, so these changes will persist.
If you don't want your table view to pick up the changes, you must delete the new entity from the context before returning to the previous view, or use two separate contexts (I urge you /not/ to chose this method unless you understand 100% how CoreData works).
You're misunderstanding the documentation.
Think of the application's data as a text document. A user can open the text document and type or delete things in it and it appears changed on screen but the file containing the text hasn't been saved yet. This is what's happening with your original table view: it's a visual presentation of the state of the data at that specific moment.
Just as a user may type into a text document without the the changes being written to file, the same thing is happening with your application's data. Every time you click on the plus button, a new model is created and the application is correctly representing it visually. However, the disk storage hasn't written a single thing.
The default Core Data application template from Xcode will save (write the data content to storage) when the user quits the app, making any data changes permanent. Until then, any changes are only affect what's in memory.