Merge branch 'develop' into feature/headers_H1_button

# Conflicts:
#	MVMCoreUI.xcodeproj/project.pbxproj
#	MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift
This commit is contained in:
Lekshmi S 2020-04-15 17:22:33 +05:30
commit d8858d4a2f
365 changed files with 7304 additions and 3534 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
//
// ActionCollapseNotificationModel.swift
// MVMCore
//
// Created by Ryan on 3/17/20.
// Copyright © 2020 myverizon. All rights reserved.
//
import UIKit
@objcMembers public class ActionCollapseNotificationModel: ActionModelProtocol {
public static var identifier: String = "collapseNotification"
public var actionType: String
public var extraParameters: JSONValueDictionary?
public var analyticsData: JSONValueDictionary?
public var title: String?
}

View File

@ -0,0 +1,31 @@
//
// ActionOpenPanelModel.swift
// MVMCore
//
// Created by Khan, Arshad on 12/02/20.
// Copyright © 2020 myverizon. All rights reserved.
//
import Foundation
public class ActionOpenPanelModel: ActionModelProtocol {
public enum Panel: String, Codable {
case left
case right
case support // Legacy, means left
case menu // Legacy, means right.
}
public static var identifier: String = "openPanel"
public var actionType: String = ActionOpenPanelModel.identifier
public var panel: Panel
public var extraParameters: JSONValueDictionary?
public var analyticsData: JSONValueDictionary?
// Temporary fix till server changes
public var title: String?
public init(panel: Panel) {
self.panel = panel
}
}

View File

@ -0,0 +1,23 @@
//
// ActionTopAlertModel.swift
// MVMCore
//
// Created by Suresh, Kamlesh on 12/16/19.
// Copyright © 2019 myverizon. All rights reserved.
//
import Foundation
@objcMembers public class ActionTopAlertModel: ActionModelProtocol {
public static var identifier: String = "topAlert"
public var actionType: String = ActionTopAlertModel.identifier
public var pageType: String
public var extraParameters: JSONValueDictionary?
public var analyticsData: JSONValueDictionary?
// Temporary fix till server changes
public var title: String?
public init(pageType: String) {
self.pageType = pageType
}
}

View File

@ -18,8 +18,7 @@ public enum ButtonSize: String, Codable {
case tiny
}
public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormActionFieldProtocol {
public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol {
public static var identifier: String = "button"
public var backgroundColor: Color?
public var title: String
@ -33,27 +32,28 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormAction
public var disabledFillColor: Color?
public var disabledTextColor: Color?
public var disabledBorderColor: Color?
public var groupName: String? = FormValidator.defaultGroupName
public var groupName: String = FormValidator.defaultGroupName
public func updateEnable(_ enabled: Bool) {
self.enabled = enabled
public func setValidity(_ valid: Bool, group: FormGroupRule) {
enabled = valid
updateUI?()
}
/// Temporary binding mechanism for the view to update on enable changes.
public var updateUI: (() -> Void)?
init(with title: String, action: ActionModelProtocol) {
public init(with title: String, action: ActionModelProtocol) {
self.title = title
self.action = action
}
init(secondaryButtonWith title: String, action: ActionModelProtocol) {
public init(secondaryButtonWith title: String, action: ActionModelProtocol) {
self.title = title
self.action = action
style = .secondary
}
init(primaryButtonWith title: String, action: ActionModelProtocol) {
public init(primaryButtonWith title: String, action: ActionModelProtocol) {
self.title = title
self.action = action
style = .primary

View File

@ -25,16 +25,37 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
@objc public var rightViewHeight: NSNumber?
@objc public var rightViewWidth: NSNumber?
@objc public var enabledColor: UIColor = .black {
@objc public var enabledColor: UIColor = .mvmBlack {
didSet { changeCaretColor() }
}
@objc public var disabledColor: UIColor = .mfSilver() {
@objc public var disabledColor: UIColor = .mvmCoolGray3 {
didSet { changeCaretColor() }
}
private var caretSpacingConstraint: NSLayoutConstraint?
//------------------------------------------------------
// MARK: - Inits
//------------------------------------------------------
// Default values for view.
public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(frame: .zero)
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
setTitleColor(enabledColor, for: .normal)
setTitleColor(disabledColor, for: .disabled)
}
public override init(frame: CGRect) {
super.init(frame: frame)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//------------------------------------------------------
// MARK: - Lifecycle
//------------------------------------------------------
@ -82,8 +103,7 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
let width = CGFloat(rightViewWidth?.floatValue ?? CARET_VIEW_WIDTH)
let height = CGFloat(rightViewHeight?.floatValue ?? CARET_VIEW_HEIGHT)
let edgeInsets: UIEdgeInsets = contentEdgeInsets
contentEdgeInsets = UIEdgeInsets(top: edgeInsets.top, left: edgeInsets.left, bottom: edgeInsets.bottom, right: 4 + width)
contentEdgeInsets.right = 4 + width
let caretView: UIView = rightView ?? createCaretView()
rightView = caretView
@ -93,12 +113,12 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
caretView.widthAnchor.constraint(equalToConstant: width).isActive = true
caretView.heightAnchor.constraint(equalToConstant: height).isActive = true
let caretLabelSpacing = NSLayoutConstraint(item: caretView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1.0, constant: 4)
let caretLabelSpacing = NSLayoutConstraint(item: caretView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1.0, constant: 7)
caretLabelSpacing.isActive = true
caretSpacingConstraint = caretLabelSpacing
NSLayoutConstraint(item: caretView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: self, attribute: .right, relatedBy: .greaterThanOrEqual, toItem: caretView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true
caretView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
trailingAnchor.constraint(greaterThanOrEqualTo: caretView.trailingAnchor).isActive = true
caretView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true
bottomAnchor.constraint(greaterThanOrEqualTo: caretView.bottomAnchor).isActive = true
contentHorizontalAlignment = .left
@ -115,28 +135,22 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol {
//------------------------------------------------------
// MARK: - Atomization
//------------------------------------------------------
// Default values for view.
@objc open func setAsMolecule() {
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
setTitleColor(enabledColor, for: .normal)
setTitleColor(disabledColor, for: .disabled)
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let caretLinkModel = model as? CaretLinkModel else { return }
if let color = caretLinkModel.backgroundColor {
guard let model = model as? CaretLinkModel else { return }
if let color = model.backgroundColor {
backgroundColor = color.uiColor
}
enabledColor = caretLinkModel.enabledColor.uiColor
if let color = caretLinkModel.disabledColor {
enabledColor = model.enabledColor.uiColor
if let color = model.disabledColor {
disabledColor = color.uiColor
}
isEnabled = caretLinkModel.enabled
set(with: caretLinkModel.action, delegateObject: delegateObject, additionalData: additionalData)
setTitle(caretLinkModel.title, for: .normal)
isEnabled = model.enabled
set(with: model.action, delegateObject: delegateObject, additionalData: additionalData)
setTitle(model.title, for: .normal)
}
public func needsToBeConstrained() -> Bool {

View File

@ -17,7 +17,7 @@ open class ExternalLink: Link {
public var exportImageView: UIImageView?
//--------------------------------------------------
// MARK: - ModelMoleculeViewProtocol
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
@ -47,11 +47,11 @@ open class ExternalLink: Link {
trailingAnchor.constraint(greaterThanOrEqualTo: exportIcon.trailingAnchor).isActive = true
if let titleLabel = titleLabel {
let dimension = round(0.6 * titleLabel.font.pointSize)
let dimension = titleLabel.font.pointSize
exportIcon.heightAnchor.constraint(equalToConstant: dimension).isActive = true
exportIcon.widthAnchor.constraint(equalToConstant: dimension).isActive = true
exportIcon.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: PaddingOne).isActive = true
exportIcon.bottomAnchor.constraint(equalTo: titleLabel.lastBaselineAnchor).isActive = true
exportIcon.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 4).isActive = true
exportIcon.bottomAnchor.constraint(equalTo: titleLabel.lastBaselineAnchor, constant: 3).isActive = true
}
}
}

View File

@ -44,7 +44,7 @@ import UIKit
}
//--------------------------------------------------
// MARK: - ModelMoleculeViewProtocol
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {

View File

@ -118,7 +118,7 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
return CGSize(width: max(width, getMinimumWidth()), height: getHeight())
}
// MARK: - ModelMoleculeViewProtocol
// MARK: - MoleculeViewProtocol
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
// The button will get styled in the enable check in super.
super.set(with: model, delegateObject, additionalData)
@ -127,10 +127,12 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol {
setTitle(model.title, for: .normal)
model.updateUI = { [weak self] in
self?.enableField(model.enabled)
MVMCoreDispatchUtility.performBlock(onMainThread: {
self?.enableField(model.enabled)
})
}
FormValidator.setupValidation(molecule: model, delegate: delegateObject?.formHolderDelegate)
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {

View File

@ -129,7 +129,7 @@ import MVMCore
(model as? CheckboxModel)?.isChecked = isSelected
shapeLayer?.removeAllAnimations()
updateCheckboxUI(isSelected: isSelected, isAnimated: isAnimated)
FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
updateAccessibilityLabel()
}
}
@ -158,6 +158,7 @@ import MVMCore
super.init(frame: frame)
accessibilityTraits = .button
isAccessibilityElement = true
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint")
updateAccessibilityLabel()
}
@ -198,8 +199,6 @@ import MVMCore
open override func setupView() {
super.setupView()
guard constraints.isEmpty else { return }
isUserInteractionEnabled = true
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .clear
@ -382,11 +381,7 @@ import MVMCore
checkWidth = 2.0
checkAndBypassAnimations(selected: false)
}
open func setAsMolecule() {
setupView()
}
public override func updateView(_ size: CGFloat) {
super.updateView(size)
@ -394,15 +389,13 @@ import MVMCore
widthConstraint?.constant = dimension
heightConstraint?.constant = dimension
}
//layoutIfNeeded()
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? CheckboxModel else { return }
self.delegateObject = delegateObject
FormValidator.setupValidation(molecule: model, delegate: delegateObject?.formHolderDelegate)
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
if let fieldKey = model.fieldKey {
self.fieldKey = fieldKey

View File

@ -29,10 +29,8 @@ import Foundation
public var disabledCheckColor: Color = Color(uiColor: .mvmCoolGray3)
public var action: ActionModelProtocol?
public var fieldKey: String?
public var fieldValue: JSONValue?
public var groupName: String? = FormValidator.defaultGroupName
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
//--------------------------------------------------
@ -41,8 +39,6 @@ import Foundation
private enum CodingKeys: String, CodingKey {
case moleculeName
case fieldKey
case fieldValue
case isChecked = "checked"
case isEnabled = "enabled"
case isAnimated = "animated"
@ -56,23 +52,25 @@ import Foundation
case disabledCheckColor
case disabledBorderColor
case action
case fieldKey
case groupName
}
init(isChecked: Bool = false) {}
public func formFieldValue() -> AnyHashable? {
return isChecked
}
public init(isChecked: Bool = false) {
self.isChecked = isChecked
baseValue = isChecked
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
fieldValue = try typeContainer.decodeIfPresent(JSONValue.self, forKey: .fieldValue)
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
borderWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .borderWidth) ?? 1
borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) ?? Color(uiColor: .black)
checkColor = try typeContainer.decodeIfPresent(Color.self, forKey: .checkColor) ?? Color(uiColor: .black)
@ -86,6 +84,9 @@ import Foundation
isRound = try typeContainer.decodeIfPresent(Bool.self, forKey: .isRound) ?? false
isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? true
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
baseValue = isChecked
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
@ -95,7 +96,6 @@ import Foundation
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(borderColor, forKey: .borderColor)
try container.encode(borderWidth, forKey: .borderWidth)

View File

@ -0,0 +1,183 @@
//
// RadioBox.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 4/9/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class RadioBox: Control {
public let label = Label.createLabelRegularBodySmall(true)
public let subTextLabel = Label.createLabelRegularMicro(true)
public var isOutOfStock = false
public var accentColor = UIColor.mvmRed
public let innerPadding: CGFloat = 12.0
private var borderLayer: CALayer?
private var strikeLayer: CALayer?
private var maskLayer: CALayer?
public var subTextLabelHeightConstraint: NSLayoutConstraint?
public var radioBoxModel: RadioBoxModel? {
return model as? RadioBoxModel
}
// MARK: - MVMCoreViewProtocol
open override 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.black.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)
}
// MARK: - MoleculeViewProtocol
public 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 }
isSelected = model.selected
isEnabled = model.enabled
label.text = model.text
subTextLabel.text = model.subText
isOutOfStock = model.strikethrough
subTextLabelHeightConstraint?.isActive = (subTextLabel.text?.count ?? 0) == 0
}
// MARK: - State Handling
open override func draw(_ layer: CALayer, in ctx: CGContext) {
// Draw the strikethrough
strikeLayer?.removeFromSuperlayer()
if isOutOfStock {
let line = getStrikeThrough(color: .black, 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
}
}
open override func layoutSubviews() {
super.layoutSubviews()
// Accounts for any size changes
layer.setNeedsDisplay()
}
@objc open func selectBox() {
isSelected = true
radioBoxModel?.selected = isSelected
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 = UIColor.mvmRed.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
}
}

View File

@ -0,0 +1,23 @@
//
// RadioBoxCollectionViewCell.swift
// MVMCoreUI
//
// Created by Dhamodaram Nandi on 01/04/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class RadioBoxCollectionViewCell: CollectionViewCell {
let radioBox = RadioBox()
open override func setupView() {
super.setupView()
addMolecule(radioBox)
MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0)
}
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let model = model as? RadioBoxModel else { return }
radioBox.set(with: model, delegateObject, additionalData)
}
}

View File

@ -0,0 +1,68 @@
//
// RadioBoxModel.swift
// MVMCoreUI
//
// Created by Dhamodaram Nandi on 31/03/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class RadioBoxModel: MoleculeModelProtocol {
public static var identifier: String = "radioBox"
public var text: String
public var subText: String?
public var backgroundColor: Color? = Color(uiColor: .white)
public var selectedAccentColor = Color(uiColor: .mvmRed)
public var selected: Bool = false
public var enabled: Bool = true
public var strikethrough: Bool = false
public var fieldValue: String?
private enum CodingKeys: String, CodingKey {
case moleculeName
case text
case subText
case selectedAccentColor
case backgroundColor
case selected
case enabled
case strikethrough
case fieldValue
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
text = try typeContainer.decode(String.self, forKey: .text)
subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText)
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) {
selectedAccentColor = color
}
if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) {
backgroundColor = color
}
if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) {
selected = isSelected
}
if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
enabled = isEnabled
}
if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) {
strikethrough = isStrikeTrough
}
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(text, forKey: .text)
try container.encodeIfPresent(subText, forKey: .subText)
try container.encode(selectedAccentColor, forKey: .selectedAccentColor)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encode(selected, forKey: .selected)
try container.encode(enabled, forKey: .enabled)
try container.encode(strikethrough, forKey: .strikethrough)
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
}
}

