Corbin's Treehouse - Corbin Dunn, Santa Cruz, CA
Plug Bug

Why the API? NSTableView -preparedCellAtColumn:row:

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.

This means:

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 
	tableColumns:(NSArray *)tableColumns 

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 
	typeSelectStringForTableColumn:(NSTableColumn *)tableColumn 

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.

6 Responses to “Why the API? NSTableView -preparedCellAtColumn:row:”

  1. Frank says:

    We used this method in our app for implementing a generic RTF copy & paste for any table content. Because in preparedCellAtColumn:row: all formatting has already been applied. This way it’s nice and simple.

  2. corbin says:

    Cool! I hadn’t even thought of that..

  3. Stephane says:

    I’m not sure to understand why the first parameter of this method is the index of the column instead of a NSTableColumn pointer. Columns can be moved as you obviously know and as you may observe the other 2 methods you listed uses a NSTableColumn pointer.

    Regarding point 3, let’s say I want to return a cell that is bigger than the dataCell. Is there any guarantee that the height of the data source can be provided?

    I mean the whole thing is somehow breaking the MVC paradigm as it seems to suggest that the tableview knows more than it should. Or said differently: why is not a method for the delegate instead of a method of NSTableView?

    I’m curious about this because I did something similar for the same purpose as point 3 but I did everything from the delegate/data source point of view.

  4. corbin says:

    Stephane — mainly because it is so easy to get the tableColumn for a given column integer.

    Making a cell larger — you have to use variable row heights; is that not working for what you want?

    Regarding this breaking MVC; it is very view specific; it is allowing the drawing portion to be setup in the cell by the view, but yes, it might be nice to have a delegate method that asks the controller for the tracking areas. But then again, that is what it already has, as the information is based on what is setup in the cell via -willDisplayCell.

  5. Scott Stevenson says:

    Thanks for writing this up. Hope to see more.

    We had a question at coder night where the programmer had a “total” column in an account ledger view. The text in the column is usually in red, but it needs to be white when selected. Is this method the best approach? I think we recommended willDisplay at the time.

    Also, same question for combo image+text cells — should the image content still be set in willDisplay or is this one better?

  6. corbin says:

    Hi Scott,

    It really depends on how complex they want to make things. The easiest approach is probably to simply setup things in -willDisplayCell — which is perfectly acceptable from the concept of MVC. The delegate/controller will get the -willDisplayCell and use this as the time to access things in the model to setup the cell properly. This is also fine for setting the image for an “image and text” cell, and is something we will always recommend.

    However, people may want to abstract things differently so the model can provide an -objectValue and something else, say an -myTableView:imageValueForTableColumn:row: delegate method for some specific cell type that allows setting both an image and text. Overriding -preparedCellAtRow:column: would be a good place to do this. The overridden point could check for the cell responding to some selector, and if so, call the delegate to get the image for that row/column. This would allow you to set the image for an image+text cell in a manner that is similar to setting the text via -tableView:objectValueForTableColumn:row:

    A side note; for setting the text value to red vs white when selected: this could be accomplished by a custom cell that properly handles the -backgroundStyle property. When the style is set to NSBackgroundStyleLight, it could use red text. When set to NSBackgroundStyleDark, it could use white text. Alternatively, this could be setup in -willDisplayCell based on what the -backgroundStyle property is already set to; it will already be properly setup before -willDisplayCell is called.

    I hope this helps clarify some things.

(c) 2008-2017 Corbin Dunn

Corbin's Treehouse is powered by WordPress. Made on a Mac.

Subscribe to RSS feeds for entries and comments.

39 queries. 0.398 seconds.