From 17fe50223c6195eb8d4a06b929dc81aa391ed2cb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 5 Jun 2023 10:43:02 -0500 Subject: [PATCH] refactored radiobutton for new subclasses Signed-off-by: Matt Bruce --- VDS/Components/RadioButton/RadioButton.swift | 360 ++---------------- .../RadioButton/RadioButtonGroup.swift | 10 +- .../RadioButton/RadioButtonItem.swift | 44 +++ 3 files changed, 82 insertions(+), 332 deletions(-) create mode 100644 VDS/Components/RadioButton/RadioButtonItem.swift diff --git a/VDS/Components/RadioButton/RadioButton.swift b/VDS/Components/RadioButton/RadioButton.swift index 705af90e..6c794f83 100644 --- a/VDS/Components/RadioButton/RadioButton.swift +++ b/VDS/Components/RadioButton/RadioButton.swift @@ -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 } } } - diff --git a/VDS/Components/RadioButton/RadioButtonGroup.swift b/VDS/Components/RadioButton/RadioButtonGroup.swift index ffdfdde3..992d76a5 100644 --- a/VDS/Components/RadioButton/RadioButtonGroup.swift +++ b/VDS/Components/RadioButton/RadioButtonGroup.swift @@ -9,12 +9,12 @@ import Foundation import UIKit @objc(VDSRadioButtonGroup) -open class RadioButtonGroup: SelectorGroupSelectedHandlerBase { +open class RadioButtonGroup: SelectorGroupSelectedHandlerBase { //-------------------------------------------------- // 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 { 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 { 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 { valueChanged() } - private func updateToggle(_ radioButton: RadioButton) { + private func updateToggle(_ radioButton: RadioButtonItem) { if radioButton.showError && radioButton.isSelected == false { radioButton.showError.toggle() } diff --git a/VDS/Components/RadioButton/RadioButtonItem.swift b/VDS/Components/RadioButton/RadioButtonItem.swift new file mode 100644 index 00000000..14ed50fb --- /dev/null +++ b/VDS/Components/RadioButton/RadioButtonItem.swift @@ -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 { + + //-------------------------------------------------- + // 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) + } +} +