From 273f45def04f66a6eafb2183ca27580a7b786d65 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 15:27:14 -0500 Subject: [PATCH 1/9] updated model Signed-off-by: Matt Bruce --- .../TextFields/TextEntryFieldModel.swift | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift index 053a5ac5..cb40ae9b 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryFieldModel.swift @@ -5,9 +5,10 @@ // 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, FormFieldInternalValidatableProtocol { + //-------------------------------------------------- // MARK: - Types //-------------------------------------------------- @@ -20,6 +21,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 +67,21 @@ 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: - FormFieldInternalValidatableProtocol + //-------------------------------------------------- + open var rules: [AnyRule]? + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -114,6 +157,9 @@ case displayFormat case displayMask case enableClipboardActions + case tooltip + case transparentBackground + case width } //-------------------------------------------------- @@ -128,7 +174,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 +195,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 +214,8 @@ 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) } } From 65be46c7678f1ff138eb07fe16b025f99e87f6c9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 15:27:27 -0500 Subject: [PATCH 2/9] added inputentryfield Signed-off-by: Matt Bruce --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../TextFields/InputEntryField.swift | 350 ++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 25045a2f..a5c48b12 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -579,6 +579,7 @@ EA17584C2BC9894800A5C0D9 /* ButtonIconModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */; }; EA17584E2BC9895A00A5C0D9 /* ButtonIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */; }; EA1B02DE2C41BFD200F0758B /* RuleVDSModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */; }; + EA1B02E02C470AFD00F0758B /* InputEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1B02DF2C470AFD00F0758B /* InputEntryField.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 */; }; @@ -1201,6 +1202,7 @@ EA17584B2BC9894800A5C0D9 /* ButtonIconModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconModel.swift; sourceTree = ""; }; EA17584D2BC9895A00A5C0D9 /* ButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIcon.swift; sourceTree = ""; }; EA1B02DD2C41BFD200F0758B /* RuleVDSModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleVDSModel.swift; sourceTree = ""; }; + EA1B02DF2C470AFD00F0758B /* InputEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputEntryField.swift; sourceTree = ""; }; EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = ""; }; EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = ""; }; EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = ""; }; @@ -2351,6 +2353,7 @@ children = ( 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */, 0A21DB7E235DECC500C160A2 /* EntryField.swift */, + EA1B02DF2C470AFD00F0758B /* InputEntryField.swift */, 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */, 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */, @@ -3140,6 +3143,7 @@ 323AC96A24C837F000F8E4C4 /* ListThreeColumnBillChangesModel.swift in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, + EA1B02E02C470AFD00F0758B /* InputEntryField.swift in Sources */, D28BA730247EC2EB00B75CB8 /* NavigationButtonModelProtocol.swift in Sources */, 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift new file mode 100644 index 00000000..198daad4 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift @@ -0,0 +1,350 @@ +// +// InputEntryField.swift +// MVMCoreUI +// +// Created by Matt Bruce on 7/16/24. +// Copyright © 2024 Verizon Wireless. All rights reserved. +// + +import Foundation +import VDS + +@objcMembers open class InputEntryField: 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: - Stored Properties + //-------------------------------------------------- + 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 + //-------------------------------------------------- + + private var observingForChange: Bool = false + + /// Validate when user resigns editing. Default: true + open var validateWhenDoneEditing: Bool = true + + open var shouldMaskWhileRecording: Bool { + return viewModel.shouldMaskRecordedView ?? false + } + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + /// The text of this TextField. + open override var text: String? { + didSet { + viewModel?.text = text + } + } + + open override var errorText: String? { + get { + viewModel.dynamicErrorMessage ?? viewModel.errorMessage + } + set {} + } + + /// Placeholder access for the TextField. + public var placeholder: String? { + get { textField.placeholder } + set { textField.placeholder = newValue } + } + + //-------------------------------------------------- + // MARK: - Delegate Properties + //-------------------------------------------------- + /// The delegate and block for validation. Validates if the text that the user has entered. + public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? + + /// If you're using a ViewController, you must set this to it + open weak var uiTextFieldDelegate: UITextFieldDelegate? + { + get { textField.delegate } + set { + textField.delegate = self + proprietorTextDelegate = newValue + } + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func setup() { + super.setup() + //turn off internal required rule + useRequiredRule = false + + 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 + text = textField.text?.replacingOccurrences(of: " ", with: "") + } + }.store(in: &subscribers) + + textField + .publisher(for: .editingDidBegin) + .sink { [weak self] textView in + guard let self else { return } + isEditting = true + if viewModel.clearTextOnTap { + text = "" + } + }.store(in: &subscribers) + + 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 + } + + //-------------------------------------------------- + // MARK: - Observing for Change (TextFieldDelegate) + //-------------------------------------------------- + + func regexTextFieldOutputIfAvailable() { + + if let regex = viewModel?.displayFormat, + let mask = viewModel?.displayMask, + let finalText = text { + + let range = NSRange(finalText.startIndex..., in: finalText) + + if let regex = try? NSRegularExpression(pattern: regex) { + let maskedText = regex.stringByReplacingMatches(in: finalText, + range: range, + withTemplate: mask) + textField.text = maskedText + } + } + } + + @objc public func dismissFieldInput(_ sender: Any?) { + _ = resignFirstResponder() + } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + open override func updateView() { + super.updateView() + + 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; + } + + // Override the preset keyboard set in type. + if let keyboardType = viewModel.assignKeyboardType() { + textField.keyboardType = keyboardType + } + } + + } + + open func viewModelDidUpdate() { + + fieldType = viewModel.type.toVDSFieldType() + text = viewModel.text + placeholder = viewModel.placeholder + + 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 + + if (viewModel.selected ?? false) && !viewModel.wasInitiallySelected { + + viewModel.wasInitiallySelected = true + isEditting = true + } + + viewModel.rules = rules + + 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 = viewModel.text, !text.isEmpty { + regexTextFieldOutputIfAvailable() + } + } + + 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 InputEntryField { + //-------------------------------------------------- + // 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 InputEntryField { + + @objc open func pushAccessibilityNotification() { + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + UIAccessibility.post(notification: .layoutChanged, argument: containerView) + } + } +} + +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) + } + } +} + From 36669b61cbd25ff3fc476290eff080dcf2b099e7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 15:27:42 -0500 Subject: [PATCH 3/9] used new input in mapping for now Signed-off-by: Matt Bruce --- MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 22f65ade..af99d1be 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -41,7 +41,7 @@ open class CoreUIModelMapping: ModelMapping { ModelRegistry.register(handler: ButtonGroup.self, for: ButtonGroupModel.self) // MARK:- Entry Field - ModelRegistry.register(handler: TextEntryField.self, for: TextEntryFieldModel.self) + ModelRegistry.register(handler: InputEntryField.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) From 239af70710c65230e405d57e57894cc61c2d0acf Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 15:27:54 -0500 Subject: [PATCH 4/9] fix initial updates Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift | 5 ++--- .../Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift index 2e098ad9..36700a9e 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/DigitEntryField.swift @@ -345,9 +345,8 @@ 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 diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift index 910712d5..6181cfdc 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift @@ -144,8 +144,8 @@ 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 viewContoller = UIApplication.topViewController() { + viewContoller.present(picker, animated: true) } } From 018fe9a25e23f44f258e7537252625fb817a6633 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 16:16:39 -0500 Subject: [PATCH 5/9] updated MDN Field to use new inputfield Signed-off-by: Matt Bruce --- .../FormFields/TextFields/MdnEntryField.swift | 138 ++++-------------- .../TextFields/MdnEntryFieldModel.swift | 5 + 2 files changed, 33 insertions(+), 110 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift index 6181cfdc..f80f89d3 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift @@ -14,7 +14,7 @@ import MVMCore /** This class provides the convenience of formatting the MDN entered/displayer for the user. */ -@objcMembers open class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { +@objcMembers open class MdnEntryField: InputEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { //-------------------------------------------------- // MARK: - Stored Properties //-------------------------------------------------- @@ -47,52 +47,17 @@ 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 setup() { + super.setup() + setupTextFieldToolbar() } - open override func setupTextFieldToolbar() { - + open func setupTextFieldToolbar() { let toolbar = UIToolbar.createEmptyToolbar() let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) let contacts = UIBarButtonItem(title: MVMCoreUIUtility.hardcodedString(withKey: "textfield_contacts_barbutton"), style: .plain, target: self, action: #selector(getContacts)) @@ -103,40 +68,7 @@ import MVMCore //-------------------------------------------------- // MARK: - Methods - //-------------------------------------------------- - - @objc public func hasValidMDN() -> Bool { - - guard let MDN = mdn, !MDN.isEmpty else { return false } - - if isNationalMDN { - return MVMCoreUIUtility.validateMDNString(MDN) - } - - return MVMCoreUIUtility.validateInternationalMDNString(MDN) - } - - @objc public func validateMDNTextField() -> Bool { - - guard !shouldValidateMDN, let MDN = mdn, !MDN.isEmpty else { - isValid = true - return true - } - - isValid = hasValidMDN() - - if self.isValid { - showError = false - - } else { - entryFieldModel?.errorMessage = entryFieldModel?.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() @@ -152,11 +84,12 @@ import MVMCore //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- - - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - - textField.keyboardType = .phonePad + public override func viewModelDidUpdate() { + viewModel.type = .phone + super.viewModelDidUpdate() + if let phoneNumber = viewModel.text { + text = phoneNumber.formatUSNumber() + } } //-------------------------------------------------- @@ -179,62 +112,47 @@ import MVMCore let startIndex = unformedMDN.index(unformedMDN.startIndex, offsetBy: 1) unformattedMDN = String(unformedMDN[startIndex...]) } - text = unformattedMDN textFieldShouldReturn(textField) textFieldDidEndEditing(textField) } } - + //-------------------------------------------------- // MARK: - Implemented TextField Delegate //-------------------------------------------------- @discardableResult - @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - - textField.resignFirstResponder() - - return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true + @objc public override func textFieldShouldReturn(_ textField: UITextField) -> Bool { + _ = resignFirstResponder() + let superValue = super.textFieldShouldReturn(textField) + return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? superValue } - @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 override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + let superValue = super.textField(textField, shouldChangeCharactersIn: range, replacementString: string) + return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? superValue } - @objc public func textFieldDidBeginEditing(_ textField: UITextField) { - - textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) + @objc public override func textFieldDidBeginEditing(_ textField: UITextField) { + super.textFieldDidBeginEditing(textField) proprietorTextDelegate?.textFieldDidBeginEditing?(textField) } - @objc public func textFieldDidEndEditing(_ textField: UITextField) { - + @objc public override 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() - } + super.textFieldDidEndEditing(textField) } - @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + @objc public override func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true } - @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + @objc public override func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true } - @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + @objc public override func textFieldShouldClear(_ textField: UITextField) -> Bool { proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryFieldModel.swift index 53d0703d..f4b94922 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryFieldModel.swift @@ -12,4 +12,9 @@ //-------------------------------------------------- public override class var identifier: String { "mdnEntryField" } + + open override func formFieldServerValue() -> AnyHashable? { + guard let value = formFieldValue() as? String else { return nil } + return value.filter { $0.isNumber } + } } From 2888eebb6996a350567ba3bdd6fe5b29a3b5b419 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 19 Jul 2024 14:16:55 -0500 Subject: [PATCH 6/9] updated validity Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/TextEntryField.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index 9d030d0f..368f634c 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -317,9 +317,9 @@ import UIKit super.shouldShowError(showError) if showError { - observingTextFieldDelegate?.isValid?(textfield: self) - } else { observingTextFieldDelegate?.isInvalid?(textfield: self) + } else { + observingTextFieldDelegate?.isValid?(textfield: self) } } From 796215f64dfb9ce5172e8bdff6cb76c11d792620 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:07:34 -0500 Subject: [PATCH 7/9] rearranged methods and comments Signed-off-by: Matt Bruce --- .../TextFields/InputEntryField.swift | 73 +++++++++---------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift index 198daad4..d1e37047 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift @@ -131,42 +131,6 @@ import VDS } - - @objc open func updateView(_ size: CGFloat) {} - - @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { - observingTextFieldDelegate = delegate - uiTextFieldDelegate = delegate - } - - //-------------------------------------------------- - // MARK: - Observing for Change (TextFieldDelegate) - //-------------------------------------------------- - - func regexTextFieldOutputIfAvailable() { - - if let regex = viewModel?.displayFormat, - let mask = viewModel?.displayMask, - let finalText = text { - - let range = NSRange(finalText.startIndex..., in: finalText) - - if let regex = try? NSRegularExpression(pattern: regex) { - let maskedText = regex.stringByReplacingMatches(in: finalText, - range: range, - withTemplate: mask) - textField.text = maskedText - } - } - } - - @objc public func dismissFieldInput(_ sender: Any?) { - _ = resignFirstResponder() - } - - //-------------------------------------------------- - // MARK: - MoleculeViewProtocol - //-------------------------------------------------- open override func updateView() { super.updateView() @@ -196,9 +160,8 @@ import VDS textField.keyboardType = keyboardType } } - } - + open func viewModelDidUpdate() { fieldType = viewModel.type.toVDSFieldType() @@ -269,7 +232,36 @@ import VDS regexTextFieldOutputIfAvailable() } } + + //-------------------------------------------------- + // MARK: - Observing for Change (TextFieldDelegate) + //-------------------------------------------------- + @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { + observingTextFieldDelegate = delegate + uiTextFieldDelegate = delegate + } + func regexTextFieldOutputIfAvailable() { + + if let regex = viewModel?.displayFormat, + let mask = viewModel?.displayMask, + let finalText = text { + + let range = NSRange(finalText.startIndex..., in: finalText) + + if let regex = try? NSRegularExpression(pattern: regex) { + let maskedText = regex.stringByReplacingMatches(in: finalText, + range: range, + withTemplate: mask) + textField.text = maskedText + } + } + } + + @objc public func dismissFieldInput(_ sender: Any?) { + _ = resignFirstResponder() + } + private func updateValidation(_ isValid: Bool) { let previousValidity = self.isValid self.isValid = isValid @@ -282,6 +274,11 @@ import VDS //observingTextFieldDelegate?.isInvalid?(textfield: self) } } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + @objc open func updateView(_ size: CGFloat) {} } extension InputEntryField { From b754b476a67f7d72a1d2dc079ca106cffee59819 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 08:29:07 -0500 Subject: [PATCH 8/9] reverted code Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift index f80f89d3..401dfa6b 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/MdnEntryField.swift @@ -76,8 +76,8 @@ import MVMCore picker.displayedPropertyKeys = ["phoneNumbers"] picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0") picker.predicateForSelectionOfProperty = NSPredicate(format: "key == 'phoneNumbers'") - if let viewContoller = UIApplication.topViewController() { - viewContoller.present(picker, animated: true) + Task(priority: .userInitiated) { + await NavigationHandler.shared().present(viewController: picker, animated: true) } } From 68083819beb33fe433ce74474de0377d3de474ac Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 30 Jul 2024 13:16:16 -0500 Subject: [PATCH 9/9] change protocol to any Signed-off-by: Matt Bruce --- .../Atomic/Atoms/FormFields/TextFields/InputEntryField.swift | 4 ++-- .../Atomic/Atoms/FormFields/TextFields/TextEntryField.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift index d1e37047..51cbd26c 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/InputEntryField.swift @@ -268,10 +268,10 @@ import VDS if previousValidity && !isValid { showError = true - //observingTextFieldDelegate?.isValid?(textfield: self) + observingTextFieldDelegate?.isValid?(textfield: self) } else if (!previousValidity && isValid) { showError = false - //observingTextFieldDelegate?.isInvalid?(textfield: self) + observingTextFieldDelegate?.isInvalid?(textfield: self) } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift index 368f634c..d4b0539d 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/TextEntryField.swift @@ -11,9 +11,9 @@ import UIKit @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: Any?) /// Called when the entered text becomes invalid based on the validation block - @objc optional func isInvalid(textfield: TextEntryField?) + @objc optional func isInvalid(textfield: Any?) /// Dismisses the keyboard. @objc optional func dismissFieldInput(_ sender: Any?) }