Compare commits
2 Commits
develop
...
stash/atom
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25730bbda3 | ||
|
|
104c35fa21 |
@ -578,6 +578,7 @@
|
||||
EA17584A2BC97EF100A5C0D9 /* BadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1758492BC97EF100A5C0D9 /* BadgeIndicatorModel.swift */; };
|
||||
EA17584C2BC9894800A5C0D9 /* ButtonIconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */; };
|
||||
EA17584E2BC9895A00A5C0D9 /* ButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */; };
|
||||
EA1B02DA2C407BD600F0758B /* LegacyTextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1B02D92C407BD600F0758B /* LegacyTextEntryField.swift */; };
|
||||
EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; };
|
||||
EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; };
|
||||
EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; };
|
||||
@ -1199,6 +1200,7 @@
|
||||
EA1758492BC97EF100A5C0D9 /* BadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeIndicatorModel.swift; sourceTree = "<group>"; };
|
||||
EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconModel.swift; sourceTree = "<group>"; };
|
||||
EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIcon.swift; sourceTree = "<group>"; };
|
||||
EA1B02D92C407BD600F0758B /* LegacyTextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyTextEntryField.swift; sourceTree = "<group>"; };
|
||||
EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = "<group>"; };
|
||||
EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = "<group>"; };
|
||||
EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = "<group>"; };
|
||||
@ -2350,6 +2352,7 @@
|
||||
0A21DB7E235DECC500C160A2 /* EntryField.swift */,
|
||||
0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */,
|
||||
0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */,
|
||||
EA1B02D92C407BD600F0758B /* LegacyTextEntryField.swift */,
|
||||
0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */,
|
||||
0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */,
|
||||
0A8321AE2355FE9500CB7F00 /* DigitBox.swift */,
|
||||
@ -2845,6 +2848,7 @@
|
||||
942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */,
|
||||
BB47A588241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift in Sources */,
|
||||
012A88C8238DB02000FE3DA1 /* MoleculeDelegateProtocol.swift in Sources */,
|
||||
EA1B02DA2C407BD600F0758B /* LegacyTextEntryField.swift in Sources */,
|
||||
8D8067D12444472F00203BE8 /* ListRightVariablePriceChangeAllTextAndLinksModel.swift in Sources */,
|
||||
0A7EF86123D8AC2500B2AAD1 /* DigitEntryFieldModel.swift in Sources */,
|
||||
D224798C231450C8003FCCF9 /* HeadlineBodyToggle.swift in Sources */,
|
||||
|
||||
@ -11,7 +11,7 @@ import UIKit
|
||||
/**
|
||||
* Subclass of TextEntryField as it is to use similar logic as a singular textField but appear separate..
|
||||
*/
|
||||
@objcMembers open class DigitEntryField: TextEntryField, DigitBoxProtocol {
|
||||
@objcMembers open class DigitEntryField: LegacyTextEntryField, DigitBoxProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Stored Properties
|
||||
//--------------------------------------------------
|
||||
@ -345,9 +345,9 @@ import UIKit
|
||||
|
||||
numberOfDigits = model.digits
|
||||
|
||||
if let entryType = model.type {
|
||||
setAsSecureTextEntry(entryType == .secure || entryType == .password)
|
||||
}
|
||||
let entryType = model.type
|
||||
setAsSecureTextEntry(entryType == .secure || entryType == .password)
|
||||
|
||||
|
||||
let observingDelegate = delegateObject?.observingTextFieldDelegate ?? self
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ import MVMCore
|
||||
This class is intended to be subclassed.
|
||||
See ItemDropdownEntryField and DateDropdownEntryField.
|
||||
*/
|
||||
@objcMembers open class BaseDropdownEntryField: TextEntryField {
|
||||
@objcMembers open class BaseDropdownEntryField: LegacyTextEntryField {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Outlets
|
||||
//--------------------------------------------------
|
||||
@ -48,7 +48,10 @@ import MVMCore
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc public convenience init() {
|
||||
self.init(frame: .zero)
|
||||
}
|
||||
|
||||
@objc public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
@ -47,51 +47,9 @@ import MVMCore
|
||||
get { MVMCoreUIUtility.removeMdnFormat(text) }
|
||||
set { text = MVMCoreUIUtility.formatMdn(newValue) }
|
||||
}
|
||||
|
||||
/// Toggles selected or original (unselected) UI.
|
||||
public override var isSelected: Bool {
|
||||
get { return entryFieldContainer.isSelected }
|
||||
set (selected) {
|
||||
if selected && showError {
|
||||
showError = false
|
||||
}
|
||||
|
||||
super.isSelected = selected
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc public override init(frame: CGRect) {
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
@objc public convenience init() {
|
||||
self.init(frame: .zero)
|
||||
}
|
||||
|
||||
@objc required public init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
fatalError("MdnEntryField xib not supported.")
|
||||
}
|
||||
|
||||
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
super.init(model: model, delegateObject, additionalData)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Setup
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc public override func setupFieldContainerContent(_ container: UIView) {
|
||||
super.setupFieldContainerContent(container)
|
||||
|
||||
textField.keyboardType = .numberPad
|
||||
}
|
||||
|
||||
open override func setupTextFieldToolbar() {
|
||||
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
|
||||
let toolbar = UIToolbar.createEmptyToolbar()
|
||||
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
|
||||
@ -99,12 +57,17 @@ import MVMCore
|
||||
let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFieldInput))
|
||||
toolbar.items = [contacts, space, dismissButton]
|
||||
textField.inputAccessoryView = toolbar
|
||||
|
||||
}
|
||||
|
||||
open override var viewModel: TextEntryFieldModel! {
|
||||
didSet {
|
||||
viewModel.type = .phone
|
||||
}
|
||||
}
|
||||
//--------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc public func hasValidMDN() -> Bool {
|
||||
|
||||
guard let MDN = mdn, !MDN.isEmpty else { return false }
|
||||
@ -129,14 +92,14 @@ import MVMCore
|
||||
showError = false
|
||||
|
||||
} else {
|
||||
entryFieldModel?.errorMessage = entryFieldModel?.errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message")
|
||||
viewModel?.errorMessage = viewModel?.errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message")
|
||||
showError = true
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: textField)
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
|
||||
@objc public func getContacts(_ sender: Any?) {
|
||||
|
||||
let picker = CNContactPickerViewController()
|
||||
@ -144,20 +107,15 @@ import MVMCore
|
||||
picker.displayedPropertyKeys = ["phoneNumbers"]
|
||||
picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0")
|
||||
picker.predicateForSelectionOfProperty = NSPredicate(format: "key == 'phoneNumbers'")
|
||||
Task(priority: .userInitiated) {
|
||||
await NavigationHandler.shared().present(viewController: picker, animated: true)
|
||||
if let topViewController = UIApplication.topViewController() {
|
||||
topViewController.present(picker, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - MoleculeViewProtocol
|
||||
//--------------------------------------------------
|
||||
|
||||
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
super.set(with: model, delegateObject, additionalData)
|
||||
|
||||
textField.keyboardType = .phonePad
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Contact Picker Delegate
|
||||
@ -168,21 +126,11 @@ import MVMCore
|
||||
if let phoneNumber = contactProperty.value as? CNPhoneNumber {
|
||||
|
||||
let MDN = phoneNumber.stringValue
|
||||
var unformattedMDN = MVMCoreUIUtility.removeMdnFormat(MDN)
|
||||
|
||||
// Sometimes user add extra 1 in front of mdn in their address book
|
||||
if isNationalMDN,
|
||||
let unformedMDN = unformattedMDN,
|
||||
unformedMDN.count == 11,
|
||||
unformedMDN[(unformedMDN.index(unformedMDN.startIndex, offsetBy: 0))] == "1" {
|
||||
|
||||
let startIndex = unformedMDN.index(unformedMDN.startIndex, offsetBy: 1)
|
||||
unformattedMDN = String(unformedMDN[startIndex...])
|
||||
}
|
||||
|
||||
let unformattedMDN = MVMCoreUIUtility.removeMdnFormat(MDN)
|
||||
text = unformattedMDN
|
||||
textFieldShouldReturn(textField)
|
||||
textFieldDidEndEditing(textField)
|
||||
_ = resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,51 +138,49 @@ import MVMCore
|
||||
// MARK: - Implemented TextField Delegate
|
||||
//--------------------------------------------------
|
||||
|
||||
@discardableResult
|
||||
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
|
||||
textField.resignFirstResponder()
|
||||
|
||||
return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true
|
||||
}
|
||||
|
||||
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
|
||||
if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) {
|
||||
return false
|
||||
}
|
||||
|
||||
return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true
|
||||
}
|
||||
|
||||
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
|
||||
textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text)
|
||||
proprietorTextDelegate?.textFieldDidBeginEditing?(textField)
|
||||
}
|
||||
|
||||
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
|
||||
proprietorTextDelegate?.textFieldDidEndEditing?(textField)
|
||||
|
||||
if validateMDNTextField() {
|
||||
if isNationalMDN {
|
||||
textField.text = MVMCoreUIUtility.formatMdn(textField.text)
|
||||
}
|
||||
// Validate the base input field along with triggering form field validation rules.
|
||||
validateText()
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
||||
proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
|
||||
}
|
||||
|
||||
@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
|
||||
proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
|
||||
}
|
||||
|
||||
@objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
|
||||
proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true
|
||||
}
|
||||
// @discardableResult
|
||||
// @objc public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
//
|
||||
// textField.resignFirstResponder()
|
||||
//
|
||||
// return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true
|
||||
// }
|
||||
//
|
||||
// @objc public override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
//
|
||||
// if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) {
|
||||
// return false
|
||||
// }
|
||||
//
|
||||
// return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true
|
||||
// }
|
||||
//
|
||||
// @objc public override func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
//
|
||||
// textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text)
|
||||
// proprietorTextDelegate?.textFieldDidBeginEditing?(textField)
|
||||
// }
|
||||
//
|
||||
// @objc public override func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
//
|
||||
// proprietorTextDelegate?.textFieldDidEndEditing?(textField)
|
||||
//
|
||||
// if validateMDNTextField() {
|
||||
// if isNationalMDN {
|
||||
// textField.text = MVMCoreUIUtility.formatMdn(textField.text)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @objc public override func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
||||
// proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
|
||||
// }
|
||||
//
|
||||
// @objc public override func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
|
||||
// proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
|
||||
// }
|
||||
//
|
||||
// @objc public override func textFieldShouldClear(_ textField: UITextField) -> Bool {
|
||||
// proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true
|
||||
// }
|
||||
}
|
||||
|
||||
@ -7,44 +7,46 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import VDS
|
||||
|
||||
@objc public protocol ObservingTextFieldDelegate {
|
||||
/// Called when the entered text becomes valid based on the validation block
|
||||
@objc optional func isValid(textfield: TextEntryField?)
|
||||
@objc optional func isValid(textfield: EntryField?)
|
||||
/// Called when the entered text becomes invalid based on the validation block
|
||||
@objc optional func isInvalid(textfield: TextEntryField?)
|
||||
@objc optional func isInvalid(textfield: EntryField?)
|
||||
/// Dismisses the keyboard.
|
||||
@objc optional func dismissFieldInput(_ sender: Any?)
|
||||
}
|
||||
|
||||
|
||||
@objcMembers open class TextEntryField: EntryField, UITextFieldDelegate, ObservingTextFieldDelegate {
|
||||
@objcMembers open class TextEntryField: VDS.InputField, VDSMoleculeViewProtocol, ObservingTextFieldDelegate, ViewMaskingProtocol {
|
||||
|
||||
//------------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//------------------------------------------------------
|
||||
open var viewModel: TextEntryFieldModel!
|
||||
open var delegateObject: MVMCoreUIDelegateObject?
|
||||
open var additionalData: [AnyHashable : Any]?
|
||||
|
||||
// Form Validation
|
||||
var fieldKey: String?
|
||||
var fieldValue: JSONValue?
|
||||
var groupName: String?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Outlets
|
||||
// MARK: - Stored Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
open private(set) var textField: TextField = {
|
||||
let textField = TextField()
|
||||
textField.isAccessibilityElement = true
|
||||
textField.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
textField.font = Styler.Font.RegularBodyLarge.getFont()
|
||||
textField.textColor = .mvmBlack
|
||||
textField.smartQuotesType = .no
|
||||
textField.smartDashesType = .no
|
||||
textField.smartInsertDeleteType = .no
|
||||
return textField
|
||||
}()
|
||||
|
||||
public lazy var errorImage: UIImageView = {
|
||||
let image = MVMCoreUIUtility.imageNamed("alert_standard")
|
||||
let imageView = UIImageView(image: image)
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
imageView.widthAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
public var isValid: Bool = true
|
||||
|
||||
/// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well.
|
||||
private weak var proprietorTextDelegate: UITextFieldDelegate?
|
||||
|
||||
private var isEditting: Bool = false {
|
||||
didSet {
|
||||
viewModel.selected = isEditting
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Stored Properties
|
||||
//--------------------------------------------------
|
||||
@ -52,52 +54,19 @@ import UIKit
|
||||
private var observingForChange: Bool = false
|
||||
|
||||
/// Validate when user resigns editing. Default: true
|
||||
public var validateWhenDoneEditing: Bool = true
|
||||
|
||||
public var textEntryFieldModel: TextEntryFieldModel? { model as? TextEntryFieldModel }
|
||||
open var validateWhenDoneEditing: Bool = true
|
||||
|
||||
open var shouldMaskWhileRecording: Bool {
|
||||
return viewModel.shouldMaskRecordedView ?? false
|
||||
}
|
||||
//--------------------------------------------------
|
||||
// MARK: - Computed Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public override var isEnabled: Bool {
|
||||
get { super.isEnabled }
|
||||
set (enabled) {
|
||||
super.isEnabled = enabled
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.textField.isEnabled = enabled
|
||||
self.textField.textColor = enabled ? self.textEntryFieldModel?.enabledTextColor.uiColor : self.textEntryFieldModel?.disabledTextColor.uiColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override var showError: Bool {
|
||||
get { super.showError }
|
||||
set (error) {
|
||||
|
||||
if error {
|
||||
textField.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField.text ?? "", entryFieldModel?.errorMessage ?? "")
|
||||
} else {
|
||||
textField.accessibilityValue = nil
|
||||
}
|
||||
|
||||
if !textField.isSecureTextEntry {
|
||||
showErrorView(error)
|
||||
}
|
||||
|
||||
super.showError = error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The text of this TextField.
|
||||
open override var text: String? {
|
||||
get { textField.text }
|
||||
set {
|
||||
textEntryFieldModel?.text = newValue
|
||||
textField.text = newValue
|
||||
didSet {
|
||||
viewModel?.text = text
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,173 +79,77 @@ import UIKit
|
||||
//--------------------------------------------------
|
||||
// MARK: - Delegate Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
/// The delegate and block for validation. Validates if the text that the user has entered.
|
||||
public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? {
|
||||
didSet {
|
||||
if observingTextFieldDelegate != nil && !observingForChange {
|
||||
observingForChange = true
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextField.textDidChangeNotification, object: textField)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(endInputing), name: UITextField.textDidEndEditingNotification, object: textField)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(startEditing), name: UITextField.textDidBeginEditingNotification, object: textField)
|
||||
|
||||
} else if observingTextFieldDelegate == nil && observingForChange {
|
||||
observingForChange = false
|
||||
NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: textField)
|
||||
NotificationCenter.default.removeObserver(self, name: UITextField.textDidEndEditingNotification, object: textField)
|
||||
NotificationCenter.default.removeObserver(self, name: UITextField.textDidBeginEditingNotification, object: textField)
|
||||
}
|
||||
}
|
||||
}
|
||||
public weak var observingTextFieldDelegate: ObservingTextFieldDelegate?
|
||||
|
||||
/// If you're using a ViewController, you must set this to it
|
||||
open weak var uiTextFieldDelegate: UITextFieldDelegate? {
|
||||
open weak var uiTextFieldDelegate: UITextFieldDelegate?
|
||||
{
|
||||
get { textField.delegate }
|
||||
set { textField.delegate = newValue }
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Constraints
|
||||
//--------------------------------------------------
|
||||
|
||||
public var textFieldTrailingConstraint: NSLayoutConstraint?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
@objc public convenience init() {
|
||||
self.init(frame: .zero)
|
||||
}
|
||||
|
||||
@objc required public init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
fatalError("TextEntryField does not support xib.")
|
||||
}
|
||||
|
||||
required public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
super.init(model: model, delegateObject, additionalData)
|
||||
set {
|
||||
textField.delegate = self
|
||||
proprietorTextDelegate = newValue
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Lifecycle
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc open override func setupFieldContainerContent(_ container: UIView) {
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
//turn off internal required rule
|
||||
useRequiredRule = false
|
||||
|
||||
textField.font = Styler.Font.RegularBodyLarge.getFont()
|
||||
container.addSubview(textField)
|
||||
publisher(for: .valueChanged)
|
||||
.sink { [weak self] control in
|
||||
guard let self else { return }
|
||||
_ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate)
|
||||
if (viewModel.type == .email) {
|
||||
// remove spaces (either user entered Or auto-correct suggestion) for the email field
|
||||
textField.text = textField.text?.replacingOccurrences(of: " ", with: "")
|
||||
}
|
||||
}.store(in: &subscribers)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
textField.heightAnchor.constraint(equalToConstant: Padding.Five),
|
||||
textField.topAnchor.constraint(equalTo: container.topAnchor, constant: Padding.Three),
|
||||
textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: Padding.Three),
|
||||
container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: Padding.Three)
|
||||
])
|
||||
textField
|
||||
.publisher(for: .editingDidBegin)
|
||||
.sink { [weak self] textView in
|
||||
guard let self else { return }
|
||||
isEditting = true
|
||||
if viewModel.clearTextOnTap {
|
||||
text = ""
|
||||
}
|
||||
}.store(in: &subscribers)
|
||||
|
||||
textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Three)
|
||||
textFieldTrailingConstraint?.isActive = true
|
||||
|
||||
textField.addTarget(self, action: #selector(startEditing), for: .editingDidBegin)
|
||||
textField.addTarget(self, action: #selector(dismissFieldInput), for: .editingDidEnd)
|
||||
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing))
|
||||
entryFieldContainer.addGestureRecognizer(tap)
|
||||
|
||||
accessibilityElements = [textField, feedbackLabel]
|
||||
}
|
||||
|
||||
@objc open override func updateView(_ size: CGFloat) {
|
||||
super.updateView(size)
|
||||
|
||||
textField.font = Styler.Font.RegularBodyLarge.getFont()
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
open override func reset() {
|
||||
super.reset()
|
||||
|
||||
textField.isSecureTextEntry = false
|
||||
textField.font = Styler.Font.RegularBodyLarge.getFont()
|
||||
textField
|
||||
.publisher(for: .editingDidEnd)
|
||||
.sink { [weak self] textView in
|
||||
guard let self else { return }
|
||||
isEditting = false
|
||||
if validateWhenDoneEditing, let valid = viewModel.isValid {
|
||||
updateValidation(valid)
|
||||
}
|
||||
regexTextFieldOutputIfAvailable()
|
||||
|
||||
}.store(in: &subscribers)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@objc open func updateView(_ size: CGFloat) {}
|
||||
|
||||
@objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) {
|
||||
observingTextFieldDelegate = delegate
|
||||
uiTextFieldDelegate = delegate
|
||||
}
|
||||
|
||||
open func setupTextFieldToolbar() {
|
||||
let observingDelegate = observingTextFieldDelegate ?? self
|
||||
textField.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate,
|
||||
action: #selector(observingDelegate.dismissFieldInput))
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Observing for Change (TextFieldDelegate)
|
||||
//--------------------------------------------------
|
||||
|
||||
@discardableResult
|
||||
@objc override open func resignFirstResponder() -> Bool {
|
||||
if validateWhenDoneEditing { validateText() }
|
||||
textField.resignFirstResponder()
|
||||
isSelected = false
|
||||
return true
|
||||
}
|
||||
|
||||
/// Validates the text of the entry field.
|
||||
@objc public override func validateText() {
|
||||
text = textField.text
|
||||
super.validateText()
|
||||
}
|
||||
|
||||
/// Executes on UITextField.textDidBeginEditingNotification
|
||||
@objc override func startEditing() {
|
||||
super.startEditing()
|
||||
|
||||
if textEntryFieldModel?.clearTextOnTap ?? false {
|
||||
text = ""
|
||||
}
|
||||
|
||||
textField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
/// Executes on UITextField.textDidChangeNotification (each character entry)
|
||||
@objc override func valueChanged() {
|
||||
super.valueChanged()
|
||||
if (textEntryFieldModel?.type == .email) {
|
||||
// remove spaces (either user entered Or auto-correct suggestion) for the email field
|
||||
textField.text = textField.text?.replacingOccurrences(of: " ", with: "")
|
||||
}
|
||||
validateText()
|
||||
}
|
||||
|
||||
/// Executes on UITextField.textDidEndEditingNotification
|
||||
@objc override func endInputing() {
|
||||
super.endInputing()
|
||||
|
||||
// Don't show error till user starts typing.
|
||||
guard text?.count ?? 0 != 0 else {
|
||||
showError = false
|
||||
return
|
||||
}
|
||||
|
||||
if let isValid = textEntryFieldModel?.isValid {
|
||||
self.isValid = isValid
|
||||
}
|
||||
|
||||
regexTextFieldOutputIfAvailable()
|
||||
|
||||
shouldShowError(!isValid)
|
||||
}
|
||||
|
||||
func regexTextFieldOutputIfAvailable() {
|
||||
|
||||
if let regex = textEntryFieldModel?.displayFormat,
|
||||
let mask = textEntryFieldModel?.displayMask,
|
||||
if let regex = viewModel?.displayFormat,
|
||||
let mask = viewModel?.displayMask,
|
||||
let finalText = text {
|
||||
|
||||
let range = NSRange(finalText.startIndex..., in: finalText)
|
||||
@ -291,114 +164,187 @@ import UIKit
|
||||
}
|
||||
|
||||
@objc public func dismissFieldInput(_ sender: Any?) {
|
||||
resignFirstResponder()
|
||||
}
|
||||
|
||||
open func showErrorView(_ show: Bool) {
|
||||
if show {
|
||||
entryFieldContainer.addSubview(errorImage)
|
||||
|
||||
textFieldTrailingConstraint?.isActive = false
|
||||
textFieldTrailingConstraint = errorImage.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Two)
|
||||
textFieldTrailingConstraint?.isActive = true
|
||||
|
||||
entryFieldContainer.trailingAnchor.constraint(equalTo: errorImage.trailingAnchor, constant: Padding.Three).isActive = true
|
||||
errorImage.centerYAnchor.constraint(equalTo: entryFieldContainer.centerYAnchor).isActive = true
|
||||
|
||||
} else {
|
||||
errorImage.removeFromSuperview()
|
||||
textFieldTrailingConstraint?.isActive = false
|
||||
textFieldTrailingConstraint = entryFieldContainer.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Two)
|
||||
textFieldTrailingConstraint?.isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
override func shouldShowError(_ showError: Bool) {
|
||||
super.shouldShowError(showError)
|
||||
|
||||
if showError {
|
||||
observingTextFieldDelegate?.isValid?(textfield: self)
|
||||
} else {
|
||||
observingTextFieldDelegate?.isInvalid?(textfield: self)
|
||||
}
|
||||
_ = resignFirstResponder()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - MoleculeViewProtocol
|
||||
//--------------------------------------------------
|
||||
|
||||
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
super.set(with: model, delegateObject, additionalData)
|
||||
open override func updateView() {
|
||||
super.updateView()
|
||||
|
||||
guard let model = model as? TextEntryFieldModel else { return }
|
||||
|
||||
self.delegateObject = delegateObject
|
||||
FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate)
|
||||
text = model.text
|
||||
placeholder = model.placeholder
|
||||
|
||||
textField.shouldMaskWhileRecording = model.shouldMaskRecordedView ?? true
|
||||
textField.enableClipboardActions = model.enableClipboardActions
|
||||
|
||||
switch model.type {
|
||||
case .password, .secure:
|
||||
textField.isSecureTextEntry = true
|
||||
textField.shouldMaskWhileRecording = true
|
||||
if let viewModel {
|
||||
switch viewModel.type {
|
||||
case .secure:
|
||||
textField.isSecureTextEntry = true
|
||||
textField.shouldMaskWhileRecording = true
|
||||
|
||||
case .numberSecure:
|
||||
textField.isSecureTextEntry = true
|
||||
textField.shouldMaskWhileRecording = true
|
||||
textField.keyboardType = .numberPad
|
||||
|
||||
case .email:
|
||||
textField.keyboardType = .emailAddress
|
||||
|
||||
case .securityCode, .creditCard, .password:
|
||||
textField.shouldMaskWhileRecording = true
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
case .numberSecure:
|
||||
textField.isSecureTextEntry = true
|
||||
textField.shouldMaskWhileRecording = true
|
||||
textField.keyboardType = .numberPad
|
||||
|
||||
case .number:
|
||||
textField.keyboardType = .numberPad
|
||||
|
||||
case .email:
|
||||
textField.keyboardType = .emailAddress
|
||||
|
||||
case .phone:
|
||||
textField.keyboardType = .phonePad
|
||||
|
||||
default:
|
||||
textField.keyboardType = .default
|
||||
// Override the preset keyboard set in type.
|
||||
if let keyboardType = viewModel.assignKeyboardType() {
|
||||
textField.keyboardType = keyboardType
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public func viewModelDidUpdate() {
|
||||
|
||||
// Override the preset keyboard set in type.
|
||||
if let keyboardType = model.assignKeyboardType() {
|
||||
textField.keyboardType = keyboardType
|
||||
}
|
||||
fieldType = viewModel.type.toVDSFieldType()
|
||||
text = viewModel.text
|
||||
placeholder = viewModel.placeholder
|
||||
|
||||
textField.accessibilityIdentifier = model.accessibilityIdentifier
|
||||
labelText = viewModel.title
|
||||
helperText = viewModel.feedback
|
||||
isEnabled = viewModel.enabled
|
||||
isReadOnly = viewModel.readOnly
|
||||
isRequired = viewModel.required
|
||||
tooltipModel = viewModel.tooltip?.toVDSTooltipModel()
|
||||
width = viewModel.width
|
||||
transparentBackground = viewModel.transparentBackground
|
||||
|
||||
containerView.accessibilityIdentifier = model.accessibilityIdentifier
|
||||
textField.textAlignment = viewModel.textAlignment
|
||||
textField.enableClipboardActions = viewModel.enableClipboardActions
|
||||
textField.placeholder = viewModel.placeholder ?? ""
|
||||
uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate
|
||||
observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate
|
||||
setupTextFieldToolbar()
|
||||
|
||||
if isSelected { startEditing() }
|
||||
if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected {
|
||||
|
||||
viewModel.wasInitiallySelected = true
|
||||
isEditting = true
|
||||
}
|
||||
|
||||
FormValidator.setupValidation(for: viewModel, delegate: delegateObject?.formHolderDelegate)
|
||||
|
||||
if isEditting {
|
||||
DispatchQueue.main.async {
|
||||
_ = self.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.updateUI = {
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
if isEditting {
|
||||
updateValidation(viewModel.isValid ?? true)
|
||||
|
||||
} else if viewModel.isValid ?? true && showError {
|
||||
showError = false
|
||||
}
|
||||
isEnabled = viewModel.enabled
|
||||
})
|
||||
}
|
||||
|
||||
viewModel.updateUIDynamicError = {
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
let validState = viewModel.isValid ?? false
|
||||
if !validState && viewModel.shouldClearText {
|
||||
text = ""
|
||||
viewModel.shouldClearText = false
|
||||
}
|
||||
updateValidation(validState)
|
||||
})
|
||||
}
|
||||
|
||||
//Added to override text when view is reloaded.
|
||||
if let text = model.text, !text.isEmpty {
|
||||
if let text = viewModel.text, !text.isEmpty {
|
||||
regexTextFieldOutputIfAvailable()
|
||||
}
|
||||
setAccessibilityString(model.title ?? "")
|
||||
}
|
||||
|
||||
private func updateValidation(_ isValid: Bool) {
|
||||
let previousValidity = self.isValid
|
||||
self.isValid = isValid
|
||||
|
||||
if previousValidity && !isValid {
|
||||
showError = true
|
||||
//observingTextFieldDelegate?.isValid?(textfield: self)
|
||||
} else if (!previousValidity && isValid) {
|
||||
showError = false
|
||||
//observingTextFieldDelegate?.isInvalid?(textfield: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TextEntryField {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Implemented TextField Delegate
|
||||
//--------------------------------------------------
|
||||
@discardableResult
|
||||
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true
|
||||
}
|
||||
|
||||
@objc public override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string)
|
||||
??
|
||||
super.textField(textField, shouldChangeCharactersIn: range, replacementString: string)
|
||||
}
|
||||
|
||||
@objc public override func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
proprietorTextDelegate?.textFieldDidBeginEditing?(textField) ?? super.textFieldDidBeginEditing(textField)
|
||||
}
|
||||
|
||||
@objc public override func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
proprietorTextDelegate?.textFieldDidEndEditing?(textField) ?? super.textFieldDidEndEditing(textField)
|
||||
}
|
||||
|
||||
@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
||||
proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true
|
||||
}
|
||||
|
||||
@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
|
||||
proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true
|
||||
}
|
||||
|
||||
@objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
|
||||
proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Accessibility
|
||||
extension TextEntryField {
|
||||
|
||||
@objc open override func pushAccessibilityNotification() {
|
||||
@objc open func pushAccessibilityNotification() {
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: self.textField)
|
||||
UIAccessibility.post(notification: .layoutChanged, argument: containerView)
|
||||
}
|
||||
}
|
||||
|
||||
@objc open override func setAccessibilityString(_ accessibilityString: String?) {
|
||||
|
||||
let accessibilityString = accessibilityString ?? ""
|
||||
|
||||
textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")"
|
||||
}
|
||||
|
||||
internal struct ViewMasking {
|
||||
static var shouldMaskWhileRecording: UInt8 = 0
|
||||
}
|
||||
|
||||
extension VDS.TextField: ViewMaskingProtocol {
|
||||
public var shouldMaskWhileRecording: Bool {
|
||||
get {
|
||||
return (objc_getAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording) as? Bool) ?? false
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &ViewMasking.shouldMaskWhileRecording, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,9 +5,12 @@
|
||||
// Created by Kevin Christiano on 1/22/20.
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
import VDS
|
||||
|
||||
|
||||
@objcMembers open class TextEntryFieldModel: EntryFieldModel {
|
||||
@objcMembers open class TextEntryFieldModel: EntryFieldModel, FormFieldInternalValidatable {
|
||||
|
||||
public var internalRules: [any RuleAnyModelProtocol]?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Types
|
||||
//--------------------------------------------------
|
||||
@ -20,6 +23,39 @@
|
||||
case email
|
||||
case text
|
||||
case phone
|
||||
|
||||
//additional
|
||||
case inlineAction
|
||||
case creditCard
|
||||
case date
|
||||
case securityCode
|
||||
|
||||
public func toVDSFieldType() -> VDS.InputField.FieldType {
|
||||
switch self {
|
||||
case .password:
|
||||
.password
|
||||
case .secure:
|
||||
.text
|
||||
case .number:
|
||||
.number
|
||||
case .numberSecure:
|
||||
.number
|
||||
case .email:
|
||||
.text
|
||||
case .text:
|
||||
.text
|
||||
case .phone:
|
||||
.telephone
|
||||
case .inlineAction:
|
||||
.inlineAction
|
||||
case .creditCard:
|
||||
.creditCard
|
||||
case .date:
|
||||
.date
|
||||
case .securityCode:
|
||||
.securityCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -33,12 +69,16 @@
|
||||
public var disabledTextColor: Color = Color(uiColor: .mvmCoolGray3)
|
||||
public var textAlignment: NSTextAlignment = .left
|
||||
public var keyboardOverride: String?
|
||||
public var type: EntryType?
|
||||
public var type: EntryType = .text
|
||||
public var clearTextOnTap: Bool = false
|
||||
public var displayFormat: String?
|
||||
public var displayMask: String?
|
||||
public var enableClipboardActions: Bool = true
|
||||
|
||||
public var tooltip: TooltipModel?
|
||||
public var transparentBackground: Bool = false
|
||||
public var width: CGFloat?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
@ -114,6 +154,9 @@
|
||||
case displayFormat
|
||||
case displayMask
|
||||
case enableClipboardActions
|
||||
case tooltip
|
||||
case transparentBackground
|
||||
case width
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -128,7 +171,7 @@
|
||||
displayFormat = try typeContainer.decodeIfPresent(String.self, forKey: .displayFormat)
|
||||
keyboardOverride = try typeContainer.decodeIfPresent(String.self, forKey: .keyboardOverride)
|
||||
displayMask = try typeContainer.decodeIfPresent(String.self, forKey: .displayMask)
|
||||
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type)
|
||||
type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type) ?? .text
|
||||
|
||||
if let clearTextOnTap = try typeContainer.decodeIfPresent(Bool.self, forKey: .clearTextOnTap) {
|
||||
self.clearTextOnTap = clearTextOnTap
|
||||
@ -149,6 +192,10 @@
|
||||
if let enableClipboardActions = try typeContainer.decodeIfPresent(Bool.self, forKey: .enableClipboardActions) {
|
||||
self.enableClipboardActions = enableClipboardActions
|
||||
}
|
||||
|
||||
tooltip = try typeContainer.decodeIfPresent(TooltipModel.self, forKey: .tooltip)
|
||||
transparentBackground = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparentBackground) ?? false
|
||||
width = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .width)
|
||||
}
|
||||
|
||||
open override func encode(to encoder: Encoder) throws {
|
||||
@ -164,5 +211,54 @@
|
||||
try container.encode(disabledTextColor, forKey: .disabledTextColor)
|
||||
try container.encode(clearTextOnTap, forKey: .clearTextOnTap)
|
||||
try container.encode(enableClipboardActions, forKey: .enableClipboardActions)
|
||||
try container.encodeIfPresent(tooltip, forKey: .tooltip)
|
||||
try container.encode(transparentBackground, forKey: .transparentBackground)
|
||||
try container.encodeIfPresent(width, forKey: .width)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol FormFieldInternalValidatable {
|
||||
var internalRules: [RuleAnyModelProtocol]? { get }
|
||||
}
|
||||
|
||||
public class RuleAnyVDSInternalRuleModel: RuleAnyModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public static var identifier: String = "anyVDSRule"
|
||||
public var type: String = RuleAnyVDSInternalRuleModel.identifier
|
||||
public var ruleId: String?
|
||||
private var rule: VDS.AnyRule<VDS.FormFieldable>
|
||||
public var errorMessage: [String: String]?
|
||||
public var fields: [String]
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
|
||||
public init(fields: [String], rule: VDS.AnyRule<VDS.FormFieldable>) {
|
||||
self.fields = fields
|
||||
self.rule = rule
|
||||
errorMessage = [:]
|
||||
ruleId = "\(rule.self)"
|
||||
fields.forEach {
|
||||
errorMessage![$0] = rule.errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Validation
|
||||
//--------------------------------------------------
|
||||
|
||||
public func isValid(_ formField: FormFieldProtocol) -> Bool {
|
||||
guard let field = formField as? any VDS.FormFieldable else { return true }
|
||||
return rule.isValid(value: field)
|
||||
}
|
||||
|
||||
/// never use this class as Codable
|
||||
public required init(from decoder: any Decoder) throws { fatalError() }
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {}
|
||||
}
|
||||
|
||||
|
||||
@ -86,14 +86,25 @@ public extension RulesContainerProtocol {
|
||||
// Validate each rule.
|
||||
var valid = true
|
||||
var previousValidity: [String: FormFieldValidity] = [:]
|
||||
var allRules = self.rules
|
||||
|
||||
// append the new rules for the internal validator of any formFields
|
||||
fields.compactMap({$0 as? FormFieldInternalValidatable}).forEach { field in
|
||||
if let internalRules = field.internalRules {
|
||||
allRules.append(contentsOf: internalRules)
|
||||
}
|
||||
}
|
||||
|
||||
fields.keys.forEach { key in
|
||||
previousValidity[key] = FormFieldValidity(key)
|
||||
}
|
||||
for rule in self.rules {
|
||||
|
||||
for rule in allRules {
|
||||
//validate the rule against the fields
|
||||
let tuple = rule.validate(fields, previousValidity)
|
||||
valid = valid && tuple.valid
|
||||
}
|
||||
|
||||
return (valid: valid, fieldValidity: previousValidity)
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,10 +43,10 @@ open class CoreUIModelMapping: ModelMapping {
|
||||
// MARK:- Entry Field
|
||||
ModelRegistry.register(handler: TextEntryField.self, for: TextEntryFieldModel.self)
|
||||
ModelRegistry.register(handler: MdnEntryField.self, for: MdnEntryFieldModel.self)
|
||||
ModelRegistry.register(handler: DigitEntryField.self, for: DigitEntryFieldModel.self)
|
||||
ModelRegistry.register(handler: ItemDropdownEntryField.self, for: ItemDropdownEntryFieldModel.self)
|
||||
ModelRegistry.register(handler: DateDropdownEntryField.self, for: DateDropdownEntryFieldModel.self)
|
||||
ModelRegistry.register(handler: MultiItemDropdownEntryField.self, for: MultiItemDropdownEntryFieldModel.self)
|
||||
//ModelRegistry.register(handler: DigitEntryField.self, for: DigitEntryFieldModel.self)
|
||||
// ModelRegistry.register(handler: ItemDropdownEntryField.self, for: ItemDropdownEntryFieldModel.self)
|
||||
// ModelRegistry.register(handler: DateDropdownEntryField.self, for: DateDropdownEntryFieldModel.self)
|
||||
// ModelRegistry.register(handler: MultiItemDropdownEntryField.self, for: MultiItemDropdownEntryFieldModel.self)
|
||||
|
||||
// MARK:- Selectors
|
||||
ModelRegistry.register(handler: RadioButton.self, for: RadioButtonModel.self)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user