This post will implement FlatButton
, a subclass of NSButton
, with a solid background color, rounded corners, and image padding.
- Override draw(:), Configure Corner Radius
- Add A Solid Background
- Set Image
- Adjust Bounds To Apply Image Padding
- Complete Implementation
Override draw(:), Configure Corner Radius
To implement a custom button, start by overriding the draw(:)
function. This method is called whenever the button is drawn.
Flat buttons often have rounded corners and a solid background color. To allow the button to have a background layer, wantsLayer
is set to true. Then, the cornerRadius
is set on layer
to apply rounded corners.
@IBDesignable class FlatButton: NSButton {
@IBInspectable var cornerRadius: CGFloat = 5
override func draw(_ dirtyRect: NSRect) {
// Set corner radius
self.wantsLayer = true
self.layer?.cornerRadius = cornerRadius
// Super
super.draw(dirtyRect)
}
}
Add A Solid Background
Next, the backgroundColor
property on layer
is set to the configured backgroundColor
. To make the button interactive, the background color is darkened when isHighlighted
is true. This will darken the background color when a user clicks on the button.
@IBInspectable var backgroundColor: NSColor = .blue
// inside of draw(_ dirtyRect:)
// Darken background color when highlighted
if isHighlighted {
layer?.backgroundColor = backgroundColor.blended(
withFraction: 0.2, of: .black
)?.cgColor
} else {
layer?.backgroundColor = backgroundColor.cgColor
}
// ...
Set Image
Often flat buttons have an image next to the button text. In this implementation of FlatButton
, the icon will be in the imageLeading
position (on the left-hand side of the text).
@IBInspectable var imageName: String = "NSActionTemplate"
// inside of draw(_ dirtyRect:)
// Set Image
imagePosition = .imageLeading
image = NSImage(named: imageName)
// ...
Adjust Bounds To Apply Image Padding
Finally, padding is applied to ensure the text and image are not touching any edge of the button. This implementation changes bounds
for the super.draw(:)
call explicitly and uses defer
to set bounds
back when the function exits.
@IBInspectable var dxPadding: CGFloat = 10
@IBInspectable var dyPadding: CGFloat = 10
// inside of draw(_ dirtyRect:)
// Reset the bounds after drawing is complete
let originalBounds = self.bounds
defer { self.bounds = originalBounds }
// Inset bounds by padding
self.bounds = originalBounds.insetBy(
dx: dxPadding, dy: dyPadding
)
// ...
Flat NSButton
That's it! With FlatButton
you can implement an NSButton
using flat design. The provided implementation of FlatButton
results in the following:

Complete Implementation
@IBDesignable class FlatButton: NSButton {
@IBInspectable var cornerRadius: CGFloat = 5
@IBInspectable var dxPadding: CGFloat = 10
@IBInspectable var dyPadding: CGFloat = 10
@IBInspectable var backgroundColor: NSColor = .blue
@IBInspectable var imageName: String = "NSActionTemplate"
override func draw(_ dirtyRect: NSRect) {
// Set corner radius
self.wantsLayer = true
self.layer?.cornerRadius = cornerRadius
// Darken background color when highlighted
if isHighlighted {
layer?.backgroundColor = backgroundColor.blended(
withFraction: 0.2, of: .black
)?.cgColor
} else {
layer?.backgroundColor = backgroundColor.cgColor
}
// Set Image
imagePosition = .imageLeading
image = NSImage(named: imageName)
// Reset the bounds after drawing is complete
let originalBounds = self.bounds
defer { self.bounds = originalBounds }
// Inset bounds by padding
self.bounds = originalBounds.insetBy(
dx: dxPadding, dy: dyPadding
)
// Super
super.draw(dirtyRect)
}
}