View File

@ -0,0 +1,143 @@
//
// RadioBoxes.swift
// MVMCoreUI
//
// Created by Dhamodaram Nandi on 31/03/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class RadioBoxes: View {
public var collectionView: CollectionView!
public var collectionViewHeight: NSLayoutConstraint!
private let boxWidth: CGFloat = 151.0
private let boxHeight: CGFloat = 64.0
private let itemSpacing: CGFloat = 8.0
private var delegateObject: MVMCoreUIDelegateObject?
/// The models for the molecules.
public var boxes: [RadioBoxModel]?
private var size: CGFloat?
open override func layoutSubviews() {
super.layoutSubviews()
// Accounts for any collection size changes
DispatchQueue.main.async {
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
// 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
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
guard let radioBoxesModel = model as? RadioBoxesModel else { return }
boxes = radioBoxesModel.boxes
FormValidator.setupValidation(for: radioBoxesModel, delegate: delegateObject?.formHolderDelegate)
backgroundColor = radioBoxesModel.backgroundColor?.uiColor
registerCells()
setHeight()
collectionView.reloadData()
}
@objc override open func updateView(_ size: CGFloat) {
super.updateView(size)
self.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) / 2.0)
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) / 2
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.radioBox.isUserInteractionEnabled = false
cell.set(with: molecule, delegateObject, nil)
cell.updateView(size ?? collectionView.bounds.width)
if molecule.selected {
collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically)
}
cell.layoutIfNeeded()
return cell
}
}
extension RadioBoxes: UICollectionViewDelegate {
public 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)
}
public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return }
cell.radioBox.deselectBox()
}
}

View File

@ -1,64 +1,57 @@
//
// ToggleModel.swift
// RadioBoxesModel.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 1/14/20.
// Created by Dhamodaram Nandi on 31/03/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
public static var identifier: String = "toggle"
import Foundation
@objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol {
public static var identifier: String = "radioBoxes"
public var backgroundColor: Color?
public var state: Bool = true
public var action: ActionModelProtocol?
public var alternateAction: ActionModelProtocol?
public var selectedAccentColor: Color?
public var boxes: [RadioBoxModel]
public var fieldKey: String?
public var groupName: String? = FormValidator.defaultGroupName
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
/// Returns the fieldValue of the selected box, otherwise the text of the selected box.
public func formFieldValue() -> AnyHashable? {
let selectedBox = boxes.first { (box) -> Bool in
return box.selected
}
return selectedBox?.fieldValue ?? selectedBox?.text
}
private enum CodingKeys: String, CodingKey {
case moleculeName
case state
case action
case selectedAccentColor
case backgroundColor
case boxes
case fieldKey
case alternateAction
case groupName
}
public func formFieldValue() -> AnyHashable? {
return state
}
public init(_ state: Bool) {
self.state = state
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
self.state = state
}
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction)
selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes)
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
baseValue = formFieldValue()
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeModelIfPresent(action, forKey: .action)
try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(state, forKey: .state)
try container.encode(boxes, forKey: .boxes)
try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(groupName, forKey: .groupName)
}
}

View File

@ -8,31 +8,37 @@
import UIKit
@objcMembers public class RadioButton: Control {
@objcMembers open class RadioButton: Control {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
var diameter: CGFloat = 30 {
public var diameter: CGFloat = 30 {
didSet {
widthConstraint?.constant = diameter
}
}
var enabledColor = UIColor.black
var disabledColor = UIColor.mfSilver()
var delegateObject: MVMCoreUIDelegateObject?
var widthConstraint: NSLayoutConstraint?
var heightConstraint: NSLayoutConstraint?
var radioModel: RadioButtonModel? {
return model as? RadioButtonModel
public override var isSelected: Bool {
didSet {
radioModel?.state = isSelected
}
}
lazy var radioGroupName: String? = {
[unowned self] in return radioModel?.fieldKey
public var enabledColor: UIColor = .mvmBlack
public var disabledColor: UIColor = .mvmCoolGray3
public var delegateObject: MVMCoreUIDelegateObject?
public var radioModel: RadioButtonModel? {
return model as? RadioButtonModel
}
lazy public var radioGroupName: String? = {
return radioModel?.fieldKey
}()
lazy var radioButtonSelectionHelper: RadioButtonSelectionHelper? = {
[unowned self] in
lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = {
if let radioGroupName = radioGroupName,
let radioButtonModel = delegateObject?.formHolderDelegate?.formValidator?.radioButtonsModelByGroup[radioGroupName] {
return radioButtonModel
@ -41,20 +47,47 @@ import UIKit
}
}()
public override var isEnabled: Bool {
didSet {
isUserInteractionEnabled = isEnabled
setNeedsDisplay()
}
}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
public var widthConstraint: NSLayoutConstraint?
public var heightConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let color = isEnabled ? enabledColor.cgColor : disabledColor.cgColor
layer.cornerRadius = bounds.width * 0.5
layer.borderColor = color
layer.borderWidth = bounds.width * 0.0333
if isSelected {
// Space around inner circle is 1/5 the size
context.addEllipse(in: CGRect(x: bounds.width*0.2, y: bounds.height*0.2, width: bounds.width*0.6, height: bounds.height*0.6))
context.addEllipse(in: CGRect(x: bounds.width * 0.2,
y: bounds.height * 0.2,
width: bounds.width * 0.6,
height: bounds.height * 0.6))
context.setFillColor(color)
context.fillPath()
}
}
//--------------------------------------------------
// MARK: - Validation
//--------------------------------------------------
/// The action performed when tapped.
func tapAction() {
if let radioButtonModel = radioButtonSelectionHelper {
@ -62,7 +95,7 @@ import UIKit
} else {
isSelected = !isSelected
}
FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
setNeedsDisplay()
}
@ -81,11 +114,15 @@ import UIKit
public func formFieldValue() -> AnyHashable? {
return radioModel?.fieldValue
}
// MARK: - MVMViewProtocol
//--------------------------------------------------
// MARK: - MVMViewProtocol
//--------------------------------------------------
open override func setupView() {
super.setupView()
backgroundColor = .white
backgroundColor = .mvmWhite
clipsToBounds = true
widthConstraint = widthAnchor.constraint(equalToConstant: 30)
widthConstraint?.isActive = true
@ -97,20 +134,20 @@ import UIKit
accessibilityTraits = .button
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint")
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? RadioButtonModel else {
return
}
guard let model = model as? RadioButtonModel else { return }
self.delegateObject = delegateObject
let radioButtonModel = RadioButtonSelectionHelper.setupForRadioButtonGroup(model,
formValidator: delegateObject?.formHolderDelegate?.formValidator)
FormValidator.setupValidation(molecule: radioButtonModel, delegate: delegateObject?.formHolderDelegate)
isSelected = model.state
isEnabled = model.enabled
RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject)
}
public override func reset() {
super.reset()
backgroundColor = .white
super.reset()
backgroundColor = .mvmWhite
}
}

View File

@ -9,47 +9,77 @@
import Foundation
import MVMCore
public class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "radioButton"
public var backgroundColor: Color?
public var state: Bool = false
public var enabled: Bool = true
public var baseValue: AnyHashable?
public var groupName: String?
public var fieldKey: String?
/// The specific value to send to server. TODO: update this to be more generic.
public var fieldValue: String?
public var baseValue: AnyHashable?
public var groupName: String = FormValidator.defaultGroupName
public var fieldKey: String?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case backgroundColor
case state
case enabled
case fieldKey
case fieldValue
case fieldKey
case groupName
}
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(_ state: Bool) {
self.state = state
baseValue = state
}
//--------------------------------------------------
// MARK: - Validation
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
return state
return fieldValue
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
self.state = state
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
baseValue = state
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
}

View File

@ -0,0 +1,82 @@
//
// RadioButtonModel.swift
// MVMCoreUI
//
// Created by Suresh, Kamlesh on 5/14/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class RadioButtonSelectionHelper: FormFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
private var selectedRadioButton: RadioButton?
private var selectedRadioButtonModel: RadioButtonModel?
public var baseValue: AnyHashable?
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public func set(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton) {
self.fieldKey = radioButtonModel.fieldKey
self.groupName = radioButtonModel.groupName
if radioButtonModel.state {
if self.baseValue == nil,
let selected = radioButtonModel.baseValue as? Bool, selected {
self.baseValue = radioButtonModel.fieldValue
}
selectedRadioButtonModel = radioButtonModel
// Below code is needed for cell resuse scenario.
radioButton.isSelected = true
selectedRadioButton = radioButton
} else {
radioButton.isSelected = false
}
}
//--------------------------------------------------
// MARK: - Functions
//--------------------------------------------------
public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton, delegateObject: MVMCoreUIDelegateObject?) {
guard let groupName = radioButtonModel.fieldKey,
let formValidator = delegateObject?.formHolderDelegate?.formValidator else {
return
}
let radioButtonSelectionHelper = formValidator.radioButtonsModelByGroup[groupName] ?? RadioButtonSelectionHelper()
radioButtonSelectionHelper.set(radioButtonModel, radioButton)
formValidator.radioButtonsModelByGroup[groupName] = radioButtonSelectionHelper
FormValidator.setupValidation(for: radioButtonSelectionHelper, delegate: delegateObject?.formHolderDelegate)
}
public func selected(_ radioButton: RadioButton) {
// Checks because the view could be reused
if selectedRadioButton?.radioModel === selectedRadioButtonModel {
selectedRadioButton?.isSelected = false
} else {
selectedRadioButtonModel?.state = false
}
selectedRadioButton = radioButton
selectedRadioButton?.isSelected = true
selectedRadioButtonModel = selectedRadioButton?.radioModel
}
}
// MARK: - FormValidationFormFieldProtocol
extension RadioButtonSelectionHelper {
public func formFieldValue() -> AnyHashable? {
return selectedRadioButtonModel?.fieldValue
}
}

