This post deals with colouring buttons. Some of the techniques described are against Apple’s InterfaceDesign guidelines; others are simply against the rules of good taste. This post is an exploration of what is possible and will provide a handy reference in the future if I ever think ‘oh, I wish I could have a coloured button’.

Please bear in mind that in Apple design, the default button – if there is one – gets highlighted. This is a design feature and resembles a button coloured blue, which might give a hint why Apple does not want you to easily add colour to a button.

I will use the following states:
bordered buttons, both on and off
unbordered (off only)

A note on operating systems:

I first wrote this post either on macOS 10.9 or 10.10, and while most of the buttons still show the same behaviour, there are some changes, so I have created all of the examples again for the updated version of this post.

Bordered buttons, off, 10.9/10.10 vs. 10.11

0) Setup:Create a new applet. In the storyboard, drag some buttons into the , create an IBOutlet for every button and create a variable to hold all buttons, so you can loop over them and apply changes to all. (‘lazy’ because the outlets are not set when the class is instantiated.) I’m using the buttons in the Object library as they appear for a storyboard project.

NSColor.blue.cgColor is the cgColor property of an NSColor, cgLayers cannot deal with NSColor directly.
button.state (the other is NSOffState) and isBordered allow you to quickly loop over the various examples.

Note the layer?: layer! caused a crash when I first ran the app, though it worked ok on subsequent builds. In any case, it’s good form to not force unwrap your optionals.

If you put this code in viewDidLoad you may need to run the app several times before the layer-based changes show up, so I found it vital to override viewWillAppear instead.

a) bordered buttons:

b) bordered buttons in their ‘on’ state

c) unbordered buttons:
The buttonness of buttons does not go away with their unbordered state – they still respond to mouseclicks – but the visual feedback of ‘you have pressed a button’ vanishes, so there’s no point in providing the ‘on’ state here.

1d) For completion’s sake, you can get the same effect by creating a subclass of NSButton and overriding drawRect(dirtyRect: NSRect) instead:

Note that the order of calls becomes extremely important here – if you call super.draw(dirtyRect) first – as the template suggests – you will overpaint all of the nice button-ness of your buttons and get the following:

borderColor

Return all buttons back to the NSButton class.

2) The first example uses the ‘backgroundColor’ property of the button’s layer; this example uses borderColor

button.layer?.borderColor = red
button.layer?.borderWidth = 2

a) bordered buttons

b) bordered buttons, all in their ‘on’ state:

c) unbordered buttons

3) Apple is making various noises about depleting, soft or otherwise, NSCell and its derivatives. They are already strongly discouraging cell-based tables (these still work but should not be used in production code); they have depleted NSMatrix (it still works, but you’re not supposed to use it because they haven’t fully deprecated it _yet_). So use the following NSButtonCell based code with extreme care:

If I hadn’t known this code was working, I would have discarded it as an option out of hand.

b) unbordered buttons and wantsLayer set to false

For comparison, this is what the same settings looked like in the previous version:

The long and short of this is that trying to colour the background of a button by these means is not overly useful.

Layer Effects with CoreImage content filters

So let’s try something else: using a layer effect on the backing layer. You can set this in InterfaceBuilder (eventually I will supply the code, but right now that’s not a priority)
[I found this via https://parmanoir.com/Changing_the_background_color_of_an_NSButton – this had not been on my radar at all]
using CALayer/Backing Layer, with screenshots

c) unbordered (off/on)
(I’m giving up on this branch: with layers, unbordered obviously does not work for the majority of buttons. Check boxes and radio buttons, on the other hand, may not be a hundred percent useful yet – there’s a considerable deviation from the colour I’ve used to the colour displayed – but I can see myself using a layer-based filter effect at some point.)

5a) And since the backing layer is almost there, here’s one with a white balance filter (‘WhitePoint adjust’); and the balance colour set to tangerine:

b) whitePoint Adjust, bordered buttons, all on

Slightly better. There are a couple of useful states in here, but overall I found the button colouring experience somewhat disappointing.

So. Images. Images should work, shouldn’t they? Images are part of a button’s interface in IB, and while you can certainly put an icon on a button, why shouldn’t you be able to use a full blown image covering the whole of the button?

Images

Purple buttons, lots of

6) Delete all layer effects and add two images to your asset catalogue: purple.jpg and purpleHighlight.jpg

set each button’s image to purple, the alternate image to purpleHighlight, and the scaling to .scaleAxesIndependently (you can do that in IB, but this is quicker; layer or not makes no visible difference):

6a) bordered buttons
b) bordered buttons, NSOnState

Apple? This ‘famous for graphical interfaces’ thing? I think we need to talk.

Remove those images. You do not want those images. Those images do not improve your interface.

7) We still want purple buttons. Obviously, the way to go is a PurpleButton class

(Here’s a cool thing: when I copied my code with image literals, the system pasted self.layer?.contents = #imageLiteral(resourceName: "purple"), which remains human-readable even in a plain text environment. Bravo Apple.)

This is a subclass of NSButton that updates its layer (and thus you must set wantsLayer to true; I do this in the same place as before, but you could also override wantsUpdateLayer, only it needs to have a getter _and_ a setter, otherwise the compiler complains and I was too lazy to implement this properly.) Set the class of all your buttons to PurpleButton.

7a) PurpleButtons, bordered

b) bordered, NSOnState

If you start with a button-shaped image with transparency around the edges, you could almost, I don’t know, have coloured button or something. But that only works if you *can* lovingly handcraft your buttons.

If you want a more dynamic way of creating images to draw onto your buttons, you can compose an NSImage and use *that* for your button’s image. Here, I’m replacing the image(named: “purpleHighlight”) with a single-colour NSImage created from NSColor.cyanColor():

c) Turn all your buttons into NSButtons again, add

button.layer?.contents = drawDynamicImage()

to the enumeration in viewWillAppear, and add the following function to the viewController class:

It appears that trying to set the button’s layer contents in ViewWillAppear is too late; I would not have thought that the difference between the two locations of what is essentiatlly the same code would make that much of a difference.

tl;dr:

All of the methods described here want adaptive measures; none of them work right out of the box. Button-shaped images with part transparency seem to be the best bet; and one might to adjust the colour/alpha value for on and off states. As it stands, I don’t find any of these truly satisfactory, though the coloured check and radio button using the whiteBalance content filter seem promising.