Brian Dunagan

November 10 2008
Cocoa Tutorial: Link Arrows, Part 2

Unfortunately, while my first pass at link arrows functionally worked, it didn't work very well.

First pass If you take the code from the original post, hook up setTitle: in a willDisplayCell: method, and connect the link arrow cell's selector to an action, you get this. The text for the link arrow cell is clearly different from the normal table cell, and clicking anywhere on the link arrow cell triggers the action. Bad.

Second pass Let's say you're okay with triggering the action by clicking anywhere in the cell. You could try to use setAttributedTitle: instead of setTitle. However, then when you highlight a row, the text doesn't turn white. Bad.

Third pass Perhaps you realize that you can fix the color by simply getting the current color of the original attributed title. My code for this has already turned into the following:

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSFont *font = [NSFont systemFontOfSize:13];
[dict setObject:font forKey:NSFontAttributeName];
NSRange range = NSMakeRange(0, 0);
NSColor *fontColor = [[aCell attributedTitle] attribute:NSForegroundColorAttributeName atIndex:0 effectiveRange:&range];
[dict setObject:fontColor forKey:NSForegroundColorAttributeName];
NSAttributedString *title = [[NSAttributedString alloc] initWithString:[currentFeedItem valueForKeyPath:@"properties.title"] attributes:dict];
[aCell setAttributedTitle:title];

The text color works, both when highlighted and defocused. But, there is still the problem of clicking anywhere triggering the action. Bad.

Subclassing NSTextFieldCell I iterated through all of these before stepping back and rethinking my approach. The problem was leveraging NSButtonCell. While the cell type worked as a quick hack, it left a lot to be desired. It didn't handle clicks correctly. And I couldn't add an icon next to the text. Subclassing was necessary to get the cell to work just right. Instead of NSButtonCell, I subclassed NSTextFieldCell and composed in an NSButtonCell. That way, the title's font/size/color just worked. To get the objects in the right place in the cell, I simply overrode the cell's draw method and positioned them.

One difficulty was registering clicks. I wanted the user to be able to click on a link arrow to perform an action (like open a feed item in a browser). In 10.5, I found the method - (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView. The resulting custom cell worked well and was extensible in case I wanted to add an icon next to the title. Check out the code below:

@implementation BDLinkArrowCell

- (id)initWithCoder:(NSCoder *)coder
	self = [super initWithCoder:coder];
	if (self)
		// Set up link arrow.
		linkArrow = [[NSButtonCell alloc] init];
		[linkArrow setButtonType:NSSwitchButton];
		[linkArrow setBezelStyle:NSSmallSquareBezelStyle];
		[linkArrow setImagePosition:NSImageRight];
		[linkArrow setTitle:@""];
		[linkArrow setBordered:NO];
		[linkArrow setImage:[NSImage imageNamed:NSImageNameFollowLinkFreestandingTemplate]];
		[linkArrow setAlternateImage:[NSImage imageNamed:NSImageNameFollowLinkFreestandingTemplate]];
	return self;

- (void)encodeWithCoder:(NSCoder *)coder
	[super encodeWithCoder:coder];

- (void)setLinkVisible:(BOOL)isVisible
	isLinkVisible = isVisible;

- (void)drawInteriorWithFrame:(NSRect)aRect inView:(NSView *)controlView
	NSRect textRect = NSMakeRect(aRect.origin.x, aRect.origin.y, aRect.size.width - 18, aRect.size.height);
	linkRect = NSMakeRect(aRect.origin.x + aRect.size.width - 12, aRect.origin.y, 12, aRect.size.height);

	// Draw text.
	[super drawInteriorWithFrame:textRect inView:controlView];

	// Draw link arrow.
	if (isLinkVisible)
		[linkArrow drawInteriorWithFrame:linkRect inView:controlView];

// 10.5+ method
- (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView
	NSPoint p = [[[NSApp  mainWindow] contentView] convertPoint:[event locationInWindow] toView:controlView];
	if (p.x > linkRect.origin.x && p.x < (linkRect.origin.x + linkRect.size.width))
		// Hit the link.
		[[NSApp delegate] openURLInBrowser:nil];
		return NSCellHitContentArea | NSCellHitEditableTextArea;
		return NSCellHitNone;

Mac OS X 10.6 for $30 Cocoa Tutorial: Source List Badges, Part 2
LinkedIn GitHub Email