Simplest iOS Checkbox (UIKit) (original) (raw)

import UIKit

/// Checkbox with title. When checked, `isSelected` is `true`.

class CheckboxButton: UIButton {

/// Handler called when the checkbox is checked or unchecked

var onCheck: ((Bool) -> Void)?

/// - Parameters:

/// - title: Text shown next to the checkbox

/// - color: Color for both checkbox and title

/// - normalImage: Unchecked image (default is empty square)

/// - selectedImage: Checked image (default is square with V)

/// - spacing: Space between checkbox and title

/// - direction: Layout direction

/// - onCheck: Handler to call when the checkbox is checked or unchecked

init(

title: String? = nil,

color: UIColor? = nil,

normalImage: UIImage = UIImage(systemName: "square")!,

selectedImage: UIImage = UIImage(systemName: "checkmark.square.fill")!,

spacing: CGFloat = 8,

direction: UIUserInterfaceLayoutDirection = UIApplication.shared.userInterfaceLayoutDirection,

onCheck: ((Bool) -> Void)? = nil

) {

super.init(frame: .zero)

self.onCheck = onCheck

setTitle(title, for: .normal)

if let color {

tintColor = color

}

setup(normalImage: normalImage, selectedImage: selectedImage, spacing: spacing, direction: direction)

}

required init?(coder: NSCoder) {

super.init(coder: coder)

setup()

}

override init(frame: CGRect) {

super.init(frame: frame)

setup()

}

override func didMoveToSuperview() {

// align imageView to top

guard let superview, let imageView else { return }

guard imageView.translatesAutoresizingMaskIntoConstraints else { return }

imageView.translatesAutoresizingMaskIntoConstraints = false

imageView.firstBaselineAnchor.constraint(equalTo: firstBaselineAnchor).isActive = true

}

@objc private func toggleCheckbox() {

isSelected.toggle()

onCheck?(isSelected)

}

private func setup(

normalImage: UIImage = UIImage(systemName: "square")!,

selectedImage: UIImage = UIImage(systemName: "checkmark.square.fill")!,

spacing: CGFloat = 8,

direction: UIUserInterfaceLayoutDirection = UIApplication.shared.userInterfaceLayoutDirection

) {

addTarget(self, action: #selector(toggleCheckbox), for: .primaryActionTriggered)

setImage(normalImage, for: .normal)

setImage(selectedImage, for: .selected)

semanticContentAttribute = direction == .leftToRight ? .forceLeftToRight : .forceRightToLeft

if #available(iOS 15.0, *) {

configuration = .plain().updated(for: self)

configuration?.imagePadding = spacing

configuration?.contentInsets = .zero

configuration?.background.backgroundColor = .clear

} else {

if direction == .leftToRight {

contentEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: spacing)

titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: -spacing)

} else {

contentEdgeInsets = .init(top: 0, left: spacing, bottom: 0, right: 0)

titleEdgeInsets = UIEdgeInsets(top: 0, left: -spacing, bottom: 0, right: spacing)

}

}

}

}