View File

@ -52,6 +52,10 @@ import UIKit
fatalError("DropdownEntryField does not support xib.")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
@ -77,15 +81,3 @@ import UIKit
dropDownCaretView.setOptional(with: model.caretView, delegateObject, additionalData)
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol
extension BaseDropdownEntryField {
@objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
guard let dictionary = json, !dictionary.isEmpty else { return }
dropDownCaretView.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData)
}
}

View File

@ -58,6 +58,10 @@ import UIKit
fatalError("DateDropdownEntryField init(coder:) has not been implemented")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
@ -119,17 +123,3 @@ import UIKit
dateFormat = model.dateFormat
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol
extension DateDropdownEntryField {
@objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
guard let dictionary = json, !dictionary.isEmpty else { return }
if let dateFormat = dictionary["dateFormat"] as? String {
self.dateFormat = dateFormat
}
}
}

View File

@ -116,7 +116,7 @@ import UIKit
for (index, field) in digitBoxes.enumerated() {
if index < newValue.count {
let indexChar = newValue.index(newValue.startIndex, offsetBy: index)
field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()])
field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [NSAttributedString.Key.foregroundColor: UIColor.mvmCoolGray6])
}
}
@ -193,6 +193,10 @@ import UIKit
fatalError("DigitEntryField xib has not been implemented")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------
@ -207,7 +211,7 @@ import UIKit
let digitBox = DigitBox()
digitBox.isAccessibilityElement = true
MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: self)
digitBox.digitField.inputAccessoryView = MVMCoreUICommonViewsUtility.getToolbarWithDoneButton(delegate: self)
digitBox.digitField.delegate = self
digitBox.digitBoxDelegate = self
return digitBox
@ -329,20 +333,22 @@ import UIKit
}
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let model = model as? DigitEntryFieldModel else { return }
numberOfDigits = model.digits
setAsSecureTextEntry(model.secureEntry)
for digitBox in digitBoxes {
MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate)
digitBox.digitField.inputAccessoryView = MVMCoreUICommonViewsUtility.getToolbarWithDoneButton(delegate: delegateObject?.observingTextFieldDelegate ?? self)
}
super.set(with: model, delegateObject, additionalData)
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 115
}
}
// MARK: - TextField Delegate
@ -440,29 +446,3 @@ extension DigitEntryField {
return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol
extension DigitEntryField {
@objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
guard let dictionary = json else { return }
numberOfDigits = dictionary["digits"] as? Int ?? 4
if let _ = dictionary["secureEntry"] as? Bool {
setAsSecureTextEntry(true)
}
if !dictionary.isEmpty{
for digitBox in digitBoxes {
MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate)
}
}
}
@objc open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
return 115
}
}

View File

@ -20,8 +20,8 @@ import UIKit
public private(set) var titleLabel: Label = {
let label = Label()
label.font = MFStyler.fontB3()
label.textColor = .mfBattleshipGrey()
label.font = MFStyler.fontRegularMicro()
label.textColor = .mvmBlack
label.setContentCompressionResistancePriority(.required, for: .vertical)
return label
}()
@ -31,8 +31,8 @@ import UIKit
/// Provides contextual information on the TextField.
public private(set) var feedbackLabel: Label = {
let label = Label()
label.font = MFStyler.fontForTextFieldUnderLabel()
label.textColor = .black
label.font = MFStyler.fontRegularMicro()
label.textColor = .mvmBlack
label.setContentCompressionResistancePriority(.required, for: .vertical)
return label
}()
@ -65,7 +65,8 @@ import UIKit
public var isEnabled: Bool {
get { return entryFieldContainer.isEnabled }
set (enabled) {
self.feedbackLabel.textColor = enabled ? .black : .mfSilver()
self.titleLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3
self.feedbackLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3
self.entryFieldContainer.isEnabled = enabled
}
}
@ -116,8 +117,7 @@ import UIKit
set (newFeedback) {
feedbackLabel.text = newFeedback
feedbackLabel.accessibilityElementsHidden = feedbackLabel.text?.isEmpty ?? true
entryFieldContainer.refreshUI()
delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self)
entryFieldContainer.refreshUI(updateMoleculeLayout: true)
}
}
@ -161,6 +161,11 @@ import UIKit
fatalError("EntryField does not support xib.")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(frame: .zero)
set(with: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
@ -174,7 +179,7 @@ import UIKit
isAccessibilityElement = false
setContentCompressionResistancePriority(.required, for: .vertical)
accessibilityElements = [titleLabel, feedbackLabel]
backgroundColor = .clear
backgroundColor = .mvmWhite
addSubview(titleLabel)
@ -226,15 +231,19 @@ import UIKit
entryFieldContainer.updateView(size)
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
@objc open override func reset() {
super.reset()
backgroundColor = .clear
isAccessibilityElement = false
titleLabel.font = MFStyler.fontB3()
titleLabel.textColor = .mfBattleshipGrey()
feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel()
feedbackLabel.textColor = .black
titleLabel.font = MFStyler.fontRegularMicro()
titleLabel.textColor = .mvmBlack
feedbackLabel.font = MFStyler.fontRegularMicro()
feedbackLabel.textColor = .mvmBlack
entryFieldContainer.reset()
}
@ -258,45 +267,8 @@ import UIKit
self.isSelected = isSelected
}
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol
extension EntryField {
@objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
self.delegateObject = delegateObject
guard let dictionary = json, !dictionary.isEmpty else { return }
entryFieldContainer.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData)
if let titleText = dictionary[KeyTitle] as? String {
title = titleText
}
if let disable = dictionary[KeyDisable] as? String, disable.isEqual(StringY) || dictionary.boolForKey(KeyDisable) {
isEnabled = false
}
if let feedback = dictionary["feedback"] as? String {
self.standardMessage = feedback
}
if let errMessage = dictionary[KeyErrorMessage] as? String {
errorMessage = errMessage
}
if let isLocked = dictionary["isLocked"] as? Bool {
self.isLocked = isLocked
}
if let isSelected = dictionary["isSelected"] as? Bool {
self.isSelected = isSelected
}
}
@objc open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 115
}
}

View File

@ -9,7 +9,7 @@
import Foundation
@objcMembers public class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, ValidProtocol {
@objcMembers public class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol {
//--------------------------------------------------
// MARK: - Properties
@ -26,9 +26,10 @@ import Foundation
public var isEnabled: Bool = true
public var isLocked: Bool?
public var isSelected: Bool?
public var fieldKey: String?
public var groupName: String? = FormValidator.defaultGroupName
public var text: String?
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
public var isValid: Bool? {
@ -36,9 +37,12 @@ import Foundation
updateUI?()
}
}
/// Temporary binding mechanism for the view to update on enable changes.
public var updateUI: (() -> Void)?
public func setValidity(_ isValid: Bool) {
self.isValid = isValid
public func setValidity(_ valid: Bool, rule: RulesProtocol) {
self.isValid = valid
}
//--------------------------------------------------
@ -54,10 +58,10 @@ import Foundation
case errorMessage = "errorMsg"
case isLocked
case isSelected
case fieldKey
case isValid
case isRequired = "required"
case text
case fieldKey
case groupName
}
@ -69,6 +73,11 @@ import Foundation
// MARK: - Initializers
//--------------------------------------------------
public init(with text: String) {
self.text = text
baseValue = text
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
@ -78,12 +87,13 @@ import Foundation
isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? true
isLocked = try typeContainer.decodeIfPresent(Bool.self, forKey: .isLocked)
isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .isSelected)
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
isValid = try typeContainer.decodeIfPresent(Bool.self, forKey: .isValid)
text = try typeContainer.decodeIfPresent(String.self, forKey: .text)
baseValue = text
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
self.groupName = groupName
}
}

View File

@ -51,6 +51,10 @@ open class ItemDropdownEntryField: BaseDropdownEntryField {
fatalError("ItemDropdownEntryField init(coder:) has not been implemented")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
@ -135,21 +139,6 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource {
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol
extension ItemDropdownEntryField {
@objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
guard let dictionary = json, !dictionary.isEmpty else { return }
if let options = dictionary["options"] as? [String] {
pickerData = options
setPickerDelegates(delegate: self)
}
}
}
// MARK: - Accessibility
extension ItemDropdownEntryField {

View File

@ -65,6 +65,10 @@ import MVMCore
fatalError("MdnEntryField xib not supported.")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Setup
//--------------------------------------------------

View File

@ -19,7 +19,7 @@ import UIKit
}
@objcMembers open class TextEntryField: EntryField, UITextFieldDelegate {
@objcMembers open class TextEntryField: EntryField, UITextFieldDelegate, ObservingTextFieldDelegate {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
@ -28,7 +28,8 @@ import UIKit
let textField = TextField()
textField.isAccessibilityElement = true
textField.setContentCompressionResistancePriority(.required, for: .vertical)
textField.font = MFStyler.fontForTextField()
textField.font = MFStyler.fontRegularBodyLarge()
textField.textColor = .mvmBlack
textField.smartQuotesType = .no
textField.smartDashesType = .no
textField.smartInsertDeleteType = .no
@ -40,7 +41,7 @@ import UIKit
//--------------------------------------------------
/// Set enabled and disabled colors to be utilized when setting this texfield's isEnabled property.
public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.black, .mfSilver())
public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.mvmBlack, .mvmCoolGray3)
private var observingForChange: Bool = false
@ -49,6 +50,10 @@ import UIKit
/// Validate when user resigns editing. Default: true
public var validateWhenDoneEditing: Bool = true
public var textEntryFieldModel: TextEntryFieldModel? {
return model as? TextEntryFieldModel
}
//--------------------------------------------------
// MARK: - Computed Properties
@ -87,7 +92,7 @@ import UIKit
get { return textField.text }
set {
textField.text = newValue
(model as? TextEntryFieldModel)?.text = newValue
textEntryFieldModel?.text = newValue
}
}
@ -154,20 +159,25 @@ import UIKit
fatalError("TextEntryField does not support xib.")
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(model: model, delegateObject, additionalData)
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
@objc open override func setupFieldContainerContent(_ container: UIView) {
MFStyler.styleTextField(textField)
textField.font = MFStyler.fontRegularBodyLarge()
container.addSubview(textField)
NSLayoutConstraint.activate([
textField.heightAnchor.constraint(equalToConstant: 24),
textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 12),
textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 12)])
container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 12)
])
textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16)
textFieldTrailingConstraint?.isActive = true
@ -184,14 +194,14 @@ import UIKit
@objc open override func updateView(_ size: CGFloat) {
super.updateView(size)
MFStyler.styleTextField(textField)
textField.font = MFStyler.fontRegularBodyLarge()
layoutIfNeeded()
}
open override func reset() {
super.reset()
textField.font = MFStyler.fontForTextField()
textField.font = MFStyler.fontRegularBodyLarge()
}
@objc deinit {
@ -228,7 +238,7 @@ import UIKit
/// Validates the text of the entry field.
@objc public func validateTextField() {
text = textField.text
FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
}
@objc public func updateValidation(_ isValid: Bool) {
@ -243,6 +253,7 @@ import UIKit
observingTextFieldDelegate?.isValid?(textfield: self)
}
}
/// Executes on UITextField.textDidBeginEditingNotification
@objc func startEditing() {
isSelected = true
@ -261,7 +272,7 @@ import UIKit
resignFirstResponder()
if isValid {
showError = false
entryFieldContainer.bottomBar?.backgroundColor = UIColor.black.cgColor
entryFieldContainer.bottomBar?.backgroundColor = UIColor.mvmBlack.cgColor
}
}
@ -269,33 +280,36 @@ import UIKit
resignFirstResponder()
}
//--------------------------------------------------
// MARK: - MoleculeViewProtocol
//--------------------------------------------------
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? TextEntryFieldModel else { return }
model.updateUI = { [weak self] in
if self?.isSelected ?? false {
self?.updateValidation(model.isValid ?? true)
}
MVMCoreDispatchUtility.performBlock(onMainThread: {
if self?.isSelected ?? false {
self?.updateValidation(model.isValid ?? true)
}
})
}
self.delegateObject = delegateObject
FormValidator.setupValidation(molecule: model, delegate: delegateObject?.formHolderDelegate)
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
textColor.enabled = model.enabledTextColor?.uiColor
textColor.disabled = model.disabledTextColor?.uiColor
text = model.text
placeholder = model.placeholder
switch model.type {
case "password":
case .password:
textField.isSecureTextEntry = true
case "number":
case .number:
textField.keyboardType = .numberPad
case "email":
case .email:
textField.keyboardType = .emailAddress
default:
break
}
@ -311,65 +325,7 @@ import UIKit
uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate
observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate)
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol
extension TextEntryField {
@objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
guard let delegateObject = delegateObject,
let dictionary = json
else { return }
if let enabledTextColorHex = dictionary["enabledTextColor"] as? String {
textColor.enabled = UIColor.mfGet(forHex: enabledTextColorHex)
}
if let disabledTextColorHex = dictionary["disabledTextColor"] as? String {
textColor.disabled = UIColor.mfGet(forHex: disabledTextColorHex)
}
if let text = dictionary[KeyText] as? String {
self.text = text
}
if let placeholder = dictionary[placeholder] as? String {
self.placeholder = placeholder
}
switch dictionary.stringForkey(KeyType) {
case "password":
textField.isSecureTextEntry = true
case "number":
textField.keyboardType = .numberPad
case "email":
textField.keyboardType = .emailAddress
default:
break
}
let regex = dictionary.stringForkey("regex")
if !regex.isEmpty {
validationBlock = { enteredValue in
guard let value = enteredValue else { return false }
return MVMCoreUIUtility.validate(value, withRegularExpression: regex)
}
} else {
defaultValidationBlock()
}
uiTextFieldDelegate = delegateObject.uiTextFieldDelegate
observingTextFieldDelegate = delegateObject.observingTextFieldDelegate
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate)
textField.inputAccessoryView = MVMCoreUICommonViewsUtility.getToolbarWithDoneButton(delegate: observingTextFieldDelegate ?? self)
}
}

