refactored radiobutton for new subclasses

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2023-06-05 10:43:02 -05:00
parent 42e6f9025c
commit 17fe50223c
3 changed files with 82 additions and 332 deletions

View File

@ -2,7 +2,7 @@
// RadioButton.swift
// VDS
//
// Created by Matt Bruce on 7/22/22.
// Created by Matt Bruce on 6/5/23.
//
import Foundation
@ -12,352 +12,58 @@ import VDSColorTokens
import VDSFormControlsTokens
@objc(VDSRadioButton)
open class RadioButton: Control, Errorable, Changeable {
open class RadioButton: SelectorBase {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
open var selectedSize = CGSize(width: 10, height: 10) { didSet { setNeedsUpdate() }}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var shouldShowError: Bool {
guard showError && !disabled && errorText?.isEmpty == false else { return false }
return true
}
private var shouldShowLabels: Bool {
guard labelText?.isEmpty == false || childText?.isEmpty == false || labelAttributedText?.string.isEmpty == false || childAttributedText?.string.isEmpty == false else { return false }
return true
}
private var mainStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .top
$0.axis = .vertical
}
private var selectorStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.alignment = .top
$0.axis = .horizontal
}
private var selectorLabelStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .vertical
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public var onChangeSubscriber: AnyCancellable? {
willSet {
if let onChangeSubscriber {
onChangeSubscriber.cancel()
}
}
}
open var label = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textPosition = .left
$0.textStyle = .boldBodyLarge
}
open var childLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textPosition = .left
$0.textStyle = .bodyLarge
}
open var errorLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textPosition = .left
$0.textStyle = .bodyMedium
}
public var selectorView = UIView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
}
open var labelText: String? { didSet { setNeedsUpdate() }}
open var labelTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }}
open var labelAttributedText: NSAttributedString? {
didSet {
label.useAttributedText = !(labelAttributedText?.string.isEmpty ?? true)
label.attributedText = labelAttributedText
setNeedsUpdate()
}
}
open var childText: String? { didSet { setNeedsUpdate() }}
open var childTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }}
open var childAttributedText: NSAttributedString? {
didSet {
childLabel.useAttributedText = !(childAttributedText?.string.isEmpty ?? true)
childLabel.attributedText = childAttributedText
setNeedsUpdate()
}
}
open var showError: Bool = false { didSet { setNeedsUpdate() }}
open override var state: UIControl.State {
get {
var state = super.state
if showError {
state.insert(.error)
}
return state
}
}
open var errorText: String? { didSet { setNeedsUpdate() }}
open var inputId: String? { didSet { setNeedsUpdate() }}
open var value: AnyHashable? { didSet { setNeedsUpdate() }}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
private var selectorHeightConstraint: NSLayoutConstraint?
private var selectorWidthConstraint: NSLayoutConstraint?
//functions
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
}
}
open override func setup() {
super.setup()
isAccessibilityElement = true
accessibilityTraits = .button
addSubview(mainStackView)
mainStackView.isUserInteractionEnabled = false
backgroundColorConfig.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: .error)
borderColorConfig.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected)
borderColorConfig.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .highlighted)
borderColorConfig.setSurfaceColors(VDSFormControlsColor.borderOnlight, VDSFormControlsColor.borderOndark, forState: .normal)
borderColorConfig.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.disabled])
borderColorConfig.setSurfaceColors(VDSColor.feedbackErrorOnlight, VDSColor.feedbackErrorOndark, forState: .error)
selectorColorConfig.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected)
selectorColorConfig.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
}
open override func layoutSubviews() {
super.layoutSubviews()
//get the colors
let backgroundColor = backgroundColorConfig.getColor(self)
let borderColor = borderColorConfig.getColor(self)
let selectorColor = selectorColorConfig.getColor(self)
mainStackView.addArrangedSubview(selectorStackView)
mainStackView.addArrangedSubview(errorLabel)
selectorStackView.addArrangedSubview(selectorView)
selectorStackView.addArrangedSubview(selectorLabelStackView)
selectorLabelStackView.addArrangedSubview(label)
selectorLabelStackView.addArrangedSubview(childLabel)
let selectorSize = getSelectorSize()
selectorHeightConstraint = selectorView.heightAnchor.constraint(equalToConstant: selectorSize.height)
selectorHeightConstraint?.isActive = true
selectorWidthConstraint = selectorView.widthAnchor.constraint(equalToConstant: selectorSize.width)
selectorWidthConstraint?.isActive = true
updateSelector()
mainStackView.pinToSuperView()
}
func updateLabels() {
//deal with labels
if shouldShowLabels {
//add the stackview to hold the 2 labels
//top label
if let labelText {
label.text = labelText
label.surface = surface
label.disabled = disabled
label.attributes = labelTextAttributes
label.isHidden = false
} else if labelAttributedText != nil {
label.isHidden = false
} else {
label.isHidden = true
}
//bottom label
if let childText {
childLabel.text = childText
childLabel.surface = surface
childLabel.disabled = disabled
childLabel.attributes = childTextAttributes
childLabel.isHidden = false
} else if childAttributedText != nil {
childLabel.isHidden = false
} else {
childLabel.isHidden = true
}
selectorStackView.spacing = 12
selectorLabelStackView.spacing = 4
selectorLabelStackView.isHidden = false
} else {
selectorStackView.spacing = 0
selectorLabelStackView.spacing = 0
selectorLabelStackView.isHidden = true
}
//either add/remove the error from the main stack
if let errorText, shouldShowError {
errorLabel.text = errorText
errorLabel.surface = surface
errorLabel.disabled = disabled
mainStackView.spacing = 8
errorLabel.isHidden = false
} else {
mainStackView.spacing = 0
errorLabel.isHidden = true
}
}
open override func reset() {
super.reset()
shouldUpdateView = false
label.reset()
childLabel.reset()
errorLabel.reset()
label.textStyle = .boldBodyLarge
childLabel.textStyle = .bodyLarge
errorLabel.textStyle = .bodyMedium
labelText = nil
labelTextAttributes = nil
labelAttributedText = nil
childText = nil
childTextAttributes = nil
childAttributedText = nil
showError = false
errorText = nil
inputId = nil
value = nil
isSelected = false
shouldUpdateView = true
setNeedsUpdate()
}
/// This will checkbox the state of the Selector and execute the actionBlock if provided.
open func toggle() {
guard !isSelected else { return }
//removed error
if showError && isSelected == false {
showError.toggle()
}
isSelected.toggle()
sendActions(for: .valueChanged)
}
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView() {
updateLabels()
updateSelector()
updateAccessibilityLabel()
}
open override func updateAccessibilityLabel() {
setAccessibilityLabel(for: [label, childLabel])
}
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
public let radioButtonSize = CGSize(width: 20, height: 20)
public let radioButtonSelectedSize = CGSize(width: 10, height: 10)
private var backgroundColorConfiguration = ControlColorConfiguration().with {
$0.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: .error)
}
private var borderColorConfiguration = ControlColorConfiguration().with {
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected)
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .highlighted)
$0.setSurfaceColors(VDSFormControlsColor.borderOnlight, VDSFormControlsColor.borderOndark, forState: .normal)
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.disabled])
$0.setSurfaceColors(VDSColor.feedbackErrorOnlight, VDSColor.feedbackErrorOndark, forState: .error)
}
private var checkColorConfiguration = ControlColorConfiguration().with {
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected)
$0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled])
}
//--------------------------------------------------
// MARK: - RadioButton View
//--------------------------------------------------
/// Manages the appearance of the radioButton.
private var shapeLayer: CAShapeLayer?
open func getSelectorSize() -> CGSize {
radioButtonSize
}
open func updateSelector() {
if let shapeLayer = shapeLayer, let sublayers = layer.sublayers, sublayers.contains(shapeLayer) {
shapeLayer.removeFromSuperlayer()
self.shapeLayer = nil
}
let bounds = radioButtonSize
let length = max(bounds.height, bounds.width)
guard length > 0.0, shapeLayer == nil else { return }
let bounds = bounds
//get the colors
let backgroundColor = backgroundColorConfiguration.getColor(self)
let borderColor = borderColorConfiguration.getColor(self)
let radioSelectedColor = checkColorConfiguration.getColor(self)
selectorView.backgroundColor = backgroundColor
selectorView.layer.borderColor = borderColor.cgColor
selectorView.layer.cornerRadius = bounds.width * 0.5
selectorView.layer.borderWidth = VDSFormControls.widthBorder
self.backgroundColor = backgroundColor
layer.borderColor = borderColor.cgColor
layer.cornerRadius = bounds.width * 0.5
layer.borderWidth = VDSFormControls.widthBorder
if shapeLayer == nil {
let selectedBounds = radioButtonSelectedSize
let selectedBounds = selectedSize
let bezierPath = UIBezierPath(ovalIn: CGRect(x: (bounds.width - selectedBounds.width) / 2,
y: (bounds.height - selectedBounds.height) / 2,
width: radioButtonSelectedSize.width,
height: radioButtonSelectedSize.height))
width: selectedSize.width,
height: selectedSize.height))
let shapeLayer = CAShapeLayer()
self.shapeLayer = shapeLayer
shapeLayer.frame = selectorView.bounds
shapeLayer.frame = bounds
layer.addSublayer(shapeLayer)
shapeLayer.fillColor = radioSelectedColor.cgColor
shapeLayer.fillColor = selectorColor.cgColor
shapeLayer.path = bezierPath.cgPath
}
}
}

