I have a Core Data project with an NSTableView where the columns are bound to an NSArrayController. In turn, the controller's content is bound to the main managed object context of the AppDelegate.
I've sub-classed NSTextFieldCell's to provide a more customised way of displaying the data. Everything seems to work well but when I enumerate through the objects in the context and change an attribute, the NSArrayController doesn't seem to pass the new values through to the table. I know they're being changed as the sortDescriptors work and the table organises itself according to the new data but while still displaying the old values. I'm guessing this is down to the recycling of the NSCell's that display the data: although the underlying values have been changed in the Core Data DB they're not re-drawing.
After trying various KVO notifications with [myArrayController willChangeValueFor: aKeyPath]
and the corresponding didChange
as well as simply [myTable reloadData]
. My only solution has been to unhook the managedObjectContext binding of the controller, and re-hook it after processing, like this:
self.hasBackgroundOp = YES;
self.isCancelled = NO; //can get changed with a 'cancel' button in the UI
if (self.managedObjectContext.hasChanges) {
[self. managedObjectContext save:nil];
}
[myArrayController setManagedObjectContext:nil]; //brutal un-hooking of controller
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dispatch_get_global_queue(0, 0), ^{
.
.
// create a thread context, set the persistent store co-ord
// and do some processing on the
// NSManagedObjects that are fetched into an array just
// for the purpose of this thread
.
.
[ctx save:&error];
//and after the processing is finished then refresh the table
dispatch_async(dispatch_get_main_queue(), ^{
[myArrayController setManagedObjectContext:self.managedObjectContext]; //this forces re-drawing of the table
self.hasBackgroundOp = NO;
self.isCancelled = NO;
});
});
This seems really brutal to be doing [myArrayController setManagedObjectContext:nil];
and then setting it again just to refresh the table's contents. I just can't believe I've done this correctly although it works just fine. Could anyone advise of a better way? Thanks!
UPDATE: actually setting the managed object context doesn't solve the problem. It seems to have stopped working. If the code is run twice in a row then the app just hangs. Comment out the -setManagedObjectContext:
lines and the code can be run as many times as required.
In response to the comments, my custom cell uses the following to display itself:
NSInteger index = [[self stringValue] integerValue] + 1;
NSAttributedString *cellText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%lu",index] attributes:attributes];
NSRect textRect;
textRect.origin = cellFrame.origin;
textRect.origin.x += (cellFrame.size.width - cellText.size.width) / 2;
textRect.origin.y += (cellFrame.size.height - cellText.size.height) / 2;
textRect.size.width = cellText.size.width;
textRect.size.height = cellText.size.height;
[cellText drawInRect:textRect];
The array controller is setup in the main nib and has it's managed object context bound to the AppDelegate's. There are no fetch requests or predicates in the nib - a sort descriptor is bound in the AppDelegate's code:
[myArrayController setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:kINDEX ascending:YES]]];
-drawRect:
to confirm that's happening. – paulmelnikow