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:
commit
b24094ea5b
@ -7,158 +7,43 @@
|
||||
//
|
||||
|
||||
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
|
||||
//--------------------------------------------------
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// 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)
|
||||
isSelected = isChecked
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// 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<UITouch>, 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()
|
||||
open func horizontalAlignment() -> UIStackView.Alignment { .leading }
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -163,50 +81,17 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
//--------------------------------------------------
|
||||
//------------------------------------------------------
|
||||
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?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Constraints
|
||||
//--------------------------------------------------
|
||||
|
||||
public var checkboxTopConstraint: NSLayoutConstraint?
|
||||
public var checkboxBottomConstraint: NSLayoutConstraint?
|
||||
public var checkboxCenterYConstraint: NSLayoutConstraint?
|
||||
private var updateSelectionOnly: Bool = false
|
||||
override open var isSelected: Bool {
|
||||
didSet {
|
||||
if !updateSelectionOnly {
|
||||
viewModel.checkbox.selected = isSelected
|
||||
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// 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 }
|
||||
open func viewModelDidUpdate() {
|
||||
surface = viewModel.surface
|
||||
|
||||
if let checkboxAlignment = checkBoxWithLabelModel.checkboxAlignment {
|
||||
alignCheckbox(checkboxAlignment)
|
||||
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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -84,8 +84,6 @@
|
||||
|
||||
var message = ""
|
||||
|
||||
checkbox.updateAccessibilityLabel()
|
||||
|
||||
if let checkboxLabel = checkbox.accessibilityLabel, !checkboxLabel.isEmpty {
|
||||
message += checkboxLabel + ", "
|
||||
}
|
||||
|
||||
@ -89,8 +89,6 @@
|
||||
|
||||
var message = ""
|
||||
|
||||
checkbox.updateAccessibilityLabel()
|
||||
|
||||
if let checkboxLabel = checkbox.accessibilityLabel {
|
||||
message += checkboxLabel + ", "
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user