I recently needed to embed the ‘Discrete’ variant of the NSLevelIndicatorCell into an NSTableView. The discrete cell looks like this:
Unfortunately adding the cell caused drawing of the table’s window to slow to crawl. The spinning beachball of death would often appear when I was merely resizing the window. After some quick experimented I narrowed it down to something within the ‘Discrete’ variant of the NSLevelIndicatorCell, the other variants had no such performance problems.
It didn’t take an expert in Shark to realise what was going on:
It looked like NSLevelIndicatorCell was perform an expensive gaussian blur per cell, possibly even for each discrete block within each cell. For a single NSLevelIndicatorCell that shouldn’t be a real problem, but for a whole table column full of them it could create major performance problems.
Some quick google searches showed that people had complained about poor NSLevelIndicatorCell performance before but failed to find a solution.
Switching to another variant of NSLevelIndicatorCell wasn’t an option, the ‘Discrete’ variant was just too sexy. So the obvious solution was to create a subclass of NSLevelIndicatorCell that posed as the real thing, but cached the drawing code of its parent class into NSImages. So I present to the the world the imaginatively titled CCachingLevelIndicatorCell.
CCachingLevelIndicatorCell is designed to pose as NSLevelIndicatorCell but shouldn’t require any other configuration. The class will only cache images for the ‘Discrete’ variant and will use a dictionary of NSImage objects to cache the drawing (one image per discrete value the cell can display). If any other attribute of the cell is changed the class will discard the cache and attempt to recache the drawing at the next opportunity.
Just so you can compare the differences in speed I’ve created two stand-alone test applications so you can compare the speed without CCachingLevelIndicatorCell and with it posing as NSLevelIndicatorCell. The binaries are checked into my public subversion.
Radar bug: rdar://problem/4601201
Add New Comment
Viewing 6 Comments
Thanks. Your comment is awaiting approval by a moderator.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
Awesome treehouse(s) by the way.
Do you already have an account? Log in and claim this comment.
Do you already have an account? Log in and claim this comment.
I should make an exception for you though Chris… How about commercial - $1,000,000 per seat?
Do you already have an account? Log in and claim this comment.
Try writing a subclass with this as its only implementation:
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
[NSGraphicsContext saveGraphicsState];
NSRectClip(cellFrame);
[super drawWithFrame:cellFrame inView:controlView];
[NSGraphicsContext restoreGraphicsState];
}
The issue is with CG transparency layers (see CGContextBeginTransparencyLayer). They're a nice, clean way to a bunch of drawing, then put a shadow or a focus ring on the whole thing. Problem is, when you composite a transparency layer, it'll do calculations over the whole clip. It happens that when a cell is drawn in a tableview, the only clip is to the whole table.
That's, uh, bad.
Do you already have an account? Log in and claim this comment.
I took out my code and replaced it with your simple clipping fix.
It certainly improved performance over the non-posed version but I think the caching version is still the fastest. Which is a shame, as your code is certainly cleaner and simpler.
You can compare all three versions here: http://toxic-public.googlecode.com//trunk/Misc/...
Add New Comment