Styling an NSTableView (DTTAH)
NSTableView 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.
Tags: Hacks, NSCell, NSTableView
February 22nd, 2008 at 4:26 pm
Neat!
What license is this under?
February 22nd, 2008 at 4:52 pm
Hi Brandon,
If you download the project again, I added a license. Thanks for pointing that out
February 23rd, 2008 at 12:36 am
very nice cathy! your code-fu is strong!
March 6th, 2008 at 11:23 am
Nice, I didn’t know you could draw the selection style !
Also there is an Apple sample showing off the new source list : http://developer.apple.com/samplecode/SourceView/index.html
April 6th, 2008 at 4:21 pm
It looks to me like what you’re doing here should be possible by overriding -drawRect: in your TableView subclass, and overriding the event methods to let you know which cell you want to highlight. Clobbering the private methods shouldn’t be necessary.
-jcr
April 6th, 2008 at 5:25 pm
The order that NSTableView draws the parts of the table are important. If you call super to draw first, then do your drawing, you will draw over the cells. If you call it after your drawing, it will draw over what you’ve drawn.
April 6th, 2008 at 6:31 pm
Drawing over the cells isn’t a problem, if what you’re drawing doesn’t cover them. That dividing line, for example, should work just fine if you draw it after the cells have done their drawing.
-jcr
April 6th, 2008 at 7:34 pm
Correct me if I’m wrong, but wouldn’t you need to completely turn off NSTable’s drag and drop handling and implement that over again yourself to get this to really work without overriding a private method? How else could you stop their default drawing methods from being executed?
April 7th, 2008 at 2:29 am
I don’t see a reason why you’d have to turn off the existing drawing behavior. If NSTableView is drawing outside of the usual -drawRect: mechanism by doing its own -lockFocus and draw operations, you should still be able to draw over that by sending -setNeedsDisplayInRect: in your own -mouseMoved: or -draggingUpdated: methods that call through to the inherited implementations.
I can find out who’s maintaining NSTableView these days and run it by him to be sure, but it seems to me that you should be able to achieve the effect you’re after without resorting to overriding the private methods.
-jcr
April 7th, 2008 at 8:06 am
Drawing on top of NSTableView’s drawing isn’t really an option. What if I design my line to be a 1 pixel thin red line? NSTableView’s thick black line would show underneath mine. In my example project, the drop highlight is set to “clear color” by overriding a private method. The default drawing in Leopard is a semi-transparent light blue rounded rect drawn above the cell. How could I draw “clear” over that? For the table’s drawing not to show, I’d have to use an opaque color that will cover up the cell.
I’d also have to exactly recreate the calculations that determine when to draw a drop highlight on top of a row versus a line between the row and the row above or below. If my implementation of this calculation is different from the table’s by a pixel, its row highlight would flash for a fraction of a second while I’m still drawing a line between a row, or the other way around.
One thing about the regular selection highlight - if you’re targeting anything before Leopard, there is a “bug” that causes the table to draw over your custom selection highlight. You have to override their private method for selection color to see your custom highlight drawing, as I mention in the post. This is well known and it’s fixed in Leopard.
The only real solution that I can see is for the drop highlight methods to be made public. I’ve filed a bug report requesting this. I can’t think of a reason why these should be private other than the fact that Apple may not want us to customize the drawing for drag and drop. If you do talk to someone about this, I’d be interested to hear their take on the matter. Please keep me posted on what you learn : )
April 11th, 2008 at 2:24 am
Unfortunately, the only way to customize the drop highlight stuff is to override the private methods. Yes, I’ve seen the bugs logged, but heck, it is much prettier in Leopard.
Notes: implement hitTestForEvent:inRect:ofView: to make your cell work when linked on Leopard.
You may be able to get away with not overriding _highlightColorForCell: on Tiger if you coordinate the cell’s drawing. The problem is the cell; it uses that color to draw the highlighting.
corbin (the person john would ask).
April 11th, 2008 at 2:30 am
And I should mention you should override this in your cell on Tiger:
- (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
and return nil, or the appropriate color that you want.
corbin
April 11th, 2008 at 9:54 am
Thanks, Corbin! It really is much prettier in Leopard.
You probably can’t answer this question, but I really really wonder - why are these particular methods are kept private? I’d get it if they provided some critical functionality to the table, but they’re just drawing methods. Also, why are some of the drawing methods public and some private?
I had to ask
April 11th, 2008 at 5:54 pm
Hey cathy,
I’m not the one to originally write the drop highlighting, but generally we make things private if we think there isn’t a reason to override the behavior, or if we don’t want people to override the behavior. Clearly, the drop highlighting was so ugly on Panther/Tiger that people wanted something better. It’s better on Leopard, but I do understand that people still want custom tables and want the ability to control the drop highlighting. Also, thank’s for mentioning radar numbers; it helps to know that you have logged a bug for this.
thanks,
corbin
May 6th, 2009 at 10:39 pm
Hi,
is there any best practice on how to get the data into a custom cell like used in the example (2strings one icon)?
I found different ideas, for example: http://www.martinkahr.com/2006/11/04/data-for-a-custom-cell-in-a-nstableview/index.html
I liked the blog. It would love to see it continued
May 11th, 2009 at 4:07 am
I am having some experience in programming Cocoa on Mac (but not a professional)
I downloaded the source code and understood how to get the custom cell into a table view. Very nice.
1. When I compiled and ran the code, it is displaying 5 rows with same custom cell. Is it possible, to have the rows added dynamically.
2. Next one is that, is it possible to bind the cells to NSArrayController so that the cells are automatically displaying the content of NSArrayController’s objects attributes.
3. My current project implementation has NSTableView bind to NSArrayController. How can I integrate this with my current project.
Thanks,
–Satyam.
September 5th, 2009 at 1:20 am
September 16th, 2009 at 7:54 pm
And why all always consider that they rule;govern on all 100% Yes too faithfully