View File

@ -8,6 +8,13 @@
@objcMembers public class TextEntryFieldModel: EntryFieldModel {
public enum EntryType: String, Codable {
case password
case number
case email
}
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -19,7 +26,7 @@
public var placeholder: String?
public var enabledTextColor: Color?
public var disabledTextColor: Color?
public var type: String?
public var type: EntryType?
public var regex: String?
//--------------------------------------------------
@ -47,7 +54,7 @@
placeholder = try typeContainer.decodeIfPresent(String.self, forKey: .placeholder)
enabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledTextColor)
disabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledTextColor)
type = try typeContainer.decodeIfPresent(String.self, forKey: .type)
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type)
regex = try typeContainer.decodeIfPresent(String.self, forKey: .regex)
}

View File

@ -178,16 +178,12 @@ open class CaretView: View {
//------------------------------------------------------
// Default values for view.
@objc open override func setAsMolecule() {
public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
super.init(frame: .zero)
defaultState()
}
public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: CaretViewModel.self) else { return }
set(with: model, delegateObject, additionalData)
}
//MARK: - MVMCoreMoleculeViewProtocol
override public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let caretModel = model as? CaretViewModel else {

View File

@ -65,6 +65,14 @@
alignCheckbox(.center)
}
@objc override open func updateView(_ size: CGFloat) {
super.updateView(size)
label.updateView(size)
checkbox.updateView(size)
layoutIfNeeded()
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -115,6 +123,9 @@
}
}
//--------------------------------------------------
// MARK: - Atomic
//--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let checkBoxWithLabelModel = model as? CheckboxLabelModel else { return }
@ -125,23 +136,11 @@
checkbox.set(with: checkBoxWithLabelModel.checkbox, delegateObject, additionalData)
label.set(with: checkBoxWithLabelModel.label, delegateObject, additionalData)
}
}
// MARK: - Molecular
extension CheckboxLabel {
open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 200
}
@objc override open func updateView(_ size: CGFloat) {
super.updateView(size)
label.updateView(size)
checkbox.updateView(size)
layoutIfNeeded()
}
open override func reset() {
super.reset()
@ -149,17 +148,4 @@ extension CheckboxLabel {
checkbox.reset()
alignCheckbox(.center)
}
override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
guard let dictionary = json else { return }
if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) {
alignCheckbox(position)
}
checkbox.setWithJSON(dictionary.dictionaryForKey("checkbox"), delegateObject: delegateObject, additionalData: additionalData)
label.setWithJSON(dictionary.dictionaryForKey("label"), delegateObject: delegateObject, additionalData: additionalData)
}
}

View File

@ -16,8 +16,8 @@ public enum CheckboxPosition: String, Codable {
@objcMembers public class CheckboxLabelModel: MoleculeModelProtocol {
public static var identifier: String = "checkboxLabel"
public var moleculeName: String
public var backgroundColor: Color?
public var checkboxAlignment: CheckboxPosition?
public var checkbox: CheckboxModel
public var label: LabelModel

View File

@ -87,12 +87,6 @@ open class DashLine: View {
isHidden = false
}
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: DashLineModel.self) else { return }
set(with: model, delegateObject, additionalData)
}
//MARK: - MVMCoreMoleculeViewProtocol
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let dashLineModel = dashModel else {

View File

@ -32,11 +32,6 @@ import UIKit
createGraphCircle(model)
rotationAnimation(model)
}
override open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: CircleProgressModel.self) else { return }
set(with: model, delegateObject, additionalData)
}
class func getAngle(_ piValue: Double) -> Double {
return piValue / (2.0 * Double.pi) * 360.0

View File

@ -11,7 +11,7 @@ import Foundation
@objcMembers public class ImageViewModel: MoleculeModelProtocol {
public static var identifier: String = "image"
public var backgroundColor: Color?
public var moleculeName: String? = ImageViewModel.identifier
public var moleculeName: String = ImageViewModel.identifier
public var image: String
public var accessibilityText: String?
public var fallbackImage: String?

View File

@ -12,7 +12,7 @@ import MVMCore
public typealias ActionBlock = () -> ()
@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol, ModelMoleculeViewProtocol {
@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol {
//------------------------------------------------------
// MARK: - Properties
@ -128,6 +128,13 @@ public typealias ActionBlock = () -> ()
}
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(frame: .zero)
setupView()
styleB2(true)
set(with: model, delegateObject, additionalData)
}
//------------------------------------------------------
// MARK: - Factory Functions
//------------------------------------------------------
@ -316,15 +323,13 @@ public typealias ActionBlock = () -> ()
text = labelModel.text
hero = labelModel.hero
Label.setLabel(self, withHTML: labelModel.html)
isAccessibilityElement = hasText
let alignment = LabelAlignment(rawValue: labelModel.textAlignment ?? "")
switch alignment {
switch labelModel.textAlignment {
case .center:
textAlignment = .center
case .right:
textAlignment = .right
default:
textAlignment = .left
}
@ -338,8 +343,7 @@ public typealias ActionBlock = () -> ()
accessibilityLabel = accessibilityText
}
if let fontStyle = labelModel.fontStyle {
MFStyler.styleLabel(self, withStyle: fontStyle)
if let fontStyle = labelModel.fontStyle?.rawValue {
MFStyler.styleLabel(self, withStyle: fontStyle, genericScaling: false)
standardFontSize = font.pointSize
} else {
@ -372,9 +376,9 @@ public typealias ActionBlock = () -> ()
attributedString.addAttribute(.baselineOffset, value: 0, range: range)
case let colorAtt as LabelAttributeColorModel:
if let colorHex = colorAtt.textColor, !colorHex.isEmpty {
if let colorHex = colorAtt.textColor {
attributedString.removeAttribute(.foregroundColor, range: range)
attributedString.addAttribute(.foregroundColor, value: UIColor.mfGet(forHex: colorHex), range: range)
attributedString.addAttribute(.foregroundColor, value: colorHex.uiColor, range: range)
}
case let imageAtt as LabelAttributeImageModel:
@ -395,7 +399,7 @@ public typealias ActionBlock = () -> ()
attributedString.insert(mutableString, at: imageAtt.location)
case let fontAtt as LabelAttributeFontModel:
if let fontStyle = fontAtt.style {
if let fontStyle = fontAtt.style?.rawValue {
let styles = MFStyler.styleGetAttributedString("0", withStyle: fontStyle)
attributedString.removeAttribute(.font, range: range)
attributedString.removeAttribute(.foregroundColor, range: range)
@ -426,6 +430,7 @@ public typealias ActionBlock = () -> ()
continue
}
}
attributedText = attributedString
originalAttributedString = attributedText
}
@ -755,8 +760,6 @@ public typealias ActionBlock = () -> ()
*/
static func getTextAttachmentImage(name: String = "externalLink", dimension: CGFloat) -> NSTextAttachment {
let dimension = round(dimension * 0.8)
let imageAttachment = NSTextAttachment()
imageAttachment.image = MVMCoreUIUtility.imageNamed(name)
imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
@ -766,23 +769,18 @@ public typealias ActionBlock = () -> ()
static func getTextAttachmentFrom(url: String, dimension: CGFloat, label: Label) -> NSTextAttachment {
let dimension = round(dimension * 0.8)
let imageAttachment = NSTextAttachment()
imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
DispatchQueue.global(qos: .default).async {
guard let url = URL(string: url),
let data = try? Data(contentsOf: url)
else { return }
DispatchQueue.main.sync {
imageAttachment.image = UIImage(data: data)
label.setNeedsDisplay()
MVMCoreCache.shared()?.getImage(url, useWidth: false, widthForS7: 0, useHeight: false, heightForS7: 0, localFallbackImageName: nil) { image, data, _ in
DispatchQueue.main.sync {
imageAttachment.image = image
label.setNeedsDisplay()
}
}
}
return imageAttachment
}
@ -870,16 +868,6 @@ extension Label {
accessibilityTraits = .staticText
}
@objc public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
clauses = []
Label.setUILabel(self, withJSON: json, delegate: delegateObject, additionalData: additionalData)
hero = json?["hero"] as? Int
}
public func setAsMolecule() {
styleB2(true)
}
public func needsToBeConstrained() -> Bool {
return true
}

View File

@ -17,7 +17,7 @@ import UIKit
return "color"
}
var textColor: String?
var textColor: Color?
//--------------------------------------------------
// MARK: - Keys
@ -33,7 +33,7 @@ import UIKit
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
textColor = try typeContainer.decodeIfPresent(String.self, forKey: .textColor)
textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor)
try super.init(from: decoder)
}

View File

@ -18,7 +18,7 @@ import UIKit
return "font"
}
var style: String?
var style: Styler.Font?
var name: String?
var size: CGFloat?
@ -38,7 +38,7 @@ import UIKit
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
style = try typeContainer.decodeIfPresent(String.self, forKey: .style)
style = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .style)
name = try typeContainer.decodeIfPresent(String.self, forKey: .name)
size = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .size)
try super.init(from: decoder)

View File

