Every so often a programmer runs into something where the documentation seems horribly unclear to the point of being downright unhelpful. When I started to write Cocoa apps, this seemed to be the case a lot. As I got used to how Cocoa does things, the gaps went away, since I was getting better at realizing how object oriented classes work and the way that Cocoa does things.Today I had that same problem, and it seems non-obvious enough that I thought I’d write up an entry about what I was doing, how I did it, and what I think the documentation could cover better.
I’m working on a drag and drop template editor for Stone Hill Invoicer. Having a highly configurable table view for itemized lists of products is quite important. I wanted to allow the user to resize and drag columns around, so writing a custom table control was going to be bad idea, especially when NSTableView supports pretty much everything I need it to do natively.
I’ve created a subclass of NSView which handles dragging resizing. Within this view, I position the control which I want the user to be able to manipulate. The parent view handles resizing it and keeping it updated.When the user drags the line ‘Default Table’ from the source list of my template editor to the editing canvas, a new CHTableView is instantiated where they drop it. It worked great, except that the table didn’t have headers showing.In Interface Builder, there’s a checkbox to display column headers in the views you create. Checking through the NSTableView class, there’s no method to set if headers are visible or not. NSTableView.h also doesn’t seem to contain any flags about if they’re shown or not.Reading the documentation soon shows that there’s an NSTableHeaderView which contains all the header cells. You can set a new header view with no problem. But it still won’t show up if you simply initWithFrame: an NSTableView in an NSView.
It turns out that the NSTableView doesn’t draw the header. The header is actually drawn by the NSScrollView which contains the NSTableView. I found this referenced at toodarkpark.net under the ‘Auxiliary Components’ section. The Apple developer pages don’t seem to contain this information.
NSScrollView queries any document view it’s given for the cornerView and headerView methods, and if the document view responds and returns objects for them, the NSScrollView automatically tiles them along with its scrollers and the document view.
Now that I know the answer, I found this fairly quickly in the “Table View Programming Guide”.
A table view is usually displayed together with a corner view and header view inside a scroll view (NSScrollView) object. The default corner view is a simple view object that fills in the corner above the vertical scroller. The header view is usually an instance of NSTableHeaderView that draws the column headers and handles column selection, rearranging, and resizing. If a table view is enclosed by a scroll view, the table view is the document view of the scroll view object.You can replace the default corner view and header view of a table with custom views if you want. For example, you might replace the corner view with a button that sorts the table contents using the selected column. To replace the views, you use the setCornerView: and setHeaderView: methods of NSTableView. During window layout, the scroll view retrieves your custom views using the cornerView and headerView methods of NSTableView and tiles them along with the scrollers and document view.
So what’d I do? I just made my custom view a subclass of NSScrollView instead of NSView.
Edit: I decided that since the NSTableView was completely filling the subclass and covering up my resize handles, I should just add the headerView of the table view as another subview of my subclass. This works just as well, but you do have to adjust its origin because NSScrollView uses flipped coordinates
How The Documentation Could Improve
In reading the documentation, I had been under the impression that the NSTableHeaderView was displayed within the NSTableView. Given the class naming, this seems like a logical assumption.I think it’d do a lot towards making this more understandable if Apple took the line:
A table view is usually displayed together with a corner view and header view inside a scroll view (NSScrollView) object.
and replaced it with
A table view is usually displayed with it’s corner view and header view as subviews of a scroll view (NSScrollView) object. A table view does not display it’s corner and header views itself.
I hate it when I fight with code for a day and then have to feel really silly afterwards.