Merge branch 'feature/atomic-vds-radiobox' into 'develop'
VDS Brand 3.0 Radiobox ### Summary VDS Brand 3.0 Radiobox and group for IOS ### JIRA Ticket https://onejira.verizon.com/browse/ONEAPP-7007 Co-authored-by: Matt Bruce <matt.bruce@verizon.com> See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1158
This commit is contained in:
commit
aca65b13d3
@ -5,243 +5,64 @@
|
|||||||
// Created by Scott Pfeil on 4/9/20.
|
// Created by Scott Pfeil on 4/9/20.
|
||||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||||
//
|
//
|
||||||
|
import VDS
|
||||||
|
|
||||||
|
@objcMembers open class RadioBox: VDS.RadioBoxItem, VDSMoleculeViewProtocol, MFButtonProtocol {
|
||||||
open class RadioBox: Control, MFButtonProtocol {
|
//------------------------------------------------------
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
//------------------------------------------------------
|
||||||
|
open var viewModel: RadioBoxModel!
|
||||||
|
open var delegateObject: MVMCoreUIDelegateObject?
|
||||||
|
open var additionalData: [AnyHashable : Any]?
|
||||||
|
|
||||||
|
open var isOutOfStock: Bool {
|
||||||
|
get { strikethrough }
|
||||||
|
set {
|
||||||
|
strikethrough = newValue
|
||||||
|
viewModel?.strikethrough = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Lifecycle
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
public let label = Label(fontStyle: .RegularBodySmall)
|
public func viewModelDidUpdate() {
|
||||||
public let subTextLabel = Label(fontStyle: .RegularMicro)
|
|
||||||
public var isOutOfStock = false
|
|
||||||
public var accentColor = UIColor.mvmRed
|
|
||||||
|
|
||||||
public let innerPadding: CGFloat = 12.0
|
text = viewModel.text
|
||||||
|
subText = viewModel.subText
|
||||||
private var borderLayer: CALayer?
|
subTextRight = viewModel.subTextRight
|
||||||
private var strikeLayer: CALayer?
|
strikethrough = viewModel.strikethrough
|
||||||
private var maskLayer: CALayer?
|
isSelected = viewModel.selected
|
||||||
|
isEnabled = viewModel.enabled && !viewModel.readOnly
|
||||||
public var subTextLabelHeightConstraint: NSLayoutConstraint?
|
|
||||||
|
|
||||||
private var delegateObject: MVMCoreUIDelegateObject?
|
|
||||||
var additionalData: [AnyHashable: Any]?
|
|
||||||
|
|
||||||
public var radioBoxModel: RadioBoxModel? {
|
|
||||||
model as? RadioBoxModel
|
|
||||||
}
|
|
||||||
|
|
||||||
public override var isSelected: Bool {
|
|
||||||
didSet { updateAccessibility() }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override var isEnabled: Bool {
|
|
||||||
didSet { updateAccessibility() }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
onChange = { [weak self] _ in
|
||||||
|
if let radioBoxModel = self?.viewModel, let actionModel = radioBoxModel.action {
|
||||||
|
Task(priority: .userInitiated) { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioBoxModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------
|
||||||
|
// MARK: - Functions
|
||||||
|
//--------------------------------------------------
|
||||||
|
|
||||||
|
@objc open func selectBox() {
|
||||||
|
toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc open func deselectBox() {
|
||||||
|
toggle()
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - MVMCoreViewProtocol
|
// MARK: - MVMCoreViewProtocol
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
open override func updateView(_ size: CGFloat) {
|
open func updateView(_ size: CGFloat) {}
|
||||||
super.updateView(size)
|
|
||||||
label.updateView(size)
|
|
||||||
subTextLabel.updateView(size)
|
|
||||||
layer.setNeedsDisplay()
|
|
||||||
}
|
|
||||||
|
|
||||||
open override func setupView() {
|
|
||||||
super.setupView()
|
|
||||||
|
|
||||||
layer.delegate = self
|
|
||||||
layer.borderColor = UIColor.mvmCoolGray6.cgColor
|
|
||||||
layer.borderWidth = 1
|
|
||||||
|
|
||||||
label.numberOfLines = 1
|
|
||||||
addSubview(label)
|
|
||||||
NSLayoutConstraint.constraintPinSubview(label, pinTop: true, topConstant: innerPadding, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding)
|
|
||||||
|
|
||||||
subTextLabel.textColor = .mvmCoolGray6
|
|
||||||
subTextLabel.numberOfLines = 1
|
|
||||||
addSubview(subTextLabel)
|
|
||||||
NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding)
|
|
||||||
bottomAnchor.constraint(greaterThanOrEqualTo: subTextLabel.bottomAnchor, constant: innerPadding).isActive = true
|
|
||||||
subTextLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 2).isActive = true
|
|
||||||
subTextLabelHeightConstraint = subTextLabel.heightAnchor.constraint(equalToConstant: 0)
|
|
||||||
subTextLabelHeightConstraint?.isActive = true
|
|
||||||
|
|
||||||
addTarget(self, action: #selector(selectBox), for: .touchUpInside)
|
|
||||||
|
|
||||||
isAccessibilityElement = true
|
|
||||||
}
|
|
||||||
|
|
||||||
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
|
||||||
super.set(with: model, delegateObject, additionalData)
|
|
||||||
guard let model = model as? RadioBoxModel else { return }
|
|
||||||
self.delegateObject = delegateObject
|
|
||||||
self.additionalData = additionalData
|
|
||||||
label.text = model.text
|
|
||||||
subTextLabel.text = model.subText
|
|
||||||
isOutOfStock = model.strikethrough
|
|
||||||
subTextLabelHeightConstraint?.isActive = (subTextLabel.text?.count ?? 0) == 0
|
|
||||||
if let color = model.selectedAccentColor?.uiColor {
|
|
||||||
accentColor = color
|
|
||||||
}
|
|
||||||
isSelected = model.selected
|
|
||||||
isEnabled = model.enabled && !model.readOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
open override func reset() {
|
|
||||||
super.reset()
|
|
||||||
backgroundColor = .white
|
|
||||||
accentColor = .mvmRed
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - State Handling
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
open override func draw(_ layer: CALayer, in ctx: CGContext) {
|
|
||||||
// Draw the strikethrough
|
|
||||||
strikeLayer?.removeFromSuperlayer()
|
|
||||||
if isOutOfStock {
|
|
||||||
let line = getStrikeThrough(color: isSelected ? .black : .mvmCoolGray6, thickness: 1)
|
|
||||||
layer.addSublayer(line)
|
|
||||||
strikeLayer = line
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the border
|
|
||||||
borderLayer?.removeFromSuperlayer()
|
|
||||||
if isSelected {
|
|
||||||
layer.borderWidth = 0
|
|
||||||
let border = getSelectedBorder()
|
|
||||||
layer.addSublayer(border)
|
|
||||||
borderLayer = border
|
|
||||||
} else {
|
|
||||||
layer.borderWidth = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Mask
|
|
||||||
maskLayer?.removeFromSuperlayer()
|
|
||||||
if !isEnabled {
|
|
||||||
let mask = getMaskLayer()
|
|
||||||
layer.mask = mask
|
|
||||||
maskLayer = mask
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
// Accounts for any size changes
|
|
||||||
layer.setNeedsDisplay()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc open func selectBox() {
|
|
||||||
guard isEnabled, !isSelected else { return }
|
|
||||||
isSelected = true
|
|
||||||
radioBoxModel?.selected = isSelected
|
|
||||||
if let radioBoxModel = radioBoxModel, let actionModel = radioBoxModel.action {
|
|
||||||
Task(priority: .userInitiated) {
|
|
||||||
try await Button.performButtonAction(with: actionModel, button: self, delegateObject: delegateObject, additionalData: additionalData, sourceModel: radioBoxModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
layer.setNeedsDisplay()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc open func deselectBox() {
|
|
||||||
isSelected = false
|
|
||||||
radioBoxModel?.selected = isSelected
|
|
||||||
layer.setNeedsDisplay()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the selected state border
|
|
||||||
func getSelectedBorder() -> CAShapeLayer {
|
|
||||||
let layer = CAShapeLayer()
|
|
||||||
|
|
||||||
let topLineWidth: CGFloat = 4
|
|
||||||
let topLinePath = UIBezierPath()
|
|
||||||
topLinePath.lineWidth = topLineWidth
|
|
||||||
topLinePath.move(to: CGPoint(x: 0, y: topLineWidth / 2.0))
|
|
||||||
topLinePath.addLine(to: CGPoint(x: bounds.width, y: topLineWidth / 2.0))
|
|
||||||
|
|
||||||
let topLineLayer = CAShapeLayer()
|
|
||||||
topLineLayer.fillColor = nil
|
|
||||||
topLineLayer.strokeColor = accentColor.cgColor
|
|
||||||
topLineLayer.lineWidth = 4
|
|
||||||
topLineLayer.path = topLinePath.cgPath
|
|
||||||
layer.addSublayer(topLineLayer)
|
|
||||||
|
|
||||||
let lineWidth: CGFloat = 1
|
|
||||||
let halfLineWidth: CGFloat = 0.5
|
|
||||||
let linePath = UIBezierPath()
|
|
||||||
linePath.move(to: CGPoint(x: halfLineWidth, y: topLineWidth))
|
|
||||||
linePath.addLine(to: CGPoint(x: halfLineWidth, y: bounds.height))
|
|
||||||
linePath.move(to: CGPoint(x: 0, y: bounds.height - halfLineWidth))
|
|
||||||
linePath.addLine(to: CGPoint(x: bounds.width, y: bounds.height - halfLineWidth))
|
|
||||||
linePath.move(to: CGPoint(x: bounds.width - halfLineWidth, y: bounds.height))
|
|
||||||
linePath.addLine(to: CGPoint(x: bounds.width - halfLineWidth, y: topLineWidth))
|
|
||||||
|
|
||||||
let borderLayer = CAShapeLayer()
|
|
||||||
borderLayer.fillColor = nil
|
|
||||||
borderLayer.strokeColor = UIColor.black.cgColor
|
|
||||||
borderLayer.lineWidth = lineWidth
|
|
||||||
borderLayer.path = linePath.cgPath
|
|
||||||
layer.addSublayer(borderLayer)
|
|
||||||
|
|
||||||
return layer
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a border to edge
|
|
||||||
func getStrikeThrough(color: UIColor, thickness: CGFloat) -> CAShapeLayer {
|
|
||||||
let border = CAShapeLayer()
|
|
||||||
border.name = "strikethrough"
|
|
||||||
border.fillColor = nil
|
|
||||||
border.opacity = 1.0
|
|
||||||
border.lineWidth = thickness
|
|
||||||
border.strokeColor = color.cgColor
|
|
||||||
|
|
||||||
let linePath = UIBezierPath()
|
|
||||||
linePath.move(to: CGPoint(x: 0, y: bounds.height))
|
|
||||||
linePath.addLine(to: CGPoint(x: bounds.width, y: 0))
|
|
||||||
border.path = linePath.cgPath
|
|
||||||
return border
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMaskLayer() -> CALayer {
|
|
||||||
let mask = CALayer()
|
|
||||||
mask.backgroundColor = UIColor.white.cgColor
|
|
||||||
mask.opacity = 0.3
|
|
||||||
mask.frame = bounds
|
|
||||||
return mask
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
|
||||||
// MARK: - Accessibility
|
|
||||||
//--------------------------------------------------
|
|
||||||
|
|
||||||
public func updateAccessibility() {
|
|
||||||
|
|
||||||
var message = ""
|
|
||||||
|
|
||||||
if let labelText = label.text, label.hasText {
|
|
||||||
message += labelText + ", "
|
|
||||||
}
|
|
||||||
|
|
||||||
if let subLabelText = subTextLabel.text, subTextLabel.hasText {
|
|
||||||
message += subLabelText + ", "
|
|
||||||
}
|
|
||||||
|
|
||||||
accessibilityLabel = message
|
|
||||||
accessibilityTraits = .button
|
|
||||||
|
|
||||||
if isSelected {
|
|
||||||
accessibilityTraits.insert(.selected)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isEnabled {
|
|
||||||
accessibilityTraits.insert(.notEnabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||||
//
|
//
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
import VDS
|
||||||
|
|
||||||
@objcMembers public class RadioBoxModel: MoleculeModelProtocol, EnableableModelProtocol {
|
@objcMembers public class RadioBoxModel: MoleculeModelProtocol, EnableableModelProtocol {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -17,15 +18,17 @@ import MVMCore
|
|||||||
|
|
||||||
public var text: String
|
public var text: String
|
||||||
public var subText: String?
|
public var subText: String?
|
||||||
|
public var subTextRight: String?
|
||||||
public var backgroundColor: Color?
|
public var backgroundColor: Color?
|
||||||
public var accessibilityIdentifier: String?
|
public var accessibilityIdentifier: String?
|
||||||
public var selectedAccentColor: Color?
|
|
||||||
public var selected: Bool = false
|
public var selected: Bool = false
|
||||||
public var enabled: Bool = true
|
public var enabled: Bool = true
|
||||||
public var readOnly: Bool = false
|
public var readOnly: Bool = false
|
||||||
public var strikethrough: Bool = false
|
public var strikethrough: Bool = false
|
||||||
public var fieldValue: String?
|
public var fieldValue: String?
|
||||||
public var action: ActionModelProtocol?
|
public var action: ActionModelProtocol?
|
||||||
|
public var inverted: Bool = false
|
||||||
|
public var surface: Surface { inverted ? .dark : .light }
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Keys
|
// MARK: - Keys
|
||||||
@ -36,7 +39,7 @@ import MVMCore
|
|||||||
case moleculeName
|
case moleculeName
|
||||||
case text
|
case text
|
||||||
case subText
|
case subText
|
||||||
case selectedAccentColor
|
case subTextRight
|
||||||
case backgroundColor
|
case backgroundColor
|
||||||
case accessibilityIdentifier
|
case accessibilityIdentifier
|
||||||
case selected
|
case selected
|
||||||
@ -45,6 +48,7 @@ import MVMCore
|
|||||||
case fieldValue
|
case fieldValue
|
||||||
case action
|
case action
|
||||||
case readOnly
|
case readOnly
|
||||||
|
case inverted
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -65,8 +69,7 @@ import MVMCore
|
|||||||
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
|
||||||
text = try typeContainer.decode(String.self, forKey: .text)
|
text = try typeContainer.decode(String.self, forKey: .text)
|
||||||
subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText)
|
subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText)
|
||||||
selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor)
|
subTextRight = try typeContainer.decodeIfPresent(String.self, forKey: .subTextRight)
|
||||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
|
||||||
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
|
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
|
||||||
|
|
||||||
if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) {
|
if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) {
|
||||||
@ -80,6 +83,10 @@ import MVMCore
|
|||||||
strikethrough = isStrikeTrough
|
strikethrough = isStrikeTrough
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let inverted = try typeContainer.decodeIfPresent(Bool.self, forKey: .inverted) {
|
||||||
|
self.inverted = inverted
|
||||||
|
}
|
||||||
|
|
||||||
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
|
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
|
||||||
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
|
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
|
||||||
}
|
}
|
||||||
@ -90,8 +97,7 @@ import MVMCore
|
|||||||
try container.encode(moleculeName, forKey: .moleculeName)
|
try container.encode(moleculeName, forKey: .moleculeName)
|
||||||
try container.encode(text, forKey: .text)
|
try container.encode(text, forKey: .text)
|
||||||
try container.encodeIfPresent(subText, forKey: .subText)
|
try container.encodeIfPresent(subText, forKey: .subText)
|
||||||
try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor)
|
try container.encodeIfPresent(subTextRight, forKey: .subTextRight)
|
||||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
|
||||||
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
|
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
|
||||||
try container.encode(selected, forKey: .selected)
|
try container.encode(selected, forKey: .selected)
|
||||||
try container.encode(enabled, forKey: .enabled)
|
try container.encode(enabled, forKey: .enabled)
|
||||||
@ -99,5 +105,6 @@ import MVMCore
|
|||||||
try container.encode(strikethrough, forKey: .strikethrough)
|
try container.encode(strikethrough, forKey: .strikethrough)
|
||||||
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
|
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
|
||||||
try container.encodeModelIfPresent(action, forKey: .action)
|
try container.encodeModelIfPresent(action, forKey: .action)
|
||||||
|
try container.encode(inverted, forKey: .inverted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,172 +7,69 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import VDS
|
||||||
|
|
||||||
public protocol RadioBoxSelectionDelegate: AnyObject {
|
public protocol RadioBoxSelectionDelegate: AnyObject {
|
||||||
func selected(radioBox: RadioBoxModel)
|
func selected(radioBox: RadioBoxModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
open class RadioBoxes: View {
|
open class RadioBoxes: VDS.RadioBoxGroup, VDSMoleculeViewProtocol {
|
||||||
|
|
||||||
public var collectionView: CollectionView!
|
|
||||||
public var collectionViewHeight: NSLayoutConstraint!
|
|
||||||
private let boxWidth: CGFloat = 151.0
|
|
||||||
private let boxHeight: CGFloat = 64.0
|
|
||||||
private var itemSpacing: CGFloat = 12.0
|
|
||||||
private var numberOfColumns: CGFloat = 2.0
|
|
||||||
private var radioBoxesModel: RadioBoxesModel? {
|
|
||||||
return model as? RadioBoxesModel
|
|
||||||
}
|
|
||||||
public weak var radioDelegate: RadioBoxSelectionDelegate?
|
|
||||||
private var delegateObject: MVMCoreUIDelegateObject?
|
|
||||||
|
|
||||||
|
//------------------------------------------------------
|
||||||
|
// MARK: - Properties
|
||||||
|
//------------------------------------------------------
|
||||||
|
open var viewModel: RadioBoxesModel!
|
||||||
|
open var delegateObject: MVMCoreUIDelegateObject?
|
||||||
|
open var additionalData: [AnyHashable : Any]?
|
||||||
|
|
||||||
|
// Form Validation
|
||||||
|
var fieldKey: String?
|
||||||
|
var fieldValue: JSONValue?
|
||||||
|
var groupName: String?
|
||||||
|
|
||||||
/// The models for the molecules.
|
/// The models for the molecules.
|
||||||
public var boxes: [RadioBoxModel]?
|
public var boxes: [RadioBoxModel]?
|
||||||
|
public weak var radioDelegate: RadioBoxSelectionDelegate?
|
||||||
|
|
||||||
private var size: CGFloat?
|
// TODO: this matches the current accessibility however not what was passed by Barbara's team.
|
||||||
|
// open override var items: [RadioBoxItem] {
|
||||||
open override func layoutSubviews() {
|
// didSet {
|
||||||
super.layoutSubviews()
|
// let total = items.count
|
||||||
// Accounts for any collection size changes
|
// for (index, radioBoxItem) in items.enumerated() {
|
||||||
DispatchQueue.main.async {
|
// radioBoxItem.selectorView.bridge_accessibilityValueBlock = {
|
||||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
// guard let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"),
|
||||||
}
|
// let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: index + 1)) else { return ""}
|
||||||
|
// return String(format: format, indexString, total)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
open override func setup() {
|
||||||
|
super.setup()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open func updateAccessibilityValue(collectionView: UICollectionView, cell: RadioBoxCollectionViewCell, indexPath: IndexPath) {
|
|
||||||
guard let format = MVMCoreUIUtility.hardcodedString(withKey: "index_string_of_total"),
|
|
||||||
let indexString = MVMCoreUIUtility.getOrdinalString(forIndex: NSNumber(value: indexPath.row + 1)) else { return }
|
|
||||||
let total = self.collectionView(collectionView, numberOfItemsInSection: indexPath.section)
|
|
||||||
cell.accessibilityValue = String(format: format, indexString, total)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - MVMCoreViewProtocol
|
|
||||||
open override func setupView() {
|
|
||||||
super.setupView()
|
|
||||||
collectionView = createCollectionView()
|
|
||||||
addSubview(collectionView)
|
|
||||||
NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)
|
|
||||||
collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300)
|
|
||||||
collectionViewHeight?.isActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - MoleculeViewProtocol
|
// MARK: - MoleculeViewProtocol
|
||||||
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
public func viewModelDidUpdate() {
|
||||||
super.set(with: model, delegateObject, additionalData)
|
boxes = viewModel.boxes
|
||||||
self.delegateObject = delegateObject
|
surface = viewModel.surface
|
||||||
|
selectorModels = viewModel.selectorModels
|
||||||
|
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
|
||||||
|
|
||||||
guard let model = model as? RadioBoxesModel else { return }
|
}
|
||||||
boxes = model.boxes
|
|
||||||
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
|
open func updateView(_ size: CGFloat) {}
|
||||||
|
|
||||||
|
open override func didSelect(_ selectedControl: RadioBoxItem) {
|
||||||
|
super.didSelect(selectedControl)
|
||||||
|
|
||||||
backgroundColor = model.backgroundColor?.uiColor
|
// since the boxes has the state being tracked, we need to update the values here.
|
||||||
|
if let index = items.firstIndex(where: {$0 === selectedControl}), let selectedBox = boxes?[index] {
|
||||||
registerCells()
|
boxes?.forEach {$0.selected = false }
|
||||||
setHeight()
|
selectedBox.selected = true
|
||||||
collectionView.reloadData()
|
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
|
||||||
}
|
radioDelegate?.selected(radioBox: selectedBox)
|
||||||
|
|
||||||
@objc override open func updateView(_ size: CGFloat) {
|
|
||||||
super.updateView(size)
|
|
||||||
self.size = size
|
|
||||||
itemSpacing = Padding.Component.gutterFor(size: size)
|
|
||||||
collectionView.updateView(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Creation
|
|
||||||
|
|
||||||
/// Creates the layout for the collection.
|
|
||||||
open func createCollectionViewLayout() -> UICollectionViewLayout {
|
|
||||||
let layout = UICollectionViewFlowLayout()
|
|
||||||
layout.scrollDirection = .vertical
|
|
||||||
layout.minimumLineSpacing = itemSpacing
|
|
||||||
layout.minimumInteritemSpacing = itemSpacing
|
|
||||||
return layout
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the collection view.
|
|
||||||
open func createCollectionView() -> CollectionView {
|
|
||||||
let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout())
|
|
||||||
collection.dataSource = self
|
|
||||||
collection.delegate = self
|
|
||||||
return collection
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Registers the cells with the collection view
|
|
||||||
open func registerCells() {
|
|
||||||
collectionView.register(RadioBoxCollectionViewCell.self, forCellWithReuseIdentifier: "RadioBoxCollectionViewCell")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - JSON Setters
|
|
||||||
open func setHeight() {
|
|
||||||
guard let boxes = boxes, boxes.count > 0 else {
|
|
||||||
collectionViewHeight.constant = 0
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the height
|
|
||||||
let rows = ceil(CGFloat(boxes.count) / numberOfColumns)
|
|
||||||
let height = (rows * boxHeight) + ((rows - 1) * itemSpacing)
|
|
||||||
collectionViewHeight?.constant = height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RadioBoxes: UICollectionViewDelegateFlowLayout {
|
|
||||||
open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
|
||||||
let itemWidth: CGFloat = (collectionView.bounds.width - itemSpacing) / numberOfColumns
|
|
||||||
return CGSize(width: itemWidth, height: boxHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RadioBoxes: UICollectionViewDataSource {
|
|
||||||
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
||||||
return boxes?.count ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
||||||
guard let molecule = boxes?[indexPath.row],
|
|
||||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
cell.reset()
|
|
||||||
cell.radioBox.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
if let color = radioBoxesModel?.boxesColor {
|
|
||||||
cell.radioBox.backgroundColor = color.uiColor
|
|
||||||
}
|
|
||||||
if let color = radioBoxesModel?.selectedAccentColor {
|
|
||||||
cell.radioBox.accentColor = color.uiColor
|
|
||||||
}
|
|
||||||
|
|
||||||
cell.set(with: molecule, delegateObject, nil)
|
|
||||||
cell.updateView(size ?? collectionView.bounds.width)
|
|
||||||
if molecule.selected {
|
|
||||||
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
|
|
||||||
}
|
|
||||||
updateAccessibilityValue(collectionView: collectionView, cell: cell, indexPath: indexPath)
|
|
||||||
cell.layoutIfNeeded()
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RadioBoxes: UICollectionViewDelegate {
|
|
||||||
open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
|
||||||
guard let molecule = boxes?[indexPath.row] else { return false }
|
|
||||||
return molecule.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
||||||
guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return }
|
|
||||||
cell.radioBox.selectBox()
|
|
||||||
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
|
|
||||||
cell.updateAccessibility()
|
|
||||||
guard let radioBox = boxes?[indexPath.row] else { return }
|
|
||||||
radioDelegate?.selected(radioBox: radioBox)
|
|
||||||
}
|
|
||||||
|
|
||||||
open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
|
|
||||||
guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return }
|
|
||||||
cell.radioBox.deselectBox()
|
|
||||||
cell.updateAccessibility()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,32 +6,36 @@
|
|||||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||||
//
|
//
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
import VDS
|
||||||
|
|
||||||
@objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol {
|
@objcMembers public class RadioBoxesModel: FormFieldModel {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
public static var identifier: String = "radioBoxes"
|
public override static var identifier: String { "radioBoxes" }
|
||||||
public var id: String = UUID().uuidString
|
|
||||||
|
|
||||||
public var boxes: [RadioBoxModel]
|
public var boxes: [RadioBoxModel]
|
||||||
public var backgroundColor: Color?
|
|
||||||
public var accessibilityIdentifier: String?
|
public var selectorModels: [VDS.RadioBoxGroup.RadioBoxItemModel] {
|
||||||
public var selectedAccentColor: Color?
|
boxes.compactMap({ item in
|
||||||
public var boxesColor: Color?
|
var radioBox = RadioBoxGroup.RadioBoxItemModel()
|
||||||
public var fieldKey: String?
|
radioBox.text = item.text
|
||||||
public var groupName: String = FormValidator.defaultGroupName
|
radioBox.subText = item.subText
|
||||||
public var baseValue: AnyHashable?
|
radioBox.subTextRight = item.subTextRight
|
||||||
public var enabled: Bool = true
|
radioBox.surface = surface
|
||||||
public var readOnly: Bool = false
|
radioBox.selected = item.selected
|
||||||
|
radioBox.strikethrough = item.strikethrough
|
||||||
|
radioBox.disabled = !(item.enabled && !item.readOnly)
|
||||||
|
return radioBox
|
||||||
|
})
|
||||||
|
}
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Form Validation
|
// MARK: - Form Validation
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
/// Returns the fieldValue of the selected box, otherwise the text of the selected box.
|
/// Returns the fieldValue of the selected box, otherwise the text of the selected box.
|
||||||
public func formFieldValue() -> AnyHashable? {
|
public override func formFieldValue() -> AnyHashable? {
|
||||||
guard enabled else { return nil }
|
guard enabled else { return nil }
|
||||||
let selectedBox = boxes.first { (box) -> Bool in
|
let selectedBox = boxes.first { (box) -> Bool in
|
||||||
return box.selected
|
return box.selected
|
||||||
@ -42,7 +46,7 @@ import MVMCore
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Server Value
|
// MARK: - Server Value
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
open func formFieldServerValue() -> AnyHashable? {
|
open override func formFieldServerValue() -> AnyHashable? {
|
||||||
return formFieldValue()
|
return formFieldValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,17 +55,7 @@ import MVMCore
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case id
|
|
||||||
case moleculeName
|
|
||||||
case enabled
|
|
||||||
case readOnly
|
|
||||||
case selectedAccentColor
|
|
||||||
case backgroundColor
|
|
||||||
case accessibilityIdentifier
|
|
||||||
case boxesColor
|
|
||||||
case boxes
|
case boxes
|
||||||
case fieldKey
|
|
||||||
case groupName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -69,7 +63,8 @@ import MVMCore
|
|||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|
||||||
public init(with boxes: [RadioBoxModel]){
|
public init(with boxes: [RadioBoxModel]){
|
||||||
self.boxes = boxes
|
self.boxes = boxes
|
||||||
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -78,32 +73,13 @@ import MVMCore
|
|||||||
|
|
||||||
required public init(from decoder: Decoder) throws {
|
required public init(from decoder: Decoder) throws {
|
||||||
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
|
|
||||||
selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor)
|
|
||||||
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
|
|
||||||
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
|
|
||||||
boxesColor = try typeContainer.decodeIfPresent(Color.self, forKey: .boxesColor)
|
|
||||||
boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes)
|
boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes)
|
||||||
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
|
try super.init(from: decoder)
|
||||||
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
|
|
||||||
self.groupName = groupName
|
|
||||||
}
|
|
||||||
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
|
|
||||||
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
|
|
||||||
baseValue = formFieldValue()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.encode(boxes, forKey: .boxes)
|
try container.encode(boxes, forKey: .boxes)
|
||||||
try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor)
|
|
||||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
|
||||||
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
|
|
||||||
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
|
|
||||||
try container.encode(groupName, forKey: .groupName)
|
|
||||||
try container.encode(enabled, forKey: .enabled)
|
|
||||||
try container.encode(readOnly, forKey: .readOnly)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user