From 844aa1327756e56f03667f461ed101fe07b1643f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 23 Aug 2022 14:49:00 -0500 Subject: [PATCH] refactored out selector base selector group base class Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 20 +- VDS/Components/Checkbox/Checkbox.swift | 262 +++++++++++++++- VDS/Components/Checkbox/CheckboxGroup.swift | 97 +++++- .../Checkbox/CheckboxGroupModel.swift | 10 +- VDS/Components/Checkbox/CheckboxModel.swift | 51 +++- VDS/Components/RadioButton/RadioButton.swift | 270 ++++++++++++++++- .../RadioButton/RadioButtonGroup.swift | 110 ++++++- .../RadioButton/RadioButtonGroupModel.swift | 10 +- .../RadioButton/RadioButtonModel.swift | 52 +++- VDS/Components/Selector/SelectorBase.swift | 284 ------------------ .../Selector/SelectorGroupBase.swift | 136 --------- .../Selector/SelectorGroupModel.swift | 13 - .../Selector/SelectorGroupModelable.swift | 50 +++ VDS/Components/Selector/SelectorModel.swift | 63 ---- 14 files changed, 875 insertions(+), 553 deletions(-) delete mode 100644 VDS/Components/Selector/SelectorBase.swift delete mode 100644 VDS/Components/Selector/SelectorGroupBase.swift delete mode 100644 VDS/Components/Selector/SelectorGroupModel.swift create mode 100644 VDS/Components/Selector/SelectorGroupModelable.swift delete mode 100644 VDS/Components/Selector/SelectorModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 9f854c25..9eb7edaa 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -43,8 +43,7 @@ EA89200828B526E0006B9984 /* CheckboxGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200728B526E0006B9984 /* CheckboxGroupModel.swift */; }; EA89200D28B530FD006B9984 /* RadioBoxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200C28B530FD006B9984 /* RadioBoxModel.swift */; }; EA89200F28B53921006B9984 /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200E28B53921006B9984 /* Selectable.swift */; }; - EAB1D29428A3ECF700DAE764 /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F13028A17FAB00B287F5 /* SelectorGroupBase.swift */; }; - EAB1D29A28A5611D00DAE764 /* SelectorGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29928A5611D00DAE764 /* SelectorGroupModel.swift */; }; + EAB1D29A28A5611D00DAE764 /* SelectorGroupModelable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29928A5611D00DAE764 /* SelectorGroupModelable.swift */; }; EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */; }; EAB1D29E28A5619500DAE764 /* RadioButtonGroupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D29D28A5619500DAE764 /* RadioButtonGroupModel.swift */; }; EAB1D2A128A598FE00DAE764 /* UsesAutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2A028A598FE00DAE764 /* UsesAutoLayout.swift */; }; @@ -76,8 +75,6 @@ EAF7F0FB289DB1AC00B287F5 /* VDSColorTokens.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EAF7F0E9289DB0DA00B287F5 /* VDSColorTokens.xcframework */; }; EAF7F11728A1475A00B287F5 /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F11528A1475A00B287F5 /* RadioButton.swift */; }; EAF7F11828A1475A00B287F5 /* RadioButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F11628A1475A00B287F5 /* RadioButtonModel.swift */; }; - EAF7F12C28A1617600B287F5 /* SelectorBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F12B28A1617600B287F5 /* SelectorBase.swift */; }; - EAF7F12F28A1619600B287F5 /* SelectorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F12E28A1619600B287F5 /* SelectorModel.swift */; }; EAF7F13328A2A16500B287F5 /* LabelAttributeAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F13228A2A16500B287F5 /* LabelAttributeAttachment.swift */; }; /* End PBXBuildFile section */ @@ -132,7 +129,7 @@ EA89200728B526E0006B9984 /* CheckboxGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxGroupModel.swift; sourceTree = ""; }; EA89200C28B530FD006B9984 /* RadioBoxModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxModel.swift; sourceTree = ""; }; EA89200E28B53921006B9984 /* Selectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Selectable.swift; sourceTree = ""; }; - EAB1D29928A5611D00DAE764 /* SelectorGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupModel.swift; sourceTree = ""; }; + EAB1D29928A5611D00DAE764 /* SelectorGroupModelable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupModelable.swift; sourceTree = ""; }; EAB1D29B28A5618900DAE764 /* RadioButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonGroup.swift; sourceTree = ""; }; EAB1D29D28A5619500DAE764 /* RadioButtonGroupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonGroupModel.swift; sourceTree = ""; }; EAB1D2A028A598FE00DAE764 /* UsesAutoLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsesAutoLayout.swift; sourceTree = ""; }; @@ -165,9 +162,6 @@ EAF7F0EB289DB0DA00B287F5 /* VDSLayoutTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSLayoutTokens.xcframework; path = ../SharedFrameworks/VDSLayoutTokens.xcframework; sourceTree = ""; }; EAF7F11528A1475A00B287F5 /* RadioButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; EAF7F11628A1475A00B287F5 /* RadioButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButtonModel.swift; sourceTree = ""; }; - EAF7F12B28A1617600B287F5 /* SelectorBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorBase.swift; sourceTree = ""; }; - EAF7F12E28A1619600B287F5 /* SelectorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorModel.swift; sourceTree = ""; }; - EAF7F13028A17FAB00B287F5 /* SelectorGroupBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = ""; }; EAF7F13228A2A16500B287F5 /* LabelAttributeAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeAttachment.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -444,10 +438,7 @@ EAF7F12D28A1617A00B287F5 /* Selector */ = { isa = PBXGroup; children = ( - EAF7F12E28A1619600B287F5 /* SelectorModel.swift */, - EAF7F12B28A1617600B287F5 /* SelectorBase.swift */, - EAB1D29928A5611D00DAE764 /* SelectorGroupModel.swift */, - EAF7F13028A17FAB00B287F5 /* SelectorGroupBase.swift */, + EAB1D29928A5611D00DAE764 /* SelectorGroupModelable.swift */, ); path = Selector; sourceTree = ""; @@ -568,13 +559,10 @@ EAF7F0B5289C126F00B287F5 /* UILabel.swift in Sources */, EAF7F0A6289B0CE000B287F5 /* Resetable.swift in Sources */, EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */, - EAB1D29428A3ECF700DAE764 /* SelectorGroupBase.swift in Sources */, EA3361C328902D960071C351 /* Toggle.swift in Sources */, - EAF7F12C28A1617600B287F5 /* SelectorBase.swift in Sources */, EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */, EAF7F11828A1475A00B287F5 /* RadioButtonModel.swift in Sources */, EA89200D28B530FD006B9984 /* RadioBoxModel.swift in Sources */, - EAF7F12F28A1619600B287F5 /* SelectorModel.swift in Sources */, EA3362402892EF6C0071C351 /* Label.swift in Sources */, EAF7F0B3289B1ADC00B287F5 /* LabelAttributeAction.swift in Sources */, EA33622E2891EA3C0071C351 /* DispatchQueue+Once.swift in Sources */, @@ -603,7 +591,7 @@ EAF7F0A2289AFB3900B287F5 /* Errorable.swift in Sources */, EA3C3B4C2894823E000CA526 /* AnyProxy.swift in Sources */, EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */, - EAB1D29A28A5611D00DAE764 /* SelectorGroupModel.swift in Sources */, + EAB1D29A28A5611D00DAE764 /* SelectorGroupModelable.swift in Sources */, EAF7F0BB289D80ED00B287F5 /* Modelable.swift in Sources */, EAF7F09E289AAEC000B287F5 /* Constants.swift in Sources */, EA3361B3288B265D0071C351 /* Changable.swift in Sources */, diff --git a/VDS/Components/Checkbox/Checkbox.swift b/VDS/Components/Checkbox/Checkbox.swift index 7a6eae88..803b001d 100644 --- a/VDS/Components/Checkbox/Checkbox.swift +++ b/VDS/Components/Checkbox/Checkbox.swift @@ -13,7 +13,263 @@ import Combine public class Checkbox: CheckboxBase{} -open class CheckboxBase: SelectorBase { +open class CheckboxBase: Control, Changable { + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var mainStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .vertical + } + }() + + private var selectorStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .horizontal + } + }() + + private var selectorLabelStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + } + }() + + private var primaryLabel = Label() + + private var secondaryLabel = Label() + + private var errorLabel = Label() + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + public var selectorView: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + }() + + public var onChange: Blocks.ActionBlock? + + @Proxy(\.model.id) + open var id: UUID + + //can't bind to @Proxy + open override var isSelected: Bool { + get { model.selected } + set { + if model.selected != newValue { + model.selected = newValue + } + } + } + + @Proxy(\.model.labelText) + open var labelText: String? + + @Proxy(\.model.childText) + open var childText: String? + + @Proxy(\.model.hasError) + open var hasError: Bool + + @Proxy(\.model.errorText) + open var errorText: String? + + @Proxy(\.model.inputId) + open var inputId: String? + + @Proxy(\.model.value) + open var value: AnyHashable? + + @Proxy(\.model.dataAnalyticsTrack) + open var dataAnalyticsTrack: String? + + @Proxy(\.model.dataClickStream) + open var dataClickStream: String? + + @Proxy(\.model.dataTrack) + open var dataTrack: String? + + @Proxy(\.model.accessibilityHintEnabled) + open var accessibilityHintEnabled: String? + + @Proxy(\.model.accessibilityHintDisabled) + open var accessibilityHintDisabled: String? + + @Proxy(\.model.accessibilityValueEnabled) + open var accessibilityValueEnabled: String? + + @Proxy(\.model.accessibilityValueDisabled) + open var accessibilityValueDisabled: String? + + @Proxy(\.model.accessibilityLabelEnabled) + open var accessibilityLabelEnabled: String? + + @Proxy(\.model.accessibilityLabelDisabled) + open var accessibilityLabelDisabled: String? + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private var selectorHeightConstraint: NSLayoutConstraint? + private var selectorWidthConstraint: NSLayoutConstraint? + + //functions + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setup() { + super.setup() + addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(Self.tap))) + + isAccessibilityElement = true + accessibilityTraits = .button + addSubview(mainStackView) + + mainStackView.addArrangedSubview(selectorStackView) + mainStackView.addArrangedSubview(errorLabel) + selectorStackView.addArrangedSubview(selectorView) + selectorStackView.addArrangedSubview(selectorLabelStackView) + selectorLabelStackView.addArrangedSubview(primaryLabel) + selectorLabelStackView.addArrangedSubview(secondaryLabel) + + let selectorSize = getSelectorSize() + selectorHeightConstraint = selectorView.heightAnchor.constraint(equalToConstant: selectorSize.height) + selectorHeightConstraint?.isActive = true + + selectorWidthConstraint = selectorView.widthAnchor.constraint(equalToConstant: selectorSize.width) + selectorWidthConstraint?.isActive = true + + updateSelector(model) + + mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true + mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + + } + + func updateLabels(_ viewModel: ModelType) { + + //deal with labels + if viewModel.shouldShowLabels { + //add the stackview to hold the 2 labels + //top label + if let labelModel = viewModel.labelModel { + primaryLabel.set(with: labelModel) + primaryLabel.isHidden = false + } else { + primaryLabel.isHidden = true + } + + //bottom label + if let childModel = viewModel.childModel { + secondaryLabel.set(with: childModel) + secondaryLabel.isHidden = false + } else { + secondaryLabel.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 errorModel = model.errorModel, model.shouldShowError { + errorLabel.set(with: errorModel) + mainStackView.spacing = 8 + errorLabel.isHidden = false + } else { + mainStackView.spacing = 0 + errorLabel.isHidden = true + } + + } + + public override func reset() { + super.reset() + updateSelector(model) + setAccessibilityLabel() + onChange = nil + } + + //-------------------------------------------------- + // MARK: - Actions + //-------------------------------------------------- + open override func sendActions(for controlEvents: UIControl.Event) { + super.sendActions(for: controlEvents) + if controlEvents.contains(.touchUpInside) { + toggle() + } + } + + @objc func tap() { + sendActions(for: .touchUpInside) + + } + + /// This will checkbox the state of the Selector and execute the actionBlock if provided. + open func toggle() { + //removed error + if hasError && isSelected == false { + hasError.toggle() + } + isSelected.toggle() + sendActions(for: .valueChanged) + onChange?() + } + + override open func accessibilityActivate() -> Bool { + // Hold state in case User wanted isAnimated to remain off. + guard isUserInteractionEnabled else { return false } + sendActions(for: .touchUpInside) + return true + } + + //-------------------------------------------------- + // MARK: - State + //-------------------------------------------------- + /// Follow the SwiftUI View paradigm + /// - Parameter viewModel: state + open override func shouldUpdateView(viewModel: ModelType) -> Bool { + let update = viewModel.selected != model.selected + || viewModel.labelText != model.labelText + || viewModel.childText != model.childText + || viewModel.hasError != model.hasError + || viewModel.surface != model.surface + || viewModel.disabled != model.disabled + return update + } + + open override func updateView(viewModel: ModelType) { + let enabled = !viewModel.disabled + + updateLabels(viewModel) + updateSelector(viewModel) + setAccessibilityHint(enabled) + setAccessibilityValue(viewModel.selected) + setAccessibilityLabel(viewModel.selected) + isUserInteractionEnabled = !viewModel.disabled + setNeedsLayout() + layoutIfNeeded() + } + + //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- @@ -64,11 +320,11 @@ open class CheckboxBase: SelectorBase { /// Manages the appearance of the checkbox. private var shapeLayer: CAShapeLayer? - open override func getSelectorSize() -> CGSize { + open func getSelectorSize() -> CGSize { return checkboxSize } - open override func updateSelector(_ viewModel: ModelType) { + open func updateSelector(_ viewModel: ModelType) { //get the colors let backgroundColor = checkboxBackgroundColorConfiguration.getColor(viewModel) let borderColor = checkboxBorderColorConfiguration.getColor(viewModel) diff --git a/VDS/Components/Checkbox/CheckboxGroup.swift b/VDS/Components/Checkbox/CheckboxGroup.swift index c1b7740c..c7c303f4 100644 --- a/VDS/Components/Checkbox/CheckboxGroup.swift +++ b/VDS/Components/Checkbox/CheckboxGroup.swift @@ -6,22 +6,107 @@ // import Foundation +import UIKit -public class CheckboxGroup: CheckboxGroupBase {} - -public class CheckboxGroupBase>: SelectorGroupBase { +public class CheckboxGroup: Control, SelectorGroupHandlerable, Changable { + public typealias ModelHandlerType = Checkbox + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + public var selectorViews: [ModelHandlerType] = [] + public var hasError: Bool { get { model.hasError } set { - var newHasError = newValue let selectors = model.selectors.compactMap { existing in return existing.copyWith { - $0.hasError = newHasError + $0.hasError = newValue } } - model.hasError = newHasError + model.hasError = newValue model.selectors = selectors } } + + public var onChange: Blocks.ActionBlock? + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var mainStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .vertical + $0.spacing = 10 + } + }() + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + override public var disabled: Bool { + didSet { + updateSelectors() + } + } + + override public var surface: Surface { + didSet { + updateSelectors() + } + } + + open override func setup() { + super.setup() + + isAccessibilityElement = true + accessibilityTraits = .button + addSubview(mainStackView) + + mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true + mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } + + open override func shouldUpdateView(viewModel: ModelType) -> Bool { + let update = viewModel.selectors.count != model.selectors.count + || viewModel.hasError != model.hasError + || viewModel.surface != model.surface + || viewModel.disabled != model.disabled + return update + } + + open override func updateView(viewModel: ModelType) { + func findSelectorView(id: UUID) -> ModelHandlerType? { + return selectorViews.first(where: { existingSelectorView in + return existingSelectorView.model.id == id + }) + } + + for selectorModel in viewModel.selectors { + //see if view is there for the model + if let foundSelectorView = findSelectorView(id: selectorModel.id) { + foundSelectorView.set(with: selectorModel) + } else { + + //create view + let newSelectorView = ModelHandlerType(with: selectorModel) + + //add model update to the subscribers + newSelectorView.handlerPublisher() + .sink { [weak self] model in + if let cached = self?.getCachedSelector(viewModel: model), newSelectorView.shouldUpdateView(viewModel: cached) { + self?.replace(viewModel: model) + } + } + .store(in: &subscribers) + + self.selectorViews.append(newSelectorView) + mainStackView.addArrangedSubview(newSelectorView) + } + } + } } diff --git a/VDS/Components/Checkbox/CheckboxGroupModel.swift b/VDS/Components/Checkbox/CheckboxGroupModel.swift index cf703b7e..efe7c6d6 100644 --- a/VDS/Components/Checkbox/CheckboxGroupModel.swift +++ b/VDS/Components/Checkbox/CheckboxGroupModel.swift @@ -7,24 +7,26 @@ import Foundation -public protocol CheckboxGroupModel: SelectorGroupModel {} +public protocol CheckboxGroupModel: SelectorGroupModelable where SelectorModelType: CheckboxModel { + +} extension CheckboxGroupModel { public var errorText: String? { return nil } } public struct DefaultCheckboxGroupModel: CheckboxGroupModel { - public typealias SelectorType = DefaultCheckboxModel + public typealias SelectorModelType = DefaultCheckboxModel public var id: UUID = UUID() public var inputId: String? public var value: AnyHashable? public var surface: Surface = .light public var disabled: Bool = false - public var selectors: [SelectorType] + public var selectors: [SelectorModelType] public var hasError: Bool = false public var errorText: String? public init() { selectors = [] } - public init(selectors: [SelectorType]){ + public init(selectors: [SelectorModelType]){ self.selectors = selectors } } diff --git a/VDS/Components/Checkbox/CheckboxModel.swift b/VDS/Components/Checkbox/CheckboxModel.swift index d03e441a..f6d76d30 100644 --- a/VDS/Components/Checkbox/CheckboxModel.swift +++ b/VDS/Components/Checkbox/CheckboxModel.swift @@ -8,11 +8,60 @@ import Foundation import UIKit -public protocol CheckboxModel: SelectorModel, BinaryColorable { +public protocol CheckboxModel: Modelable, FormFieldable, Errorable, DataTrackable, Accessable, Selectable, BinaryColorable { + var labelText: String? { get set } + var labelTextAttributes: [LabelAttributeModel]? { get set } + var childText: String? { get set } + var childTextAttributes: [LabelAttributeModel]? { get set } } extension CheckboxModel { public var userTrueColor: Bool { return selected } + + public var shouldShowError: Bool { + guard hasError && !disabled && errorText?.isEmpty == false else { return false } + return true + } + + public var shouldShowLabels: Bool { + guard labelText?.isEmpty == false || childText?.isEmpty == false else { return false } + return true + } + + public var labelModel: DefaultLabelModel? { + guard let labelText = labelText else { return nil } + var model = DefaultLabelModel() + model.textPosition = .left + model.typograpicalStyle = .BoldBodyLarge + model.text = labelText + model.surface = surface + model.disabled = disabled + model.attributes = labelTextAttributes + return model + } + + public var childModel: DefaultLabelModel? { + guard let childText = childText else { return nil } + var model = DefaultLabelModel() + model.textPosition = .left + model.typograpicalStyle = .BodyLarge + model.text = childText + model.surface = surface + model.disabled = disabled + model.attributes = childTextAttributes + return model + } + + public var errorModel: DefaultLabelModel? { + guard let errorText = errorText, hasError else { return nil } + var model = DefaultLabelModel() + model.textPosition = .left + model.typograpicalStyle = .BodyMedium + model.text = errorText + model.surface = surface + model.disabled = disabled + return model + } } public struct DefaultCheckboxModel: CheckboxModel { diff --git a/VDS/Components/RadioButton/RadioButton.swift b/VDS/Components/RadioButton/RadioButton.swift index 8348a9b7..da887cdb 100644 --- a/VDS/Components/RadioButton/RadioButton.swift +++ b/VDS/Components/RadioButton/RadioButton.swift @@ -12,7 +12,263 @@ import VDSFormControlsTokens public class RadioButton: RadioButtonBase{} -open class RadioButtonBase: SelectorBase { +open class RadioButtonBase: Control, Changable { + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var mainStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .vertical + } + }() + + private var selectorStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .horizontal + } + }() + + private var selectorLabelStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + } + }() + + private var primaryLabel = Label() + + private var secondaryLabel = Label() + + private var errorLabel = Label() + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + public var selectorView: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + }() + + public var onChange: Blocks.ActionBlock? + + @Proxy(\.model.id) + open var id: UUID + + //can't bind to @Proxy + open override var isSelected: Bool { + get { model.selected } + set { + if model.selected != newValue { + model.selected = newValue + } + } + } + + @Proxy(\.model.labelText) + open var labelText: String? + + @Proxy(\.model.childText) + open var childText: String? + + @Proxy(\.model.hasError) + open var hasError: Bool + + @Proxy(\.model.errorText) + open var errorText: String? + + @Proxy(\.model.inputId) + open var inputId: String? + + @Proxy(\.model.value) + open var value: AnyHashable? + + @Proxy(\.model.dataAnalyticsTrack) + open var dataAnalyticsTrack: String? + + @Proxy(\.model.dataClickStream) + open var dataClickStream: String? + + @Proxy(\.model.dataTrack) + open var dataTrack: String? + + @Proxy(\.model.accessibilityHintEnabled) + open var accessibilityHintEnabled: String? + + @Proxy(\.model.accessibilityHintDisabled) + open var accessibilityHintDisabled: String? + + @Proxy(\.model.accessibilityValueEnabled) + open var accessibilityValueEnabled: String? + + @Proxy(\.model.accessibilityValueDisabled) + open var accessibilityValueDisabled: String? + + @Proxy(\.model.accessibilityLabelEnabled) + open var accessibilityLabelEnabled: String? + + @Proxy(\.model.accessibilityLabelDisabled) + open var accessibilityLabelDisabled: String? + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private var selectorHeightConstraint: NSLayoutConstraint? + private var selectorWidthConstraint: NSLayoutConstraint? + + //functions + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setup() { + super.setup() + addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(Self.tap))) + + isAccessibilityElement = true + accessibilityTraits = .button + addSubview(mainStackView) + + mainStackView.addArrangedSubview(selectorStackView) + mainStackView.addArrangedSubview(errorLabel) + selectorStackView.addArrangedSubview(selectorView) + selectorStackView.addArrangedSubview(selectorLabelStackView) + selectorLabelStackView.addArrangedSubview(primaryLabel) + selectorLabelStackView.addArrangedSubview(secondaryLabel) + + let selectorSize = getSelectorSize() + selectorHeightConstraint = selectorView.heightAnchor.constraint(equalToConstant: selectorSize.height) + selectorHeightConstraint?.isActive = true + + selectorWidthConstraint = selectorView.widthAnchor.constraint(equalToConstant: selectorSize.width) + selectorWidthConstraint?.isActive = true + + updateSelector(model) + + mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true + mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + + } + + func updateLabels(_ viewModel: ModelType) { + + //deal with labels + if viewModel.shouldShowLabels { + //add the stackview to hold the 2 labels + //top label + if let labelModel = viewModel.labelModel { + primaryLabel.set(with: labelModel) + primaryLabel.isHidden = false + } else { + primaryLabel.isHidden = true + } + + //bottom label + if let childModel = viewModel.childModel { + secondaryLabel.set(with: childModel) + secondaryLabel.isHidden = false + } else { + secondaryLabel.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 errorModel = model.errorModel, model.shouldShowError { + errorLabel.set(with: errorModel) + mainStackView.spacing = 8 + errorLabel.isHidden = false + } else { + mainStackView.spacing = 0 + errorLabel.isHidden = true + } + + } + + public override func reset() { + super.reset() + updateSelector(model) + setAccessibilityLabel() + onChange = nil + } + + //-------------------------------------------------- + // MARK: - Actions + //-------------------------------------------------- + open override func sendActions(for controlEvents: UIControl.Event) { + super.sendActions(for: controlEvents) + if controlEvents.contains(.touchUpInside) { + toggle() + } + } + + @objc func tap() { + sendActions(for: .touchUpInside) + + } + + /// This will checkbox the state of the Selector and execute the actionBlock if provided. + open func toggle() { + guard !isSelected else { return } + + //removed error + if hasError && isSelected == false { + hasError.toggle() + } + isSelected.toggle() + sendActions(for: .valueChanged) + onChange?() + } + + override open func accessibilityActivate() -> Bool { + // Hold state in case User wanted isAnimated to remain off. + guard isUserInteractionEnabled else { return false } + sendActions(for: .touchUpInside) + return true + } + + //-------------------------------------------------- + // MARK: - State + //-------------------------------------------------- + /// Follow the SwiftUI View paradigm + /// - Parameter viewModel: state + open override func shouldUpdateView(viewModel: ModelType) -> Bool { + let update = viewModel.selected != model.selected + || viewModel.labelText != model.labelText + || viewModel.childText != model.childText + || viewModel.hasError != model.hasError + || viewModel.surface != model.surface + || viewModel.disabled != model.disabled + return update + } + + open override func updateView(viewModel: ModelType) { + let enabled = !viewModel.disabled + + updateLabels(viewModel) + updateSelector(viewModel) + setAccessibilityHint(enabled) + setAccessibilityValue(viewModel.selected) + setAccessibilityLabel(viewModel.selected) + isUserInteractionEnabled = !viewModel.disabled + setNeedsLayout() + layoutIfNeeded() + } //-------------------------------------------------- // MARK: - Configuration Properties @@ -56,22 +312,17 @@ open class RadioButtonBase: SelectorBase $0.forTrue.disabled.darkColor = VDSColor.interactiveDisabledOndark } }() - - open override func toggle() { - guard !isSelected else { return } - super.toggle() - } //-------------------------------------------------- // MARK: - RadioButton View //-------------------------------------------------- /// Manages the appearance of the radioButton. private var shapeLayer: CAShapeLayer? - open override func getSelectorSize() -> CGSize { - return radioButtonSize + open func getSelectorSize() -> CGSize { + radioButtonSize } - open override func updateSelector(_ viewModel: ModelType) { + open func updateSelector(_ viewModel: ModelType) { if let shapeLayer = shapeLayer, let sublayers = layer.sublayers, sublayers.contains(shapeLayer) { shapeLayer.removeFromSuperlayer() @@ -125,3 +376,4 @@ open class RadioButtonBase: SelectorBase } } + diff --git a/VDS/Components/RadioButton/RadioButtonGroup.swift b/VDS/Components/RadioButton/RadioButtonGroup.swift index 92498d72..f05bc5a0 100644 --- a/VDS/Components/RadioButton/RadioButtonGroup.swift +++ b/VDS/Components/RadioButton/RadioButtonGroup.swift @@ -6,13 +6,18 @@ // import Foundation +import UIKit -public class RadioButtonGroup: RadioButtonGroupBase {} - -public class RadioButtonGroupBase>: SelectorGroupBase { - +public class RadioButtonGroup: Control, SelectorGroupHandlerable, Changable { + public typealias ModelHandlerType = RadioButton + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + public var selectorViews: [ModelHandlerType] = [] + @Proxy(\.model.selectedModel) - public var selectedModel: RadioButtonGroupModelType.SelectorType? { + public var selectedModel: ModelHandlerType.ModelType? { didSet{ if hasError, selectedModel != nil { hasError = false @@ -37,14 +42,98 @@ public class RadioButtonGroupBase.ModelType) -> Bool { - let update = viewModel.selectedModel?.id != model.selectedModel?.id - || super.shouldUpdateView(viewModel: viewModel) - return update + + public var onChange: Blocks.ActionBlock? + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var mainStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.alignment = .top + $0.axis = .vertical + $0.spacing = 10 + } + }() + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + override public var disabled: Bool { + didSet { + updateSelectors() + } } - public override func didSelect(selector: RadioButtonGroupModelType.SelectorType) { + override public var surface: Surface { + didSet { + updateSelectors() + } + } + + open override func setup() { + super.setup() + isAccessibilityElement = true + accessibilityTraits = .button + addSubview(mainStackView) + + mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true + mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } + + open override func shouldUpdateView(viewModel: ModelType) -> Bool { + let update = viewModel.selectedModel?.id != model.selectedModel?.id + || viewModel.selectors.count != model.selectors.count + || viewModel.hasError != model.hasError + || viewModel.surface != model.surface + || viewModel.disabled != model.disabled + return update + } + + open override func updateView(viewModel: ModelType) { + func findSelectorView(id: UUID) -> ModelHandlerType? { + return selectorViews.first(where: { existingSelectorView in + return existingSelectorView.model.id == id + }) + } + + for selectorModel in viewModel.selectors { + //see if view is there for the model + if let foundSelectorView = findSelectorView(id: selectorModel.id) { + foundSelectorView.set(with: selectorModel) + } else { + + //create view + let newSelectorView = ModelHandlerType(with: selectorModel) + + //add the selectedPublisher for the change + newSelectorView.publisher(for: .valueChanged) + .sink(receiveValue: { [weak self] control in + guard self?.model.selectors.count ?? 0 > 0 else { return } + self?.didSelect(selector: control.model) + }) + .store(in: &subscribers) + + //add model update to the subscribers + newSelectorView.handlerPublisher() + .sink { [weak self] model in + if let cached = self?.getCachedSelector(viewModel: model), newSelectorView.shouldUpdateView(viewModel: cached) { + self?.replace(viewModel: model) + } + } + .store(in: &subscribers) + + self.selectorViews.append(newSelectorView) + mainStackView.addArrangedSubview(newSelectorView) + } + } + } + + open func didSelect(selector: ModelHandlerType.ModelType) { if var oldSelectedModel = selectedModel { oldSelectedModel.selected = false replace(viewModel: oldSelectedModel) @@ -60,6 +149,5 @@ public class RadioButtonGroupBase: Control, Changable { - - //-------------------------------------------------- - // MARK: - Private Properties - //-------------------------------------------------- - private var mainStackView: UIStackView = { - return UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.alignment = .top - $0.axis = .vertical - } - }() - - private var selectorStackView: UIStackView = { - return UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.alignment = .top - $0.axis = .horizontal - } - }() - - private var selectorLabelStackView: UIStackView = { - return UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .vertical - } - }() - - private var primaryLabel = Label() - - private var secondaryLabel = Label() - - private var errorLabel = Label() - - //-------------------------------------------------- - // MARK: - Public Properties - //-------------------------------------------------- - public var selectorView: UIView = { - return UIView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - }() - - public var onChange: Blocks.ActionBlock? - - @Proxy(\.model.id) - open var id: UUID - - //can't bind to @Proxy - open override var isSelected: Bool { - get { model.selected } - set { - if model.selected != newValue { - model.selected = newValue - } - } - } - - @Proxy(\.model.labelText) - open var labelText: String? - - @Proxy(\.model.childText) - open var childText: String? - - @Proxy(\.model.hasError) - open var hasError: Bool - - @Proxy(\.model.errorText) - open var errorText: String? - - @Proxy(\.model.inputId) - open var inputId: String? - - @Proxy(\.model.value) - open var value: AnyHashable? - - @Proxy(\.model.dataAnalyticsTrack) - open var dataAnalyticsTrack: String? - - @Proxy(\.model.dataClickStream) - open var dataClickStream: String? - - @Proxy(\.model.dataTrack) - open var dataTrack: String? - - @Proxy(\.model.accessibilityHintEnabled) - open var accessibilityHintEnabled: String? - - @Proxy(\.model.accessibilityHintDisabled) - open var accessibilityHintDisabled: String? - - @Proxy(\.model.accessibilityValueEnabled) - open var accessibilityValueEnabled: String? - - @Proxy(\.model.accessibilityValueDisabled) - open var accessibilityValueDisabled: String? - - @Proxy(\.model.accessibilityLabelEnabled) - open var accessibilityLabelEnabled: String? - - @Proxy(\.model.accessibilityLabelDisabled) - open var accessibilityLabelDisabled: String? - - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - private var knobLeadingConstraint: NSLayoutConstraint? - private var knobTrailingConstraint: NSLayoutConstraint? - private var knobHeightConstraint: NSLayoutConstraint? - private var knobWidthConstraint: NSLayoutConstraint? - private var selectorHeightConstraint: NSLayoutConstraint? - private var selectorWidthConstraint: NSLayoutConstraint? - - //functions - //-------------------------------------------------- - // MARK: - Lifecycle - //-------------------------------------------------- - - open override func setup() { - super.setup() - addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(Self.tap))) - - isAccessibilityElement = true - accessibilityTraits = .button - addSubview(mainStackView) - - mainStackView.addArrangedSubview(selectorStackView) - mainStackView.addArrangedSubview(errorLabel) - selectorStackView.addArrangedSubview(selectorView) - selectorStackView.addArrangedSubview(selectorLabelStackView) - selectorLabelStackView.addArrangedSubview(primaryLabel) - selectorLabelStackView.addArrangedSubview(secondaryLabel) - - let selectorSize = getSelectorSize() - selectorHeightConstraint = selectorView.heightAnchor.constraint(equalToConstant: selectorSize.height) - selectorHeightConstraint?.isActive = true - - selectorWidthConstraint = selectorView.widthAnchor.constraint(equalToConstant: selectorSize.width) - selectorWidthConstraint?.isActive = true - - updateSelector(model) - - mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true - mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - - } - - func updateLabels(_ viewModel: ModelType) { - - //deal with labels - if viewModel.shouldShowLabels { - //add the stackview to hold the 2 labels - //top label - if let labelModel = viewModel.labelModel { - primaryLabel.set(with: labelModel) - primaryLabel.isHidden = false - } else { - primaryLabel.isHidden = true - } - - //bottom label - if let childModel = viewModel.childModel { - secondaryLabel.set(with: childModel) - secondaryLabel.isHidden = false - } else { - secondaryLabel.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 errorModel = model.errorModel, model.shouldShowError { - errorLabel.set(with: errorModel) - mainStackView.spacing = 8 - errorLabel.isHidden = false - } else { - mainStackView.spacing = 0 - errorLabel.isHidden = true - } - - } - - public override func reset() { - super.reset() - updateSelector(model) - setAccessibilityLabel() - onChange = nil - } - - //-------------------------------------------------- - // MARK: - Selector View - //-------------------------------------------------- - open func getSelectorSize() -> CGSize { - fatalError("must override") - } - - open func updateSelector(_ viewModel: ModelType) { - } - - //-------------------------------------------------- - // MARK: - Actions - //-------------------------------------------------- - open override func sendActions(for controlEvents: UIControl.Event) { - super.sendActions(for: controlEvents) - if controlEvents.contains(.touchUpInside) { - toggle() - } - } - - @objc func tap() { - sendActions(for: .touchUpInside) - - } - - /// This will checkbox the state of the Selector and execute the actionBlock if provided. - open func toggle() { - //removed error - if hasError && isSelected == false { - hasError.toggle() - } - isSelected.toggle() - sendActions(for: .valueChanged) - onChange?() - } - - override open func accessibilityActivate() -> Bool { - // Hold state in case User wanted isAnimated to remain off. - guard isUserInteractionEnabled else { return false } - sendActions(for: .touchUpInside) - return true - } - - //-------------------------------------------------- - // MARK: - State - //-------------------------------------------------- - /// Follow the SwiftUI View paradigm - /// - Parameter viewModel: state - open override func shouldUpdateView(viewModel: ModelType) -> Bool { - let update = viewModel.selected != model.selected - || viewModel.labelText != model.labelText - || viewModel.childText != model.childText - || viewModel.hasError != model.hasError - || viewModel.surface != model.surface - || viewModel.disabled != model.disabled - return update - } - - open override func updateView(viewModel: ModelType) { - let enabled = !viewModel.disabled - - updateLabels(viewModel) - updateSelector(viewModel) - setAccessibilityHint(enabled) - setAccessibilityValue(viewModel.selected) - setAccessibilityLabel(viewModel.selected) - isUserInteractionEnabled = !viewModel.disabled - setNeedsLayout() - layoutIfNeeded() - } -} - diff --git a/VDS/Components/Selector/SelectorGroupBase.swift b/VDS/Components/Selector/SelectorGroupBase.swift deleted file mode 100644 index feea9ab2..00000000 --- a/VDS/Components/Selector/SelectorGroupBase.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// RadioButtonGroup.swift -// VDS -// -// Created by Matt Bruce on 8/8/22. -// - -import Foundation -import UIKit -import Combine - -open class SelectorGroupBase>: View, Changable where SelectorModelType == SelectorGroupType.SelectorModelType { - - public var selectorViews: [SelectorHandlerType] = [] - - public var onChange: Blocks.ActionBlock? - - //-------------------------------------------------- - // MARK: - Private Properties - //-------------------------------------------------- - private var mainStackView: UIStackView = { - return UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.alignment = .top - $0.axis = .vertical - $0.spacing = 10 - } - }() - - //-------------------------------------------------- - // MARK: - Overrides - //-------------------------------------------------- - override public var disabled: Bool { - didSet { - updateSelectors() - } - } - - override public var surface: Surface { - didSet { - updateSelectors() - } - } - - private func updateSelectors(){ - let selectors = model.selectors.compactMap { existing in - return existing.copyWith { - $0.disabled = disabled - $0.surface = surface - } - } - model.selectors = selectors - } - - open override func setup() { - super.setup() - - isAccessibilityElement = true - accessibilityTraits = .button - addSubview(mainStackView) - - mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true - mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - } - - open override func shouldUpdateView(viewModel: SelectorGroupType) -> Bool { - let update = viewModel.selectors.count != model.selectors.count - || viewModel.hasError != model.hasError - || viewModel.surface != model.surface - || viewModel.disabled != model.disabled - return update - } - - open override func updateView(viewModel: ModelType) { - func findSelectorView(id: UUID) -> SelectorHandlerType? { - return selectorViews.first(where: { existingSelectorView in - return existingSelectorView.model.id == id - }) - } - - for selectorModel in viewModel.selectors { - //see if view is there for the model - if let foundSelectorView = findSelectorView(id: selectorModel.id) { - foundSelectorView.set(with: selectorModel) - } else { - - //create view - let newSelectorView = SelectorHandlerType(with: selectorModel) - - //add the selectedPublisher for the change - newSelectorView.publisher(for: .valueChanged) - .sink(receiveValue: { [weak self] control in - guard self?.model.selectors.count ?? 0 > 0 else { return } - self?.didSelect(selector: control.model) - }) - .store(in: &subscribers) - - //add model update to the subscribers - newSelectorView.handlerPublisher() - .sink { [weak self] model in - if let cached = self?.getCachedSelector(viewModel: model), newSelectorView.shouldUpdateView(viewModel: cached) { - self?.replace(viewModel: model) - } - } - .store(in: &subscribers) - - self.selectorViews.append(newSelectorView) - mainStackView.addArrangedSubview(newSelectorView) - } - } - - } - - public func getCachedSelector(viewModel: SelectorModelType) -> SelectorModelType? { - if let index = model.selectors.firstIndex(where: { element in - return element.id == viewModel.id - }) { - return model.selectors[index] - } else { - return nil - } - } - - public func replace(viewModel: SelectorModelType){ - if let index = model.selectors.firstIndex(where: { element in - return element.id == viewModel.id - }) { - model.selectors[index] = viewModel - } - } - - open func didSelect(selector: SelectorModelType) { } - -} diff --git a/VDS/Components/Selector/SelectorGroupModel.swift b/VDS/Components/Selector/SelectorGroupModel.swift deleted file mode 100644 index b967350d..00000000 --- a/VDS/Components/Selector/SelectorGroupModel.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// SelectorGroupModel.swift -// VDS -// -// Created by Matt Bruce on 8/11/22. -// - -import Foundation - -public protocol SelectorGroupModel: Modelable, FormFieldable, Errorable { - associatedtype SelectorModelType: SelectorModel - var selectors: [SelectorModelType] { get set } -} diff --git a/VDS/Components/Selector/SelectorGroupModelable.swift b/VDS/Components/Selector/SelectorGroupModelable.swift new file mode 100644 index 00000000..301a4c81 --- /dev/null +++ b/VDS/Components/Selector/SelectorGroupModelable.swift @@ -0,0 +1,50 @@ +// +// SelectorGroupModel.swift +// VDS +// +// Created by Matt Bruce on 8/11/22. +// + +import Foundation + +public protocol SelectorGroupModelable: Modelable, FormFieldable, Errorable { + associatedtype SelectorModelType: Modelable where SelectorModelType: FormFieldable + var selectors: [SelectorModelType] { get set } +} + + +public protocol SelectorGroupHandlerable: ModelHandlerable, Disabling, Surfaceable where ModelType: SelectorGroupModelable { + associatedtype ModelHandlerType: ModelHandlerable where ModelType.SelectorModelType == ModelHandlerType.ModelType + var selectorViews: [ModelHandlerType] { get set } +} + +extension SelectorGroupHandlerable { + + public func updateSelectors(){ + let selectors = model.selectors.compactMap { existing in + return existing.copyWith { + $0.disabled = disabled + $0.surface = surface + } + } + model.selectors = selectors + } + + public func getCachedSelector(viewModel: ModelHandlerType.ModelType) -> ModelHandlerType.ModelType? { + if let index = model.selectors.firstIndex(where: { element in + return element.id == viewModel.id + }) { + return model.selectors[index] + } else { + return nil + } + } + + public func replace(viewModel: ModelHandlerType.ModelType){ + if let index = model.selectors.firstIndex(where: { element in + return element.id == viewModel.id + }) { + model.selectors[index] = viewModel + } + } +} diff --git a/VDS/Components/Selector/SelectorModel.swift b/VDS/Components/Selector/SelectorModel.swift deleted file mode 100644 index 4cf59732..00000000 --- a/VDS/Components/Selector/SelectorModel.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// SelectorModel.swift -// VDS -// -// Created by Matt Bruce on 8/8/22. -// - -import Foundation - -public protocol SelectorModel: Modelable, FormFieldable, Errorable, DataTrackable, Accessable, Selectable { - var labelText: String? { get set } - var labelTextAttributes: [LabelAttributeModel]? { get set } - var childText: String? { get set } - var childTextAttributes: [LabelAttributeModel]? { get set } -} - -extension SelectorModel { - - public var shouldShowError: Bool { - guard hasError && !disabled && errorText?.isEmpty == false else { return false } - return true - } - - public var shouldShowLabels: Bool { - guard labelText?.isEmpty == false || childText?.isEmpty == false else { return false } - return true - } - - public var labelModel: DefaultLabelModel? { - guard let labelText = labelText else { return nil } - var model = DefaultLabelModel() - model.textPosition = .left - model.typograpicalStyle = .BoldBodyLarge - model.text = labelText - model.surface = surface - model.disabled = disabled - model.attributes = labelTextAttributes - return model - } - - public var childModel: DefaultLabelModel? { - guard let childText = childText else { return nil } - var model = DefaultLabelModel() - model.textPosition = .left - model.typograpicalStyle = .BodyLarge - model.text = childText - model.surface = surface - model.disabled = disabled - model.attributes = childTextAttributes - return model - } - - public var errorModel: DefaultLabelModel? { - guard let errorText = errorText, hasError else { return nil } - var model = DefaultLabelModel() - model.textPosition = .left - model.typograpicalStyle = .BodyMedium - model.text = errorText - model.surface = surface - model.disabled = disabled - return model - } -}