Skip to content

Instantly share code, notes, and snippets.

@ArtiomKha
Last active November 9, 2023 02:38
Show Gist options
  • Save ArtiomKha/1f1e6eb601e42e4a9f2ca3a0f751c062 to your computer and use it in GitHub Desktop.
Save ArtiomKha/1f1e6eb601e42e4a9f2ca3a0f751c062 to your computer and use it in GitHub Desktop.
Gist for Medium article about radio button
import UIKit
class RadioButton: UIControl {
var unselectedBackgroundColor: UIColor = .white {
didSet {
contentView.backgroundColor = unselectedBackgroundColor
}
}
var borderColor: UIColor = .black {
didSet {
contentView.layer.borderColor = (isOn ? selectedColor : borderColor).cgColor
}
}
var selectedColor: UIColor = .blue {
didSet {
checkedView.backgroundColor = selectedColor
contentView.layer.borderColor = (isOn ? selectedColor : borderColor).cgColor
}
}
var isOn: Bool = false {
didSet {
updateState()
}
}
private let size: CGFloat = 24
private let checkedViewSize: CGFloat = 12
private let contentView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.borderWidth = 1.2
return view
}()
private let checkedView: UIView = {
let view = UIView()
view.backgroundColor = .blue
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
addSubview(contentView)
contentView.addSubview(checkedView)
contentView.layer.cornerRadius = size / 2
checkedView.layer.cornerRadius = checkedViewSize / 2
updateState()
NSLayoutConstraint.activate([
checkedView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
checkedView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
checkedView.widthAnchor.constraint(equalToConstant: checkedViewSize),
checkedView.heightAnchor.constraint(equalToConstant: checkedViewSize),
contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
contentView.topAnchor.constraint(equalTo: topAnchor),
contentView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor),
contentView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor),
contentView.widthAnchor.constraint(equalToConstant: size),
contentView.heightAnchor.constraint(equalToConstant: size)
])
}
private func updateState() {
contentView.backgroundColor = unselectedBackgroundColor
checkedView.backgroundColor = selectedColor
contentView.layer.borderColor = (isOn ? selectedColor : borderColor).cgColor
checkedView.isHidden = !isOn
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
isOn.toggle()
sendActions(for: .valueChanged)
}
}
import UIKit
class RadioButtonsStack: UIView {
private let stackView: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.alignment = .fill
view.distribution = .fill
view.axis = .vertical
view.spacing = 12
return view
}()
private var radioViews = [RadioButtonView]()
var selectedIndex: Int?
init() {
super.init(frame: .zero)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
func set(_ options: [String]) {
radioViews.removeAll()
stackView.removeAllArrangedSubviews()
for (index, text) in options.enumerated() {
let radioView: RadioButtonView = {
let view = RadioButtonView()
view.radioButton.tag = index
view.radioButton.addTarget(self, action: #selector(radioSelected(_:)), for: .valueChanged)
view.set(text)
return view
}()
stackView.addArrangedSubview(radioView)
radioViews.append(radioView)
}
}
@objc private func radioSelected(_ sender: RadioButton?) {
guard let sender else { return }
selectedIndex = sender.tag
radioViews.forEach {
$0.select($0.radioButton.tag == sender.tag)
}
}
class RadioButtonView: UIView {
let radioButton: RadioButton = {
let radioButton = RadioButton(frame: .zero)
radioButton.translatesAutoresizingMaskIntoConstraints = false
return radioButton
}()
let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textAlignment = .left
label.font = .systemFont(ofSize: 18)
return label
}()
let stackView: UIStackView = {
let view = UIStackView()
view.translatesAutoresizingMaskIntoConstraints = false
view.alignment = .center
view.distribution = .fill
view.axis = .horizontal
view.spacing = 16
return view
}()
init() {
super.init(frame: .zero)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
addSubview(stackView)
stackView.addArrangedSubview(radioButton)
stackView.addArrangedSubview(label)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
radioButton.widthAnchor.constraint(equalToConstant: 30)
])
}
func set(_ text: String) {
label.text = text
}
func select(_ select: Bool) {
radioButton.isOn = select
}
}
}
extension UIStackView {
func removeAllArrangedSubviews() {
let removedSubviews = arrangedSubviews.reduce([]) { (allSubviews, subview) -> [UIView] in
self.removeArrangedSubview(subview)
return allSubviews + [subview]
}
NSLayoutConstraint.deactivate(removedSubviews.flatMap({ $0.constraints }))
removedSubviews.forEach({ $0.removeFromSuperview() })
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment