CocoaDev

Edit AllPages

One way to sort a table view that is expected by many users is to be able to click on a table column header cell to toggle the sort order. A prime example of this is found in the iTunes application.


One of the basic things you have to do is intercept mouse down events in order to get the table to sort itself. But there is no need to go the direct route, as the following discussion reveals:

Using

tableView:(NSTableView *)tableView mouseDownInHeaderOfTableColumn:(NSTableColumn *)tableColumn

brings two problems.

*First, since it’s mouseDown, even a column resize triggers the sorting. I don’t want this… *Next, I don’t know how to reverse the sorting. Are there any hints about the “sorted state” of a column?


It is much better to use this delegate method which only gets triggered only when clicking the header cell (not when resizing a column):

To reverse the sorting of a column, you should reverse the array that the table is getting data from. Something like this (category method which extends NSMutableArray):

@implementation NSMutableArray (ReverseArray)

@end


Here’s a simple example that shows how to sort NSTableView columns in ascending or descending order by clicking the column header. It also illustrates two undocumented methods in NSTableView to display the familiar up and down arrows in the column header to indicate sorting order. (WARNING: Use undocumented method calls at your own risk. Apple could change or remove these without notice in a future release of OS X.)

Our example has two classes: the Cartoon class is used to represent cartoon characters, which will be shown in a table view. The C**‘artoonTableController class is used as the data source and delegate for the NSTableView.

The Cartoon class is very simple, it has three instance variables and three corresponding accessors.

// Cartoon.h #import <Foundation/Foundation.h> @interface Cartoon : NSObject { NSString * name; int shoeSize; NSString * tagLine; }

// Cartoon.m #import “Cartoon.h” @implementation Cartoon

Next, we create an extension to the Cartoon class containing methods to compare two Cartoon objects.

These methods could be put in Cartoon.h/.m, but we choose to put these methods in a separate file because they aren’t part of the Cartoon’s zen nature; they are only useful to the C**‘artoonTableController object to sort the table. This is called creating a category. For details see ClassCategories.

Note that the names for these sort routines are in a specific form, closely related to the method names for the Cartoon accessors.

// Cartoon-Sorting.h. #import “Cartoon.h” @interface Cartoon(Sorting)

// Cartoon-Sorting.m #import “Cartoon-Sorting.h” @implementation Cartoon(Sorting)

Finally, let’s create the C**‘artoonTableController class. It has four instance variables to manage the list of Cartoon objects and keep sorting information. It also implements the NSTableDataSource protocol to act as a data source for the NSTableView, and one of the table view’s delegate methods to respond to clicks in a column header.

// CartoonTableController.h #import <AppKit/AppKit.h> @interface CartoonTableController : NSObject { NSMutableArray * characters; // array of Cartoon objects NSTableColumn * lastColumn; // track last column chosen SEL columnSortSelector; // holds a method pointer BOOL sortDescending; // sort in descending order } // NSTableView data source/delegate methods

The -tableView:didClickTableColumn: method is where we handle the user’s column click action. We determine whether the clicked column is already selected, determine the new sorting order and re-sort the table. It constructs a selector name as a string, and uses NSSelectorFromString to find the selector by that name, which is stored in the columnSortSelector instance variable.

Note the two class method calls [NSTableView _defaultTableHeaderSortImage] and [NSTableView _defaultTableHeaderReverseSortImage], which return NSImage objects; these calls will generate warning messages during compile, since the method names are not declared in the NSTableView header file.

Finally, the [tableView setHighlightedTableColumn: tableColumn]; method call makes the selected column header display in blue. If you don’t want this behavior, just remove that line.

// CartoonTableController.m #import “CartoonTableController.m”

#import “Cartoon.h” #import “Cartoon-Sorting.m” // don’t really need this import statement

// To prevent compiler warnings, declare the two undocumented methods @interface NSTableView(SortImages)

@implementation CartoonTableController

// The next two functions implement the [[NSTableView’s // data source protocol

// NSTableView delegate function

That’s it for the code. If you want to try this example, create a new Cocoa app, add the code above, and create C**‘artoonTableController and NSTableView objects in Interface Builder. The table view should have three columns: Name, Shoe Size, and Tag Line that have identifiers of “name”, “shoeSize”, and “tagLine” respectively. Wire it all up, be sure to connect the table’s data source and delegate to the CartoonTableController object.

Enjoy!

pdferguson@attbi.com


/Tobias


Thanks, Tobias. I incorporated your suggestion about only re-sorting when necessary. It also halves the number of comparison methods needed on the Cartoon class, and simplifies the didClickTableColumn method.


Hillegass implements table sorting in an example in the second edition of his book using bindings and NSArrayController and sort descriptors. Nary an NSComparisonResult in view (although perhaps behind the scenes?)

(Note: Bindings are nice, but be aware that they are only available on Panther (10.3) and newer.)

See JaguarOrPanther. This is no longer a limitation in most cases.


The documented way for displaying sorting arrows is using [NSImage imageNamed:@] as follows:

[aTableView setIndicatorImage: (sortDescending ? [NSImage imageNamed:@”NSDescendingSortIndicator”] : [NSImage imageNamed:@”NSAscendingSortIndicator”]) inTableColumn: aTableColumn];

I’m leary of using private methods as they may change in the future.


But is there any way to know if the last clicked column is ascending or descending ? Or do you have to create the mecanism “by hand” ?


Why not just override - (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors in your table view data source (NSTableDataSource)?

  • (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors { [myTableDataArray sortUsingDescriptors:[tableView sortDescriptors]]; [tableView reloadData]; }

Do this in conjunction with setting sort keys for each table column (can be done in Interface Builder) and you are done.