September 11th, 2008 at 10:21 pm (trackback
I think I’ll do a few articles on why certain API was introduced in Leopard.
I’ll start with one of the new methods in NSTableView:
/* Returns the fully prepared cell that the view will normally use for
drawing or any processing. The value for the cell will be correctly set,
and the delegate method 'willDisplayCell:' will have be called. You can
override this method to do any additional setting up of the cell that is
required, or call it to retrieve a cell that will have its contents properly
set for the particular column and row.
- (NSCell *)preparedCellAtColumn:(NSInteger)column row:(NSInteger)row;
What is this method and what is it useful for? There are many reasons why it is useful, and they all are based on this fact: *all* of NSTableView’s operations that involve a cell at a particular row/column will be filtered through this method. Previously, NSTableView would have various internal methods and ways of obtaining the cell and there was no single spot where everything filtered through. Now there is.
1. You can acquire a “fully prepared” cell that is ready to draw — either in the NSTableView itself, or somewhere else. Somewhere else? Yes — an example where AppKit does this is for expansion tool tips, which draw in a separate tool tip window and in a view that is not actually an NSTableView. You might also want to access the “fully prepared” cell to generate a drag image when you override:
- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)dragRows
2. You can override the method to modify the cell. Think about this in a simple way, and it means you could easily bold odd rows without having to use -willDisplayCell:. Depending on how your code is setup, it might make more sense to do this work in the view, as opposed to the controller (via the delegate).
3. You can return an entirely different cell. Let’s say you want to have one cell that doesn’t change for one particular row; well, you can just always return that same cell, and it will always be used. You might want to do this if that particular cell has some sort of important state (such as an animation), which you don’t want to have to keep resetting or managing. Another example is for showing highlighting when you mouse over the cell.
Take this example from WWDC that I wrote a few years ago: PhotoSearch. Look at TrackableOutlineView.m, and you’ll see that the cell which is being tracked (ie: mouse is over it), is always returned from preparedCellAtColumn:row:. This was important from an abstraction perspective; the cell will get a single -mouseEntered: from the tracking area, and the same cell will eventually get a -mouseExited: — this allows the cell to set and maintain its own state, without forcing the delegate or NSTableView to save off the state and always reset it at draw time.
Why does this demo copy the cell, and not just use the single cell that is shared from the [tableColumn dataCell]? I mean, if the cell gets a mouseEntered:, and the tableView redraws *just* that cell, then things will work out fine, right? Yes — but if something else triggers any other row to redraw, then your tracked cell will be used, and it currently has the wrong state in it. That would be bad, and it would draw the wrong thing.
4. Another reason this method is useful is for type selection. When you type select, and NSTableView is attempting to find a row, it can acquire a fully prepared cell (including setting the stringValue, calling -willDisplayCell on the delegate, and setting up bindings). The stringValue of the cell can then be compared for type-selection to find a match. But, you wonder, isn’t that doing too much work for just matching on the string? Yes — for any decent sized table, you should implement this delegate method to directly return information from your model:
- (NSString *)tableView:(NSTableView *)tableView
5. Finally, another reason for the API; there is no easy way to know what the value of a cell will be if you are using bindings. Now, you can find out by using this method, since it will fill the cell’s content with bound values (if there are any).
Anyways, my first entry of “Why the API”. More to follow…hopefully.