@ -19,10 +19,10 @@ import Foundation
public var text: String
public var accessibilityText: String?
public var textColor: Color?
public var fontStyle: String?
public var fontStyle: Styler.Font?
public var fontName: String?
public var fontSize: CGFloat?
public var textAlignment: String?
public var textAlignment: NSTextAlignment?
public var attributes: [LabelAttributeModel]?
public var html: String?
public var hero: Int?
@ -70,10 +70,10 @@ import Foundation
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
fontStyle = try typeContainer.decodeIfPresent(String.self, forKey: .fontStyle)
fontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .fontStyle)
fontName = try typeContainer.decodeIfPresent(String.self, forKey: .fontName)
fontSize = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .fontSize)
textAlignment = try typeContainer.decodeIfPresent(String.self, forKey: .textAlignment)
textAlignment = try typeContainer.decodeIfPresent(NSTextAlignment.self, forKey: .textAlignment)
attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes)
html = try typeContainer.decodeIfPresent(String.self, forKey: .html)
hero = try typeContainer.decodeIfPresent(Int.self, forKey: .hero)

View File

@ -14,7 +14,7 @@ import UIKit
//--------------------------------------------------
public static var identifier: String = "leftRightLabelView"
public var moleculeName: String? = LeftRightLabelModel.identifier
public var moleculeName: String = LeftRightLabelModel.identifier
public var backgroundColor: Color?
public var leftText: LabelModel
public var rightText: LabelModel?

View File

@ -40,11 +40,6 @@ import Foundation
super.init(coder: aDecoder)
}
public convenience init(json: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) {
self.init()
setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
}
override open func setupView() {
super.setupView()
@ -162,23 +157,6 @@ import Foundation
// MARK: - Atomization
//------------------------------------------------------
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject as? MVMCoreUIDelegateObject, additionalData: additionalData)
guard let dictionary = json else { return }
leftTextLabel.setWithJSON(dictionary.optionalDictionaryForKey("leftText"), delegateObject: delegateObject as? MVMCoreUIDelegateObject, additionalData: additionalData)
rightTextLabel.setWithJSON(dictionary.optionalDictionaryForKey("rightText"), delegateObject: delegateObject as? MVMCoreUIDelegateObject, additionalData: additionalData)
if !leftTextLabel.hasText {
constrainRightLabel()
} else if !rightTextLabel.hasText {
constrainLeftLabel()
}
}
//MARK: - MVMCoreMoleculeViewProtocol
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let leftRightLabelModel = model as? LeftRightLabelModel else {

View File

@ -54,22 +54,10 @@ import UIKit
setStyle(.standard)
}
// MARK: - MVMCoreUIMoleculeViewProtocol
open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
// If no type, default to standard.
if let typeString = json?.optionalStringForKey(KeyType), let type = LineModel.Style.init(rawValue: typeString) {
setStyle(type)
} else {
setStyle(.standard)
}
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
}
// MARK: - MoleculeViewProtocol
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
if let lineModel = model as? LineModel {
setStyle(lineModel.type ?? .standard)
setStyle(lineModel.type)
}
super.set(with: model, delegateObject, additionalData)
}
@ -78,13 +66,9 @@ import UIKit
setStyle(.standard)
}
open func copyBackgroundColor() -> Bool {
return false
}
public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
guard let type = json?.optionalStringForKey(KeyType), let style = LineModel.Style(rawValue: type) else { return 1 }
switch style {
public override static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
guard let type = (model as? LineModel)?.type else { return 1 }
switch type {
case .none:
return 0
case .medium:
@ -101,4 +85,8 @@ extension Line: MVMCoreUIViewConstrainingProtocol {
open func needsToBeConstrained() -> Bool {
return true
}
open func copyBackgroundColor() -> Bool {
return false
}
}

View File

@ -8,7 +8,8 @@
import UIKit
@objcMembers open class MFLoadImageView: ViewConstrainingView, ModelMoleculeViewProtocol {
// TODO: Change to container instead of ViewConstraining without breaking legacy stack.
@objcMembers open class MFLoadImageView: ViewConstrainingView, MoleculeViewProtocol {
public let loadingSpinner = MFLoadingSpinner(frame: .zero)
public let imageView = MFTransparentGIFView(frame: .zero)
public var addSizeConstraintsForAspectRatio = false
@ -46,6 +47,13 @@ import UIKit
super.init(coder: aDecoder)
}
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.init(frame: .zero)
addSizeConstraintsForAspectRatio = true
pinEdges(.all)
set(with: model, delegateObject, additionalData)
}
public func pinEdges(_ edge: UIRectEdge) {
edges = edge
if edge == UIRectEdge.all {
@ -211,11 +219,7 @@ import UIKit
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
self.delegateObject = delegateObject
// TODO: Temporary, should be moved to init once we have type erasure ready.
setAsMolecule()
guard let imageModel = model as? ImageViewModel else {
return
}
guard let imageModel = model as? ImageViewModel else { return }
if let accessibilityString = imageModel.accessibilityText {
imageView.accessibilityLabel = accessibilityString
imageView.accessibilityTraits = .staticText
@ -241,40 +245,6 @@ import UIKit
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol functions
open override func setAsMolecule() {
addSizeConstraintsForAspectRatio = true
pinEdges(.all)
}
public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
return json?.optionalCGFloatForKey("height") ?? 0
}
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
self.delegateObject = delegateObject
if let accessibilityString = json?.optionalStringForKey("accessibilityText") {
imageView.accessibilityLabel = accessibilityString
imageView.accessibilityTraits = .staticText
imageView.isAccessibilityElement = true
}
let width = json?.optionalCGFloatForKey("width") ?? imageWidth
let height = json?.optionalCGFloatForKey("height") ?? imageHeight
// For smoother transitions, set constraints that we know immediately.
if let width = width, addSizeConstraintsForAspectRatio {
setWidth(width)
}
if let height = height, addSizeConstraintsForAspectRatio {
setHeight(height)
}
if let imageName = json?.optionalStringForKey("image"), shouldLoadImage(withName: imageName, width: width, height: height) {
imageView.image = nil
imageView.animatedImage = nil
loadImage(withName: imageName, format: json?.optionalStringForKey("imageFormat"), width: width as NSNumber?, height: height as NSNumber?, customFallbackImage: json?.optionalStringForKey("fallbackImage"))
}
}
// MARK: - load functions
public func loadImage(withName imageName: String?, format: String? = nil, width: NSNumber? = nil, height: NSNumber? = nil, customFallbackImage: String? = nil, allowServerParameters: Bool = false, localBundle: Bundle? = nil, completionHandler: MVMCoreGetImageBlock? = nil) {
@ -306,7 +276,7 @@ import UIKit
let fallbackImageName = customFallbackImage ?? MVMCoreUIUtility.localizedImageName("fallback")
if let format = format, format.lowercased().contains("gif") {
// Gifs aren't supported by default and need special handling
MVMCoreCache.shared()?.getGif(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, completionHandler: finishedLoadingBlock)
MVMCoreCache.shared()?.getGif(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, localBundle: localBundle, completionHandler: finishedLoadingBlock)
} else {
MVMCoreCache.shared()?.getImage(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, format: format, localFallbackImageName: fallbackImageName, allowServerQueryParameters: allowServerParameters, localBundle: localBundle, completionHandler: finishedLoadingBlock)
}

View File

@ -63,7 +63,7 @@ import UIKit
}
}
//MARK: - MVMCoreMoleculeViewProtocol
//MARK: - MoleculeViewProtocol
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let multiProgressModel = multiProgressModel else {
@ -81,8 +81,7 @@ import UIKit
progressList = nil
}
public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: MultiProgressBarModel.self) else { return }
set(with: model, delegateObject, additionalData)
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return (model as? MultiProgressBarModel)?.thickness ?? 8
}
}

View File

@ -8,7 +8,7 @@
import Foundation
@objcMembers open class ProgressBar: UIProgressView, MVMCoreViewProtocol, ModelMoleculeViewProtocol, MVMCoreUIMoleculeViewProtocol {
@objcMembers open class ProgressBar: UIProgressView, MVMCoreViewProtocol, MoleculeViewProtocol {
var progressBarModel: ProgressBarModel?
var thickness: CGFloat = 8.0 {
@ -32,7 +32,7 @@ import Foundation
setupView()
}
init() {
public init() {
super.init(frame: .zero)
setupView()
}
@ -50,7 +50,7 @@ import Foundation
public func updateView(_ size: CGFloat) {
}
//MARK: - MVMCoreMoleculeViewProtocol
//MARK: - MoleculeViewProtocol
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let progressBarModel = model as? ProgressBarModel else {
return
@ -63,12 +63,6 @@ import Foundation
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol
public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: ProgressBarModel.self) else { return }
set(with: model, delegateObject, additionalData)
}
public func reset() {
thickness = 8
progress = 0
@ -76,7 +70,7 @@ import Foundation
trackTintColor = UIColor.mfLightSilver()
}
public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 8
}
}

View File

@ -25,13 +25,13 @@ public typealias ActionBlockConfirmation = () -> (Bool)
//--------------------------------------------------
/// Holds the on and off colors for the container.
public var containerTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black)
public var containerTintColor: (on: UIColor?, off: UIColor?)? = (on: .mvmGreen, off: .mvmBlack)
/// Holds the on and off colors for the knob.
public var knobTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white)
public var knobTintColor: (on: UIColor?, off: UIColor?)? = (on: .mvmWhite, off: .mvmWhite)
/// Holds the on and off colors for the disabled state..
public var disabledTintColor: (container: UIColor?, knob: UIColor?)? = (container: .mfSilver(), knob: .white)
public var disabledTintColor: (container: UIColor?, knob: UIColor?)? = (container: .mvmCoolGray3, knob: .mvmWhite)
/// Set this flag to false if you do not want to animate state changes.
public var isAnimated = true
@ -102,7 +102,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
}
(model as? ToggleModel)?.state = isOn
FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
accessibilityValue = isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff")
setNeedsLayout()
layoutIfNeeded()
@ -189,8 +189,11 @@ public typealias ActionBlockConfirmation = () -> (Bool)
public override func setupView() {
super.setupView()
guard subviews.isEmpty else { return }
isAccessibilityElement = true
accessibilityTraits = .button
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccToggleHint")
accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel")
heightConstraint = heightAnchor.constraint(equalToConstant: Self.containerSize.height)
heightConstraint?.isActive = true
@ -214,8 +217,6 @@ public typealias ActionBlockConfirmation = () -> (Bool)
knobTrailingConstraint = trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 1)
knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1)
knobLeadingConstraint?.isActive = true
accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel")
}
public override func reset() {
@ -334,57 +335,38 @@ public typealias ActionBlockConfirmation = () -> (Bool)
}
}
// MARK:- ModelMoleculeViewProtocol
// MARK:- MoleculeViewProtocol
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let toggleModel = model as? ToggleModel else {
return
}
self.model = model
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
let toggleModelJSON = toggleModel.toJSON()
FormValidator.setupValidation(molecule: toggleModel, delegate: delegateObject?.formHolderDelegate)
setWithJSON(toggleModelJSON, delegateObject: delegateObject, additionalData: additionalData)
}
//TODO: change to setWith Model
public override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
guard let dictionary = json else { return }
if let color = dictionary["onTintColor"] as? String {
containerTintColor?.on = UIColor.mfGet(forHex: color)
guard let model = model as? ToggleModel else { return }
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
if let color = model.onTintColor?.uiColor {
containerTintColor?.on = color
}
if let color = dictionary["offTintColor"] as? String {
containerTintColor?.off = UIColor.mfGet(forHex: color)
if let color = model.offTintColor?.uiColor {
containerTintColor?.off = color
}
if let color = dictionary["onKnobTintColor"] as? String {
knobTintColor?.on = UIColor.mfGet(forHex: color)
if let color = model.onKnobTintColor?.uiColor {
knobTintColor?.on = color
}
if let color = dictionary["offKnobTintColor"] as? String {
knobTintColor?.off = UIColor.mfGet(forHex: color)
if let color = model.offKnobTintColor?.uiColor {
knobTintColor?.off = color
}
if let state = dictionary["state"] as? Bool {
changeStateNoAnimation(state)
}
changeStateNoAnimation(model.state)
isAnimated = model.animated
isEnabled = model.enabled
if let actionMap = dictionary.optionalDictionaryForKey("action") {
if let actionMap = model.action?.toJSON() {
didToggleAction = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) }
}
if let isAnimated = dictionary["isAnimated"] as? Bool {
self.isAnimated = isAnimated
}
if let isEnabled = dictionary["isEnabled"] as? Bool{
self.isEnabled = isEnabled
}
}
public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
@ -392,13 +374,8 @@ public typealias ActionBlockConfirmation = () -> (Bool)
}
}
// MARK: - MVMCoreUIMoleculeViewProtocol
// MARK: - MVMCoreUIViewConstrainingProtocol
extension Toggle {
public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat {
return Self.getContainerHeight()
}
public func needsToBeConstrained() -> Bool {
return true

View File

@ -0,0 +1,117 @@
//
// ToggleModel.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 1/14/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableModelProtocol {
public static var identifier: String = "toggle"
public var backgroundColor: Color?
public var state: Bool = false
public var animated: Bool = true
public var enabled: Bool = true
public var action: ActionModelProtocol?
public var alternateAction: ActionModelProtocol?
public var accessibilityText: String?
public var onTintColor: Color?
public var offTintColor: Color?
public var onKnobTintColor: Color?
public var offKnobTintColor: Color?
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
//--------------------------------------------------
// MARK: - Keys
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case state
case animated
case enabled
case action
case backgroundColor
case alternateAction
case accessibilityText
case onTintColor
case offTintColor
case onKnobTintColor
case offKnobTintColor
case fieldKey
case groupName
}
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
public func formFieldValue() -> AnyHashable? {
return state
}
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
public init(_ state: Bool) {
self.state = state
baseValue = state
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
self.state = state
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) {
self.animated = animated
}
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
onTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onTintColor)
offTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offTintColor)
onKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onKnobTintColor)
offKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offKnobTintColor)
accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText)
baseValue = state
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeModelIfPresent(action, forKey: .action)
try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(state, forKey: .state)
try container.encode(animated, forKey: .animated)
try container.encode(enabled, forKey: .enabled)
try container.encodeIfPresent(onTintColor, forKey: .onTintColor)
try container.encodeIfPresent(onKnobTintColor, forKey: .onKnobTintColor)
try container.encodeIfPresent(onKnobTintColor, forKey: .onKnobTintColor)
try container.encodeIfPresent(offKnobTintColor, forKey: .offKnobTintColor)
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
}
}

