How to save temp. changes of an object? - objective-c

I need some advice how to handle the following case. I'm saving an object graph within core data. For simplicity lets say i have a User object (name, age, adress). Adress is another object with some properties. Now the User can change his Adress. If he changes it i need to remember these changes for the next order. After that i need to revert back to the original Adress. The User can also revert back at any time. Where should i save these temporary changes? I thought about adding a new entity like ChangedData but this somehow does not feel right. Basicly i need to remember the original object and if the User changes it i need to remember those for some Time as well. I hope i could express my problem well enough.

you need to use an NSUndoManager paired with your managed object context. Just add the following code to
- (NSManagedObjectContext *) managedObjectContext
in your app delegate implementation:
NSUndoManager *undoManager = [[NSUndoManager alloc] init];
[managedObjectContext setUndoManager:undoManager];
then use the methods of the undo manager to undo and redo your saves of the context.

Related

How do I prevent NSMutableArray from losing data?

The first view of my app is a UITableView.
The user will choose an option and the next view will be another UITableView. Now the user can tap on an "add" button to be taken to another UIViewController to enter text in a UITextField. That text will then appear in the previous UITableViewCell when saved.
The issue I am having: if I back out to the main view and then go back to where I previously was, that inputed text is now gone.
How can I make sure that text is not being released or disappears like this?
You might want to store this array somewhere else in your project, like in an MVC (data model). You could create a new class for it that passes the information through the classes and stores the array in one place. Then once you add to the array, you could reference that class and call a method in that class to store the text in the array and whenever you load the table view it loads with that array in the class.
In my case, I would do this, but I would make everything class methods (where you cannot access properties or ivars) and just store the array in the user defaults / web service or wherever you need and retrieve and add/return it like this:
+ (NSMutableArray *)arrayOfSavedData {
return [[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"];
}
+ (void)addStringToArray: (NSString *)stringFromTextField {
[[[[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"] mutableCopy] addObject: stringFromTextField];
}
The mutableCopy part is important because arrays don't stay mutable after you store them into the user defaults
The reason the text is gone, is probably because you're instantiating new controllers when you go back to where you were. You can keep a strong reference to your controllers, and only instantiate one if it doesn't exist yet. Exactly how to do this depends on how you're moving between controllers -- whether you're doing it in code, or using a storyboard for instance.
This kind of issue is very frequent. When you move around multiple controllers and views.
Each time you load a new view and controllers are alloc+init, new set of values are assigned and previous values are not used!!!.
You can use SharedInstance/SingletonClass so that it is allocated & assigned once and does not get re created with new set of values.

MagicalRecord: Create now, (possibly) save later

I've been using MagicalRecord quite a bit lately - and blimey it's been making my Core Data life much, much easier, so full credit to the author of what is a quality library!
Anyway, the scenario is I have navigation controller stack of 3 controllers, referred here from bottom to top as A-B-C:
View controller A is a form where I would like to fill in the details about the entity (i.e., its properties).
View controller B shows the result of a calculation from the entity (e.g., via an instance function).
For the sake of simplicity, view controller C is just a confirmation & is where I'd like to save the entity.
Is there a way in MagicalRecord I can call [MyEntity createEntity] in view controller A with its properties set, pass it through to C via B & only save it in C? This would also include the possibility of not saving it at all should the user decide to go back to A from B or C.
I fully appreciate I may well be getting the wrong end of the stick with Core Data & it may not be possible. As a workaround, already I know I can create a class method that does the same calculation given the relevant parameters & pass all the parameters through the stack from A to C.
Edit: Just to make it clear, I want to call [[NSManagedObjectContext defaultContext] save] in View Controller C.
yes sure.. just dont save the managedContext.
the VCs should run all on the main thread anyways.. so all can use
[NSManagedObjectContext defaultContext]
OR
pass the MOC between the three classes using a property on the controllers.
#property NSManagedObjectContext *context;
After some testing, I knew #Daij-Djan had the right idea in that I don't call [[NSManagedObjectContext defaultContext] save] if I don't want to save my entity. As a result I leave that call until View Controller C to do the saving.
However, my testing showed I need to do a little more to avoid saving any unwanted entities. I've noticed if I go to A from B via the back button, I want to discard the entity there. In my use-case, it doesn't matter if I just create another new entity going from A to B, and I never pass back through View Controller B if I have successfully saved the entity.
So basically I need to delete the unsaved entity if the back button is pressed on View Controller B. This answer helps me massively there, resulting in this code in View Controller B:
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound) {
// self.entity is the instance of my entity
[self.entity deleteEntity];
self.entity = nil;
}
[super viewWillDisappear:animated];
}

CoreData: Clear changes from NSManagedObjectContext

I'm instantiating a NSManagedObjectContext object at the Application delegate level and sharing it across all my UIViewControllers. Here's the code that I use to access it in one of my View Controllers:
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
modelObj = (Model *) [NSEntityDescription insertNewObjectForEntityForName:#"Model" inManagedObjectContext:[appDelegate managedObjectContext]];
Now in this screen, I have a UITableView with 9 rows & each cell has a UITextField. As the user inputs values into the textfields, I assign them into modelObj. Now, my user has an option to cancel out and discard all the changes or save them to disk. I have the save code working fine. But in the case when a user tries to discard the changes, I'm not sure what to do. There doesn't seem to be a [managedObjectContext discardChanges] method to throw them all away.
I can think of a couple of ways of solving this.
Create a new instance of NSManagedObjectContext for each controller instead of sharing one across the application.
Or, I could create a bunch of NSStrings in my code and save user values in them and call insertNewObjectForEntityForName: only if the user clicks save.
Which way is the right one? Or is there a way to make NSManagedObjectConext discard all the changes that were made to it?
Thanks,
Teja.
NSManagedObjectContext has a simple method for this:
[managedObjectContext rollback];
This method "removes everything from the undo stack, discards all insertions and deletions, and restores updated objects to their last committed values." (documentation)
Unless I'm missing something, that should give you everything you need.
You might be looking for -refreshObject:mergeChanges: - the docs say that it resets an object from the persistent store, and if you pass NO as the second argument, you can choose not to reapply changes that have been made.
This will likely require you to store a set of objects that you have changed (for the first argument), then clear that set when you commit changes in your context to the store. This should be a pretty trivial addition, though.
Swift 5
managedObjectContext.rollback()

Editing NSManagedObject in duplicate context to merge it later on

When a user double taps a view in my application a uipopovercontroller presents him with the fields which he can edit. (Much like in the iPad calendar app)
The view represents a NSmanagedobject. To be able to cancel the operations done in the uipopovercontroller my idea was as follows:
1) create a "editManagedObjectContext" in my viewcontroller for the popover and give it the persistentstorecoordinator of my main context used throughout my app.
editContext = [[NSManagedObjectContext alloc] init];
[editContext setPersistentStoreCoordinator:[myContext persistentStoreCoordinator]];
2) fetch the object represented on the tapped view (Task*) from the new "editContext"
task = [editContext objectWithID:[taskOrNilForNewTask objectID]];
3) Use this task to do all the editing and when the user finishes he can either:
Cancel the entire editing operation. This would just discard of the editContext and return.
Save. This would than merge the editcontext with the original context through mergeChangesFromContextDidSaveNotification :
Thus commiting the changes to the corresponding task in the original context.
Problem is task = [editContext objectWithID:[taskOrNilForNewTask objectID]];
results in a faulted object. And later on when I try to access the properties of a task object I get either the BAD_EXC error or my task object seems to be of some strange type ranging from: CALayer, NSCFData,...
My thought was that I might have to first save the original context, but that results in about the same errors. But since I saved just before I made the editContext I thought the save operation could be done in another thread and that could be a reason?
I just can't get my head around what I'm doing wrong and hope you guys can come up with some advice.
My approach was based on the approach in the CoreDataBook codesample from Apple (rootviewcontroller.m - (IBAction)addBook:)
Your problem was that objectWithID: returns an autoreleased object, which you were then storing in an ivar without retaining it. The system later deallocated it, and either you wound up with a garbage that gives you EXC_BAD_ACCESS or you wound up coincidentally with a different object at the same memory location. The errors you described made this clear.
The reason self.task fixes it is because the property self.task is declared retain, so assigning through the property automatically does the necessary retain. Do note that if you are not releasing it in dealloc then you will be leaking memory.

Get newest object added to NSFetchedResultsController?

Not even sure if this is feasible, but here's the use case:
I have a RootView tableview that uses NSFetchedResultsController to manage the list. I tap an add button, which presents the AdditionViewController. The AdditionViewController uses a separatemanagedObjectContext to create the new object. On Save, the object is passed back to the RootView, where the new object (in the new managedObjectContext) is merged into the main managedObjectContext. The AdditionViewController is then dismissed, revealing the RootView.
What I would like to do, is to push my DetailViewController with the new object loaded after the merge, so that when the AdditionViewController is dismissed, the full detail view is revealed.
How can I get the object that has just been added to the fetchedResultsController in order to pass it to the DetailViewController?
--UPDATE--
Still nothing on this. Let me try to explain what I need to do (hopefully) a bit better. If this is still confusing, ask questions. I'm having a hard time thinking of how to describe the problem.
I am fully aware how to push the Detail view underneath the modal addition view upon saving the new object. The problem is that the object I am saving is in its own fetchedReaultsController. I am merging this frc into the main fetchedResultsController, so if I try to sent the object to the detailview, I get a crash, because the object has been invalidated (due to the merge) by the time the modal addition view is dismissed, and the detailview calls viewWillAppear. That is what I am trying to get around. How can I figure out what object was just added to the main fetchedResultsController in order to send it to the detailViewController?
--UPDATE--
Adding a bounty for anyone who can tell me how to retrieve the most recently added object from a fetched results controller. Or how to retrieve a specific object from a fetched results controller without knowing it's indexPath.
Here's how I did it in an almost identical use case:
While the AdditionViewController is displayed, the user has the option of saving the item they created or cancelling out of the new item dialog. I communicated the user's choice back to the RootViewController.
If the user cancelled, remove the object you created from your context.
If the user chose to save, save the context and display the DetailViewController.
For the record, the answer was to grab the object ID of the object in the addingManagedObjectContext AFTER saving the context (since the ID changes after saving), and passing that ID to the main managedObjectContext after the merge. The full code required for this is below (if anyone has an easier way, let me know)
detailViewController.object = (customObject *)[[fetchedResultsController managedObjectContext] objectWithID:[[[[addingManagedObjectContext registeredObjects] allObjects] objectAtIndex:0] objectID]];
Thanks to frenetisch applaudierend for pointing me in the right direction.