Custom themed table view

While most Mac applications rely on the standard controls provided by MacOS X to do their work, most application developers like to follow Apple's lead and apply their own textures, colors and fonts. And why not? Screens today have a much higher resolution and aren't limited to basic B/W shapes. Tinting or texturing a user interface -- when done with restraint and taste -- helps the user find your application's windows on multiple-window desktops more easily, and can increase recognizability of your application for other purposes.

But how does one correctly theme user interface controls with minimal interference to Apple-provided default behaviour? After all, we want our application to still look and, more importantly, behave in recognizable and familiar ways to the user. We just want it to look like a different material, or match the application's logo and packaging better.

Let's start with a table view. While an NSCollectionView can be easily themed by modifying various views' color settings in Interface Builder, the best you can do for an NSTableView or its subclass NSOutlineView is set one single solid background color.

We need to write code, then. There is enough documentation for the old NSCell-based table views out there that describe how to override - (void)drawRow:(NSInteger)row clipRect:(NSRect)clipRect to theme those. So I'll restrict myself to themeing the view-based table view variant. Most of the cells can easily be edited, but what if you want custom alternating row colors?

Luckily, NSTableRowView has a backgroundColor property that you can change. So all you need to do is make sure that you provide a - (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row method in your delegate that uses the % operator to grab the corresponding color from an array of alternating row colors and sets the appropriate one as each rowView's backgroundColor.

However, this only fixes the cells. The area below the rows, or an empty table, will still have whatever standard alternating row colors the system uses. You will have to subclass NSTableView and override  - (void)drawBackgroundInClipRect:(NSRect)clipRect to fill the area below the rows.

Call -rowViewAtRow:(NSInteger)row makeIfNecessary: YES to get the last row's NSMaxY(), if it is inside the bounds, draw alternating rectangles with a height of -(CGFloat) rowHeight  underneath it until the row rect is fully out of view (start their row number at -(NSInteger) numberOfRows).

It is unfortunate that there seems to be no public equivalent to tableView:didAddRowView:forRow: in NSTableView itself. That way, one could create one subclass that takes care of the whole appearance consistently, instead having to add code for that to each delegate.