View File

@ -0,0 +1,196 @@
//
// MVMCoreUIWebView.swift
// MVMCoreUI
//
// Created by Ryan on 8/29/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import UIKit
import WebKit
@objcMembers open class WebView: View, MVMCoreUIViewConstrainingProtocol {
let mvmWebViewMessageHandler = "mvmWebViewMessageHandler"
var webviewModel: WebViewModel? {
return model as? WebViewModel
}
var webView: WKWebView?
var overLayer = MVMCoreUICommonViewsUtility.commonView()
public let loadingSpinner = MFLoadingSpinner(frame: .zero)
var delegateObject: MVMCoreUIDelegateObject?
var webViewHeight: NSLayoutConstraint?
var dynamicHeight: Bool = true
override open func setupView() {
super.setupView()
createWebView(messageHandler: mvmWebViewMessageHandler)
setupOverLayer()
pinSpinnerView()
//init height for loading spinner
webViewHeight = webView?.heightAnchor.constraint(equalToConstant: 44)
webViewHeight?.isActive = true
}
func createWebView(messageHandler: String?) {
let wkUserController = WKUserContentController()
if let messageHandlerName = messageHandler {
wkUserController.add(self, name: messageHandlerName)
}
let wkConfig = WKWebViewConfiguration()
wkConfig.userContentController = wkUserController
let webView = WKWebView(frame: .zero, configuration: wkConfig)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.uiDelegate = self
webView.navigationDelegate = self
self.webView = webView
addSubview(webView)
NSLayoutConstraint.constraintPinSubview(toSuperview: webView)
}
// MARK: - MVMCoreUIMoleculeViewProtocol
override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
//store the previous webview properties
let previousHtmlString = webviewModel?.htmlString
let previousURL = webView?.url
super.set(with: model, delegateObject, additionalData)
self.delegateObject = delegateObject
var webViewHeightConstant: CGFloat = 44
if let height = webviewModel?.height {
webViewHeightConstant = height
webViewHeight?.constant = height
dynamicHeight = false
}
if let url = webviewModel?.url {
if let previousUrl = previousURL, url == previousUrl {
//dont load the new
} else {
webView?.stopLoading()
webView?.load(URLRequest(url: url))
webViewHeight?.constant = webViewHeightConstant
overLayer.isHidden = false
loadingSpinner.resumeSpinner()
}
} else if let htmlString = webviewModel?.htmlString {
if let previousHTML = previousHtmlString, previousHTML == htmlString {
//dont load the new html since they are the same html string as preivous
} else {
webView?.stopLoading()
webViewHeight?.constant = webViewHeightConstant
webView?.loadHTMLString(htmlString, baseURL: nil)
overLayer.isHidden = false
loadingSpinner.resumeSpinner()
}
}
if let borderColor = webviewModel?.borderColor?.cgColor {
webView?.layer.borderWidth = 1.0
webView?.layer.borderColor = borderColor
} else {
webView?.layer.borderWidth = 0.0
webView?.layer.borderColor = UIColor.clear.cgColor
}
}
func pinSpinnerView() {
addSubview(loadingSpinner)
// Setup spinner.
loadingSpinner.clipsToBounds = true
loadingSpinner.translatesAutoresizingMaskIntoConstraints = false
loadingSpinner.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
loadingSpinner.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
loadingSpinner.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
loadingSpinner.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
func setupOverLayer() {
addSubview(overLayer)
overLayer.backgroundColor = .white
NSLayoutConstraint.constraintPinSubview(toSuperview: overLayer)
}
}
// MARK: - WKUIDelegate
extension WebView : WKUIDelegate {
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// hide loading
overLayer.isHidden = true
loadingSpinner.pause()
//update webview's heigth when webview is ready
if !dynamicHeight {
return
}
/* was using "document.readyState" to check the state, while evaluateJavaScript "document.readyState",only works when webview contains userscrpt.otherwise, it would return WKErrorDomain Code=4 "A JavaScript exception occurred".
so webView.isLoading to check load finished state
*/
if !webView.isLoading {
webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { [weak self] (result, error) in
guard let self = self else {
return
}
if let height = result as? CGFloat {
self.webViewHeight?.constant = height
} else {
//if failed to get height from javascript, using scrollview.contensize's height
let scrollHeight = self.webView?.scrollView.contentSize.height
self.webViewHeight?.constant = scrollHeight ?? 44
}
self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self)
})
}
}
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
//actually no error handle page show in webview. We can handle the error display view by our self.
//or stop loading by default
overLayer.isHidden = true
loadingSpinner.pause()
}
}
// MARK: - WKNavigationDelegate
extension WebView : WKNavigationDelegate {
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
//validate request url
//all validated link should be open in safari
if (navigationAction.navigationType == .linkActivated), let urlString = navigationAction.request.url?.absoluteString.removingPercentEncoding, !urlString.contains("#") {
MVMCoreActionHandler.shared()?.openURL(inWebView: navigationAction.request.url, actionInformation: nil, additionalData: nil, delegateObject: nil)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}
// MARK: - WKScriptMessageHandler
extension WebView: WKScriptMessageHandler {
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == mvmWebViewMessageHandler, let text = message.body as? String {
/*
receiving JavaScript func webkit.messageHandlers.{callHandler}.postMessage(body);
if body is string, will decode to actionmodel.
use legacy MVMCoreActionHanlder handleAction method for now
*/
if let data = text.data(using: .utf8) {
do {
let actionMap = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: nil, delegateObject: self.delegateObject)
} catch {
//actionModel should report error when actionhandler is finshed with actionmodel
}
}
}
}
}

View File

@ -0,0 +1,50 @@
//
// WebviewModel.swift
// MVMCoreUI
//
// Created by Kruthika KP on 09/03/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class WebViewModel: MoleculeModelProtocol {
public static var identifier: String = "webview"
public var moleculeName: String = WebViewModel.identifier
public var backgroundColor: Color?
public var url: URL?
public var htmlString: String?
public var height: CGFloat?
public var borderColor: Color?
private enum CodingKeys: String, CodingKey{
case moleculeName
case backgroundColor
case url
case htmlString
case height
case borderColor
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
url = try typeContainer.decodeIfPresent(URL.self, forKey: .url)
htmlString = try typeContainer.decodeIfPresent(String.self, forKey: .htmlString)
if url == nil, htmlString == nil {
throw ModelRegistry.Error.decoderErrorModelNotMapped
}
height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height)
borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(url, forKey: .url)
try container.encodeIfPresent(htmlString, forKey: .htmlString)
try container.encodeIfPresent(height, forKey: .height)
try container.encodeIfPresent(borderColor, forKey: .borderColor)
}
}

View File

@ -0,0 +1,89 @@
//
// NSTextAlignment+Extension.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 3/24/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
/**
When using this class in codable for a String value from server.
Example use case....
var alignment: NSTextAlignment
enum CodingKeys: String, CodingKey {
case alignment
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let word = try container.decode(String.self, forKey: .alignment)
alignment = NSTextAlignment(rawValue: word)!
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(alignment.rawValueString, forKey: .alignment)
}
*/
enum TextAlignmentError: Error {
case notAnAlignment
}
extension NSTextAlignment: RawRepresentable {
init?(rawValue: String) {
switch rawValue {
case "left":
self = .left
case "center":
self = .center
case "right":
self = .right
case "justified":
self = .justified
case "natural":
self = .natural
default:
return nil
}
}
var rawValueString: String {
switch self {
case .left:
return "left"
case .center:
return "center"
case .right:
return "right"
case .justified:
return "justified"
case .natural:
return "natural"
@unknown default:
return ""
}
}
}
extension NSTextAlignment: Codable {
public init(from decoder: Decoder) throws {
let typeContainer = try decoder.singleValueContainer()
let string = try typeContainer.decode(String.self)
guard let alignment = NSTextAlignment(rawValue: string) else {
throw TextAlignmentError.notAnAlignment
}
self = alignment
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValueString)
}
}

View File

@ -0,0 +1,71 @@
//
// UICollectionViewScrollPosition+Extension.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 3/24/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
enum ScrollPositionError: Error {
case notAPosition
}
extension UICollectionView.ScrollPosition: RawRepresentable {
init?(rawValue: String) {
switch rawValue {
case "left", "leading":
self = .left
case "centeredHorizontally", "center":
self = .centeredHorizontally
case "right", "trailing":
self = .right
case "top":
self = .top
case "bottom":
self = .bottom
case "centeredVertically":
self = .centeredVertically
default:
return nil
}
}
var rawValueString: String {
switch self {
case .left:
return "left"
case .centeredHorizontally:
return "centeredHorizontally"
case .right:
return "right"
case .top:
return "top"
case .bottom:
return "bottom"
case .centeredVertically:
return "centeredVertically"
default:
return ""
}
}
}
extension UICollectionView.ScrollPosition: Codable {
public init(from decoder: Decoder) throws {
let typeContainer = try decoder.singleValueContainer()
let string = try typeContainer.decode(String.self)
guard let position = UICollectionView.ScrollPosition(rawValue: string) else {
throw ScrollPositionError.notAPosition
}
self = position
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValueString)
}
}

View File

