Consider your first column. It is bound to Item Controller, arrangedObjects
, name
. Does each cell get an array of names? No. Each gets a single name.
Although that column binding is sometimes expressed as a key path like Item Controller.arrangedObjects.name
, the way it actually works is that the column as a whole shows the arrangedObjects
, one element per row, but name
is applied to each element of that set separately. So, each cell has a single name.
Now consider your new column. The rows again correspond to the arrangedObjects
of the Item Controller, but the model key path is applied to each element individually. But the model key path contains a collection operator, @sum
, which isn't appropriate for an individual element (Item
entity). Hence the error.
You could create a text field (outside of the table) which shows the sum of the price of all of the items of the selected store. You would bind the text field's Value binding to Item Controller, arrangedObjects
, @sum.price
. The text field works differently than the table column since it has a single thing to display. It really does use the result of [ItemController valueForKeyPath:@"[email protected]"]
. The collection operator will be applied to a collection.
You could also bind a text field to Item Controller, selection
, @sum.price
to have it show the sum of the prices of the items selected in the item table.
Bindings don't provide any way to get a running sum, if I understand what you mean by that (first row shows the price of the first item, second row shows the sum of the prices of the first and second items, etc.). Such a running sum would be context dependent. A given row's value would depend on the previous rows' values. For example, sorting the table differently would mean that the running sum next to a given item would change, because the set of items before it had changed. Bindings can't do that. They don't know about position, index, or siblings.
Update:
To get a running sum, you'll need to not use bindings for the column. Make your view or window controller adopt NSTableViewDataSource
if it doesn't already. Then connect the dataSource
outlet of the table view to it.
In your data source class, implement -tableView:objectValueForTableColumn:row:
. Check the column identifier
. For any columns other than the running sum column, return nil
so it uses the value from the column bindings.
For the running sum column, the straightforward-but-inefficient implementation would be something like:
NSRange range = NSMakeRange(0, rowIndex + 1);
NSArray* rowsToSum = [self.itemController.arrangedObjects subarrayWithRange:range];
return [rowsToSum valueForKeyPath:@"@sum.price"];
You would also need a way to inform the table view when cells in the running sum column need to be reloaded (recomputed). You would use Key-Value Observing to observe self
for a change in the key path @"itemController.arrangedObjects.price"
. You would set this up in -viewDidLoad
or -windowDidLoad
. Don't forget to tear it down when the controller is done.
When the change notification is delivered — i.e. when -observeValueForKeyPath:ofObject:change:context:
is called — you would call -reloadDataForRowIndexes:columnIndexes:
on the table view to indicate that all row indexes in the running sum column should be reloaded.
That should work but it will be horribly inefficient once you get a significant number of rows.
So, to optimize, you should cache the running sums, but you need to be careful to invalidate the cache appropriately.
Basically, have an instance variable like _cacheIsValid
. Like all instance variables, it will start out zero (false) by default. In -tableView:objectValueForTableColumn:row:
, you'd check if it's valid. If it's not, you would build it and record that it's valid. Then, or if it was already valid, just return the element for the requested row.
To build the cache, iterate over self.itemController.arrangedObjects
computing the running sum as you go and adding each value onto the end of an array. You could use a C-style array of primitive types or an NSMutableArray
of NSNumber
s, as you prefer. (The memory management for C-style arrays can be made simpler by using an NSMutableData
for the buffer.)
You would invalidate the cache in -observeValueForKeyPath:...
, before telling the table view to reload the running sum column.
For the next step in efficiency, you might recompute the cache at that time and compare the values to the existing cache (if it's valid) as you go. Accumulate in an NSMutableIndexSet
the row indexes of only those rows for which the cached running sum actually changed and use that in the call to -reloadDataForRowIndexes:columnIndexes:
. That way, the table view only reloads the cells that actually changed.
NSArrayController
Item Controller has its Content binding bound to theNSArrayController
Store Controller, controller keyselection
, model key pathitems
. The new column's Content binding is bound to Item Controller, controller key ???, model key path@sum.price
." Or whatever is actually the case. Which bindings of which objects are bound to what, with what controller key and what model key path? Also, you have left information out of the error, such as which class is not KVC-compliant. – Ken Thomases