Styling an NSTableView (DTTAH)
Friday, February 22nd, 2008NSTableView is a great class for displaying long lists of data, but its default implementation is kind of boring looking. This post will show you how give your table views extra sex appeal. I’m categorizing it as a “don’t try this at home” tutorial because it sometimes involves overriding private NSTableView methods. Be warned that Apple can and will change the private framework code without notice and that some of the techniques used in this tutorial may no longer work. The tutorial and the example project are meant to be educational and exploratory. They’re not necessarily “right”, so use them wisely.
If anyone out there knows how to get comparable results without using NSTableView private methods, please please leave a comment.
The Example Project

The CustomTableView Xcode project can be dowloaded here.
If you’re new to Cocoa or subclassing NSTableView, the tutorial will be most useful if you download the project and follow along in the source code. The comments will help explain implementation details. The project has 3 classes:
- MyController (the application’s main controller and the table’s delegate and data source)
- MyTableView (the NSTableView subclass)
- MyCell (the NSCell subclass)
It also adds categories to NSColor and NSBezierPath for custom alternating background colors and rounded rectangles.
In the sample project, customizations are made to the following visual properties of the table:
- Alternating background colors
- Selection highlight
- Drag image
- Drop highlight on rows
- Drop highlight between rows
The table also uses an NSCell subclass to draw an icon and two rows of text. Let’s start with NSCell.
NSCell
Table views use the NSCell class to draw content in their rows. The first step to customizing the look of your table is to create an NSCell subclass and override the method
-(void)drawInteriorWithFrame:(NSRect)theFrame inView:(NSView *)theView
In the example project, the MyCell class divides the NSRect passed in the frame argument into three smaller rectangles that it uses to position the image, title and subtitle. The image and text are then drawn into those rectangles using NSImage and NSString’s respective -(void)drawInRect methods.
After you make your subclass, you need to let the table view know about it. You can set the cell for the columns of the table using NSTableColumn’s method
-(void)setDataCell:(NSCell *)theCell
In the example project, the MyController class sets the data cell for the table’s column with the following lines of code in awakeFromNib:
MyCell * aCustomCell = [[[MyCell alloc] init] autorelease]; [[oMyTableView tableColumnWithIdentifier:@"theTableColumn"] setDataCell:aCustomCell];
NSTableView
After you have an NSCell subclass, you may also want to customize some of NSTableView’s visual properties. You can set the background color, row height, whether or not the table should draw horizontal or vertical lines and the color of the lines using the NSTableView inspector palette in Interface Builder. Other adjustments take a little bit of code.
Alternating Background Colors
If you choose to display alternating background colors, the default colors are light blue and white. A simple way to customize the alternating background color scheme in your application is to add a category to NSColor that overrides the method
+(NSArray *)controlAlternatingRowBackgroundColors
In your implementation of this method, return an NSArray containing the two colors you want your tables to use.
Selection Highlight
In the example project, MyTableView draws a rounded rectangle in the selected rows. If you want to draw your own selection highlight, override the following method in your NSTableView subclass:
-(void)highlightSelectionInClipRect:(NSRect)theClipRect
This method is asking you to draw the highlights for the rows that are currently selected within the “clip rect”, or the area of the table that is visible. One way to do this is to:
- Use NSTableView’s method
-(NSRange)rowsInRect:(NSRect)theRect;
to get the range of row indexes that are currently visible in the clip rect.
- Use NSTableView’s method
-(NSIndexSet)selectedRowIndexes;
to get a list of all of the selected rows in the table.
- Go through the list of visible row indexes. If an index is included in the list of selected indexes, get the NSRect for that row with the NSTableView method:
-(NSRect)rectOfRow:(int)theRowIndex;
- Draw your custom highlight style inside this rectangle.
If your application is running on Tiger or any previous OS and you only override this method, you’ll notice that the table still draws its default highlight on top of your drawing. The second thing you must do to draw a custom selection highlight in this case is override the private method:
-(id)_highlightColorForCell:(NSCell *)cell
{
return nil;
}
Returning nil in this method will “stop” the table view from drawing the default highlight rectangle (weird, huh?).
If you only want to change the highlight selection color instead of drawing a custom highlight, you can simply override the method
-(id)_highlightColorForCell:(NSCell *)cell
to return the color of your choice.
Drag Image
If your NSTableView subclass supports dragging, you can create a custom drag image by overriding the method
- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)theRowIndexes tableColumns:(NSArray *)theTableColumns event:(NSEvent *)theEvent offset:(NSPointPointer)theOffset
In this method make an NSImage, draw what you want into the image and return it. You can get your NSTableView subclass to give you an NSImage with the content of the cell drawn inside by calling super’s implementation of the method.
In the example project, MyTableView composites the NSImage returned by super onto another NSImage with a rounded rectangle drawn inside. The new NSImage is returned as the drag image.
Drop Highlights
Drop highlights in NSTableView are notoriously fugly and they are not “legally” customizable. In the example project, MyTableView overrides some private methods to draw a black rounded rectangle to highlight drags over rows and a line with a small accent circle (like NSOutlineView) to highlight drags between rows.
For Tiger and earlier, there are two private methods that your NSTableView subclass can override to draw custom drop highlights on and between rows:
-(void)_drawDropHighlightOnRow:(int)theRowIndex; -(void)_drawDropHighlightBetweenUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex atOffset:(float)theOffset;
In both cases, use the NSTableView method
-(NSRect)rectOfRow:(int)theRowIndex
to retrieve the rectangle of the row you want to highlight.
In Leopard, things get a little more complicated. There are several private methods that NSTableView might use to draw the drop highlight between rows. Through trial and error, I found that overriding the following method does the trick:
- (void)_drawDropHighlightBetweenUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex onRow:(int)theRow atOffset:(float)theOffset
A new NSTableView feature in Leopard is the drop highlight color. Like the rest of the drop highlight visual properties, it is only customizable by overriding a private method. If you don’t override this method, the default implementation will draw a light blue background over the row that is the current drop target. If light blue isn’t your thing, you can override
+(id)_dropHighlightBackgroundColor;
to return the color of your choice. If you want your table to have the same highlights running in Leopard as it does in Tiger, return [NSColor clearColor].
Do Not Try This At Home!!!
I hope that this post will help you build a beuatiful table view that your users will love to look at, but once again, please please be careful when using private NSTableView methods for selection and drop highlights. While it’s fun and educational to explore the AppKit framework, you absolutely should not override private methods in your shipping app. I’ve filed an enhancement radar in the hopes that Apple will make these methods public:
rdar://problem/5758731
If anyone knows of other techniques that are more legal, please share them in the comments.
Resources
The source code in the CustomTableView example project has detailed comments that should help answer any questions you may have about implementing the techniques described in this post.
Other useful projects with alternate approaches to customizing the visual elements of a table:
- Mark Alldritt’s SourceListView
- Cocoa Pirate’s Source List implementation
- Apple’s ImageAndTextCell example code
If you are new to Cocoa drawing, I’d suggest reading the Quartz 2D Programming Guide as well the Learn Quartz tutorials at CocoaDevCentral.
For more information on NSTableView, start with the Introduction to Table Views Programming Guide. There is an NSTableView tutorial on the CocoaDev wiki as well as one at CocoaDevCentral. These will help with the less superficial parts of programming an NSTableView (like how to really implement drag and drop and data sources).
For more information on NSCell, read the Control and Cell Programming Topics for Cocoa.
If NSTableView isn’t exactly what you’re looking for and you’re targeting OS 10.5+, you might want to look into the new Cocoa class, NSCollectionView.
Also, If you’re targeting OS 10.5+, take a look at the new source list selection style for NSTableView.
Finally, class-dump lets you look at stuff you shouldn’t be looking at.