mergeChangesFromContextDidSaveNotification Briefly Displays Both Contexts In NSTableView - objective-c

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.

Related

Core Data managedObjectContext not being updated with UI changes

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

Using NSManagedObjectContextObjectsDidChangeNotification

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.

UITableView not actually reloading it's data even though datasource methods are returning the new data

I have a tableview controller where the data for this table comes in from HTTP requests. When new data comes in (which should be reflected as new rows in my table), but when I call
[self.tableView reloadData]
nothing changes in the table. No new rows! I have log statements in my datasource methods confirming that after I call reloadData, the table asks how many rows and sections to draw. My controller for sure returns the new number of rows like it should, but the table doesn't seem to care. I also checked to make sure my cellForRow... method returned a proper instance of the cell that has been configured with the proper data object. I've never had this problem before!
I'm running iOS 4.2 in the simulator with an iPad app built for 4.2.
Perhaps you may have more than one tableview? You could print pointers of the table from the didSelectRowAtIndexPath: and cellForRowAtIndexPath: delegate methods. Including the description of your self.tableView could also shine some light on the issue.
Sounds like the old model/view confusion. reloadData will merely re-display the data that is already in the data source, not load in new data. You can call
[yourFetchedResultsController performFetch:&*error];
everytime new data is available, to force an immediate update. As you saw, the NSFetchedResultsController will update its results, but when and how often is dependant on whether it has a delegate and whether the cache file name is set. I quote from NSFetchedResultsController Class Reference:
In addition,
NSFetchedResultsController provides
the following features:
It optionally monitors changes to
objects in its associated managed
object context, and reports changes in
the results set to its delegate (see
“The Controller’s Delegate”). It
optionally caches the results of its
computation so that if the same data
is subsequently re-displayed, the work
does not have to be repeated (see “The
Cache”).
A controller thus effectively has three modes of operation, determined by whether it has a delegate and whether the cache file name is set.
No tracking: the delegate is set to
nil. The controller simply provides
access to the data as it was when the
fetch was executed.
Memory-only tracking: the delegate is
non-nil and the file cache name is set
to nil. The controller monitors
objects in its result set and updates
section and ordering information in
response to relevant changes.
Full persistent tracking: the delegate
and the file cache name are non-nil.
The controller monitors objects in its
result set and updates section and
ordering information in response to
relevant changes. The controller
maintains a persistent cache of the
results of its computation.
Are your row heights > 0.0? What does tableView.visibleCells return? I'd verify that my cells have hidden != YES and that frame.size > (0x0)

NSTableViewDataSource or NSArrayController?

I need to load data dynamically as a user scrolls through an NSTableView. For example, the table might display 50 rows, and as it scrolls further I need to fetch more data from the network. The number of of objects/rows is known beforehand, so I want the table to have the right number of rows from the start, but showing empty cells while data is loading.
I'm using Core Data so it's easy to connect the table to my model using bindings. This would also take care of cells being updated as data comes in and is parsed. I've tried to figure out how I could do this by subclassing NSArrayController but from what I can tell there is no information flowing from the table to the controller about which rows actually need data. Therefore, I'm thinking of implementing NSTableViewDataSource instead, where I can easily check if the table has scrolled beyond the rows for which data is available. On the other hand, I don't know if I will get cells automatically updating as easily with this solution.
In case anyone comes across this, here's my own answer:
Yes, you need to implement NSTableViewDataSource on a controller, observe changes on the data and call reloadData manually on the table when changes occur. The main reason for doing this is that you can defer loading of data until it's actually needed (when the table view scrolls). Using the data source protocol keeps you informed of which indexes are requested.

NSTableView doesn't increase the number of elements in table?

I've an NSTableView that uses the controller object for the NIB being displayed as the data source. I implement the NSTableView informal protocol.
This NSTableView gets its values from Core Data. I startup the application, load all values I have in XML and then display them.
My problem is, the NSTableView doesn't seem to add any new rows to the end of the table. If I start the application with no values in permanent storage and add another one (adding values works as I can see them being saved to XML), the table view simply ignores the new value.
If I add a value I know will go to the end of the table (the contents are organized alphabetically), I won't see the new value.
If I and a value that I know won't go to the end of the table, the value will be added, I will see it on the table, but the last value on the table will be pushed out and disappear.
I've noticed that - (int)numberOfRowsInTableView:(NSTableView *)tv is only being called when the application starts up and not when I do [tableView reloadData]. What causes this event to be fired? I tried firing it by hand before calling reloadData on the tableView but doesn't seem to work.
Any ideas to what might be causing this?
Has anyone encountered something like this? Any clues to what might be?
Alex's comment made me review the code, specifically bindings in Interface Builder. Turns out I had set the bindings between the table and my controller object and implemented the NSTableDataSource.
I've removed all bindings and only implement the NSTableDataSource protocol.