Skip to content

Instantly share code, notes, and snippets.

@yonat
Last active March 1, 2024 07:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yonat/167a375988da6b538cc9a36a1c92a328 to your computer and use it in GitHub Desktop.
Save yonat/167a375988da6b538cc9a36a1c92a328 to your computer and use it in GitHub Desktop.
Simplest iOS Checkbox (UIKit)
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)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment