CustomControlsInASidebar

In the previous article, I illustrated how one can theme NSTableView. Another control that commonly needs to be customized in themed Mac applications is NSButton. Since NSButton is a cell-based NSControl subclass, we don't actually need to subclass it. Rather, we need to subclass NSButtonCell.

If you are using a XIB file, you can then just select the NSButton's NSButtonCell by clicking the button a couple of times, or by uncollapsing the button in the outline view and selecting the cell there, and setting its class to the name of our subclass.

In most cases, it is sufficient to override - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView*)controlView and draw your custom button body there. NSButtonCell will take care of drawing the title, image etc.

The biggest difficulty in that case is to find out when and how to highlight your button. There are two ways a button can be highlighted, which are most obvious in a checkbox button: The actual highlight when a button is pressed is indicated by the -(BOOL) isHighlighted property.

In addition to that, a checkbox can be selected or unselected, which is indicated by its -(NSInteger) state property being set to NSOnState, NSOffState or NSMixedState. However, while every button toggles its state when clicked, not all buttons draw differently when their state is not NSOffState To find out whether you should look at the button's state, query the -(NSInteger) showsStateBy bit mask to check whether the NSChangeGrayCell bit is set. Your button is only supposed to reflect its state if this bit is set.

If your button shape and position differs from the standard size and shape, you will also want to override - (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView to return NSCellHitContentArea | NSCellHitTrackableArea so the user doesn't click beside your button or on its shadow to drag the window and accidentally triggers its action, or worse, click on the button to have nothing happen because the standard system button doesn't extend as far as your custom drawing.

Usually that is all you need. You will get a custom look, but the system will provide all the correct tracking behaviour and implement accessibility actions etc. for you, swap out the title and image for the alternate title and image as needed etc.

However, sometimes you need to also adjust the title text color, e.g. because you are implementing a dark button, or because you want to reflect the button's selected state through its text color being different from the highlighted variant. To do that, you override - (NSRect)drawTitle:(NSAttributedString*)title withFrame:(NSRect)frame inView:(NSView*)controlView. Note that the frame that is passed to this is usually already correct. Just draw your text at the origin of this rect in the font set on the cell, and you should be fine. And return the actual rectangle in which your drawing ended up.

If the standard button text positioning or image positioning doesn't match your button's design, there are - (NSRect)titleRectForBounds:(NSRect)theRect and - (NSRect)imageRectForBounds:(NSRect)theRect for you to override.

Finally, if you plan to create your buttons in code instead of thawing them from a XIB file, you will also have to create a subclass of NSButton and call +(void) setCellClass: (Class)inClass in its +(void) load method to tell it what cell class to instantiate.