WEBサイト制作・アプリ開発・システム開発・ブランディングデザイン制作に関するご相談はお気軽にご連絡ください。
構想段階からじっくりとヒアリングし、お客様の課題にあわせたアプローチ手法でお客様の“欲しかった”をカタチにしてご提案いたします。
Blog スタッフブログ
iOS
Swift
システム開発
ひとくちコードスニペット
[Swift]UIControlのサブクラスでチェックボックスを作ってみた

こんにちは、株式会社MIXシステム開発担当のBloomです。
今回はUISwitchの存在により標準コンポーネントに採用されていない(筆者の思い込みです)チェックボックスをUIControlのサブクラスとして作ってみましょう。
import UIKit
@MainActor
@IBDesignable
final class CheckboxControl: UIControl {
@IBInspectable var isOn: Bool = false {
didSet {
guard oldValue != isOn else { return }
updateAppearance(animated: true)
sendActions(for: .valueChanged)
UIAccessibility.post(notification: .announcement,
argument: isOn ? "Checked" : "Unchecked")
}
}
@IBInspectable var iconSize: CGFloat = 22 { didSet { invalidateIntrinsicContentSize(); setNeedsLayout() } }
@IBInspectable var spacing: CGFloat = 8 { didSet { invalidateIntrinsicContentSize(); setNeedsLayout() } }
@IBInspectable var onTintColor: UIColor = .systemBlue { didSet { updateAppearance(animated: false) } }
@IBInspectable var offTintColor: UIColor = .secondaryLabel { didSet { updateAppearance(animated: false) } }
@IBInspectable var text: String = "" { didSet { label.text = text; invalidateIntrinsicContentSize() } }
@IBInspectable var textColor: UIColor = .label { didSet { label.textColor = textColor } }
@IBInspectable var onSymbolName: String = "checkmark.square.fill" { didSet { updateAppearance(animated: false) } }
@IBInspectable var offSymbolName: String = "square" { didSet { updateAppearance(animated: false) } }
// MARK: - Views
private let imageView = UIImageView()
private let label = UILabel()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
Task { @MainActor in
commonInit()
updateAppearance(animated: false)
}
}
private func commonInit() {
isAccessibilityElement = true
accessibilityTraits = [.button]
imageView.contentMode = .scaleAspectFit
imageView.setContentHuggingPriority(.required, for: .horizontal)
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
label.textColor = textColor
label.numberOfLines = 1
addSubview(imageView)
addSubview(label)
addTarget(self, action: #selector(didTap), for: .touchUpInside)
updateAppearance(animated: false)
}
// MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
let h = bounds.height
let icon = min(iconSize, h)
let iconY = (h - icon) / 2
imageView.frame = CGRect(x: 0, y: iconY, width: icon, height: icon)
let labelX = imageView.frame.maxX + (text.isEmpty ? 0 : spacing)
let labelW = max(0, bounds.width - labelX)
label.frame = CGRect(x: labelX, y: 0, width: labelW, height: h)
}
override var intrinsicContentSize: CGSize {
let labelSize = label.intrinsicContentSize
let w = iconSize + (text.isEmpty ? 0 : spacing + labelSize.width)
let h = max(iconSize, labelSize.height)
return CGSize(width: w, height: h)
}
// MARK: - Interaction
@objc private func didTap() {
isOn.toggle()
}
override var isHighlighted: Bool {
didSet {
let alpha: CGFloat = isHighlighted ? 0.6 : 1.0
UIView.animate(withDuration: 0.12) { [weak self] in
self?.alpha = alpha
}
}
}
// MARK: - Update
private func updateAppearance(animated: Bool) {
accessibilityLabel = text.isEmpty ? "Checkbox" : text
accessibilityValue = isOn ? "On" : "Off"
let symbolName = isOn ? onSymbolName : offSymbolName
let tint = isOn ? onTintColor : offTintColor
let config = UIImage.SymbolConfiguration(pointSize: iconSize, weight: .regular)
let img = UIImage(systemName: symbolName, withConfiguration: config)
let apply = { [weak self] in
guard let self else { return }
imageView.image = img
imageView.tintColor = tint
label.text = text
}
if animated {
UIView.transition(with: imageView, duration: 0.15, options: .transitionCrossDissolve) {
apply()
}
} else {
apply()
}
}
}
storyboard上で配置したい時はUIViewを仮置きしてからCheckboxControlクラスを指定してあげましょう。それでは実際に実行してみます。
実行結果


これだけでラベル領域もタップが反応するチェックボックスを実装することができました。良かったですね。