Sorting NSTableColumn with NSArrayController - objective-c

I have a NSArrayController bound to an NSTableView so the table column like the following:
NSTableView bindings:
Content -> ArrayController.arrangedObjects
SelectionIndexs -> ArrayController.arrangedObjects
SortDescriptors -> ArrayController.sortDescriptors
NSTableColumn bindings:
Value -> ArrayController.arrangedObjects.description
When I try and sort it using the column header it just crashes with something like
error setting value for key path sortDescriptors of object NSArrayController
Any ideas?

I struggled with the exact same problem today.
It seems that binding the content and selectionIndexes of the tableView to the array controller IB > inspector window > select your tableView > bindings tab, disables the sorting by clicking on the table header. This makes sense, because the table view now shows you the exact contents (and ordering) of the array controller.
I unchecked these bindings in the IB, also removed any sort keys from the table columns IB > inspector window > select your NSTableColumn > attributes pane. Select the checkbox Creates Sort Descriptor in the table column's binding tab. No sortDescriptor is needed on the table, although I think binding the table's sortDescriptor to Shared User Defaults Controller saves the ordering when you quit your application.
If you need to sort your table, put a sortDescriptor on the array controller, maybe in the awakeFromNib.
- (void)awakeFromNib {
[super awakeFromNib];
[self setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"propertyOfYourObject" ascending:YES selector:#selector(compare:)]]];
}
This does not interfere with clicking the table column headers.
I couldn't get a sortDescriptor on the array controller to work with bindings.

I see several issues in your binding attempt.
One doesn't usually need to bind the NSTableView at all. Binding the values of specific NSTableColumns to the NSArrayController is enough.
You try to bind something against a .description property. Please remember - "description" is like a "reserved word" in Obj-C. Any NSObject should present itself as NSString in its "description" method. This is what is called when you po <object> in the debugger, or pass an NSObject to NSLog via "%#". So... probably you'd want to rename your property to something else.
You do NOT need to bind the sort descriptors of the NSArrayController or the NSTableView or the NSTableColumn at all. As it happens, when you bind an NSTableColumn's value to the NSArrayController's arrangedObjects, the NSTableColumn (actually the NSColumnHeader) object knows to set the NSArrayController's sortDescriptor to the same path as the one you specified for the column's value binding - as you click on the column header. In other words - sorting by clicking on column headers comes free, if you just bind your column's value to the NSArrayController's arrangedObjects.
Documentation on table binding is bad and frustrating. There are several different schemes for working with a Table, and debugging binding problems is a real nightmare. However, there are plenty essays and tutorials on the net for this.
Hope this helps.

Let me suggest you a simple way to do this -
NSTableColumn bindings:
Value ->
Bind to: ArrayController
Controller Key : arrangedObjects
Model Key Path : keyPath (such as name)
If you are new to using bindings with table view, this article will be of great help to you-
EDIT: Project relocated to Github. (No more explanation - code only)
NSTableView, NSArrayController and More Bindings

Related

Auto-sort table column contents bound to NSArrayController

So, this my case :
I've got an NSMutableArray (of NSMutableDictionary instances) bound to an NSArrayController. Each element's name value is, in turn, bound to the value of the first table column.
The thing is :
How should I make it auto-sort the elements in that specific table column (of my NSTableView) ?
Is the column sortable through user action? For the table column's Value binding, you can enable the Creates Sort Descriptor option. That's enabled by default, usually. You should also set the Sort Key and Selector in the attributes inspector.
If you're trying to make the table sorted when the window is first loaded, you should construct an array of NSSortDescriptors and pass that to the array controller's -setSortDescriptors: method. A good time to do this is in an override of -windowDidLoad in the window controller or during -awakeFromNib.

How can an NSCell detect the model key path it's bound to?

Imagine the following:
You have an NSTableView with multiple columns.
Each NSTableColumn is bound to the same NSArrayController (we'll call it myArrayController).
This array controller holds many instances of a model class.
One column has an NSPopupButtonCell where selectedObject is bound to myArrayController.arrangedObject.somePropertyOfTheModel.
The table gets properly populated.
Q: How can an NSCell detect the model key path it's bound to? (somePropertyOfTheModel in this example)
I'm trying to make a cell reusable by not having it assume its represented value is always from somePropertyOfTheModel (could be from somethingElse). Upon a given action, it needs to bind the content of a 2nd controller to somePropertyOfTheModel or somethingElse.
[Edited] A bit more (maybe too much?) explanation:
I'm creating a popup-button which displays a few preset values of a property, and a "Custom Value" item which triggers a PopOver window to allows configuration of the property. I want to make it so that I can drop this cell into a table and having it manage the PopOver much like it already manages its own Menu.
What I've tried:
[self representedObject] returns the actual value. Setting it as content to the 2nd controller is all good and well... but whenever the model's property changes, the 2nd controller won't be notified since it's tied to the actual instance of the value... not a binding to the model property.
Querying the cell's binding gives me nothing:
[[self infoForBinding:#"selectedObject"] objectForKey:NSObservedObjectKey]; // nil returned
[[self infoForBinding:#"selectedObject"] objectForKey:NSObservedKeyPathKey]; // nil returned
Querying the cell's control's (the NSTableView) binding doesn't give me much:
[(NSTableView*)[self controlView] infoForBinding:#"content"] objectForKey:NSObservedObjectKey]; // returns myArrayController or a poxy to it.
[(NSTableView*)[self controlView] infoForBinding:#"content"] objectForKey:NSObservedKeyPathKey]; // returns #"arrangedObject"
// running the same but for #"selectedObject" returns nothing but nils
I'd like to query the NSTableColumn itself -- that's where the bindings are defined in IB -- but cell's aren't aware of their existence (unless I've overlooked something obvious). Even passing via NSTableView, no method returns an NSTableColumn for a given cell (and considering prototype cells, I doubt it would help).

Sorting a column in NSOutlineView that is bound to NSTreeController

I have an NSOutlineView that is bound to an NSTreeController.
In Interface builder, I have bound each column of NSOutlineView to the tree controller with the Controller key "arrangedObjects" and model key path as the entity attribute or a method in my entity class.
Now all other columns sort perfectly except for one special column. The special column has a model key path bound to a method that is declared in my Entity class. This method depending on some condition in my code will return either NSString or NSDictionary.
When it returns an NSDictionary, the delegate method:
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
parses the NSDictionary and returns a double value for that cell. Also note that the cell in this case is derived from NSCell and displays a progress bar.
In short, my special column will display a mix of progress bars and strings depending on the situation.
I would like to implement sorting so that all progress bars stay together and the strings are sorted alphabetically.
Perhaps you will have some luck if you try setting the table column's sort descriptor to a descriptor you create with a comparator that looks at the objects' classes. (You could also try filling in the "Sort Key" in IB, using the "class" key, but I think this might be less likely to work...)
Well i found a solution to this myself and just 2 minutes after posting my query :) The solution was to add a custom sort key in IB and define a method with that key name in my Entity class that returns an NSInteger. Since i want all NSDictionary objects to stay together and all NSStrings to be sorted, i return appropriate integer based on the object type.
Note for anyone who might be stuggling with sort not working: Make sure binding for sortDescriptor is enabled in IB.
try
NSSortDescriptor *sorter = [[[NSSortDescriptor alloc]
initWithKey:NULL
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)] autorelease];
[[myOutlineView tableColumnWithIdentifier:#"Name"]setSortDescriptorPrototype:sorter];

Binding columns in an NSTableView

I have two classes: GHTable and GHColumn. A GHTable object has an NSMutableArray with GHColumn objects. Each GHColumn has a name property (NSString).
I have made an UML diagram to make this more clear. Note that I am not using Core Data:
I want to bind the columns property of the GHTable object to the columns of an NSTableView. I want to bind the titles of the columns of the NSTableView to the name property of the corresponding GHColumn.
My question: is there a way to do this through Cocoa Bindings, and if so: how? Or do I need to manually implement the data source for the NSTableView?
You will need to use an NSArrayController. Bind its Content Array binding to the mutable array on your GHTable object.
In the NSTableView, bind Content to the NSArrayController's arrangedObjects contoller key.
In the NSTableView's column, bind Value to the NSArrayController's arrangedObjects controller key with the model key path name.
If the inspector window shows "Scroll View Bindings" as its title when you click the table view, click it again on the content area and it should change to "Table View Bindings".
Click again on the table's column to select it and the title should change to "Table Column Bindings".

Populating NSTableview from a mutable array

I've been attempting this for two days, and constantly running into dead ends.
I've been through Aaron Hillegass's Cocoa Programming for MAC OS X, and done all the relevant exercises dealing with NSTableview and mutable arrays, and I have been attempting to modify them to suit my needs.
However none of them seem to be using an array with objects as a data source, it seems to use the tableview as the datasource.
I'm trying to implement Jonas Jongejan's "reworking" of my code here, with a Cocoa front end to display the results.
Any pointers or suggestions I know this should be simple, but I'm lost in the wilderness here.
I can populate the table by setting the array
It's pretty simple really, once you get to understand it (of course!). You can't use an NSArray directly as a table source. You need to either create a custom object that implements NSTableViewDataSource or implement that protocol in some existing class - usually a controller. If you use Xcode to create a standard document based application, the document controller class - (it will be called MyDocument) is a good class to use.
You need to implement at least these two methods:
– numberOfRowsInTableView:
– tableView:objectValueForTableColumn:row:
If you have a mutable array whose values you'd like to use in a table view with one column, something like the following should do as a start:
– numberOfRowsInTableView: (NSTableView*) aTableView
{
return [myMutableArray count];
}
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
return [myMutableArray objectAtIndex: rowIndex];
}
It has just occurred to me that you could add the above two methods as a category to NSArray replacing myMutableArray with self and then you can use an array as a data source.
Anyway, with a mutable array, it is important that any time you change it, you need to let the table view know it has been changed, so you need to send the table view -reloadData.
If your table view has more than one column and you want to populate it with properties of objects in your array, there's a trick you can do to make it easier for yourself. Let's say the objects in your array are instances of a class called Person with two methods defined:
-(NSString*) givenName;
-(NSString*) familyName;
and you want your table view to have a column for each of those, you can set the identifier property of each column to the name of the property in Person that that column displays and use something like the following:
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
Person* item = [myMutableArray objectAtIndex: rowIndex];
return [item valueForKey: [tableColumn identifier]];
}
If you replace valueForKey: with valueForKeyPath: and your Person class also has the following methods:
-(Person*) mother;
-(Person*) father;
-(NSString*) fullName; // concatenation of given name and family name
you can add table columns with identifiers like: father.fullName or mother.familyName and the values will be automatically populated.
You could go the datasource route and do all of the heavy lifting yourself, or you could let bindings do all the heavy lifting for you. Add an NSArrayController to the nib file that has the table view in it. Make sure that the File's Owner of the nib is set to the same class that has the mutable array in it. Bind the contentArray of the array controller to File's Owner.myMutableArray. For each column bind Value to the array controller arrangedObjects and add the appropriate key path. This will allow you to get things like user sorting for free if you ever need it.
On the iPhone (I know you're talking about Mac, but maybe this could help) you have to use delegation for loading a tableView. It asks for a cell and you use your array to fill-in the data where needed.
I'm not sure if this works for the Mac, but it'd be worth looking into.
Maybe set dataSource to self and use those delegate methods to access your array based on the row and column #
Apple has a whole guide for Table View Programming so I suggest you start with the Using a Table Data Source section of the that guide.