If you use macOS, you have probably realized many apps have the following UI component on their settings:

Screenshots that shows the UI controller that many apps use to add or remove items from a list

I had to add one of those to the settings view of Angle, and then I realized that it's not a pre-defined component that you can drag & drop and use. Does that mean we have to implement a custom view for it? You are right! And that's what I ended up doing.

It took me several iterations until I got it right. Since I'm sure I'm not the first one coming across this need, I'll leave the code snippet here:

import Foundation
import AppKit

class AddRemoveFooter: NSBox {

 // MARK: - Attributes

 fileprivate var addButton: NSButton!
 fileprivate var removeButton: NSButton!

 // MARK: - Init

 override init(frame frameRect: NSRect) {
 super.init(frame: frameRect)
 setup()
 }

 required init?(coder: NSCoder) {
 super.init(coder: coder)
 setup()
 }

 // MARK: - Internal

 func setAddAction(_ action: Selector?, target: AnyObject?) {
 addButton.action = action
 addButton.target = target
 }

 func setRemoveAction(_ action: Selector?, target: AnyObject?) {
 removeButton.action = action
 removeButton.target = target
 }

 // MARK: - Fileprivate

 fileprivate func setup() {
 setupStyle()
 setupButtons()
 }

 fileprivate func setupButtons() {
 contentView = NSView()
 contentView!.translatesAutoresizingMaskIntoConstraints = false
 NSLayoutConstraint.activate([
 contentView!.leadingAnchor.constraint(equalTo: self.leadingAnchor),
 contentView!.topAnchor.constraint(equalTo: self.topAnchor),
 contentView!.bottomAnchor.constraint(equalTo: self.bottomAnchor),
 contentView!.trailingAnchor.constraint(equalTo: self.trailingAnchor),
 ])

 addButton = NSButton(title: "+", target: nil, action: nil)
 addButton.bezelStyle = .shadowlessSquare
 addButton.translatesAutoresizingMaskIntoConstraints = false

 removeButton = NSButton(title: "﹣", target: nil, action: nil)
 removeButton.bezelStyle = .shadowlessSquare
 removeButton.translatesAutoresizingMaskIntoConstraints = false

 contentView!.addSubview(addButton)
 contentView!.addSubview(removeButton)

 NSLayoutConstraint.activate([
 addButton.leadingAnchor.constraint(equalTo: contentView!.leadingAnchor, constant: 0),
 addButton.topAnchor.constraint(equalTo: contentView!.topAnchor, constant: 0),
 addButton.bottomAnchor.constraint(equalTo: contentView!.bottomAnchor, constant: 0),
 addButton.widthAnchor.constraint(equalToConstant: 30)
 ])

 NSLayoutConstraint.activate([
 removeButton.leadingAnchor.constraint(equalTo: addButton.trailingAnchor, constant: -1),
 removeButton.topAnchor.constraint(equalTo: contentView!.topAnchor, constant: 0),
 removeButton.bottomAnchor.constraint(equalTo: contentView!.bottomAnchor, constant: 0),
 removeButton.widthAnchor.constraint(equalToConstant: 30)
 ])
 }

 fileprivate func setupStyle() {
 self.boxType = .custom
 self.alphaValue = 1
 self.borderColor = NSColor.gridColor
 self.borderType = .lineBorder
 self.borderWidth = 1
 }

}