vds_ios/VDS/BaseClasses/Selector/SelectorItemBase.swift
Matt Bruce 9aa026ee94 Selector called 2x for the items/group
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-05-29 09:40:57 -05:00

294 lines
9.4 KiB
Swift

//
// SelectorItemBase.swift
// VDS
//
// Created by Matt Bruce on 6/5/23.
//
import Foundation
import UIKit
import Combine
import VDSTokens
/// Base Class used to build out a SelectorControlable control.
open class SelectorItemBase<Selector: SelectorControlable>: Control, Errorable, Changeable, Groupable {
//--------------------------------------------------
// 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 && isEnabled && 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?
/// Label used to render labelText.
open var label = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textStyle = .boldBodyLarge
}
/// Label used to render childText.
open var childLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textStyle = .bodyLarge
}
/// Label used to render errorText.
open var errorLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textStyle = .bodyMedium
}
/// Generic Object used to allow the user to 'Select'.
open var selectorView = Selector()
open override var isSelected: Bool { didSet { setNeedsUpdate() } }
/// Text shown in the label.
open var labelText: String? { didSet { setNeedsUpdate() } }
/// Array of LabelAttributeModel objects used in rendering the labelText.
open var labelTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() } }
/// Instead of use labelText and labelTextAttirbutes, this is a fully baked NSAttributedString with both text and attributes.
open var labelAttributedText: NSAttributedString? {
didSet {
label.attributedText = labelAttributedText
setNeedsUpdate()
}
}
/// Text shown in the childLabel.
open var childText: String? { didSet { setNeedsUpdate() } }
/// Array of LabelAttributeModel objects used in rendering the childText.
open var childTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() } }
/// Instead of use childText and childTextAttirbutes, this is a fully baked NSAttributedString with both text and attributes.
open var childAttributedText: NSAttributedString? {
didSet {
childLabel.attributedText = childAttributedText
setNeedsUpdate()
}
}
/// Override UIControl state to add the .error state if showError is true.
open override var state: UIControl.State {
get {
var state = super.state
if showError {
state.insert(.error)
}
return state
}
}
var _showError: Bool = false
open var showError: Bool {
get { _showError }
set {
if !isSelected && _showError != newValue {
_showError = newValue
setNeedsUpdate()
}
}
}
open var errorText: String? { didSet { setNeedsUpdate() } }
open var inputId: String? { didSet { setNeedsUpdate() } }
open var value: AnyHashable? { hiddenValue }
open var hiddenValue: AnyHashable? { didSet { setNeedsUpdate() } }
open var accessibilityValueText: String?
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
}
}
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
open override func setup() {
super.setup()
selectorView.isAccessibilityElement = false
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
.pinTop()
.pinLeading()
.pinTrailing()
.pinBottom(0, .defaultHigh)
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
updateLabels()
selectorView.showError = showError
selectorView.isSelected = isSelected
selectorView.isHighlighted = isHighlighted
selectorView.isEnabled = isEnabled
selectorView.surface = surface
}
/// Used to update any Accessibility properties.
open override func updateAccessibility() {
super.updateAccessibility()
setAccessibilityLabel(for: [selectorView, label, childLabel, errorLabel])
accessibilityValue = accessibilityValueText
}
/// Resets to default settings.
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
isSelected = false
onChange = nil
shouldUpdateView = true
setNeedsUpdate()
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
/// Update all labels with Text as well as adding and removing the labels.
func updateLabels() {
//deal with labels
if shouldShowLabels {
//add the stackview to hold the 2 labels
//top label
if let labelText {
label.surface = surface
label.isEnabled = isEnabled
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.isEnabled = isEnabled
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.isEnabled = isEnabled
mainStackView.spacing = 8
errorLabel.isHidden = false
} else {
mainStackView.spacing = 0
errorLabel.isHidden = true
}
}
/// This will change to state of the Selector.
open func toggle() {}
}