View File

@ -9,12 +9,12 @@ import Foundation
import UIKit
@objc(VDSRadioButtonGroup)
open class RadioButtonGroup: SelectorGroupSelectedHandlerBase<RadioButton> {
open class RadioButtonGroup: SelectorGroupSelectedHandlerBase<RadioButtonItem> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public override var selectorViews: [RadioButton] {
public override var selectorViews: [RadioButtonItem] {
willSet {
mainStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
}
@ -33,7 +33,7 @@ open class RadioButtonGroup: SelectorGroupSelectedHandlerBase<RadioButton> {
didSet {
if let selectorModels {
selectorViews = selectorModels.map { model in
return RadioButton().with {
return RadioButtonItem().with {
$0.disabled = model.disabled
$0.surface = model.surface
$0.inputId = model.inputId
@ -93,7 +93,7 @@ open class RadioButtonGroup: SelectorGroupSelectedHandlerBase<RadioButton> {
mainStackView.pinToSuperView()
}
public override func didSelect(_ selectedControl: RadioButton) {
public override func didSelect(_ selectedControl: RadioButtonItem) {
if let selectedHandler {
updateToggle(selectedHandler)
}
@ -104,7 +104,7 @@ open class RadioButtonGroup: SelectorGroupSelectedHandlerBase<RadioButton> {
valueChanged()
}
private func updateToggle(_ radioButton: RadioButton) {
private func updateToggle(_ radioButton: RadioButtonItem) {
if radioButton.showError && radioButton.isSelected == false {
radioButton.showError.toggle()
}

View File

@ -0,0 +1,44 @@
//
// RadioButton.swift
// VDS
//
// Created by Matt Bruce on 7/22/22.
//
import Foundation
import UIKit
@objc(VDSRadioButtonItem)
open class RadioButtonItem: SelectorItemBase<RadioButton> {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// This will checkbox the state of the Selector and execute the actionBlock if provided.
open override func toggle() {
guard !isSelected else { return }
//removed error
if showError && isSelected == false {
showError.toggle()
}
isSelected.toggle()
sendActions(for: .valueChanged)
}
}