refactored to remove models

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2022-10-20 10:55:36 -05:00
parent dead6947f1
commit 4bf9ce83b5
21 changed files with 668 additions and 777 deletions

View File

@ -9,13 +9,12 @@ import Foundation
import UIKit
import Combine
open class CollectionView<ModelType: Modelable>: UICollectionView, ModelHandlerable, ViewProtocol, Resettable {
open class CollectionView: UICollectionView, ModelHandlerable, ViewProtocol, Resettable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
@ -23,24 +22,18 @@ open class CollectionView<ModelType: Modelable>: UICollectionView, ModelHandlera
//--------------------------------------------------
private var initialSetupPerformed = false
@Proxy(\.model.surface)
open var surface: Surface
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
self.isEnabled = !disabled
}
}
open var surface: Surface = .light { didSet { subject.send() }}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
subject.send()
}
}
@ -52,22 +45,14 @@ open class CollectionView<ModelType: Modelable>: UICollectionView, ModelHandlera
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
initialSetup()
set(with: model)
}
public required init(with model: ModelType, collectionViewLayout layout: UICollectionViewLayout) {
public required init(collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: .zero, collectionViewLayout: layout)
initialSetup()
set(with: model)
}
public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
initialSetup()
set(with: model)
}
public required init?(coder: NSCoder) {
@ -90,15 +75,14 @@ open class CollectionView<ModelType: Modelable>: UICollectionView, ModelHandlera
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
open func updateView() {
fatalError("Implement updateView")
}
open func reset() {
backgroundColor = .clear
if let model = model as? Resettable {
model.reset()
}
surface = .light
disabled = false
}
// MARK: - ViewProtocol

View File

@ -10,7 +10,6 @@ import UIKit
import Combine
open class CollectionViewCell<ModelHandlerType: ModelHandlerable & UIView>: UICollectionViewCell, ViewProtocol {
public typealias ModelType = ModelHandlerType.ModelType
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -18,10 +17,10 @@ open class CollectionViewCell<ModelHandlerType: ModelHandlerable & UIView>: UICo
public var modelHandler: ModelHandlerType = ModelHandlerType()
@Proxy(\.modelHandler.model.surface)
open var surface: Surface
@Proxy(\.modelHandler.surface)
open var surface: Surface
@Proxy(\.modelHandler.model.disabled)
@Proxy(\.modelHandler.disabled)
open var disabled: Bool
//--------------------------------------------------
@ -32,12 +31,6 @@ open class CollectionViewCell<ModelHandlerType: ModelHandlerable & UIView>: UICo
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: frame)
initialSetup()
@ -58,22 +51,7 @@ open class CollectionViewCell<ModelHandlerType: ModelHandlerable & UIView>: UICo
setup()
}
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func shouldUpdateView(viewModel: ModelType) -> Bool {
return modelHandler.shouldUpdateView(viewModel: viewModel)
}
open func updateView(viewModel: ModelType) {
modelHandler.updateView(viewModel: viewModel)
}
public func set(with model: ModelType) {
modelHandler.set(with: model)
}
// MARK: - ViewProtocol
/// Will be called only once.
open func setup() {

View File

@ -87,6 +87,10 @@ public protocol BinaryColorable{
var userTrueColor: Bool { get }
}
extension BinaryColorable where Self: Control {
public var userTrueColor: Bool { return isSelected }
}
extension BinaryColorable where Self: Selectable {
public var userTrueColor: Bool { return selected }
}

View File

@ -10,13 +10,12 @@ import UIKit
import Combine
open class Control<ModelType: Modelable>: UIControl, ModelHandlerable, ViewProtocol, Resettable {
open class Control: UIControl, ModelHandlerable, ViewProtocol, Resettable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
@ -24,25 +23,18 @@ open class Control<ModelType: Modelable>: UIControl, ModelHandlerable, ViewProto
//--------------------------------------------------
private var initialSetupPerformed = false
@Proxy(\.model.surface)
open var surface: Surface
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
self.isEnabled = !disabled
}
}
open var surface: Surface = .light { didSet { subject.send() }}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open override var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
subject.send()
}
}
@ -54,12 +46,6 @@ open class Control<ModelType: Modelable>: UIControl, ModelHandlerable, ViewProto
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
@ -92,15 +78,14 @@ open class Control<ModelType: Modelable>: UIControl, ModelHandlerable, ViewProto
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
open func updateView() {
fatalError("Implement updateView")
}
open func reset() {
backgroundColor = .clear
if let model = model as? Resettable {
model.reset()
}
surface = .light
disabled = false
}
// MARK: - ViewProtocol

View File

@ -8,7 +8,7 @@
import Foundation
import UIKit
public class SelectorGroupHandlerBase<GroupModelType: SelectorGroupModelable, ModelHandlerType: Control<GroupModelType.SelectorModelType>>: Control<GroupModelType> {
public class SelectorGroupHandlerBase<ModelHandlerType: Control>: Control {
//--------------------------------------------------
// MARK: - Public Properties
@ -33,42 +33,10 @@ public class SelectorGroupHandlerBase<GroupModelType: SelectorGroupModelable, Mo
}
}
}
public func findSelectorView(id: UUID?) -> ModelHandlerType? {
return selectorViews.first(where: { existingSelectorView in
return existingSelectorView.model.id == id
})
}
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
}
}
public func createModelHandler(selector: ModelHandlerType.ModelType) -> ModelHandlerType {
public func createModelHandler() -> ModelHandlerType {
//create view
let newSelectorView = ModelHandlerType(with: selector)
//add model update to the subscribers
newSelectorView
.modelPublisher
.sink { [weak self] model in
self?.replace(viewModel: model)
}
.store(in: &subscribers)
let newSelectorView = ModelHandlerType()
//add the selectedPublisher for the change
newSelectorView
@ -93,9 +61,8 @@ public class SelectorGroupHandlerBase<GroupModelType: SelectorGroupModelable, Mo
}
}
public class SelectorGroupSelectedHandlerBase<GroupModelType: SelectorGroupSelectedModelable, ModelHandlerType: Control<GroupModelType.SelectorModelType>>: SelectorGroupHandlerBase<GroupModelType, ModelHandlerType> where GroupModelType.SelectorModelType == ModelHandlerType.ModelType {
public var selectedModel: ModelHandlerType.ModelType? {
return model.selectedModel
public class SelectorGroupSelectedHandlerBase<ModelHandlerType: Control>: SelectorGroupHandlerBase<ModelHandlerType>{
public var selectedHandler: ModelHandlerType? {
return selectorViews.filter { $0.isSelected == true }.first
}
}

View File

@ -10,39 +10,31 @@ import UIKit
import Combine
open class View<ModelType: Modelable>: UIView, ModelHandlerable, ViewProtocol, Resettable {
open class View: UIView, ModelHandlerable, ViewProtocol, Resettable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
private var initialSetupPerformed = false
@Proxy(\.model.surface)
open var surface: Surface
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
self.isEnabled = !disabled
}
}
open var surface: Surface = .light { didSet { subject.send() }}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
subject.send()
}
}
@ -53,12 +45,6 @@ open class View<ModelType: Modelable>: UIView, ModelHandlerable, ViewProtocol, R
super.init(frame: .zero)
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
@ -85,15 +71,14 @@ open class View<ModelType: Modelable>: UIView, ModelHandlerable, ViewProtocol, R
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
open func updateView() {
fatalError("Implement updateView")
}
open func reset() {
backgroundColor = .clear
if let model = model as? Resettable {
model.reset()
}
surface = .light
disabled = false
}
// MARK: - ViewProtocol

View File

