diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift index f771dddf..e864caa1 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift @@ -7,146 +7,36 @@ // import MVMCore +import VDS /** This class expects its height and width to be equal. */ -@objcMembers open class Checkbox: Control, MVMCoreUIViewConstrainingProtocol { - //-------------------------------------------------- +@objcMembers open class Checkbox: VDS.Checkbox, VDSMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { + //------------------------------------------------------ // MARK: - Properties - //-------------------------------------------------- - - public var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) - + //------------------------------------------------------ + open var viewModel: CheckboxModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + // Form Validation var fieldKey: String? var fieldValue: JSONValue? var groupName: String? - var delegateObject: MVMCoreUIDelegateObject? - - public var checkboxModel: CheckboxModel? { - model as? CheckboxModel - } - - public static let defaultHeightWidth: CGFloat = 18.0 - - /// If true the border of this checkbox will be circular. - public var isRound: Bool = false - - /// Determined if the checkbox's UI should animated when selected. - public var isAnimated: Bool = true - - /// Disables all selection logic when setting the value of isSelected, reducing it to a stored property. - public var updateSelectionOnly: Bool = false - - /// The color of the background when checked. - public var checkedBackgroundColor: UIColor = .clear { - didSet { - if isSelected { - backgroundColor = checkedBackgroundColor - } - } - } - - /// The color of the background when unChecked. - public var unCheckedBackgroundColor: UIColor = .clear { - didSet { - if !isSelected { - backgroundColor = unCheckedBackgroundColor - } - } - } - - /// Retrieves ideeal radius value to curve square into a circle. - public var cornerRadiusValue: CGFloat { - bounds.size.height / 2 - } - + /// Action Block called when the switch is selected. - public var actionBlock: ActionBlock? - - /// Manages the appearance of the checkbox. - private var shapeLayer: CAShapeLayer? - - /// Width of the check mark. - public var checkWidth: CGFloat = 2 { + open var actionBlock: ActionBlock? { didSet { - if let shapeLayer = shapeLayer { - CATransaction.withDisabledAnimations { - shapeLayer.lineWidth = checkWidth + if let actionBlock { + onChange = { _ in + actionBlock() } - } - } - } - - open override var isEnabled: Bool { - didSet { - - isUserInteractionEnabled = isEnabled - - if isEnabled { - layer.borderColor = borderColor.cgColor - backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor - setShapeLayerStrokeColor(checkColor) } else { - layer.borderColor = disabledBorderColor.cgColor - backgroundColor = disabledBackgroundColor - setShapeLayerStrokeColor(disabledCheckColor) + onChange = nil } } } - - public var disabledBackgroundColor: UIColor = .clear - public var disabledBorderColor: UIColor = .mvmCoolGray3 - public var disabledCheckColor: UIColor = .mvmCoolGray3 - - /// Color of the check mark. - public var checkColor: UIColor = .mvmBlack { - didSet { setShapeLayerStrokeColor(checkColor) } - } - - /// Border width of the checkbox - public var borderWidth: CGFloat = 1 { - didSet { layer.borderWidth = borderWidth } - } - - /// border color of the Checkbox - public var borderColor: UIColor = .mvmBlack { - didSet { layer.borderColor = borderColor.cgColor } - } - - /** - The represented state of the Checkbox. - - Setting updateSelectionOnly to true bypasses the animation logic inherent with setting this property. - */ - override open var isSelected: Bool { - didSet { - if !updateSelectionOnly { - layoutIfNeeded() - (model as? CheckboxModel)?.selected = isSelected - shapeLayer?.removeAllAnimations() - updateCheckboxUI(isSelected: isSelected, isAnimated: isAnimated) - _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) - updateAccessibilityLabel() - } - } - } - - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - - public var heightConstraint: NSLayoutConstraint? - public var widthConstraint: NSLayoutConstraint? - - /// Updates the height and width anchors of the Checkbox with the assigned value. - public var heigthWidthConstant: CGFloat = Checkbox.defaultHeightWidth { - didSet { - heightConstraint?.constant = heigthWidthConstant - widthConstraint?.constant = heigthWidthConstant - } - } //-------------------------------------------------- // MARK: - Initializers @@ -154,11 +44,6 @@ import MVMCore override public init(frame: CGRect) { super.init(frame: frame) - - isAccessibilityElement = true - accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") - accessibilityTraits = .button - updateAccessibilityLabel() } /// There is currently no intention on using xib files. @@ -167,278 +52,109 @@ import MVMCore fatalError("xib file is not implemented for Checkbox.") } - public convenience override init() { + public convenience required init() { self.init(frame:.zero) } public convenience init(isChecked: Bool) { self.init(frame: .zero) - checkAndBypassAnimations(selected: isChecked) + isSelected = isChecked } public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) { self.init(frame: .zero) - checkAndBypassAnimations(selected: isChecked) - self.checkedBackgroundColor = checkedBackgroundColor - self.unCheckedBackgroundColor = unCheckedBackgroundColor + isSelected = isChecked } - - //-------------------------------------------------- - // MARK: - Lifecycle - //-------------------------------------------------- - - override open func layoutSubviews() { - super.layoutSubviews() - drawShapeLayer() - layer.cornerRadius = isRound ? cornerRadiusValue : 0 - } - - open override func setupView() { - super.setupView() - - isUserInteractionEnabled = true - translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .clear - - widthConstraint = widthAnchor.constraint(equalToConstant: Checkbox.defaultHeightWidth) - heightConstraint = heightAnchor.constraint(equalToConstant: Checkbox.defaultHeightWidth) - heightWidthIsActive(true) - } - //-------------------------------------------------- // MARK: - Actions //-------------------------------------------------- - open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { - super.sendAction(action, to: target, for: event) - toggleAndAction() - } - - open override func sendActions(for controlEvents: UIControl.Event) { - super.sendActions(for: controlEvents) - toggleAndAction() - } - /// This will toggle the state of the Checkbox and execute the actionBlock if provided. - public func toggleAndAction() { - isSelected.toggle() - actionBlock?() + open func toggleAndAction() { + toggle() } + open override func toggle() { + super.toggle() + viewModel.selected = isSelected + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - - /// Creates the check mark layer. - private func drawShapeLayer() { - - if shapeLayer == nil { - - let shapeLayer = CAShapeLayer() - self.shapeLayer = shapeLayer - shapeLayer.frame = bounds - layer.addSublayer(shapeLayer) - shapeLayer.strokeColor = isEnabled ? checkColor.cgColor : disabledCheckColor.cgColor - shapeLayer.fillColor = UIColor.clear.cgColor - shapeLayer.path = checkMarkPath() - shapeLayer.lineJoin = .miter - shapeLayer.lineWidth = checkWidth - - CATransaction.withDisabledAnimations { - shapeLayer.strokeEnd = isSelected ? 1 : 0 - } - } - } - - /// - returns: The CGPath of a UIBezierPath detailing the path of a checkmark - func checkMarkPath() -> CGPath { - - let length = max(bounds.size.height, bounds.size.width) - let xInsetLeft = length * 0.25 - let yInsetTop = length * 0.3 - let innerWidth = length - (xInsetLeft + length * 0.25) // + Right X Inset - let innerHeight = length - (yInsetTop + length * 0.35) // + Bottom Y Inset - - let startPoint = CGPoint(x: xInsetLeft, y: yInsetTop + (innerHeight / 2)) - let pivotOffSet = CGPoint(x: xInsetLeft + (innerWidth * 0.33), y: yInsetTop + innerHeight) - let endOffset = CGPoint(x: xInsetLeft + innerWidth, y: yInsetTop) - - let bezierPath = UIBezierPath() - bezierPath.move(to: startPoint) - bezierPath.addLine(to: pivotOffSet) - bezierPath.addLine(to: endOffset) - - return bezierPath.cgPath - } - /// Programmatic means to check/uncheck the box. /// - parameter selected: state of the check box: true = checked OR false = unchecked. /// - parameter animated: allows the state of the checkbox to change with or without animation. - public func updateSelection(to selected: Bool, animated: Bool) { + open func updateSelection(to selected: Bool, animated: Bool) { DispatchQueue.main.async { - - self.checkAndBypassAnimations(selected: selected) - self.drawShapeLayer() - self.shapeLayer?.removeAllAnimations() - self.updateCheckboxUI(isSelected: selected, isAnimated: animated) + self.isAnimated = animated + self.isSelected = selected } } /// updates the visuals of the check mark and background. /// - parameter isSelected: the check state of the checkbox. /// - parameter isAnimated: determines of the changes should animate or immediately refelect. - public func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) { + open func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) { - if isAnimated { - let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") - animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) - animateStrokeEnd.duration = 0.3 - animateStrokeEnd.fillMode = .both - animateStrokeEnd.isRemovedOnCompletion = false - animateStrokeEnd.fromValue = !isSelected ? 1 : 0 - animateStrokeEnd.toValue = isSelected ? 1 : 0 - self.shapeLayer?.add(animateStrokeEnd, forKey: "strokeEnd") - - UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { - self.backgroundColor = isSelected ? self.checkedBackgroundColor : self.unCheckedBackgroundColor - }) - } else { - CATransaction.withDisabledAnimations { - self.shapeLayer?.strokeEnd = isSelected ? 1 : 0 - } - - backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor + DispatchQueue.main.async { + self.isAnimated = isAnimated + self.isSelected = isSelected } } - - /// Adjust accessibility label based on state of Checkbox. - public func updateAccessibilityLabel() { - // Attention: This needs to be addressed with the accessibility team. - // NOTE: Currently emptying description part of MVMCoreUICheckBox accessibility label to avoid crashing! - if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { - accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@%@", "", state) - } - } - - private func setShapeLayerStrokeColor(_ color: UIColor) { - - if let shapeLayer = shapeLayer { - CATransaction.withDisabledAnimations { - shapeLayer.strokeColor = color.cgColor - } - } - } - - public func heightWidthIsActive(_ isActive: Bool) { - - heightConstraint?.isActive = isActive - widthConstraint?.isActive = isActive - } - - private func checkAndBypassAnimations(selected: Bool) { - updateSelectionOnly = true - isSelected = selected - updateSelectionOnly = false - } - - //-------------------------------------------------- - // MARK: - UITouch - //-------------------------------------------------- - - open override func touchesEnded(_ touches: Set, with event: UIEvent?) { - - sendActions(for: .touchUpInside) - } - - override open func accessibilityActivate() -> Bool { - guard isEnabled else { return false } - sendActions(for: .touchUpInside) - return true - } - + //-------------------------------------------------- // MARK: - Molecular //-------------------------------------------------- open func needsToBeConstrained() -> Bool { true } - - open override func reset() { - super.reset() - isEnabled = true - shapeLayer?.removeAllAnimations() - shapeLayer?.removeFromSuperlayer() - shapeLayer = nil - backgroundColor = .clear - borderColor = .mvmBlack - borderWidth = 1 - checkColor = .mvmBlack - checkWidth = 2 - checkAndBypassAnimations(selected: false) - } - - public override func updateView(_ size: CGFloat) { - super.updateView(size) - - if let dimension = sizeObject?.getValueBased(onSize: size) { - widthConstraint?.constant = dimension - heightConstraint?.constant = dimension - } - } + open func horizontalAlignment() -> UIStackView.Alignment { .leading } + + open func updateView(_ size: CGFloat) {} private func performCheckboxAction(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: checkboxModel, additionalData: additionalData, delegateObject: delegateObject) + MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel, additionalData: additionalData, delegateObject: delegateObject) } - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - self.delegateObject = delegateObject - - guard let model = model as? CheckboxModel else { return } - - FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) - - if let fieldKey = model.fieldKey { + + public func viewModelDidUpdate() { + //forms + FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate) + groupName = viewModel.groupName + if let fieldKey = viewModel.fieldKey { self.fieldKey = fieldKey } - borderColor = (model.inverted ? model.invertedColor : model.borderColor).uiColor - borderWidth = model.borderWidth + //properties + isEnabled = viewModel.enabled && !viewModel.readOnly + isAnimated = viewModel.animated + isSelected = viewModel.selected - checkColor = (model.inverted ? model.invertedColor : model.checkColor).uiColor - unCheckedBackgroundColor = (model.inverted ? model.invertedBackgroundColor : model.unCheckedBackgroundColor).uiColor - checkedBackgroundColor = (model.inverted ? model.invertedBackgroundColor : model.checkedBackgroundColor).uiColor - disabledCheckColor = (model.inverted ? model.invertedColor : model.disabledCheckColor).uiColor - disabledBorderColor = (model.inverted ? model.invertedColor : model.disabledBorderColor).uiColor - disabledBackgroundColor = (model.inverted ? model.invertedColor : model.disabledBackgroundColor).uiColor - - isAnimated = model.animated - isRound = model.round - - if model.selected { - checkAndBypassAnimations(selected: model.selected) - } - - model.updateUI = { [weak self] in - MVMCoreDispatchUtility.performBlock(onMainThread: { + //events + viewModel.updateUI = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in guard let self = self else { return } - self.isEnabled = model.enabled + let isValid = viewModel.isValid ?? true + showError = !isValid + isEnabled = viewModel.enabled + }) } - isEnabled = model.enabled && !model.readOnly - - if (model.action != nil || model.offAction != nil) { + //onChange + if (viewModel.action != nil || viewModel.offAction != nil) { actionBlock = { [weak self] in guard let self = self else { return } - if let offAction = model.offAction, !self.isSelected { - self.performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData) + if let offAction = viewModel.offAction, !isSelected { + performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData) - } else if let action = model.action { - self.performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData) + } else if let action = viewModel.action { + performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData) } } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift index 604a8a8d..00554c2e 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift @@ -5,6 +5,7 @@ // Created by Chintakrinda, Arun Kumar (Arun) on 21/01/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +import VDS /// Protocol to apply to any model of a UI Control with a binary on/off nature. /// @@ -13,67 +14,25 @@ var selected: Bool { get set } } -@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModelProtocol, FormFieldProtocol, UIUpdatableModelProtocol { +@objcMembers public class CheckboxModel: FormFieldModel, SelectableMoleculeModelProtocol{ //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - - public static var identifier: String = "checkbox" - public var id: String = UUID().uuidString - public var backgroundColor: Color? - public var accessibilityIdentifier: String? + public static override var identifier: String { "checkbox" } public var selected: Bool = false - public var enabled: Bool = true - public var readOnly: Bool = false public var animated: Bool = true - public var inverted: Bool = false - public var round: Bool = false - public var borderWidth: CGFloat = 1 - public var borderColor: Color = Color(uiColor: .mvmBlack) - public var checkColor: Color = Color(uiColor: .mvmBlack) - public var unCheckedBackgroundColor: Color = Color(uiColor: .clear) - public var checkedBackgroundColor: Color = Color(uiColor: .clear) - public var disabledBackgroundColor: Color = Color(uiColor: .clear) - public var disabledBorderColor: Color = Color(uiColor: .mvmCoolGray3) - public var disabledCheckColor: Color = Color(uiColor: .mvmCoolGray3) - public var invertedColor: Color = Color(uiColor: .mvmWhite) - public var invertedBackgroundColor: Color = Color(uiColor: .mvmBlack) public var action: ActionModelProtocol? public var offAction: ActionModelProtocol? - - public var fieldKey: String? - public var groupName: String = FormValidator.defaultGroupName - public var baseValue: AnyHashable? - public var updateUI: ActionBlock? - + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- private enum CodingKeys: String, CodingKey { - case id - case moleculeName - case accessibilityIdentifier case checked - case enabled - case readOnly - case inverted case animated - case round - case borderWidth - case borderColor - case checkColor - case invertedColor - case invertedBackgroundColor - case unCheckedBackgroundColor - case checkedBackgroundColor - case disabledBackgroundColor - case disabledCheckColor - case disabledBorderColor case action - case fieldKey - case groupName case offAction } @@ -81,16 +40,17 @@ // MARK: - Form Validation //-------------------------------------------------- - public func formFieldValue() -> AnyHashable? { + open override func formFieldValue() -> AnyHashable? { guard enabled else { return nil } return selected } - //-------------------------------------------------- - // MARK: - Server Value - //-------------------------------------------------- - open func formFieldServerValue() -> AnyHashable? { - return formFieldValue() + open override func setValidity(_ valid: Bool, errorMessage: String?) { + if let ruleErrorMessage = errorMessage, fieldKey != nil { + self.errorMessage = ruleErrorMessage + } + isValid = valid + updateUI?() } //-------------------------------------------------- @@ -98,7 +58,8 @@ //-------------------------------------------------- public init(isChecked: Bool = false) { - self.selected = isChecked + super.init() + selected = isChecked baseValue = isChecked } @@ -107,52 +68,9 @@ //-------------------------------------------------- required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - - id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString - - accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) - - if let borderWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .borderWidth) { - self.borderWidth = borderWidth - } - - if let borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) { - self.borderColor = borderColor - } - - if let checkColor = try typeContainer.decodeIfPresent(Color.self, forKey: .checkColor) { - self.checkColor = checkColor - } - - if let unCheckedBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .unCheckedBackgroundColor) { - self.unCheckedBackgroundColor = unCheckedBackgroundColor - } - - if let checkedBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .checkedBackgroundColor) { - self.checkedBackgroundColor = checkedBackgroundColor - } - - if let disabledBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBackgroundColor) { - self.disabledBackgroundColor = disabledBackgroundColor - } - - if let disabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBorderColor) { - self.disabledBorderColor = disabledBorderColor - } - - if let disabledCheckColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledCheckColor) { - self.disabledCheckColor = disabledCheckColor - } - - if let invertedColor = try typeContainer.decodeIfPresent(Color.self, forKey: .invertedColor) { - self.invertedColor = invertedColor - } - - if let invertedBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .invertedBackgroundColor) { - self.invertedBackgroundColor = invertedBackgroundColor - } - if let checked = try typeContainer.decodeIfPresent(Bool.self, forKey: .checked) { self.selected = checked } @@ -162,51 +80,18 @@ if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) { self.animated = animated } - - if let round = try typeContainer.decodeIfPresent(Bool.self, forKey: .round) { - self.round = round - } - - if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) { - self.inverted = inverted - } - - enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true - readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false + action = try typeContainer.decodeModelIfPresent(codingKey: .action) - fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) - - if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { - self.groupName = groupName - } offAction = try typeContainer.decodeModelIfPresent(codingKey: .offAction) + } - public func encode(to encoder: Encoder) throws { + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(groupName, forKey: .groupName) - try container.encodeIfPresent(fieldKey, forKey: .fieldKey) - try container.encodeIfPresent(borderColor, forKey: .borderColor) - try container.encode(borderWidth, forKey: .borderWidth) try container.encode(selected, forKey: .checked) - try container.encode(inverted, forKey: .inverted) - try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier) - try container.encodeIfPresent(checkColor, forKey: .checkColor) - try container.encodeIfPresent(invertedColor, forKey: .invertedColor) - try container.encodeIfPresent(invertedBackgroundColor, forKey: .invertedBackgroundColor) - try container.encodeIfPresent(unCheckedBackgroundColor, forKey: .unCheckedBackgroundColor) - try container.encodeIfPresent(checkedBackgroundColor, forKey: .checkedBackgroundColor) - try container.encodeIfPresent(disabledBorderColor, forKey: .disabledBorderColor) - try container.encodeIfPresent(disabledBackgroundColor, forKey: .disabledBackgroundColor) - try container.encodeIfPresent(disabledCheckColor, forKey: .disabledCheckColor) try container.encodeIfPresent(animated, forKey: .animated) - try container.encodeIfPresent(round, forKey: .round) - try container.encode(enabled, forKey: .enabled) - try container.encode(readOnly, forKey: .readOnly) try container.encodeModelIfPresent(action, forKey: .action) - try container.encodeIfPresent(groupName, forKey: .groupName) try container.encodeModelIfPresent(offAction, forKey: .offAction) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift index 2ce1254b..9473a324 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabel.swift @@ -5,141 +5,108 @@ // Created by Kevin Christiano on 9/13/19. // Copyright © 2019 Verizon Wireless. All rights reserved. // +import VDS - -@objcMembers open class CheckboxLabel: View { - //-------------------------------------------------- - // MARK: - Outlets - //-------------------------------------------------- - - public let checkbox = Checkbox() - public let label = Label(fontStyle: .RegularBodySmall) - private var observation: NSKeyValueObservation? = nil - - //-------------------------------------------------- +@objcMembers open class CheckboxLabel: VDS.CheckboxItem, VDSMoleculeViewProtocol { + //------------------------------------------------------ // MARK: - Properties - //-------------------------------------------------- - - public var checkboxPosition: CheckboxPosition = .center - - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- + //------------------------------------------------------ + open var viewModel: CheckboxLabelModel! + open var delegateObject: MVMCoreUIDelegateObject? + open var additionalData: [AnyHashable : Any]? + + // Form Validation + var fieldKey: String? + var fieldValue: JSONValue? + var groupName: String? + + private var updateSelectionOnly: Bool = false + override open var isSelected: Bool { + didSet { + if !updateSelectionOnly { + viewModel.checkbox.selected = isSelected + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + } + } + } - public var checkboxTopConstraint: NSLayoutConstraint? - public var checkboxBottomConstraint: NSLayoutConstraint? - public var checkboxCenterYConstraint: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- - - override open func setupView() { - super.setupView() - - guard subviews.isEmpty else { return } - - addSubview(checkbox) - addSubview(label) - - label.text = "" - - checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true - - checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor) - checkboxBottomConstraint?.isActive = true - - checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) - checkboxTopConstraint?.isActive = true - - checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor) - - label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true - layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true - label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true - - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true - let bottomLabelConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor) - bottomLabelConstraint.priority = .defaultLow - bottomLabelConstraint.isActive = true - - alignCheckbox(.center) - isAccessibilityElement = false - accessibilityElements = [checkbox, label] - observation = observe(\.checkbox.isSelected, options: [.new]) { [weak self] _, _ in - self?.updateAccessibilityLabel() - } - } - - @objc override open func updateView(_ size: CGFloat) { - super.updateView(size) - - label.updateView(size) - checkbox.updateView(size) - layoutIfNeeded() - } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - /// Aligns Checkbox and Label relative to the desired position of the Checkbox. - private func alignCheckbox(_ position: CheckboxPosition) { - checkboxPosition = position - - switch position { - case .center: - checkboxBottomConstraint?.isActive = false - checkboxTopConstraint?.isActive = false - checkboxCenterYConstraint?.isActive = true - - case .top: - checkboxBottomConstraint?.isActive = false - checkboxTopConstraint?.isActive = true - checkboxCenterYConstraint?.isActive = false - - case .bottom: - checkboxBottomConstraint?.isActive = true - checkboxTopConstraint?.isActive = false - checkboxCenterYConstraint?.isActive = false - } - } + @objc open func updateView(_ size: CGFloat) {} //-------------------------------------------------- // MARK: - Atomic //-------------------------------------------------- - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let checkBoxWithLabelModel = model as? CheckboxLabelModel else { return } - - if let checkboxAlignment = checkBoxWithLabelModel.checkboxAlignment { - alignCheckbox(checkboxAlignment) + open func viewModelDidUpdate() { + surface = viewModel.surface + + updateCheckbox() + + //primary label + labelText = viewModel.label.text + if let attributes = viewModel.label.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) { + labelTextAttributes = attributes } - checkbox.set(with: checkBoxWithLabelModel.checkbox, delegateObject, additionalData) - label.set(with: checkBoxWithLabelModel.label, delegateObject, additionalData) - updateAccessibilityLabel() + //secondary label + if let subTitleModel = viewModel.subTitle { + childText = subTitleModel.text + if let attributes = subTitleModel.attributes?.toVDSLabelAttributeModel(delegateObject: delegateObject, additionalData: additionalData) { + childTextAttributes = attributes + } + } } - open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + private func performCheckboxAction(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + MVMCoreUIActionHandler.performActionUnstructured(with: actionModel, sourceModel: viewModel.checkbox, additionalData: additionalData, delegateObject: delegateObject) + } + + open func updateCheckbox() { + //forms + FormValidator.setupValidation(for: viewModel.checkbox, delegate: delegateObject?.formHolderDelegate) + groupName = viewModel.checkbox.groupName + if let fieldKey = viewModel.checkbox.fieldKey { + self.fieldKey = fieldKey + } + + //properties + isAnimated = viewModel.checkbox.animated + isEnabled = viewModel.checkbox.enabled && !viewModel.checkbox.readOnly + if viewModel.checkbox.selected { + updateSelectionOnly = false + isSelected = viewModel.checkbox.selected + updateSelectionOnly = true + } + + //events + viewModel.checkbox.updateUI = { + MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + guard let self = self else { return } + let isValid = viewModel.checkbox.isValid ?? true + showError = !isValid + errorText = viewModel.checkbox.errorMessage + isEnabled = viewModel.checkbox.enabled + }) + } + + //onChange + if (viewModel.checkbox.action != nil || viewModel.checkbox.offAction != nil) { + onChange = { [weak self] control in + guard let self = self else { return } + + if let offAction = viewModel.checkbox.offAction, !isSelected { + performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData) + + } else if let action = viewModel.checkbox.action { + performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData) + } + } + } + + } + + open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 200 } - - open override func reset() { - super.reset() - - label.text = "" - checkbox.reset() - alignCheckbox(.center) - } - - override open func accessibilityActivate() -> Bool { - checkbox.accessibilityActivate() - } - - open func updateAccessibilityLabel() { - checkbox.updateAccessibilityLabel() - if let text = label.text { - checkbox.accessibilityLabel?.append(", \(text)") - } - } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift index 9618cfab..faa13e60 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CheckboxLabelModel.swift @@ -8,12 +8,7 @@ import Foundation import MVMCore - -public enum CheckboxPosition: String, Codable { - case center - case top - case bottom -} +import VDS @objcMembers open class CheckboxLabelModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { open class var identifier: String { "checkboxLabel" } @@ -21,18 +16,25 @@ public enum CheckboxPosition: String, Codable { @DecodableDefault.UUIDString public var id: String public var backgroundColor: Color? - public var checkboxAlignment: CheckboxPosition? public var checkbox: CheckboxModel public var label: LabelModel + public var subTitle: LabelModel? + public var inverted: Bool? = false + public var surface: Surface { inverted ?? false ? .dark : .light } + + public var children: [MoleculeModelProtocol] { + guard let subTitle else { return [checkbox, label] } + return [checkbox, label, subTitle] + } - public var children: [MoleculeModelProtocol] { [checkbox, label] } //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- - public init(checkbox: CheckboxModel, label: LabelModel) { + public init(checkbox: CheckboxModel, label: LabelModel, subTitle: LabelModel?) { self.checkbox = checkbox self.label = label + self.subTitle = subTitle } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift index 55ae3e02..cbeca37b 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift @@ -83,9 +83,7 @@ func updateAccessibilityLabel() { var message = "" - - checkbox.updateAccessibilityLabel() - + if let checkboxLabel = checkbox.accessibilityLabel, !checkboxLabel.isEmpty { message += checkboxLabel + ", " } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift index 1d173406..42bbb2d1 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift @@ -88,9 +88,7 @@ func updateAccessibilityLabel() { var message = "" - - checkbox.updateAccessibilityLabel() - + if let checkboxLabel = checkbox.accessibilityLabel { message += checkboxLabel + ", " }