Merge branch 'feature/atomic-vds-checkbox' into 'develop'

VDS Brand 3.0 Checkbox

### Summary
VDS Brand 3.0 Checkbox, CheckboxItem for IOS
- CheckboxGroup will be a later integration since this is not in the app at this time.
 
### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7001

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1152
This commit is contained in:
Pfeil, Scott Robert 2024-07-30 21:36:51 +00:00
commit b24094ea5b
6 changed files with 180 additions and 614 deletions

View File

@ -7,158 +7,43 @@
// //
import MVMCore import MVMCore
import VDS
/** /**
This class expects its height and width to be equal. 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 // MARK: - Properties
//-------------------------------------------------- //------------------------------------------------------
open var viewModel: CheckboxModel!
public var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
// Form Validation // Form Validation
var fieldKey: String? var fieldKey: String?
var fieldValue: JSONValue? var fieldValue: JSONValue?
var groupName: String? 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. /// Action Block called when the switch is selected.
public var actionBlock: ActionBlock? open var actionBlock: ActionBlock? {
/// Manages the appearance of the checkbox.
private var shapeLayer: CAShapeLayer?
/// Width of the check mark.
public var checkWidth: CGFloat = 2 {
didSet { didSet {
if let shapeLayer = shapeLayer { if let actionBlock {
CATransaction.withDisabledAnimations { onChange = { _ in
shapeLayer.lineWidth = checkWidth actionBlock()
} }
}
}
}
open override var isEnabled: Bool {
didSet {
isUserInteractionEnabled = isEnabled
if isEnabled {
layer.borderColor = borderColor.cgColor
backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor
setShapeLayerStrokeColor(checkColor)
} else { } else {
layer.borderColor = disabledBorderColor.cgColor onChange = nil
backgroundColor = disabledBackgroundColor
setShapeLayerStrokeColor(disabledCheckColor)
} }
} }
} }
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 // MARK: - Initializers
//-------------------------------------------------- //--------------------------------------------------
override public init(frame: CGRect) { override public init(frame: CGRect) {
super.init(frame: frame) 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. /// There is currently no intention on using xib files.
@ -167,195 +52,58 @@ import MVMCore
fatalError("xib file is not implemented for Checkbox.") fatalError("xib file is not implemented for Checkbox.")
} }
public convenience override init() { public convenience required init() {
self.init(frame:.zero) self.init(frame:.zero)
} }
public convenience init(isChecked: Bool) { public convenience init(isChecked: Bool) {
self.init(frame: .zero) self.init(frame: .zero)
checkAndBypassAnimations(selected: isChecked) isSelected = isChecked
} }
public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) { public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) {
self.init(frame: .zero) self.init(frame: .zero)
checkAndBypassAnimations(selected: isChecked) isSelected = isChecked
self.checkedBackgroundColor = checkedBackgroundColor
self.unCheckedBackgroundColor = unCheckedBackgroundColor
}
//--------------------------------------------------
// 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 // 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. /// This will toggle the state of the Checkbox and execute the actionBlock if provided.
public func toggleAndAction() { open func toggleAndAction() {
isSelected.toggle() toggle()
actionBlock?() }
open override func toggle() {
super.toggle()
viewModel.selected = isSelected
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Methods // 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. /// Programmatic means to check/uncheck the box.
/// - parameter selected: state of the check box: true = checked OR false = unchecked. /// - 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. /// - 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 { DispatchQueue.main.async {
self.isAnimated = animated
self.checkAndBypassAnimations(selected: selected) self.isSelected = selected
self.drawShapeLayer()
self.shapeLayer?.removeAllAnimations()
self.updateCheckboxUI(isSelected: selected, isAnimated: animated)
} }
} }
/// updates the visuals of the check mark and background. /// updates the visuals of the check mark and background.
/// - parameter isSelected: the check state of the checkbox. /// - parameter isSelected: the check state of the checkbox.
/// - parameter isAnimated: determines of the changes should animate or immediately refelect. /// - 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 { DispatchQueue.main.async {
let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") self.isAnimated = isAnimated
animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) self.isSelected = isSelected
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
}
}
/// 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<UITouch>, with event: UIEvent?) {
sendActions(for: .touchUpInside)
}
override open func accessibilityActivate() -> Bool {
guard isEnabled else { return false }
sendActions(for: .touchUpInside)
return true
} }
//-------------------------------------------------- //--------------------------------------------------
@ -364,81 +112,49 @@ import MVMCore
open func needsToBeConstrained() -> Bool { true } open func needsToBeConstrained() -> Bool { true }
open override func reset() { open func horizontalAlignment() -> UIStackView.Alignment { .leading }
super.reset()
isEnabled = true open func updateView(_ size: CGFloat) {}
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
}
}
private func performCheckboxAction(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { 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 } public func viewModelDidUpdate() {
//forms
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
groupName = viewModel.groupName
if let fieldKey = model.fieldKey { if let fieldKey = viewModel.fieldKey {
self.fieldKey = fieldKey self.fieldKey = fieldKey
} }
borderColor = (model.inverted ? model.invertedColor : model.borderColor).uiColor //properties
borderWidth = model.borderWidth isEnabled = viewModel.enabled && !viewModel.readOnly
isAnimated = viewModel.animated
isSelected = viewModel.selected
checkColor = (model.inverted ? model.invertedColor : model.checkColor).uiColor //events
unCheckedBackgroundColor = (model.inverted ? model.invertedBackgroundColor : model.unCheckedBackgroundColor).uiColor viewModel.updateUI = {
checkedBackgroundColor = (model.inverted ? model.invertedBackgroundColor : model.checkedBackgroundColor).uiColor MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
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: {
guard let self = self else { return } 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 //onChange
if (viewModel.action != nil || viewModel.offAction != nil) {
if (model.action != nil || model.offAction != nil) {
actionBlock = { [weak self] in actionBlock = { [weak self] in
guard let self = self else { return } guard let self = self else { return }
if let offAction = model.offAction, !self.isSelected { if let offAction = viewModel.offAction, !isSelected {
self.performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData) performCheckboxAction(with: offAction, delegateObject: delegateObject, additionalData: additionalData)
} else if let action = model.action { } else if let action = viewModel.action {
self.performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData) performCheckboxAction(with: action, delegateObject: delegateObject, additionalData: additionalData)
} }
} }
} }

View File

@ -5,6 +5,7 @@
// Created by Chintakrinda, Arun Kumar (Arun) on 21/01/20. // Created by Chintakrinda, Arun Kumar (Arun) on 21/01/20.
// Copyright © 2020 Verizon Wireless. All rights reserved. // 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. /// 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 } var selected: Bool { get set }
} }
@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModelProtocol, FormFieldProtocol, UIUpdatableModelProtocol { @objcMembers public class CheckboxModel: FormFieldModel, SelectableMoleculeModelProtocol{
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public static override var identifier: String { "checkbox" }
public static var identifier: String = "checkbox"
public var id: String = UUID().uuidString
public var backgroundColor: Color?
public var accessibilityIdentifier: String?
public var selected: Bool = false public var selected: Bool = false
public var enabled: Bool = true
public var readOnly: Bool = false
public var animated: Bool = true 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 action: ActionModelProtocol?
public var offAction: 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 // MARK: - Keys
//-------------------------------------------------- //--------------------------------------------------
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case id
case moleculeName
case accessibilityIdentifier
case checked case checked
case enabled
case readOnly
case inverted
case animated 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 action
case fieldKey
case groupName
case offAction case offAction
} }
@ -81,16 +40,17 @@
// MARK: - Form Validation // MARK: - Form Validation
//-------------------------------------------------- //--------------------------------------------------
public func formFieldValue() -> AnyHashable? { open override func formFieldValue() -> AnyHashable? {
guard enabled else { return nil } guard enabled else { return nil }
return selected return selected
} }
//-------------------------------------------------- open override func setValidity(_ valid: Bool, errorMessage: String?) {
// MARK: - Server Value if let ruleErrorMessage = errorMessage, fieldKey != nil {
//-------------------------------------------------- self.errorMessage = ruleErrorMessage
open func formFieldServerValue() -> AnyHashable? { }
return formFieldValue() isValid = valid
updateUI?()
} }
//-------------------------------------------------- //--------------------------------------------------
@ -98,7 +58,8 @@
//-------------------------------------------------- //--------------------------------------------------
public init(isChecked: Bool = false) { public init(isChecked: Bool = false) {
self.selected = isChecked super.init()
selected = isChecked
baseValue = isChecked baseValue = isChecked
} }
@ -107,52 +68,9 @@
//-------------------------------------------------- //--------------------------------------------------
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
try super.init(from: decoder)
let typeContainer = try decoder.container(keyedBy: CodingKeys.self) 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) { if let checked = try typeContainer.decodeIfPresent(Bool.self, forKey: .checked) {
self.selected = checked self.selected = checked
} }
@ -163,50 +81,17 @@
self.animated = 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) 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) 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) 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(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(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.encodeModelIfPresent(action, forKey: .action)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encodeModelIfPresent(offAction, forKey: .offAction) try container.encodeModelIfPresent(offAction, forKey: .offAction)
} }
} }

View File

@ -5,141 +5,108 @@
// Created by Kevin Christiano on 9/13/19. // Created by Kevin Christiano on 9/13/19.
// Copyright © 2019 Verizon Wireless. All rights reserved. // Copyright © 2019 Verizon Wireless. All rights reserved.
// //
import VDS
@objcMembers open class CheckboxLabel: VDS.CheckboxItem, VDSMoleculeViewProtocol {
@objcMembers open class CheckboxLabel: View { //------------------------------------------------------
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
public let checkbox = Checkbox()
public let label = Label(fontStyle: .RegularBodySmall)
private var observation: NSKeyValueObservation? = nil
//--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //------------------------------------------------------
open var viewModel: CheckboxLabelModel!
open var delegateObject: MVMCoreUIDelegateObject?
open var additionalData: [AnyHashable : Any]?
public var checkboxPosition: CheckboxPosition = .center // Form Validation
var fieldKey: String?
var fieldValue: JSONValue?
var groupName: String?
//-------------------------------------------------- private var updateSelectionOnly: Bool = false
// MARK: - Constraints override open var isSelected: Bool {
//-------------------------------------------------- didSet {
if !updateSelectionOnly {
public var checkboxTopConstraint: NSLayoutConstraint? viewModel.checkbox.selected = isSelected
public var checkboxBottomConstraint: NSLayoutConstraint? _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
public var checkboxCenterYConstraint: NSLayoutConstraint? }
}
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Life Cycle // MARK: - Life Cycle
//-------------------------------------------------- //--------------------------------------------------
@objc open func updateView(_ size: CGFloat) {}
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
}
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Atomic // MARK: - Atomic
//-------------------------------------------------- //--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { open func viewModelDidUpdate() {
guard let checkBoxWithLabelModel = model as? CheckboxLabelModel else { return } surface = viewModel.surface
if let checkboxAlignment = checkBoxWithLabelModel.checkboxAlignment { updateCheckbox()
alignCheckbox(checkboxAlignment)
//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) //secondary label
label.set(with: checkBoxWithLabelModel.label, delegateObject, additionalData) if let subTitleModel = viewModel.subTitle {
updateAccessibilityLabel() 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 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)")
}
}
} }

View File

@ -8,12 +8,7 @@
import Foundation import Foundation
import MVMCore import MVMCore
import VDS
public enum CheckboxPosition: String, Codable {
case center
case top
case bottom
}
@objcMembers open class CheckboxLabelModel: MoleculeModelProtocol, ParentMoleculeModelProtocol { @objcMembers open class CheckboxLabelModel: MoleculeModelProtocol, ParentMoleculeModelProtocol {
open class var identifier: String { "checkboxLabel" } open class var identifier: String { "checkboxLabel" }
@ -21,18 +16,25 @@ public enum CheckboxPosition: String, Codable {
@DecodableDefault.UUIDString public var id: String @DecodableDefault.UUIDString public var id: String
public var backgroundColor: Color? public var backgroundColor: Color?
public var checkboxAlignment: CheckboxPosition?
public var checkbox: CheckboxModel public var checkbox: CheckboxModel
public var label: LabelModel 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 // MARK: - Initializer
//-------------------------------------------------- //--------------------------------------------------
public init(checkbox: CheckboxModel, label: LabelModel) { public init(checkbox: CheckboxModel, label: LabelModel, subTitle: LabelModel?) {
self.checkbox = checkbox self.checkbox = checkbox
self.label = label self.label = label
self.subTitle = subTitle
} }
} }

View File

@ -84,8 +84,6 @@
var message = "" var message = ""
checkbox.updateAccessibilityLabel()
if let checkboxLabel = checkbox.accessibilityLabel, !checkboxLabel.isEmpty { if let checkboxLabel = checkbox.accessibilityLabel, !checkboxLabel.isEmpty {
message += checkboxLabel + ", " message += checkboxLabel + ", "
} }

View File

@ -89,8 +89,6 @@
var message = "" var message = ""
checkbox.updateAccessibilityLabel()
if let checkboxLabel = checkbox.accessibilityLabel { if let checkboxLabel = checkbox.accessibilityLabel {
message += checkboxLabel + ", " message += checkboxLabel + ", "
} }