I have suddenly encountered a strange problem when making updates to core data objects using UI controls bound to core data NSManagedObjects.
Symptoms are as follows:
- An OutlineView displays a list of hierarchical objects
- A Detailed view shows the data fields from the selected object, these include text fields, dates etc.
- When updates are made in the detailed view these are reflected in the OutlineView (e.g. diplayName is used in the OutlineView).
- However when I save the changes the managedObjectContext tells me there are no changes to be saved. So for some reason the UI is not letting the context know that things have changed.
- Given that the bindings are done in IB I assume that any changes in the UI would automatically be reflected in the managedObjectContext
- For some reason one of the fields seems to always result in the context recognising that changes have been made but not the others. This one field happens to be a popup list containing objects from another entity in the core data database.
If anyone has any inkling on what could be causing this - is there any way to monitor when UI changes are made, for example changing text in a text field, and whether these changes are propagated to the managedObjects.
EDIT
Found the problem - for some reason I had changed some of the properties from #dynamic to #synthesize in the objective-c classes for the core data entities. This was breaking things !
The IBOutlets need to be hooked up to the callbacks so that changes are known. They don't report changes unless you hook it up. So even though textField.text != oldText, it doesn't matter unless you take that update, save it to the entity, then see if it recognizes
Related
I have a document-based Cocoa application in OS X with a Core Data backed NSArrayController bound to the columns of an NSTableView and "Add" and "Remove" buttons.
This all works fine, objects added and modified using these bindings get added to the undo stack and save to files as expected.
However when I .addObject() programmatically, it is reflected in the table (and therefore, it would seem, the NSManagedObjectContext), but not added to the undo stack, or saved to files.
What am I missing? Some setting in my NSArrayController? Or some other call after .addObject()?
I have heard "Handles Content As Compound Value" mentioned in relation to similar problems, but this seems to be when using the Content Array binding, which I am not.
Are you calling .addObject() on the array that your NSArrayController is managing? That won't work because you are bypassing Core Data.
To insert programmatically, use insertNewObjectForEntityForName(inManagedObjectContext). Then the array controller will see the change. Or call add() on your array controller, which is what your Add button is doing. But insertNewObjectForEntityForName() is a better choice, because it will work independent of whether you have an active NSDocument.
I would like to know if there are any apple Xcode APIs for monitoring state changes before I attempt to build my own. The app I work on changes out several View Controllers. Each controller can have a couple smaller custom views plus the usual check boxes, text fields etc.
The main app needs to know if a view controller or anything on it is edited from its previous state when it is pulled up before it is saved again. We only need to know if the state has changed. The caveat is this: if a user checks a checkbox , that is considered a change of state, but if the user also unchecks the check box, then the state is not changed.
I was looking at the NSUndoManager but I'm nor sure if it will work.
Any suggestions appreciated
There are a couple of approaches:
Implement a centralized "model" object. In this scenario, view controllers would just update properties of this main model object and there's little else you have to do. View controllers would then, in viewDidAppear, check the state of this model object and see if anything changed and act accordingly.
Another approach would be to implement a delegate-protocol pattern, by which the various controllers might have some delegate property that would indicate what object must be informed of data changes. This object that would be the data delegate would be defined to conform to some well defined protocol that indicate how to inform it of the changes.
If, though, you (a) have multiple objects that need to be informed of changes; and/or (b) these changes might happen asynchronously while a view is presented, you need some mechanism to do this notification. The two common approaches would be either with key-value-observing of that model object or by posting a custom notification to the NSNotificationCenter.
To advise you better, we'd need a better sense of the nature of your model object, whether updates are happening asynchronously in the background, etc.
In many of my UIViewControllers, I update certain controls based on the state of my data. For example, I might have an edit button on a UITableViewController that should only be enabled when there is one or more items. Or perhaps I want to limit the number of items that can be added, and disable the 'add' button otherwise.
Every time I add or delete an item (or take any other action that can add/remove items), I have to remember to update any controls that might need enabling/disabling. This is trivial for the most part, but doesn't feel comfortable - there is a lot of repetition, and I have to remember to add the calls to updateControlEnabled (or whatever) whenever I add new functionality that might affect the data.
And then I noticed NSManagedObjectContextObjectsDidChangeNotification. Reading the docs, it looks like I can receive a notification whenever something changes in my managed object context. This seems ideal, but I have a few questions:
Is this an appropriate use of
NSManagedObjectContextObjectsDidChangeNotification?
Should I anticipate any performance impact if a controller
subscribes to these and parses each one to see if it needs to update
the UI? I will be checking the userInfo for every change, instead of
only those that I know I will care about.
Where should I subscribe to the notifications? My UIViewController has a
reference to the context, which helps, but I don't know where to
subscribe (loadView? viewDidLoad? init?) such that the view
controller will always have one and only one subscription.
The view controller will continue to receive and process notifications
when it's offscreen - enabling and disabling controls as the
data model is affected from elsewhere. Is this ok?
I guess I'm mostly just wondering if anyone else uses this approach and if so, what their experience is.
Q) Is this an appropriate use of NSManagedObjectContextObjectsDidChangeNotification?
A) Yes - I used it on OSX for a similar purpose.
Q) Should I anticipate any performance impact if a controller subscribes to these and parses each one to see if it needs to update the UI? I will be checking the userInfo for every change, instead of only those that I know I will care about.
A) NO - it will normally be a very small set of objects - ones that were directly changed.
Q) Where should I subscribe to the notifications? My UIViewController has a reference to the context, which helps, but I don't know where to subscribe (loadView? viewDidLoad? init?) such that the view controller will always have one and only one subscription.
A) Well, you cannot affect the UI til the view shows - so probably viewDidLoad or viewWillAppear. The problem with the later is you may get it a few times depending on push/pops, so maybe I'd do it in viewDidLoad.
Q) The view controller will continue to receive and process notifications when it's offscreen - enabling and disabling controls as the data model is affected from elsewhere. Is this ok?
A) Sure - when the view reappears all the elements will be setup correctly.
What you want to do is a classical use of that notification. Just check the thread it comes in on - if its not the mainThread then you want to make all your changes in a block posted to the mainThread.
Not sure if this is an issue with the way Magical Record does saves, or I'm just making an noob mistake somewhere.
I'm using an NSFetchedResultController (FRC) and UITableView to display a list of entities, when the user taps "Add" a new View Controller with an editor is pushed, a new entity is created with [MyEntity MR_createEntity]. The user can add additional entities here that are added to the main entity via a relationship. When the user taps "Save" in this View Controller the context is saved using [[NSManagedObjectContext MR_contextForCurrentThread] MR_save]
The NSFetchedResultsController appears to update, but when I tap to edit the entity none of the child entities are there. Debugging seems to show that even though the entity has been saved the FRC still has the entity with it's temporary ID.
I'm doing a naive [self.tableView reloadData] in the FRC controllerDidChangeContent delegate method.
Restarting the application loads the correct entities and the child entities are show properly in the editor view controller.
It looks like the FRC responds to the "main thread" save event, but the save is actually happening on a background thread so the FRC doesn't see it. I've checked and all "my" operations (setting up the FRC, creating and fetching entities) are all happening on the main thread context.
I tried listening for change notifications on MR_rootSavingContext and merging them with the main thread context, which sort of worked but I ended up with duplicates rows in the FRC (one was the correct "permanent" entity and one was the temporary one).
OK, I'm not sure if this is "the right way to do it" but I've found that it works correctly if I created my NSFetchedResultsController in the MR_rootSavingContext instead of the default context using the "inContext" version of MR_fetchAllSortedBy.
I guess this makes sense from the point of view that the FRC is now watching the rootSavingContext instead of one of it's children. Still, I would have thought since I'm doing all my operations on the same thread that wouldn't be an issue.
Update: The only gotcha with this approach is that if I just grab the entity using [frc objectAtIndexPath:] to give it to the editing view controller then it's no longer in the default context. Worked around this by re-fetching the entity in the default context using NSManagedObjectContext's existingObjectWithID. Still doesn't all feel quite right, but it's working for me.
Aware this is an old answer but none of the above worked for me and hopefully will help future readers
For me the problem was caused by trying to setup a purely local store using a sqlite file that had previously been used with iCloud.
Basically I tried to implement iCloud with my CoreData app, did the basic steps to set it up with an ubiquity container etc but then reverted back because of the inherent instability this seemed to cause (why is it that CoreData and iCloud STILL dont get along?!), but cocoa doesn't like you backtracking like that.
Fortunately I haven't done this in a live app so it's relatively easy to change as it will only affect development devices, but if you're moving from iCloud to a local store in a live app I think you might need to check out one of these solutions:
Migrating a Core Data Store from iCloud to local
I have a managedObjectContext for the main application that has a bound NSArrayController and NSTableView displaying the contents of the NSArrayController.
Periodically I use a background thread with a second managedObjectContext that goes and retrieves the latest data from a web server. Once the data has been returned and parsed on the background thread, it is merged with the main context after a mergeChangesFromContextDidSaveNotification.
Immediately after the notification fires (and I assume during the merge), the table shows duplicate entries of every item (which would be accurate if the two contexts were showing both their data at the same time). After a couple of seconds the table correctly displays the data and the duplicates are removed.
I presume the NSArrayController is observing the context as it is going through the merge and allowing the table to show the pre- and post- merge files.
Is there an acceptable method to stop the array controller from updating itself until the merge is complete?
I could do this outside of IB bindings and forcibly update the array controller, but I'm pretty sure that I must be doing something slightly wrong in either my bindings or during the process of the merge.
Any help or suggestions much appreciated.
You are most likely not sending a beginUpdate to the tableview before merging the two context so the tableview ends up trying to display the logical table in its in-between state. Freeze the tableview upon receiving the notification and then unfreeze it when the context have been merged.