Customize NSButton Colors And Rounded Corners On macOS Projects

Xcode makes available a variety of buttons to use when developing macOS applications. However, none of the predefined button styles leaves much room for customization. For that reason, in this short post I'm going to show how to customize NSButton colors and corner radius; including background, text and highlight colors.

Note

Find this topic as video tutorial on YouTube.

As you'll see next, all configurable properties will become available in the Interface Builder too. By doing so, it will be possible to customize buttons graphically straight in a storyboard or a XIB file; no need for further configuration in code.

That said, let's get into some coding. We'll start by subclassing the NSButton class as shown next:

class RoundedColoredButton: NSButton {

}

Initially, we'll declare the properties we'd like to configure, and we'll give them some initial values. We'll mark all of them with the @IBInspectable attribute, so they become available in Interface Builder too.

@IBInspectable var bgColor: NSColor = .darkGray
@IBInspectable var foreColor: NSColor = .white
@IBInspectable var highlightColor: NSColor = .black
@IBInspectable var cornerRadius: CGFloat = 8

Next, we'll implement a custom method that will do three things:

  1. It will set the background color of the button. Note that we'll do that conditionally; if the button is highlighted, then we'll apply the highlightColor that we declared above. Otherwise we'll use the bgColor.
  2. It will specify the text color. This is not as straightforward as it sounds; it's necessary to create an attributed string that will use the provided text color, and have the button to use that as a title.
  3. It will set the corner radius of the button.

Both background colors and the corner radius will be applied to the button's layer. Note that NSButton has a backing layer on by default, so it's not necessary to start with the wantsLayer = true statement.

Here's that new method:

func configure() {
    // Set the proper background color depending on
    // whether the button is highlighted or not.
    if !isHighlighted {
        self.layer?.backgroundColor = bgColor.cgColor
    } else {
        self.layer?.backgroundColor = highlightColor.cgColor
    }
    
    // Create an attributed string using the
    // provided title color, and use that attributed
    // string as title.
    let attributedString = NSAttributedString(string: title,
                                              attributes: [NSAttributedString.Key.foregroundColor: foreColor])
    self.attributedTitle = attributedString
    
    // Set the corner radius.
    self.layer?.cornerRadius = cornerRadius
}

Finally, let's override the draw(_:) method of the NSButton class:

override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)
    configure()
}

That was the only code we had to write! To use the above, open a storyboard or a XIB file, and add a button to a view. Preferably, add a gradient button so you can set its height as well if you want so.

With the button selected, open the Identity inspector and set the RoundedColoredButton as its custom class.

Then, switch to the Attributes inspector where you will find the custom properties we specified as @IBInspectable in the class.

Choose and set any values you want, and finally run the app.

Note

In case you're wondering why there is no live rendering in Interface Builder, that's because the RoundedColoredButton class is not marked with the @IBDesignable attribute. I avoided it on purpose for a single reason; NSButton does not render live like other views do, and no solution or workaround currently seems to exist for that. Nevertheless, it's not that important as we want custom buttons to look properly at runtime. Having them presented nicely in IB is a convenience that we're just missing.

I hope you found this post useful! As an exercise, add more configurable properties to the custom button implementation as shown here, and make things work according to your needs. Thanks for reading!