~/ppinera.es/src
Thoughts, reflections, and ideas



Add and remove footer using NSBox

Posted on

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
    }

}