// // SelectorItemBase.swift // VDS // // Created by Matt Bruce on 6/5/23. // import Foundation import UIKit import Combine import VDSColorTokens import VDSFormControlsTokens open class SelectorItemBase: Control, Errorable, Changeable { //-------------------------------------------------- // 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: - 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 //-------------------------------------------------- open 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 } open var selectorView = Selector() open override var isSelected: Bool { didSet { setNeedsUpdate() }} 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() } } var _showError: Bool = false open var showError: Bool { get { _showError } set { if !isSelected && _showError != newValue { _showError = newValue 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() }} //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 mainStackView.addArrangedSubview(selectorStackView) mainStackView.addArrangedSubview(errorLabel) selectorStackView.addArrangedSubview(selectorView) selectorStackView.addArrangedSubview(selectorLabelStackView) selectorLabelStackView.addArrangedSubview(label) selectorLabelStackView.addArrangedSubview(childLabel) mainStackView.pinToSuperView() } func updateLabels() { //deal with labels if shouldShowLabels { //add the stackview to hold the 2 labels //top label if let labelText { label.surface = surface label.disabled = disabled label.attributes = labelTextAttributes label.text = labelText 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() {} //-------------------------------------------------- // MARK: - State //-------------------------------------------------- open override func updateView() { updateLabels() selectorView.showError = showError selectorView.isSelected = isSelected selectorView.isHighlighted = isHighlighted selectorView.disabled = disabled selectorView.surface = surface updateAccessibilityLabel() } open override func updateAccessibilityLabel() { if !accessibilityTraits.contains(.notEnabled) && !isEnabled { accessibilityTraits.insert(.notEnabled) } else { accessibilityTraits.remove(.notEnabled) } setAccessibilityLabel(for: [label, childLabel, errorLabel]) } }