@ -0,0 +1,201 @@
//
// MoleculeObjectMapping.swift
// MVMCoreUI
//
// Created by Suresh, Kamlesh on 10/28/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class MoleculeObjectMapping: NSObject {
public var moleculeMapping: [String: MoleculeViewProtocol.Type] = [:]
/// Returns the mapping object stored in the singleton
public static func shared() -> Self? {
return MVMCoreActionUtility.initializerClassCheck(CoreUIObject.sharedInstance()?.moleculeMap, classToVerify: self) as? Self
}
/// Registers the model with the model registry and the view with the mapper.
public func register<M: ModelProtocol, V: MoleculeViewProtocol>(viewClass: V.Type, viewModelClass: M.Type) {
try? ModelRegistry.register(viewModelClass)
moleculeMapping.updateValue(viewClass, forKey: viewModelClass.identifier)
}
/// Returns the type of molecule view for the given model
public func getMoleculeClass(_ model: MoleculeModelProtocol) -> MoleculeViewProtocol.Type? {
return moleculeMapping[model.moleculeName]
}
/// Creates a molecule with the given model.
public func createMolecule(_ model: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> MoleculeViewProtocol? {
guard let type = moleculeMapping[model.moleculeName] else { return nil }
return type.init(model: model, delegateObject, additionalData)
}
/// Call to register all of the CoreUI molecules.
public static func registerObjects() {
// Stacks
MoleculeObjectMapping.shared()?.register(viewClass: MoleculeStackView.self, viewModelClass: MoleculeStackModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: Stack<StackModel>.self, viewModelClass: StackModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: UnOrderedList.self, viewModelClass: UnOrderedListModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: NumberedList.self, viewModelClass: NumberedListModel.self)
// Label
MoleculeObjectMapping.shared()?.register(viewClass: Label.self, viewModelClass: LabelModel.self)
// need to move labelattributemodel to different method
try? ModelRegistry.register(LabelAttributeFontModel.self)
try? ModelRegistry.register(LabelAttributeColorModel.self)
try? ModelRegistry.register(LabelAttributeImageModel.self)
try? ModelRegistry.register(LabelAttributeUnderlineModel.self)
try? ModelRegistry.register(LabelAttributeStrikeThroughModel.self)
try? ModelRegistry.register(LabelAttributeActionModel.self)
// Buttons
MoleculeObjectMapping.shared()?.register(viewClass: PillButton.self, viewModelClass: ButtonModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: TwoButtonView.self, viewModelClass: TwoButtonViewModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ExternalLink.self, viewModelClass: ExternalLinkModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: Link.self, viewModelClass: LinkModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: CaretLink.self, viewModelClass: CaretLinkModel.self)
// Entry Field
MoleculeObjectMapping.shared()?.register(viewClass: TextEntryField.self, viewModelClass: TextEntryFieldModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: MdnEntryField.self, viewModelClass: MdnEntryFieldModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: DigitEntryField.self, viewModelClass: DigitEntryFieldModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ItemDropdownEntryField.self, viewModelClass: ItemDropdownEntryFieldModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: DateDropdownEntryField.self, viewModelClass: DateDropdownEntryFieldModel.self)
// Selectors
MoleculeObjectMapping.shared()?.register(viewClass: RadioButton.self, viewModelClass: RadioButtonModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: RadioBoxes.self, viewModelClass: RadioBoxesModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: Checkbox.self, viewModelClass: CheckboxModel.self)
// Other Atoms
MoleculeObjectMapping.shared()?.register(viewClass: ProgressBar.self, viewModelClass: ProgressBarModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: MultiProgress.self, viewModelClass: MultiProgressBarModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: CaretView.self, viewModelClass: CaretViewModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: DashLine.self, viewModelClass: DashLineModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: MFLoadImageView.self, viewModelClass: ImageViewModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: Line.self, viewModelClass: LineModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: GraphView.self, viewModelClass: CircleProgressModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: Toggle.self, viewModelClass: ToggleModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: CheckboxLabel.self, viewModelClass: CheckboxLabelModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: Arrow.self, viewModelClass: ArrowModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: RadioButtonLabel.self, viewModelClass: RadioButtonLabelModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: WebView.self, viewModelClass: WebViewModel.self)
// Horizontal Combination Molecules
MoleculeObjectMapping.shared()?.register(viewClass: StringAndMoleculeView.self, viewModelClass: StringAndMoleculeModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ImageHeadlineBody.self, viewModelClass: ImageHeadlineBodyModel.self)
// Vertical Combination Molecules
MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBody.self, viewModelClass: HeadlineBodyModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: HeadLineBodyCaretLinkImage.self, viewModelClass: HeadlineBodyCaretLinkImageModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: EyebrowHeadlineBodyLink.self, viewModelClass: EyebrowHeadlineBodyLinkModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBodyLink.self, viewModelClass: HeadlineBodyLinkModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBodyButton.self, viewModelClass: HeadlineBodyButtonModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: BGImageHeadlineBodyButton.self, viewModelClass: BGImageHeadlineBodyButtonModel.self)
// Left Right Molecules
MoleculeObjectMapping.shared()?.register(viewClass: CornerLabels.self, viewModelClass: CornerLabelsModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: LeftRightLabelView.self, viewModelClass: LeftRightLabelModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: LabelToggle.self, viewModelClass: LabelToggleModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBodyToggle.self, viewModelClass: HeadlineBodyToggleModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBodyLinkToggle.self, viewModelClass: HeadlineBodyLinkToggleModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ActionDetailWithImage.self, viewModelClass: ActionDetailWithImageModel.self)
// List items
MoleculeObjectMapping.shared()?.register(viewClass: MoleculeTableViewCell.self, viewModelClass: MoleculeListItemModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: DropDownFilterTableViewCell.self, viewModelClass: DropDownListItemModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: AccordionMoleculeTableViewCell.self, viewModelClass: AccordionListItemModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: TabsTableViewCell.self, viewModelClass: TabsListItemModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListProgressBarData.self, viewModelClass: ListProgressBarDataModel.self)
// Other Items
MoleculeObjectMapping.shared()?.register(viewClass: MoleculeStackItem.self, viewModelClass: MoleculeStackItemModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: StackItem.self, viewModelClass: StackItemModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: MoleculeCollectionViewCell.self, viewModelClass: MoleculeCollectionItemModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: CarouselItem.self, viewModelClass: CarouselItemModel.self)
// Other Container Molecules
MoleculeObjectMapping.shared()?.register(viewClass: MoleculeHeaderView.self, viewModelClass: MoleculeHeaderModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: FooterView.self, viewModelClass: FooterModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: Scroller.self, viewModelClass: ScrollerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ModuleMolecule.self, viewModelClass: ModuleMoleculeModel.self)
// Other Molecules
MoleculeObjectMapping.shared()?.register(viewClass: DoughnutChartView.self, viewModelClass: DoughnutChartModel.self)
// Other Organisms
MoleculeObjectMapping.shared()?.register(viewClass: Carousel.self, viewModelClass: CarouselModel.self)
// Designed List Items
MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableIconWithRightCaret.self, viewModelClass: ListLeftVariableIconWithRightCaretModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableCheckboxAllTextAndLinks.self, viewModelClass: ListLeftVariableCheckboxAllTextAndLinksModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableRadioButtonAndPaymentMethod.self, viewModelClass: ListLeftVariableRadioButtonAndPaymentMethodModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableRadioButtonBodyText.self, viewModelClass: ListLeftVariableRadioButtonBodyTextModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListRVWheel.self, viewModelClass: ListRVWheelModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariablePayments.self, viewModelClass: ListRightVariablePaymentsModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariableTotalData.self, viewModelClass: ListRightVariableTotalDataModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariableTextLinkAllTextAndLinks.self, viewModelClass: ListRightVariableTextLinkAllTextAndLinksModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariableButtonAllTextAndLinks.self, viewModelClass: ListRightVariableButtonAllTextAndLinksModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListOneColumnFullWidthTextAllTextAndLinks.self, viewModelClass: ListOneColumnFullWidthTextAllTextAndLinksModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListOneColumnFullWidthTextBodyText.self, viewModelClass: ListOneColumnFullWidthTextBodyTextModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListTwoColumnCompareChanges.self, viewModelClass: ListTwoColumnCompareChangesModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListTwoColumnPriceDetails.self, viewModelClass: ListTwoColumnPriceDetailsModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListTwoColumnPriceDescription.self, viewModelClass: ListTwoColumnPriceDescriptionModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnInternationalData.self, viewModelClass: ListThreeColumnInternationalDataModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListFourColumnDataUsageListItem.self, viewModelClass: ListFourColumnDataUsageListItemModel.self)
// Designed Section Dividers
MoleculeObjectMapping.shared()?.register(viewClass: ListFourColumnDataUsageDivider.self, viewModelClass: ListFourColumnDataUsageDividerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnPlanDataDivider.self, viewModelClass: ListThreeColumnPlanDataDividerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListOneColumnTextWithWhitespaceDividerShort.self, viewModelClass: ListOneColumnTextWithWhitespaceDividerShortModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListOneColumnTextWithWhitespaceDividerTall.self, viewModelClass: ListOneColumnTextWithWhitespaceDividerTallModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListOneColumnFullWidthTextDividerSubsection.self, viewModelClass: ListOneColumnFullWidthTextDividerSubsectionModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListTwoColumnSubsectionDivider.self, viewModelClass: ListTwoColumnSubsectionDividerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnInternationalDataDivider.self, viewModelClass: ListThreeColumnInternationalDataDividerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnSpeedTestDivider.self, viewModelClass: ListThreeColumnSpeedTestDividerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnBillChangesDivider.self, viewModelClass: ListThreeColumnBillChangesDividerModel.self)
MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnDataUsageDivider.self, viewModelClass: ListThreeColumnDataUsageDividerModel.self)
// Designed Headers
MoleculeObjectMapping.shared()?.register(viewClass: HeadersH2NoButtonsBodyText.self, viewModelClass: HeadersH2NoButtonsBodyTextModel.self)
// TODO: Need View
try? ModelRegistry.register(TabsModel.self)
// Helper models
try? ModelRegistry.register(RuleRequiredModel.self)
try? ModelRegistry.register(RuleAnyRequiredModel.self)
try? ModelRegistry.register(RuleAnyValueChangedModel.self)
try? ModelRegistry.register(RuleAllValueChangedModel.self)
try? ModelRegistry.register(RuleEqualsModel.self)
try? ModelRegistry.register(RuleRegexModel.self)
// Actions
try? ModelRegistry.register(ActionTopAlertModel.self)
try? ModelRegistry.register(ActionCollapseNotificationModel.self)
try? ModelRegistry.register(ActionOpenPanelModel.self)
}
/// Convenience function to get required modules for a give model
public static func getRequiredModules(for model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
guard let model = model else { return nil }
return MoleculeObjectMapping.shared()?.getMoleculeClass(model)?.requiredModules(with: model, delegateObject, error: error)
}
/// Convenience function to add require modules for the given model to the passed in array.
public static func addRequiredModules(for model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, moduleList: inout [String]?, errorList: inout [MVMCoreErrorObject]?) {
guard let model = model else { return }
let error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>? = nil
if let modules = getRequiredModules(for: model, delegateObject, error: error) {
moduleList?.append(contentsOf: modules)
}
if let error = error?.pointee {
errorList?.append(error)
}
}
}

View File