@ -11,44 +11,34 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class Badge: BadgeBase<DefaultBadgeModel>{}
public class Badge: BadgeBase{}
open class BadgeBase<ModelType: BadgeModel>: View<ModelType> {
open class BadgeBase: View, Accessable {
private var label = Label()
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@Proxy(\.model.fillColor)
open var fillColor: BadgeFillColor
open var fillColor: BadgeFillColor = .red { didSet { subject.send() }}
@Proxy(\.model.text)
open var text: String
open var text: String = "" { didSet { subject.send() }}
@Proxy(\.model.maxWidth)
open var maxWidth: CGFloat?
open var maxWidth: CGFloat? { didSet { subject.send() }}
@Proxy(\.model.numberOfLines)
open var numberOfLines: Int
open var numberOfLines: Int = 1 { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var accessibilityHintEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { subject.send() }}
//--------------------------------------------------
// MARK: - Constraints
@ -90,9 +80,9 @@ open class BadgeBase<ModelType: BadgeModel>: View<ModelType> {
//--------------------------------------------------
// MARK: - Configuration
//--------------------------------------------------
public func backgroundColor(for fillColor: BadgeFillColor) -> UIColor {
public func backgroundColor() -> UIColor {
var config: SurfaceColorConfiguration
switch model.fillColor {
switch fillColor {
case .red:
config = SurfaceColorConfiguration().with {
$0.lightColor = VDSColor.backgroundBrandhighlight
@ -130,10 +120,10 @@ open class BadgeBase<ModelType: BadgeModel>: View<ModelType> {
}
}
return config.getColor(model)
return config.getColor(self)
}
public func textColorConfiguration(for fillColor: BadgeFillColor) -> AnyColorable {
public func textColorConfiguration() -> AnyColorable {
switch fillColor {
@ -165,19 +155,23 @@ open class BadgeBase<ModelType: BadgeModel>: View<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
backgroundColor = backgroundColor(for: viewModel.fillColor)
open override func updateView() {
backgroundColor = backgroundColor()
label.textColorConfiguration = textColorConfiguration()
label.numberOfLines = numberOfLines
label.textPosition = .left
label.typograpicalStyle = .BoldBodySmall
label.text = text
label.surface = surface
label.disabled = disabled
label.textColorConfiguration = textColorConfiguration(for: viewModel.fillColor)
label.numberOfLines = viewModel.numberOfLines
if let maxWidth = viewModel.maxWidth, let minWidth = minWidthConstraint?.constant, maxWidth > minWidth {
if let maxWidth = maxWidth, let minWidth = minWidthConstraint?.constant, maxWidth > minWidth {
maxWidthConstraint?.constant = maxWidth
maxWidthConstraint?.isActive = true
} else {
maxWidthConstraint?.isActive = false
}
label.set(with: viewModel.label)
setAccessibilityLabel()
}

View File

@ -11,15 +11,12 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class Button:ButtonBase<DefaultButtonModel>{}
open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewProtocol, Resettable {
open class Button: UIButton, ModelHandlerable, ViewProtocol, Resettable, Useable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
@ -32,39 +29,29 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@Proxy(\.model.surface)
open var surface: Surface
open var text: String? { didSet { subject.send() }}
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
isEnabled = !disabled
}
}
open var use: Use = .primary { didSet { subject.send() }}
open var size: ButtonSize = .large { didSet { subject.send() }}
@Proxy(\.model.text)
open var text: String?
open var width: CGFloat? { didSet { subject.send() }}
@Proxy(\.model.use)
open var use: Use
@Proxy(\.model.size)
open var size: ButtonSize
@Proxy(\.model.width)
open var width: CGFloat?
open var surface: Surface = .light { didSet { subject.send() }}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open override var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
subject.send()
}
}
//--------------------------------------------------
// MARK: - Configuration Properties
@ -113,16 +100,9 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public required init?(coder: NSCoder) {
@ -149,15 +129,19 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
//only 1 of the 2 widths can be on at the same time
widthConstraint = widthAnchor.constraint(equalToConstant: 0)
minWidthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: model.size.minimumWidth)
minWidthConstraint = widthAnchor.constraint(greaterThanOrEqualToConstant: size.minimumWidth)
//height
heightConstraint = heightAnchor.constraint(equalToConstant: model.size.height)
heightConstraint = heightAnchor.constraint(equalToConstant: size.height)
heightConstraint?.isActive = true
}
open func reset() {
model = ModelType()
surface = .light
disabled = false
use = .primary
width = nil
size = .large
accessibilityCustomActions = []
accessibilityTraits = .staticText
}
@ -165,19 +149,19 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
open func updateView() {
let bgColor = buttonBackgroundColorConfiguration.getColor(viewModel)
let borderColor = buttonBorderColorConfiguration.getColor(viewModel)
let titleColor = buttonTitleColorConfiguration.getColor(viewModel)
let borderWidth = viewModel.use == .secondary ? 1.0 : 0.0
let buttonHeight = viewModel.size.height
let bgColor = buttonBackgroundColorConfiguration.getColor(self)
let borderColor = buttonBorderColorConfiguration.getColor(self)
let titleColor = buttonTitleColorConfiguration.getColor(self)
let borderWidth = use == .secondary ? 1.0 : 0.0
let buttonHeight = size.height
let cornerRadius = buttonHeight / 2
let minWidth = viewModel.size.minimumWidth
let font = viewModel.size == .large ? TypographicalStyle.BoldBodyLarge.font : TypographicalStyle.BoldBodySmall.font
let edgeInsets = viewModel.size.edgeInsets
let minWidth = size.minimumWidth
let font = size == .large ? TypographicalStyle.BoldBodyLarge.font : TypographicalStyle.BoldBodySmall.font
let edgeInsets = size.edgeInsets
if let text = viewModel.text {
if let text = text {
setTitle(text, for: .normal)
} else {
setTitle("No ViewModel Text", for: .normal)
@ -193,7 +177,7 @@ open class ButtonBase<ModelType: ButtonModel>: UIButton, ModelHandlerable, ViewP
minWidthConstraint?.constant = minWidth
heightConstraint?.constant = buttonHeight
if let width = viewModel.width, width > minWidth {
if let width, width > minWidth {
widthConstraint?.constant = width
widthConstraint?.isActive = true
minWidthConstraint?.isActive = false

View File

@ -24,7 +24,6 @@ public struct DefaultButtonModel: ButtonModel {
public var id = UUID()
public var text: String?
public var typograpicalStyle: TypographicalStyle = .BoldBodyLarge
public var surface: Surface = .light
public var use: Use = .primary
public var disabled: Bool = false

View File

@ -11,9 +11,9 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class Checkbox: CheckboxBase<DefaultCheckboxModel>{}
public class Checkbox: CheckboxBase{}
public class SoloCheckbox: CheckboxBase<DefaultCheckboxModel>{
public class SoloCheckbox: CheckboxBase{
public override func initialSetup() {
super.initialSetup()
publisher(for: .touchUpInside)
@ -23,11 +23,39 @@ public class SoloCheckbox: CheckboxBase<DefaultCheckboxModel>{
}
}
open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
open class CheckboxBase: Control, Accessable, BinaryColorable, Errorable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
initialSetup()
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// 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 else { return false }
return true
}
private var mainStackView: UIStackView = {
return UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
@ -67,60 +95,77 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
}()
//can't bind to @Proxy
open override var isSelected: Bool {
get { model.selected }
set {
if model.selected != newValue {
model.selected = newValue
}
}
}
open override var isSelected: Bool { didSet { subject.send() }}
@Proxy(\.model.labelText)
open var labelText: String?
open var labelText: String? { didSet { subject.send() }}
@Proxy(\.model.childText)
open var childText: String?
open var labelTextAttributes: [any LabelAttributeModel]? { didSet { subject.send() }}
@Proxy(\.model.showError)
open var showError: Bool
@Proxy(\.model.errorText)
open var errorText: String?
open var childText: String? { didSet { subject.send() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var childTextAttributes: [any LabelAttributeModel]? { didSet { subject.send() }}
@Proxy(\.model.value)
open var value: AnyHashable?
open var showError: Bool = false { didSet { subject.send() }}
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var errorText: String? { didSet { subject.send() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var inputId: String? { didSet { subject.send() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
open var value: AnyHashable? { didSet { subject.send() }}
open var dataAnalyticsTrack: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var dataClickStream: String? { didSet { subject.send() }}
open var dataTrack: String? { didSet { subject.send() }}
open var accessibilityHintEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { subject.send() }}
private 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
}
private 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
}
private var errorModel: DefaultLabelModel? {
guard let errorText = errorText, showError else { return nil }
var model = DefaultLabelModel()
model.textPosition = .left
model.typograpicalStyle = .BodyMedium
model.text = errorText
model.surface = surface
model.disabled = disabled
return model
}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
@ -159,7 +204,7 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
selectorWidthConstraint = selectorView.widthAnchor.constraint(equalToConstant: selectorSize.width)
selectorWidthConstraint?.isActive = true
updateSelector(model)
updateSelector()
mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
@ -168,22 +213,32 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
}
func updateLabels(_ viewModel: ModelType) {
func updateLabels() {
//deal with labels
if viewModel.shouldShowLabels {
if shouldShowLabels {
//add the stackview to hold the 2 labels
//top label
if let labelModel = viewModel.labelModel {
primaryLabel.set(with: labelModel)
if let labelText {
primaryLabel.textPosition = .left
primaryLabel.typograpicalStyle = .BoldBodyLarge
primaryLabel.text = labelText
primaryLabel.surface = surface
primaryLabel.disabled = disabled
primaryLabel.attributes = labelTextAttributes
primaryLabel.isHidden = false
} else {
primaryLabel.isHidden = true
}
//bottom label
if let childModel = viewModel.childModel {
secondaryLabel.set(with: childModel)
if let childText {
secondaryLabel.textPosition = .left
secondaryLabel.typograpicalStyle = .BodyLarge
secondaryLabel.text = childText
secondaryLabel.surface = surface
secondaryLabel.disabled = disabled
secondaryLabel.attributes = childTextAttributes
secondaryLabel.isHidden = false
} else {
secondaryLabel.isHidden = true
@ -199,20 +254,23 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
}
//either add/remove the error from the main stack
if let errorModel = model.errorModel, model.shouldShowError {
errorLabel.set(with: errorModel)
if let errorText, shouldShowError {
errorLabel.textPosition = .left
errorLabel.typograpicalStyle = .BodyMedium
errorLabel.text = errorText
errorLabel.surface = surface
errorLabel.disabled = disabled
mainStackView.spacing = 8
errorLabel.isHidden = false
} else {
mainStackView.spacing = 0
errorLabel.isHidden = true
}
}
public override func reset() {
super.reset()
updateSelector(model)
updateSelector()
setAccessibilityLabel()
}
@ -229,12 +287,12 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
updateLabels(viewModel)
updateSelector(viewModel)
open override func updateView() {
updateLabels()
updateSelector()
setAccessibilityHint()
setAccessibilityValue(viewModel.selected)
setAccessibilityLabel(viewModel.selected)
setAccessibilityValue(isSelected)
setAccessibilityLabel(isSelected)
setNeedsLayout()
layoutIfNeeded()
}
@ -294,11 +352,11 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
return checkboxSize
}
open func updateSelector(_ viewModel: ModelType) {
open func updateSelector() {
//get the colors
let backgroundColor = checkboxBackgroundColorConfiguration.getColor(viewModel)
let borderColor = checkboxBorderColorConfiguration.getColor(viewModel)
let checkColor = checkboxCheckColorConfiguration.getColor(viewModel)
let backgroundColor = checkboxBackgroundColorConfiguration.getColor(self)
let borderColor = checkboxBorderColorConfiguration.getColor(self)
let checkColor = checkboxCheckColorConfiguration.getColor(self)
if let shapeLayer = shapeLayer, let sublayers = layer.sublayers, sublayers.contains(shapeLayer) {
shapeLayer.removeFromSuperlayer()
@ -340,7 +398,7 @@ open class CheckboxBase<ModelType: CheckboxModel>: Control<ModelType> {
shapeLayer.lineJoin = .miter
shapeLayer.lineWidth = 2
CATransaction.withDisabledAnimations {
shapeLayer.strokeEnd = model.selected ? 1 : 0
shapeLayer.strokeEnd = isSelected ? 1 : 0
}
}
}

View File

@ -8,7 +8,7 @@
import Foundation
import UIKit
public class CheckboxGroup: CheckboxGroupBase<DefaultCheckboxGroupModel, Checkbox> {
public class CheckboxGroup: CheckboxGroupBase<Checkbox> {
public override func didSelect(_ selectedControl: Checkbox) {
selectedControl.toggle()
if selectedControl.isSelected, showError{
@ -18,13 +18,25 @@ public class CheckboxGroup: CheckboxGroupBase<DefaultCheckboxGroupModel, Checkbo
}
}
public class CheckboxGroupBase<GroupModelType: CheckboxGroupModel, ModelHandlerType: CheckboxBase<GroupModelType.SelectorModelType>>: SelectorGroupHandlerBase<GroupModelType, ModelHandlerType> {
public class CheckboxGroupBase<ModelHandlerType: CheckboxBase>: SelectorGroupHandlerBase<ModelHandlerType> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public override var selectorViews: [ModelHandlerType] {
didSet {
for selector in selectorViews {
if !mainStackView.arrangedSubviews.contains(selector) {
mainStackView.addArrangedSubview(selector)
}
}
}
}
private var _showError: Bool = false
public var showError: Bool {
get { model.showError }
get { _showError }
set {
var newShowError = newValue
let anySelected = selectorViews.filter { $0.isSelected == true }.count > 0
@ -34,8 +46,7 @@ public class CheckboxGroupBase<GroupModelType: CheckboxGroupModel, ModelHandlerT
selectorViews.forEach { handler in
handler.showError = newShowError
}
model.showError = newShowError
_showError = newShowError
}
}
@ -67,22 +78,6 @@ public class CheckboxGroupBase<GroupModelType: CheckboxGroupModel, ModelHandlerT
mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
open override func updateView(viewModel: ModelType) {
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 = createModelHandler(selector: selectorModel)
self.selectorViews.append(newSelectorView)
mainStackView.addArrangedSubview(newSelectorView)
}
}
}
public var selectedModelHandlers: [ModelHandlerType]? {
let selected = selectorViews.filter{ $0.isSelected == true }

View File

@ -10,57 +10,43 @@ import UIKit
import VDSColorTokens
import Combine
public class Label:LabelBase<DefaultLabelModel>{}
public class Label: LabelBase {}
open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProtocol, Resettable {
open class LabelBase: UILabel, ModelHandlerable, ViewProtocol, Resettable {
//--------------------------------------------------
// MARK: - Combine Properties
//--------------------------------------------------
@Published public var model: ModelType = ModelType()
public var modelPublisher: Published<ModelType>.Publisher { $model }
public var subject = PassthroughSubject<Void, Never>()
public var subscribers = Set<AnyCancellable>()
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@Proxy(\.model.surface)
open var surface: Surface
open var surface: Surface = .light { didSet { subject.send() }}
@Proxy(\.model.disabled)
open var disabled: Bool {
didSet {
self.isEnabled = !disabled
}
}
open var disabled: Bool = false { didSet { isEnabled = !disabled } }
open var attributes: [any LabelAttributeModel]? { didSet { subject.send() }}
open var typograpicalStyle: TypographicalStyle = .defaultStyle { didSet { subject.send() }}
open var textPosition: TextPosition = .left { didSet { subject.send() }}
open override var isEnabled: Bool {
get { !model.disabled }
get { !disabled }
set {
//create local vars for clear coding
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
if disabled != !newValue {
disabled = !newValue
}
isUserInteractionEnabled = isEnabled
subject.send()
}
}
@Proxy(\.model.attributes)
open var attributes: [any LabelAttributeModel]?
@Proxy(\.model.typograpicalStyle)
open var typograpicalStyle: TypographicalStyle
@Proxy(\.model.textPosition)
open var textPosition: TextPosition
//can't use @Proxy here
override open var text: String? {
didSet {
if model.text != oldValue {
model.text = text
}
subject.send()
}
}
@ -81,17 +67,10 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
super.init(frame: .zero)
initialSetup()
}
public required init(with model: ModelType) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
set(with: model)
}
public required init?(coder: NSCoder) {
@ -116,11 +95,13 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
open func setup() {}
open func reset() {
surface = .light
disabled = false
attributes = nil
typograpicalStyle = .defaultStyle
textPosition = .left
text = nil
attributedText = nil
textColor = .black
font = TypographicalStyle.BodyLarge.font
textAlignment = .left
accessibilityCustomActions = []
accessibilityTraits = .staticText
numberOfLines = 0
@ -129,17 +110,12 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open func updateView(viewModel: ModelType) {
textAlignment = viewModel.textPosition.textAlignment
textColor = textColorConfiguration.getColor(viewModel)
open func updateView() {
textAlignment = textPosition.textAlignment
textColor = textColorConfiguration.getColor(self)
font = typograpicalStyle.font
if let vdsFont = viewModel.font {
font = vdsFont
} else {
font = TypographicalStyle.defaultStyle.font
}
if let text = viewModel.text, let font = font, let textColor = textColor {
if let text = text, let font = font, let textColor = textColor {
//clear the arrays holding actions
accessibilityCustomActions = []
actions = []
@ -149,9 +125,9 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
let mutableText = NSMutableAttributedString(string: text, attributes: startingAttributes)
//set the local lineHeight/lineSpacing attributes
setStyleAttributes(viewModel: viewModel, attributedString: mutableText)
setStyleAttributes(attributedString: mutableText)
if let attributes = viewModel.attributes {
if let attributes = attributes {
//loop through the models attributes
for attribute in attributes {
@ -170,42 +146,40 @@ open class LabelBase<ModelType: LabelModel>: UILabel, ModelHandlerable, ViewProt
}
//only enabled if enabled and has actions
isUserInteractionEnabled = !viewModel.disabled && !actions.isEmpty
isUserInteractionEnabled = !disabled && !actions.isEmpty
//set the attributed text
attributedText = mutableText
} else {
text = viewModel.text
}
}
// MARK: - Private Attributes
private func setStyleAttributes(viewModel: ModelType, attributedString: NSMutableAttributedString) {
private func setStyleAttributes(attributedString: NSMutableAttributedString) {
//get the range
let entireRange = NSRange(location: 0, length: attributedString.length)
//set letterSpacing
if viewModel.typograpicalStyle.letterSpacing > 0.0 {
attributedString.addAttribute(.kern, value: viewModel.typograpicalStyle.letterSpacing, range: entireRange)
if typograpicalStyle.letterSpacing > 0.0 {
attributedString.addAttribute(.kern, value: typograpicalStyle.letterSpacing, range: entireRange)
}
//set lineHeight
if viewModel.typograpicalStyle.lineHeight > 0.0 {
let lineHeight = viewModel.typograpicalStyle.lineHeight
if typograpicalStyle.lineHeight > 0.0 {
let lineHeight = typograpicalStyle.lineHeight
let adjustment = lineHeight > font.lineHeight ? 2.0 : 1.0
let baselineOffset = (lineHeight - font.lineHeight) / 2.0 / adjustment
let paragraph = NSMutableParagraphStyle().with {
$0.maximumLineHeight = lineHeight
$0.minimumLineHeight = lineHeight
$0.alignment = viewModel.textPosition.textAlignment
$0.alignment = textPosition.textAlignment
$0.lineBreakMode = lineBreakMode
}
attributedString.addAttribute(.baselineOffset, value: baselineOffset, range: entireRange)
attributedString.addAttribute( .paragraphStyle, value: paragraph, range: entireRange)
} else if viewModel.textPosition != .left {
} else if textPosition != .left {
let paragraph = NSMutableParagraphStyle().with {
$0.alignment = viewModel.textPosition.textAlignment
$0.alignment = textPosition.textAlignment
$0.lineBreakMode = lineBreakMode
}
attributedString.addAttribute( .paragraphStyle, value: paragraph, range: entireRange)

View File

@ -11,9 +11,9 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class RadioBox: RadioBoxBase<DefaultRadioBoxModel>{}
public class RadioBox: RadioBoxBase{}
public class SolorRadioBox: RadioBoxBase<DefaultRadioBoxModel>{
public class SolorRadioBox: RadioBoxBase{
public override func initialSetup() {
super.initialSetup()
@ -24,8 +24,25 @@ public class SolorRadioBox: RadioBoxBase<DefaultRadioBoxModel>{
}
}
open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
open class RadioBoxBase: Control, BinaryColorable, Accessable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
initialSetup()
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -67,61 +84,42 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
$0.translatesAutoresizingMaskIntoConstraints = false
}
}()
//can't bind to @Proxy
open override var isSelected: Bool {
get { model.selected }
set {
if model.selected != newValue {
model.selected = newValue
}
}
}
@Proxy(\.model.text)
open var text: String
open var text: String = "Default Text" { didSet { subject.send() }}
@Proxy(\.model.subText)
open var subText: String?
open var textAttributes: [any LabelAttributeModel]? { didSet { subject.send() }}
open var subText: String? { didSet { subject.send() }}
@Proxy(\.model.subTextRight)
open var subTextRight: String?
open var subTextAttributes: [any LabelAttributeModel]? { didSet { subject.send() }}
@Proxy(\.model.strikethrough)
open var strikethrough: Bool
open var subTextRight: String? { didSet { subject.send() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var subTextRightAttributes: [any LabelAttributeModel]? { didSet { subject.send() }}
@Proxy(\.model.value)
open var value: AnyHashable?
open var strikethrough: Bool = false { didSet { subject.send() }}
open var inputId: String? { didSet { subject.send() }}
open var value: AnyHashable? { didSet { subject.send() }}
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var dataAnalyticsTrack: String? { didSet { subject.send() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var dataClickStream: String? { didSet { subject.send() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
open var dataTrack: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var accessibilityHintEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { subject.send() }}
//functions
//--------------------------------------------------
@ -153,7 +151,7 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
selectorLeftLabelStackView.spacing = 4
selectorLeftLabelStackView.isHidden = false
updateSelector(model)
updateSelector()
selectorView.topAnchor.constraint(equalTo: topAnchor).isActive = true
selectorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
@ -167,23 +165,38 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
}
func updateLabels(_ viewModel: ModelType) {
func updateLabels() {
//add the stackview to hold the 2 labels
//text label
textLabel.set(with: viewModel.textModel)
textLabel.textPosition = .left
textLabel.typograpicalStyle = .BoldBodyLarge
textLabel.text = text
textLabel.surface = surface
textLabel.disabled = disabled
textLabel.attributes = textAttributes
//subText label
if let subTextModel = viewModel.subTextModel {
subTextLabel.set(with: subTextModel)
if let subText {
subTextLabel.textPosition = .left
subTextLabel.typograpicalStyle = .BodyLarge
subTextLabel.text = subText
subTextLabel.surface = surface
subTextLabel.disabled = disabled
subTextLabel.attributes = subTextAttributes
subTextLabel.isHidden = false
} else {
subTextLabel.isHidden = true
}
//subTextRight label
if let subTextRightModel = viewModel.subTextRightModel {
subTextRightLabel.set(with: subTextRightModel)
if let subTextRight {
subTextRightLabel.textPosition = .right
subTextRightLabel.typograpicalStyle = .BodyLarge
subTextRightLabel.text = subTextRight
subTextRightLabel.surface = surface
subTextRightLabel.disabled = disabled
subTextRightLabel.attributes = subTextRightAttributes
subTextRightLabel.isHidden = false
} else {
subTextRightLabel.isHidden = true
@ -192,7 +205,7 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
public override func reset() {
super.reset()
updateSelector(model)
updateSelector()
setAccessibilityLabel()
}
@ -206,12 +219,12 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
updateLabels(viewModel)
updateSelector(viewModel)
open override func updateView() {
updateLabels()
updateSelector()
setAccessibilityHint()
setAccessibilityValue(viewModel.selected)
setAccessibilityLabel(viewModel.selected)
setAccessibilityValue(isSelected)
setAccessibilityLabel(isSelected)
setNeedsLayout()
layoutIfNeeded()
}
@ -256,11 +269,11 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
private var shapeLayer: CAShapeLayer?
open func updateSelector(_ viewModel: ModelType) {
open func updateSelector() {
//get the colors
let backgroundColor = radioBoxBackgroundColorConfiguration.getColor(viewModel)
let borderColor = radioBoxBorderColorConfiguration.getColor(viewModel)
let borderWidth = viewModel.selected ? selectorBorderWidthSelected : selectorBorderWidth
let backgroundColor = radioBoxBackgroundColorConfiguration.getColor(self)
let borderColor = radioBoxBorderColorConfiguration.getColor(self)
let borderWidth = isSelected ? selectorBorderWidthSelected : selectorBorderWidth
selectorView.backgroundColor = backgroundColor
selectorView.layer.borderColor = borderColor.cgColor
@ -278,12 +291,12 @@ open class RadioBoxBase<ModelType: RadioBoxModel>: Control<ModelType> {
open override func draw(_ layer: CALayer, in ctx: CGContext) {
let borderColor = radioBoxBorderColorConfiguration.getColor(model)
let borderColor = radioBoxBorderColorConfiguration.getColor(self)
shapeLayer?.removeFromSuperlayer()
shapeLayer = nil
if model.strikethrough {
if strikethrough {
let bounds = selectorView.bounds
let length = max(bounds.size.height, bounds.size.width)
guard length > 0.0, shapeLayer == nil else { return }

View File

@ -8,7 +8,7 @@
import Foundation
import UIKit
public class RadioBoxGroup: RadioBoxGroupBase<DefaultRadioBoxGroupModel, RadioBox> {
public class RadioBoxGroup: RadioBoxGroupBase<RadioBox> {
public override func didSelect(_ selectedControl: RadioBox) {
let oldSelectedControl = selectorViews.filter { $0.isSelected == true }.first
@ -18,8 +18,21 @@ public class RadioBoxGroup: RadioBoxGroupBase<DefaultRadioBoxGroupModel, RadioBo
}
}
public class RadioBoxGroupBase<GroupModelType: RadioBoxGroupModel, ModelHandlerType: RadioBoxBase<GroupModelType.SelectorModelType>>: SelectorGroupSelectedHandlerBase<GroupModelType, ModelHandlerType> {
public class RadioBoxGroupBase<ModelHandlerType: RadioBoxBase>: SelectorGroupSelectedHandlerBase<ModelHandlerType> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public override var selectorViews: [ModelHandlerType] {
didSet {
for selector in selectorViews {
if !mainStackView.arrangedSubviews.contains(selector) {
mainStackView.addArrangedSubview(selector)
}
}
}
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -68,22 +81,6 @@ public class RadioBoxGroupBase<GroupModelType: RadioBoxGroupModel, ModelHandlerT
}
}.store(in: &subscribers)
}
open override func updateView(viewModel: ModelType) {
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 = createModelHandler(selector: selectorModel)
self.selectorViews.append(newSelectorView)
mainStackView.addArrangedSubview(newSelectorView)
}
}
}
public var selectedModelHandler: ModelHandlerType? {
if let index = selectorViews.firstIndex(where: { element in

View File

@ -10,7 +10,7 @@ import UIKit
import VDSColorTokens
import VDSFormControlsTokens
public class RadioButton: RadioButtonBase<DefaultRadioButtonModel>{
public class RadioButton: RadioButtonBase {
//for groups allows "toggle"
open override func toggle() {
//removed error
@ -21,7 +21,7 @@ public class RadioButton: RadioButtonBase<DefaultRadioButtonModel>{
}
}
public class SoloRadioButton: RadioButtonBase<DefaultRadioButtonModel>{
public class SoloRadioButton: RadioButtonBase {
public override func initialSetup() {
super.initialSetup()
publisher(for: .touchUpInside)
@ -31,11 +31,38 @@ public class SoloRadioButton: RadioButtonBase<DefaultRadioButtonModel>{
}
}
open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
open class RadioButtonBase: Control, Accessable, BinaryColorable, Errorable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
initialSetup()
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// 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 else { return false }
return true
}
private var mainStackView: UIStackView = {
return UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
@ -73,61 +100,40 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
$0.translatesAutoresizingMaskIntoConstraints = false
}
}()
//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?
open var labelText: String? { didSet { subject.send() }}
@Proxy(\.model.childText)
open var childText: String?
open var labelTextAttributes: [any LabelAttributeModel]? { didSet { subject.send() }}
open var childText: String? { didSet { subject.send() }}
@Proxy(\.model.showError)
open var showError: Bool
@Proxy(\.model.errorText)
open var errorText: String?
open var childTextAttributes: [any LabelAttributeModel]? { didSet { subject.send() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var showError: Bool = false { didSet { subject.send() }}
open var errorText: String? { didSet { subject.send() }}
@Proxy(\.model.value)
open var value: AnyHashable?
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var inputId: String? { didSet { subject.send() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var value: AnyHashable? { didSet { subject.send() }}
open var dataAnalyticsTrack: String? { didSet { subject.send() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
open var dataClickStream: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var dataTrack: String? { didSet { subject.send() }}
open var accessibilityHintEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { subject.send() }}
//--------------------------------------------------
// MARK: - Constraints
@ -167,7 +173,7 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
selectorWidthConstraint = selectorView.widthAnchor.constraint(equalToConstant: selectorSize.width)
selectorWidthConstraint?.isActive = true
updateSelector(model)
updateSelector()
mainStackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
mainStackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
@ -176,22 +182,32 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
}
func updateLabels(_ viewModel: ModelType) {
func updateLabels() {
//deal with labels
if viewModel.shouldShowLabels {
if shouldShowLabels {
//add the stackview to hold the 2 labels
//top label
if let labelModel = viewModel.labelModel {
primaryLabel.set(with: labelModel)
if let labelText {
primaryLabel.textPosition = .left
primaryLabel.typograpicalStyle = .BoldBodyLarge
primaryLabel.text = labelText
primaryLabel.surface = surface
primaryLabel.disabled = disabled
primaryLabel.attributes = labelTextAttributes
primaryLabel.isHidden = false
} else {
primaryLabel.isHidden = true
}
//bottom label
if let childModel = viewModel.childModel {
secondaryLabel.set(with: childModel)
if let childText {
secondaryLabel.textPosition = .left
secondaryLabel.typograpicalStyle = .BodyLarge
secondaryLabel.text = childText
secondaryLabel.surface = surface
secondaryLabel.disabled = disabled
secondaryLabel.attributes = childTextAttributes
secondaryLabel.isHidden = false
} else {
secondaryLabel.isHidden = true
@ -207,8 +223,12 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
}
//either add/remove the error from the main stack
if let errorModel = model.errorModel, model.shouldShowError {
errorLabel.set(with: errorModel)
if let errorText, shouldShowError {
errorLabel.textPosition = .left
errorLabel.typograpicalStyle = .BodyMedium
errorLabel.text = errorText
errorLabel.surface = surface
errorLabel.disabled = disabled
mainStackView.spacing = 8
errorLabel.isHidden = false
} else {
@ -220,7 +240,7 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
public override func reset() {
super.reset()
updateSelector(model)
updateSelector()
setAccessibilityLabel()
}
@ -239,12 +259,12 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
updateLabels(viewModel)
updateSelector(viewModel)
open override func updateView() {
updateLabels()
updateSelector()
setAccessibilityHint()
setAccessibilityValue(viewModel.selected)
setAccessibilityLabel(viewModel.selected)
setAccessibilityValue(isSelected)
setAccessibilityLabel(isSelected)
setNeedsLayout()
layoutIfNeeded()
}
@ -295,7 +315,7 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
radioButtonSize
}
open func updateSelector(_ viewModel: ModelType) {
open func updateSelector() {
if let shapeLayer = shapeLayer, let sublayers = layer.sublayers, sublayers.contains(shapeLayer) {
shapeLayer.removeFromSuperlayer()
@ -307,9 +327,9 @@ open class RadioButtonBase<ModelType: RadioButtonModel>: Control<ModelType> {
guard length > 0.0, shapeLayer == nil else { return }
//get the colors
let backgroundColor = radioButtonBackgroundColorConfiguration.getColor(viewModel)
let borderColor = radioButtonBorderColorConfiguration.getColor(viewModel)
let radioSelectedColor = radioButtonCheckColorConfiguration.getColor(viewModel)
let backgroundColor = radioButtonBackgroundColorConfiguration.getColor(self)
let borderColor = radioButtonBorderColorConfiguration.getColor(self)
let radioSelectedColor = radioButtonCheckColorConfiguration.getColor(self)
selectorView.backgroundColor = backgroundColor
selectorView.layer.borderColor = borderColor.cgColor

View File

@ -8,7 +8,7 @@
import Foundation
import UIKit
public class RadioButtonGroup: RadioButtonGroupBase<DefaultRadioButtonGroupModel, RadioButton> {
public class RadioButtonGroup: RadioButtonGroupBase<RadioButton> {
public override func didSelect(_ selectedControl: RadioButton) {
let oldSelectedControl = selectorViews.filter { $0.isSelected == true }.first
@ -21,22 +21,33 @@ public class RadioButtonGroup: RadioButtonGroupBase<DefaultRadioButtonGroupModel
}
}
public class RadioButtonGroupBase<GroupModelType: RadioButtonGroupModel, ModelHandlerType: RadioButtonBase<GroupModelType.SelectorModelType>>: SelectorGroupSelectedHandlerBase<GroupModelType, ModelHandlerType> {
public class RadioButtonGroupBase<ModelHandlerType: RadioButtonBase>: SelectorGroupSelectedHandlerBase<ModelHandlerType> {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public override var selectorViews: [ModelHandlerType] {
didSet {
for selector in selectorViews {
if !mainStackView.arrangedSubviews.contains(selector) {
mainStackView.addArrangedSubview(selector)
}
}
}
}
private var _showError: Bool = false
public var showError: Bool {
get { model.showError }
get { _showError }
set {
var newShowError = newValue
if selectedModel != nil, newShowError {
if selectedModelHandler != nil, newShowError {
newShowError = false
}
selectorViews.forEach { handler in
handler.showError = newShowError
}
model.showError = newShowError
_showError = newShowError
}
}
@ -68,22 +79,6 @@ public class RadioButtonGroupBase<GroupModelType: RadioButtonGroupModel, ModelHa
mainStackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
mainStackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
open override func updateView(viewModel: ModelType) {
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 = createModelHandler(selector: selectorModel)
self.selectorViews.append(newSelectorView)
mainStackView.addArrangedSubview(newSelectorView)
}
}
}
public var selectedModelHandler: ModelHandlerType? {
if let index = selectorViews.firstIndex(where: { element in

View File

@ -11,7 +11,7 @@ import VDSColorTokens
import VDSFormControlsTokens
import Combine
public class RadioSwatch: RadioSwatchBase<DefaultRadioSwatchModel>{
public class RadioSwatch: RadioSwatchBase{
public override func initialSetup() {
super.initialSetup()
publisher(for: .touchUpInside)
@ -21,8 +21,26 @@ public class RadioSwatch: RadioSwatchBase<DefaultRadioSwatchModel>{
}
}
open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
open class RadioSwatchBase: Control, Accessable, DataTrackable, BinaryColorable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
initialSetup()
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@ -38,55 +56,38 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
$0.contentMode = .scaleAspectFit
}
}()
open var fillImage: UIImage? { didSet { subject.send() }}
open var text: String = "" { didSet { subject.send() }}
open var primaryColor: UIColor? { didSet { subject.send() }}
open var secondaryColor: UIColor? { didSet { subject.send() }}
open var strikethrough: Bool = false { didSet { subject.send() }}
//can't bind to @Proxy
open override var isSelected: Bool {
get { model.selected }
set {
if model.selected != newValue {
model.selected = newValue
}
}
}
@Proxy(\.model.text)
open var text: String
@Proxy(\.model.strikethrough)
open var strikethrough: Bool
open var inputId: String? { didSet { subject.send() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var value: AnyHashable? { didSet { subject.send() }}
open var dataAnalyticsTrack: String? { didSet { subject.send() }}
@Proxy(\.model.value)
open var value: AnyHashable?
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var dataClickStream: String? { didSet { subject.send() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var dataTrack: String? { didSet { subject.send() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var accessibilityHintEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { subject.send() }}
//functions
//--------------------------------------------------
@ -107,7 +108,7 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
selectorView.addSubview(fillView)
updateSelector(model)
updateSelector()
selectorView.topAnchor.constraint(equalTo: topAnchor).isActive = true
selectorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
@ -128,7 +129,7 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
public override func reset() {
super.reset()
updateSelector(model)
updateSelector()
setAccessibilityLabel()
}
@ -140,11 +141,11 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
updateSelector(viewModel)
open override func updateView() {
updateSelector()
setAccessibilityHint()
setAccessibilityValue(viewModel.selected)
setAccessibilityLabel(viewModel.selected)
setAccessibilityValue(isSelected)
setAccessibilityLabel(isSelected)
setNeedsLayout()
layoutIfNeeded()
}
@ -199,15 +200,15 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
return swatchSize
}
open func updateSelector(_ viewModel: ModelType) {
open func updateSelector() {
//get the colors
let backgroundColor = radioSwatchBackgroundColorConfiguration.getColor(viewModel)
let borderColor = viewModel.selected ? radioSwatchBorderColorConfiguration.getColor(viewModel) : .clear
let fillBorderColor = radioSwatchFillBorderColorConfiguration.getColor(viewModel)
let backgroundColor = radioSwatchBackgroundColorConfiguration.getColor(self)
let borderColor = isSelected ? radioSwatchBorderColorConfiguration.getColor(self) : .clear
let fillBorderColor = radioSwatchFillBorderColorConfiguration.getColor(self)
selectorView.backgroundColor = backgroundColor
selectorView.layer.borderColor = borderColor.cgColor
selectorView.layer.cornerRadius = selectorView.bounds.width * 0.5
selectorView.layer.borderWidth = viewModel.selected ? selectorBorderWidth : 0
selectorView.layer.borderWidth = isSelected ? selectorBorderWidth : 0
selectorView.layer.masksToBounds = true
gradientLayer?.removeFromSuperlayer()
@ -215,14 +216,14 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
var fillColorBackground: UIColor = .clear
if let fillImage = viewModel.fillImage {
fillView.image = viewModel.disabled ? fillImage.image(alpha: disabledAlpha) : fillImage
if let fillImage {
fillView.image = disabled ? fillImage.image(alpha: disabledAlpha) : fillImage
} else {
fillView.image = nil
if let primary = viewModel.primaryColor, let secondary = viewModel.secondaryColor {
let firstColor = viewModel.disabled ? primary.withAlphaComponent(disabledAlpha) : primary
let secondColor = viewModel.disabled ? secondary.withAlphaComponent(disabledAlpha) : secondary
if let primary = primaryColor, let secondary = secondaryColor {
let firstColor = disabled ? primary.withAlphaComponent(disabledAlpha) : primary
let secondColor = disabled ? secondary.withAlphaComponent(disabledAlpha) : secondary
let gradient = CAGradientLayer()
gradientLayer = gradient
gradient.frame = fillView.bounds
@ -231,10 +232,11 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
gradient.transform = CATransform3DMakeRotation(135.0 / 180.0 * .pi, 0.0, 0.0, 1.0)
fillView.layer.addSublayer(gradient)
} else {
fillColorBackground = viewModel.primaryColor ?? .white
fillColorBackground = primaryColor ?? .white
}
}
fillView.backgroundColor = viewModel.disabled ? fillColorBackground.withAlphaComponent(disabledAlpha) : fillColorBackground
fillView.backgroundColor = disabled ? fillColorBackground.withAlphaComponent(disabledAlpha) : fillColorBackground
fillView.layer.borderColor = fillBorderColor.cgColor
fillView.layer.cornerRadius = fillView.bounds.width * 0.5
fillView.layer.borderWidth = selectorBorderWidth
@ -250,12 +252,12 @@ open class RadioSwatchBase<ModelType: RadioSwatchModel>: Control<ModelType> {
open override func draw(_ layer: CALayer, in ctx: CGContext) {
let borderColor = radioSwatchBorderColorConfiguration.getColor(model)
let borderColor = radioSwatchBorderColorConfiguration.getColor(self)
shapeLayer?.removeFromSuperlayer()
shapeLayer = nil
if model.strikethrough {
if strikethrough {
let bounds = selectorView.bounds
let length = max(bounds.size.height, bounds.size.width)
guard length > 0.0, shapeLayer == nil else { return }

View File

@ -9,29 +9,31 @@ import Foundation
import UIKit
import Combine
public class RadioSwatchGroup: RadioSwatchGroupBase<DefaultRadioSwatchGroupModel, RadioSwatch> {
public class RadioSwatchGroup: RadioSwatchGroupBase<RadioSwatch> {
public override func didSelect(selector: RadioSwatch) {
if let index = model.selectors.firstIndex(where: {$0.selected == true }),
if let index = selectorViews.firstIndex(where: {$0.isSelected == true }),
let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) as? CollectionViewCell<RadioSwatch> {
cell.modelHandler.toggle()
}
selector.toggle()
label.text = selector.model.text
label.text = selector.text
valueChanged()
}
}
public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHandlerType: RadioSwatchBase<GroupModelType.SelectorModelType>>: Control<GroupModelType>, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
public class RadioSwatchGroupBase<ModelHandlerType: RadioSwatchBase>: SelectorGroupSelectedHandlerBase<ModelHandlerType>, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
public var selectedModel: ModelHandlerType.ModelType? {
return model.selectedModel
public override var selectorViews: [ModelHandlerType] {
didSet {
collectionView.reloadData()
}
}
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -109,16 +111,14 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
}
open func setHeight() {
let swatches = model.selectors
guard swatches.count > 0 else {
guard selectorViews.count > 0 else {
collectionViewHeight?.constant = 0
return
}
// Calculate the height
let swatchesInRow = floor(CGFloat(collectionView.bounds.width/(cellSize + itemSpacing)))
let numberOfRows = ceil(CGFloat(swatches.count)/swatchesInRow)
let numberOfRows = ceil(CGFloat(selectorViews.count)/swatchesInRow)
let height = (numberOfRows * cellSize) + (itemSpacing * (numberOfRows-1))
collectionViewHeight?.constant = CGFloat(height)
@ -130,31 +130,23 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
collectionView.dataSource = self
}
open override func updateView(viewModel: ModelType) {
label.set(with: viewModel.labelModel)
open override func updateView() {
label.textPosition = .left
label.typograpicalStyle = .BodySmall
label.text = selectedHandler?.text ?? " "
label.surface = surface
label.disabled = disabled
collectionView.reloadData()
setNeedsLayout()
}
//Refactor into new CollectionView Selector protocol
public func updateSelectors(){
let selectors = model.selectors.compactMap { existing in
return existing.copyWith {
$0.disabled = disabled
$0.surface = surface
}
}
model.selectors = selectors
}
public func replace(viewModel: ModelHandlerType.ModelType){
if let index = model.selectors.firstIndex(where: { element in
return element.id == viewModel.id
}) {
model.selectors[index] = viewModel
}
}
private func updateSelectors() {
for selector in selectorViews {
selector.surface = surface
selector.disabled = disabled
}
}
//--------------------------------------------------
// MARK: - UICollectionViewDelegateFlowLayout
//--------------------------------------------------
@ -166,7 +158,7 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
// MARK: - UICollectionViewDelegate
//--------------------------------------------------
open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
return !model.selectors[indexPath.row].disabled
return !selectorViews[indexPath.row].disabled
}
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
@ -182,34 +174,16 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return model.selectors.count
return selectorViews.count
}
var cellsubs: [Int: AnyCancellable] = [:]
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as? CollectionViewCell<ModelHandlerType> else { return UICollectionViewCell() }
let model = model.selectors[indexPath.row]
let model = selectorViews[indexPath.row]
cell.modelHandler = selectorViews[indexPath.row]
cell.modelHandler.isUserInteractionEnabled = false
//cancel if sub exists
if let sub = cellsubs[indexPath.row] {
sub.cancel()
cellsubs[indexPath.row] = nil
}
let sub = cell.modelHandler
.handlerPublisher()
.sink { [weak self] changed in
if cell.modelHandler.shouldUpdateView(viewModel: model) {
print("Model Change: \(changed)")
self?.replace(viewModel: changed)
}
}
cellsubs[indexPath.row] = sub
cell.set(with: model)
return cell
}
@ -217,18 +191,4 @@ public class RadioSwatchGroupBase<GroupModelType: RadioSwatchGroupModel, ModelHa
open func didSelect(selector: ModelHandlerType) {
fatalError("Must override didSelect")
}
public func valueChanged() {
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.ModelStateDebounce) { [weak self] in
self?.sendActions(for: .valueChanged)
}
}
public var selectedModelHandler: ModelHandlerType? {
guard let index = model.selectors.firstIndex(where: {$0.selected == true }),
let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) as? CollectionViewCell<ModelHandlerType> else {
return nil
}
return cell.modelHandler
}
}

View File

@ -18,7 +18,7 @@ import Combine
Knob: The circular indicator that slides on the container.
*/
public class Toggle: ToggleBase<DefaultToggleModel>{
public class Toggle: ToggleBase{
public override func initialSetup() {
super.initialSetup()
publisher(for: .touchUpInside)
@ -28,7 +28,25 @@ public class Toggle: ToggleBase<DefaultToggleModel>{
}
}
open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
open class ToggleBase: Control, Accessable, DataTrackable, BinaryColorable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
initialSetup()
}
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
initialSetup()
}
//--------------------------------------------------
// MARK: - Private Properties
@ -86,62 +104,60 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
$0.forFalse.disabled.darkColor = VDSColor.paletteGray44
}
private var typograpicalStyle: TypographicalStyle {
if textSize == .small {
if textWeight == .bold {
return .BoldBodySmall
} else {
return .BodySmall
}
} else {
if textWeight == .bold {
return .BoldBodyLarge
} else {
return .BodyLarge
}
}
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@Proxy(\.model.on)
open var isOn: Bool
open var isOn: Bool = false { didSet { subject.send() }}
@Proxy(\.model.showText)
public var showText: Bool
public var showText: Bool = false { didSet { subject.send() }}
@Proxy(\.model.onText)
public var onText: String
public var onText: String = "On" { didSet { subject.send() }}
@Proxy(\.model.offText)
public var offText: String
public var offText: String = "Off" { didSet { subject.send() }}
@Proxy(\.model.textSize)
public var textSize: ToggleTextSize
public var textSize: ToggleTextSize = .small { didSet { subject.send() }}
@Proxy(\.model.textWeight)
public var textWeight: ToggleTextWeight
public var textWeight: ToggleTextWeight = .regular { didSet { subject.send() }}
@Proxy(\.model.textPosition)
public var textPosition: ToggleTextPosition
public var textPosition: ToggleTextPosition = .left { didSet { subject.send() }}
@Proxy(\.model.inputId)
open var inputId: String?
open var inputId: String? { didSet { subject.send() }}
@Proxy(\.model.value)
open var value: AnyHashable?
open var value: AnyHashable? { didSet { subject.send() }}
@Proxy(\.model.dataAnalyticsTrack)
open var dataAnalyticsTrack: String?
open var dataAnalyticsTrack: String? { didSet { subject.send() }}
@Proxy(\.model.dataClickStream)
open var dataClickStream: String?
open var dataClickStream: String? { didSet { subject.send() }}
@Proxy(\.model.dataTrack)
open var dataTrack: String?
open var dataTrack: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintEnabled)
open var accessibilityHintEnabled: String?
open var accessibilityHintEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityHintDisabled)
open var accessibilityHintDisabled: String?
open var accessibilityHintDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueEnabled)
open var accessibilityValueEnabled: String?
open var accessibilityValueEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityValueDisabled)
open var accessibilityValueDisabled: String?
open var accessibilityValueDisabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelEnabled)
open var accessibilityLabelEnabled: String?
open var accessibilityLabelEnabled: String? { didSet { subject.send() }}
@Proxy(\.model.accessibilityLabelDisabled)
open var accessibilityLabelDisabled: String?
open var accessibilityLabelDisabled: String? { didSet { subject.send() }}
//--------------------------------------------------
// MARK: - Constraints
@ -157,12 +173,12 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - Toggle
//--------------------------------------------------
private func updateToggle(_ viewModel: ModelType) {
private func updateToggle() {
//private func
func constrainKnob(){
self.knobLeadingConstraint?.isActive = false
self.knobTrailingConstraint?.isActive = false
if viewModel.on {
if isOn {
self.knobTrailingConstraint = self.toggleView.trailingAnchor.constraint(equalTo: self.knobView.trailingAnchor, constant: 2)
self.knobLeadingConstraint = self.knobView.leadingAnchor.constraint(greaterThanOrEqualTo: self.toggleView.leadingAnchor)
} else {
@ -175,10 +191,10 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
self.layoutIfNeeded()
}
let toggleColor = toggleColorConfiguration.getColor(viewModel)
let knobColor = knobColorConfiguration.getColor(viewModel)
let toggleColor = toggleColorConfiguration.getColor(self)
let knobColor = knobColorConfiguration.getColor(self)
if viewModel.disabled {
if disabled {
toggleView.backgroundColor = toggleColor
knobView.backgroundColor = knobColor
constrainKnob()
@ -197,16 +213,21 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - Labels
//--------------------------------------------------
private func updateLabel(_ viewModel: ModelType) {
let showText = viewModel.showText
stackView.spacing = showText ? 12 : 0
label.set(with: viewModel.labelModel)
private func updateLabel() {
stackView.spacing = showText ? 12 : 0
if stackView.subviews.contains(label) {
label.removeFromSuperview()
}
if showText {
label.textPosition = textPosition == .left ? .left : .right
label.typograpicalStyle = typograpicalStyle
label.text = isOn ? onText : offText
label.surface = surface
label.disabled = disabled
if textPosition == .left {
stackView.insertArrangedSubview(label, at: 0)
} else {
@ -246,7 +267,7 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
toggleView.layer.cornerRadius = toggleSize.height / 2.0
knobView.layer.cornerRadius = knobSize.height / 2.0
toggleView.backgroundColor = toggleColorConfiguration.getColor(model)
toggleView.backgroundColor = toggleColorConfiguration.getColor(self)
toggleContainerView.addSubview(toggleView)
toggleView.addSubview(knobView)
@ -261,7 +282,7 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
toggleView.bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true
updateLabel(model)
updateLabel()
stackView.addArrangedSubview(toggleContainerView)
stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
@ -275,8 +296,8 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
public override func reset() {
super.reset()
toggleView.backgroundColor = toggleColorConfiguration.getColor(model)
knobView.backgroundColor = knobColorConfiguration.getColor(model)
toggleView.backgroundColor = toggleColorConfiguration.getColor(self)
knobView.backgroundColor = knobColorConfiguration.getColor(self)
setAccessibilityLabel()
}
@ -289,13 +310,13 @@ open class ToggleBase<ModelType: ToggleModel>: Control<ModelType> {
//--------------------------------------------------
// MARK: - State
//--------------------------------------------------
open override func updateView(viewModel: ModelType) {
updateLabel(viewModel)
updateToggle(viewModel)
open override func updateView() {
updateLabel()
updateToggle()
setAccessibilityHint()
setAccessibilityValue(viewModel.on)
setAccessibilityLabel(viewModel.on)
backgroundColor = viewModel.surface.color
setAccessibilityValue(isOn)
setAccessibilityLabel(isOn)
backgroundColor = surface.color
setNeedsLayout()
layoutIfNeeded()
}

View File

@ -18,37 +18,31 @@ public protocol Accessable {
}
//Configurations to set within the UIControl
extension ModelHandlerable where Self: UIView {
private var accessableModel: Accessable? {
guard let model = self.model as? Accessable else {
return nil
}
return model
}
extension ModelHandlerable where Self: UIView, Self: Accessable {
public func setAccessibilityHint(_ override: Bool? = nil) {
let check = override ?? !model.disabled
if let value = accessableModel?.accessibilityHintEnabled, check {
let check = override ?? !disabled
if let value = accessibilityHintEnabled, check {
accessibilityHint = value
} else if let value = accessableModel?.accessibilityHintDisabled, !check {
} else if let value = accessibilityHintDisabled, !check {
accessibilityHint = value
}
}
public func setAccessibilityValue(_ override: Bool? = nil) {
let check = override ?? !model.disabled
if let value = accessableModel?.accessibilityValueEnabled, check {
let check = override ?? !disabled
if let value = accessibilityValueEnabled, check {
accessibilityValue = value
} else if let value = accessableModel?.accessibilityValueDisabled, !check {
} else if let value = accessibilityValueDisabled, !check {
accessibilityValue = value
}
}
public func setAccessibilityLabel(_ override: Bool? = nil) {
let check = override ?? !model.disabled
if let value = accessableModel?.accessibilityLabelEnabled, check {
let check = override ?? !disabled
if let value = accessibilityLabelEnabled, check {
accessibilityLabel = value
} else if let value = accessableModel?.accessibilityLabelDisabled, !check {
} else if let value = accessibilityLabelDisabled, !check {
accessibilityLabel = value
}
}

View File

@ -9,44 +9,26 @@ import Foundation
import Combine
import UIKit
public protocol ModelHandlerable: AnyObject, Initable {
associatedtype ModelType: Modelable
var model: ModelType { get set }
var modelPublisher: Published<ModelType>.Publisher { get }
public protocol ModelHandlerable: AnyObject, Initable, Disabling, Surfaceable {
var subject: PassthroughSubject<Void, Never> { get set }
var subscribers: Set<AnyCancellable> { get set }
init(with model: ModelType)
func set(with model: ModelType)
func shouldUpdateView(viewModel: ModelType) -> Bool
func updateView(viewModel: ModelType)
func updateView()
}
extension ModelHandlerable {
public init() {
self.init(with: ModelType())
}
public func set(with model: ModelType) {
if shouldUpdateView(viewModel: model){
updateView(viewModel: model)
self.model = model
}
}
public func shouldUpdateView(viewModel: ModelType) -> Bool {
model != viewModel
}
public func setupUpdateView() {
handlerPublisher()
.subscribe(on: RunLoop.main)
.sink { [weak self] viewModel in
self?.updateView(viewModel: viewModel)
.sink { [weak self] _ in
self?.updateView()
}
.store(in: &subscribers)
}
public func handlerPublisher() -> AnyPublisher<ModelType, Never> {
modelPublisher
public func handlerPublisher() -> AnyPublisher<Void, Never> {
subject
.eraseToAnyPublisher()
.debounce(for: .seconds(Constants.ModelStateDebounce), scheduler: RunLoop.main)
.eraseToAnyPublisher()
}