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()
Related
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.
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.
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.
I'm initializing a simple interface, with an NSTableView bound to an array controller (which manages an array of dictionaries). I want to load the content for the array in the background (it's a very time-consuming process), updating the table view every 100 or 1000 elements. The idea is that the interface is available and responsive. I can't figure out how to also trigger an update / refresh afterwards. The table remains empty. Can anyone offer pointers?
My current approach is:
// In init for my app controller. This seems to work well, but I've tried other methods here.
[self performSelectorInBackground:#selector(loadTable) withObject:nil];
- (void)loadTable {
tracks = [[NSMutableArray alloc] initWithCapacity:[masters count]];
// ... create each object one-by-one. Add it to tracks.
for (... in ...) {
[tracks addObject:newObject];
}
// Now I don't know what to do next. The table remains empty.
// Things I've tried (though possibly not in all combinations with the
// method above):
// 1. With a suitably-defined reloadData method, which just reloads
// the table view and sets needs display.
[self performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
// 2. Reload directly.
[tv reloadData];
[tv setNeedsDisplay];
}
If I just load the data directly, and don't try to do that in the background, everything works fine, but it takes almost 30s.
You have the table columns (I assume you meant) bound to an array controller, so that's where the table view gets its data from. The table view may very well be asking for updated arrays, but it's asking the array controller, which doesn't know anything has changed.
The array controller won't simply turn around and ask you for fresh data; that would imply it exists solely to make it harder for you to bind the table view to your array, and that isn't the case. It's a controller; its job is to own (a copy of) the array and maintain its order and the user's selection of some subset of its objects.
Therefore, you need the array controller to find out when you add items to your array. The best way to make this happen is to bind the array controller's contentArray to a property of your controller, and update that property in a KVO-compliant manner.
That means:
Create the mutable array in your init method. (And, of course, release it in dealloc.)
Implement the array accessor methods, plus addTracksObject: and removeTracksObject: (which are technically set accessor methods, so KVO will ignore them for an array property) for your convenience.
To add a track, send yourself an addTracksObject: message. You should respond to that by sending yourself an insertObject:inTracksAtIndex: message (with [self countOfTracks] for the index, unless you want to do an insort), and you should respond to insertObject:inTracksAtIndex: by sending your tracks array an insertObject:atIndex: message.
As I mentioned, KVO will ignore addFooObject: and removeFooObject: when foo is an NSArray property, considering those only NSSet-property accessors, so you need to implement them on top of insertObject:inFooAtIndex: and removeObjectFromFooAtIndex: because those are array accessors, which means KVO will react to them.
Step 3, as I just described it, will be pretty slow, because it will cause the array controller to re-fetch your property and the table view to re-fetch the array controller's arrangedObjects at least once each for every row you add.
So, you should maintain your batch-adding behavior with this alternate step 3:
Implement insertTracks:atIndexes:, and pass it an array of one batch of (e.g., 100 or 1000) tracks and an index set formed by [NSIndexSet indexSetWithRange:(NSRange){ [self countOfTracks], countOfBatch }]. You'll also need to implement removeTracksAtIndexes:, only because KVO will ignore each insert method if you don't also have its counterpart.
You probably should have the array controller set to attempt to preserve the selection, so as not to frustrate the user too much while you're still bringing in rows.
Also, you may want to create the objects on a background thread, periodically sending yourself another batch to add using a main-thread perform. I'm ordinarily an advocate of doing things on the main thread run loop whenever possible, but this sort of thing could easily make your interface laggy while your periodic load builds up another batch.
You need to call setNeedsDisplay:YES on your table view on the main thread. Don't call it from a background thread. All Cocoa UI calls must be done on the main thread or weird things happen.
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.