@ -0,0 +1,77 @@
//
// ListFourColumnDataUsageListItem.swift
// MVMCoreUI
//
// Created by Kruthika KP on 27/03/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers public class ListFourColumnDataUsageListItem: TableViewCell {
//-----------------------------------------------------
// MARK: - Outlets
//-----------------------------------------------------
var stack: Stack<StackModel>
let label1 = Label.commonLabelB2(true)
let label2 = Label.commonLabelB2(true)
let label3 = Label.commonLabelB2(true)
let label4 = Label.commonLabelB2(true)
let arrow = Arrow(frame: .zero)
let arrowAndLabel2Stack: Stack<StackModel>
//-----------------------------------------------------
// MARK: - Initializers
//-----------------------------------------------------
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
arrowAndLabel2Stack = Stack<StackModel>.createStack(with: [(view: arrow, model: StackItemModel(horizontalAlignment: .fill)),
(view: label2, model: StackItemModel(horizontalAlignment: .leading))],
axis: .horizontal, spacing: 4)
label2.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal)
label2.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .horizontal)
stack = Stack<StackModel>.createStack(with: [(view: label1, model: StackItemModel(percent: 19, horizontalAlignment: .leading)),
(view: arrowAndLabel2Stack, model: StackItemModel(percent: 44, horizontalAlignment: .fill)),
(view: label3, model: StackItemModel(percent:17,horizontalAlignment: .leading)),
(view: label4, model: StackItemModel(percent:20,horizontalAlignment: .leading))],
axis: .horizontal,spacing: 8)
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//-----------------------------------------------------
// MARK: - View Lifecycle
//-----------------------------------------------------
override open func setupView() {
super.setupView()
addMolecule(stack)
arrow.pinHeightAndWidth()
arrowAndLabel2Stack.restack()
stack.restack()
}
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? ListFourColumnDataUsageListItemModel else { return }
label1.set(with: model.label1, delegateObject, additionalData)
label2.set(with: model.label2, delegateObject, additionalData)
label3.set(with: model.label3, delegateObject, additionalData)
label4.set(with: model.label4, delegateObject, additionalData)
arrow.set(with: model.arrow, delegateObject, additionalData)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 121
}
open override func reset() {
super.reset()
label1.styleB2(true)
label2.styleB2(true)
label3.styleB2(true)
label4.styleB2(true)
}
}

View File

@ -0,0 +1,57 @@
//
// ListFourColumnDataUsageListItemModel.swift
// MVMCoreUI
//
// Created by Kruthika KP on 27/03/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
public class ListFourColumnDataUsageListItemModel: ListItemModel, MoleculeModelProtocol {
public static var identifier: String = "list4C"
public var label1: LabelModel
public var arrow: ArrowModel
public var label2: LabelModel
public var label3: LabelModel
public var label4: LabelModel
public init(label1:LabelModel, label2:LabelModel, label3:LabelModel,label4:LabelModel, arrow:ArrowModel) {
self.label1 = label1
self.label2 = label2
self.label3 = label3
self.label4 = label4
self.arrow = arrow
super.init()
}
private enum CodingKeys: String, CodingKey {
case moleculeName
case label1
case label2
case label3
case label4
case arrow
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
label1 = try typeContainer.decode(LabelModel.self, forKey: .label1)
label2 = try typeContainer.decode(LabelModel.self, forKey: .label2)
label3 = try typeContainer.decode(LabelModel.self, forKey: .label3)
label4 = try typeContainer.decode(LabelModel.self, forKey: .label4)
arrow = try typeContainer.decode(ArrowModel.self, forKey: .arrow)
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(label1, forKey: .label1)
try container.encode(label2, forKey: .label2)
try container.encode(label3, forKey: .label3)
try container.encode(label4, forKey: .label4)
try container.encode(arrow, forKey: .arrow)
}
}

View File

@ -9,11 +9,18 @@
import Foundation
@objcMembers open class ListLeftVariableCheckboxAllTextAndLinks: TableViewCell {
public let checkbox = Checkbox(frame: .zero)
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public let checkbox = Checkbox()
public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(frame: .zero)
public var stack: Stack<StackModel>
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
stack = Stack<StackModel>.createStack(with: [(view: checkbox, model: StackItemModel(horizontalAlignment: .fill)),
(view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .leading))],
@ -25,17 +32,25 @@ import Foundation
fatalError("init(coder:) has not been implemented")
}
// MARK: - View Lifecycle
//--------------------------------------------------
// MARK: - Life Cycle
//--------------------------------------------------
override open func setupView() {
super.setupView()
addMolecule(stack)
stack.restack()
}
// MARK:- MVMCoreUIMoleculeViewProtocol
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
//--------------------------------------------------
// MARK: - MVMCoreUIMoleculeViewProtocol
//--------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? ListLeftVariableCheckboxAllTextAndLinksModel else { return}
guard let model = model as? ListLeftVariableCheckboxAllTextAndLinksModel else { return }
checkbox.set(with: model.checkbox, delegateObject, additionalData)
eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData)
}

View File

@ -8,12 +8,13 @@
import UIKit
@objcMembers open class ListLeftVariableRadioButtonAndPaymentMethod: TableViewCell {
//-----------------------------------------------------
// MARK: - Outlets
//-----------------------------------------------------
let radioButton = RadioButton(frame: .zero)
let radioButton = RadioButton()
let leftImage = MFLoadImageView(pinnedEdges: .all)
let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink()
var stack: Stack<StackModel>
@ -21,6 +22,7 @@ import UIKit
//-----------------------------------------------------
// MARK: - Initializers
//-----------------------------------------------------
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
stack = Stack<StackModel>.createStack(with: [(view: radioButton, model: StackItemModel(horizontalAlignment: .fill)),
(view: leftImage, model: StackItemModel(horizontalAlignment: .fill)),
@ -36,8 +38,10 @@ import UIKit
//-----------------------------------------------------
// MARK: - View Lifecycle
//-----------------------------------------------------
override open func setupView() {
super.setupView()
leftImage.addSizeConstraintsForAspectRatio = true
addMolecule(stack)
stack.restack()
eyebrowHeadlineBodyLink.body.textColor = .mvmOrangeAA
@ -53,7 +57,8 @@ import UIKit
//----------------------------------------------------
// MARK: - Molecule
//----------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? ListLeftVariableRadioButtonAndPaymentMethodModel else { return}
radioButton.set(with: model.radioButton, delegateObject, additionalData)
@ -64,4 +69,11 @@ import UIKit
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 90
}
public override func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
if radioButton.isEnabled {
radioButton.tapAction()
}
}
}

View File

@ -0,0 +1,67 @@
//
// ListLeftVariableRadioButtonBodyText.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 4/1/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import UIKit
open class ListLeftVariableRadioButtonBodyText: TableViewCell {
//-----------------------------------------------------
// MARK: - Outlets
//-----------------------------------------------------
let radioButton = RadioButton(frame: .zero)
let headlineBody = HeadlineBody()
var stack: Stack<StackModel>
//-----------------------------------------------------
// MARK: - Initializers
//-----------------------------------------------------
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
stack = Stack<StackModel>.createStack(with: [(view: radioButton, model: StackItemModel(horizontalAlignment: .fill)),
(view: headlineBody, model: StackItemModel(horizontalAlignment: .leading))],
axis: .horizontal)
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//-----------------------------------------------------
// MARK: - View Lifecycle
//-----------------------------------------------------
override open func setupView() {
super.setupView()
addMolecule(stack)
stack.restack()
}
//----------------------------------------------------
// MARK: - Molecule
//----------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? ListLeftVariableRadioButtonBodyTextModel else { return }
radioButton.set(with: model.radioButton, delegateObject, additionalData)
headlineBody.set(with: model.headlineBody, delegateObject, additionalData)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 90
}
public override func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
radioButton.tapAction()
}
}

View File

@ -0,0 +1,68 @@
//
// ListLeftVariableRadioButtonBodyTextModel.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 4/1/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
open class ListLeftVariableRadioButtonBodyTextModel: ListItemModel, MoleculeModelProtocol {
//-----------------------------------------------------
// MARK: - Properties
//-----------------------------------------------------
public static var identifier: String = "listLVRBBdy"
public var radioButton: RadioButtonModel
public var headlineBody: HeadlineBodyModel
//-----------------------------------------------------
// MARK: - Initializer
//-----------------------------------------------------
public init(radioButton: RadioButtonModel, headlineBody: HeadlineBodyModel) {
self.radioButton = radioButton
self.headlineBody = headlineBody
super.init()
}
//-----------------------------------------------------
// MARK: - Methods
//-----------------------------------------------------
open override func setDefaults() {
super.setDefaults()
headlineBody.style = .item
}
//-----------------------------------------------------
// MARK: - Keys
//-----------------------------------------------------
private enum CodingKeys: String, CodingKey {
case moleculeName
case radioButton
case headlineBody
}
//-----------------------------------------------------
// MARK: - Codec
//-----------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
radioButton = try typeContainer.decode(RadioButtonModel.self, forKey: .radioButton)
headlineBody = try typeContainer.decode(HeadlineBodyModel.self, forKey: .headlineBody)
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(radioButton, forKey: .radioButton)
try container.encode(headlineBody, forKey: .headlineBody)
}
}

View File

@ -20,7 +20,7 @@ public class ListOneColumnFullWidthTextBodyTextModel: ListItemModel, MoleculeMod
// Defaults to set
override public func setDefaults() {
super.setDefaults()
headlineBody.style = "item"
headlineBody.style = .item
}
private enum CodingKeys: String, CodingKey {

View File

@ -39,7 +39,7 @@ import Foundation
}
//-------------------------------------------------
// MARK: - ModelMoleculeViewProtocol
// MARK: - MoleculeViewProtocol
//-------------------------------------------------
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
super.set(with: model, delegateObject, additionalData)
@ -50,7 +50,7 @@ import Foundation
}
//-------------------------------------------------
// MARK: - MVMCoreUIMoleculeViewProtocol
// MARK: - MoleculeViewProtocol
//-------------------------------------------------
open override func reset() {
super.reset()

View File

@ -0,0 +1,51 @@
//
// ListRightVariableButtonAllTextAndLinks.swift
// MVMCoreUI
//
// Created by Dhamodaram Nandi on 17/03/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
@objcMembers open class ListRightVariableButtonAllTextAndLinks: TableViewCell {
//-----------------------------------------------------
// MARK: - Outlets
//-----------------------------------------------------
public var stack: Stack<StackModel>
public let button = PillButton(frame: .zero)
public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(frame: .zero)
// MARK: - Initializers
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
stack = Stack<StackModel>.createStack(with: [(view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .leading)),
(view: button, model: StackItemModel(horizontalAlignment:.fill))],
axis: .horizontal)
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//-----------------------------------------------------
// MARK: - View Lifecycle
//-----------------------------------------------------
override open func setupView() {
super.setupView()
addMolecule(stack)
stack.restack()
}
open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? ListRightVariableButtonAllTextAndLinksModel else { return }
button.set(with: model.button, delegateObject, additionalData)
eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData)
}
open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return 90
}
}

View File

@ -0,0 +1,49 @@
//
// ListRightVariableButtonAllTextAndLinksModel.swift
// MVMCoreUI
//
// Created by Dhamodaram Nandi on 17/03/20.
// Copyright © 2020 Verizon Wireless. All rights reserved.
//
import Foundation
public class ListRightVariableButtonAllTextAndLinksModel: ListItemModel, MoleculeModelProtocol {
public static var identifier: String = "listRVBtn"
public var button: ButtonModel
public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel
public init(button: ButtonModel, eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel) {
self.button = button
self.eyebrowHeadlineBodyLink = eyebrowHeadlineBodyLink
super.init()
}
/// Defaults to set
override public func setDefaults() {
super.setDefaults()
self.button.size = .tiny
self.button.style = ButtonStyle.secondary
}
private enum CodingKeys: String, CodingKey {
case moleculeName
case button
case eyebrowHeadlineBodyLink
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
button = try typeContainer.decode(ButtonModel.self, forKey: .button)
eyebrowHeadlineBodyLink = try typeContainer.decode(EyebrowHeadlineBodyLinkModel.self, forKey: .eyebrowHeadlineBodyLink)
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(button, forKey: .button)
try container.encode(eyebrowHeadlineBodyLink, forKey: .eyebrowHeadlineBodyLink)
}
}

View File

@ -36,6 +36,7 @@ import Foundation
//-------------------------------------------------------
override open func setupView() {
super.setupView()
rightImage.addSizeConstraintsForAspectRatio = true
addMolecule(stack)
stack.restack()
}

Some files were not shown because too many files have changed in this diff Show More