From e92616a7f5a91b5c19ba2521a77c04fa16a7fca4 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 15 Oct 2019 13:46:50 -0400 Subject: [PATCH] latest state. --- MVMCoreUI.xcodeproj/project.pbxproj | 30 +- MVMCoreUI/Atoms/TextFields/DigitTextBox.swift | 21 ++ .../Atoms/TextFields/DigitTextField.swift | 133 +++++++++ MVMCoreUI/Atoms/TextFields/MdnTextField.swift | 253 +++++++++++++++++ MVMCoreUI/Atoms/TextFields/TextField.swift | 257 +++++++++--------- .../FormValidator+TextFields.swift | 4 + MVMCoreUI/FormUIHelpers/FormValidator.swift | 1 - 7 files changed, 559 insertions(+), 140 deletions(-) create mode 100644 MVMCoreUI/Atoms/TextFields/DigitTextBox.swift create mode 100644 MVMCoreUI/Atoms/TextFields/DigitTextField.swift create mode 100644 MVMCoreUI/Atoms/TextFields/MdnTextField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 5b3f7c8b..f270063e 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -20,8 +20,14 @@ 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; - 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; }; + 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 0A8321A82355062F00CB7F00 /* MdnTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321A72355062F00CB7F00 /* MdnTextField.swift */; }; + 0A8321AF2355FE9500CB7F00 /* DigitTextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */; }; + 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; + 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; }; + 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; }; + 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; @@ -97,17 +103,13 @@ D29DF18121E69E50003B2FB9 /* MFView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF17F21E69E2E003B2FB9 /* MFView.m */; }; D29DF18221E69E54003B2FB9 /* SeparatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF15921E697DA003B2FB9 /* SeparatorView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF15A21E697DA003B2FB9 /* SeparatorView.m */; }; - D29DF24D21E6A177003B2FB9 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; D29DF24E21E6A177003B2FB9 /* MFDigitTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF24F21E6A177003B2FB9 /* MFTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24421E6A176003B2FB9 /* MFTextField.xib */; }; D29DF25021E6A177003B2FB9 /* MFDigitTextBox.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */; }; - D29DF25221E6A177003B2FB9 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */; }; - D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; D29DF25521E6A177003B2FB9 /* MFDigitTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */; }; D29DF25621E6A177003B2FB9 /* MFTextFieldSubclassExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF25721E6A177003B2FB9 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF26821E6AA0B003B2FB9 /* FLAnimatedImage.m */; }; D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF26921E6AA0B003B2FB9 /* FLAnimatedImageView.m */; }; @@ -208,9 +210,12 @@ 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; + 0A41BA7E23453A6400D4C0BC /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; - 0A41BA7E23453A6400D4C0BC /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; + 0A8321A72355062F00CB7F00 /* MdnTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnTextField.swift; sourceTree = ""; }; + 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitTextField.swift; sourceTree = ""; }; + 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitTextBox.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; @@ -752,6 +757,9 @@ D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, 0A41BA7E23453A6400D4C0BC /* TextField.swift */, + 0A8321A72355062F00CB7F00 /* MdnTextField.swift */, + 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */, + 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */, ); path = TextFields; sourceTree = ""; @@ -867,12 +875,12 @@ D29DF2B021E7B3A4003B2FB9 /* MFTextView.h in Headers */, D29DF2A921E7B2F9003B2FB9 /* MVMCoreUIConstants.h in Headers */, 0198F7A62256A80B0066C936 /* MFRadioButton.h in Headers */, - D29DF25221E6A177003B2FB9 /* MFMdnTextField.h in Headers */, D22D1F1A220341F60077CEC0 /* MVMCoreUICheckBox.h in Headers */, D29DF29921E7ADB8003B2FB9 /* ProgrammaticScrollViewController.h in Headers */, D29DF11C21E684A9003B2FB9 /* MVMCoreUISplitViewController.h in Headers */, D29DF29B21E7ADB9003B2FB9 /* StackableViewController.h in Headers */, D29770F421F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.h in Headers */, + 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */, D29DF15421E69760003B2FB9 /* MVMCoreUIPanelButtonProtocol.h in Headers */, D2A514582211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h in Headers */, D29DF0D121E404D4003B2FB9 /* MVMCoreUI.h in Headers */, @@ -882,6 +890,7 @@ D29DF2BC21E7BEA4003B2FB9 /* TopTabbar.h in Headers */, D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */, D22D1F46220496A30077CEC0 /* MVMCoreUISwitch.h in Headers */, + 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */, D22D1F1E220343560077CEC0 /* MVMCoreUICheckMarkView.h in Headers */, D29DF28421E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.h in Headers */, D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */, @@ -894,7 +903,6 @@ D29DF25021E6A177003B2FB9 /* MFDigitTextBox.h in Headers */, D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */, D29DF2C621E7BF57003B2FB9 /* MFTabBarInteractor.h in Headers */, - D29DF25721E6A177003B2FB9 /* MFTextField.h in Headers */, D29DF17521E69E1F003B2FB9 /* ButtonDelegateProtocol.h in Headers */, D29DF18221E69E54003B2FB9 /* SeparatorView.h in Headers */, D29DF26E21E6AA0B003B2FB9 /* FLAnimatedImage.h in Headers */, @@ -1038,10 +1046,9 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, - D29DF24D21E6A177003B2FB9 /* MFTextField.m in Sources */, + 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, - D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */, B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, @@ -1054,6 +1061,7 @@ D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */, + 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, @@ -1103,6 +1111,7 @@ D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, + 0A8321A82355062F00CB7F00 /* MdnTextField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, @@ -1111,6 +1120,7 @@ D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, + 0A8321AF2355FE9500CB7F00 /* DigitTextBox.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift new file mode 100644 index 00000000..48d6734d --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DigitTextBox.swift @@ -0,0 +1,21 @@ +// +// DigitTextBox.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/15/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +class DigitTextBox: UITextField { + + /* + // Only override draw() if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func draw(_ rect: CGRect) { + // Drawing code + } + */ + +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift new file mode 100644 index 00000000..bf2c2572 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift @@ -0,0 +1,133 @@ +// +// DigitTextField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/15/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +class DigitTextField: TextField, UITextFieldDelegate, MFDigitTextBoxDelegate { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + private weak var textFieldsView: UIView? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + private var numberOfDigits = 0 + private var switchedAutomatically = false + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private weak var messageToTextFieldPin: NSLayoutConstraint? + private weak var labelToTextFieldPin: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + func digitField() -> MFDigitTextBox? { + let textField = MFDigitTextBox() + textField.delegate = self + textField.mfTextBoxDelegate = self + return textField + } + + class func withNumberOfDigits(_ numberOfDigits: Int) -> Self? { + let field = self.mf() + field?.numberOfDigits = numberOfDigits + field?.buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) + return field + } + + class func withNumberOfDigits(_ numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? { + let field = self.mfTextField(withBothDelegates: delegate) + field?.numberOfDigits = numberOfDigits + field?.buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) + return field + } + + class func withNumberOfDigits(_ numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) -> Self? { + let field = self.mfTextField(withBothDelegates: delegate) + field?.numberOfDigits = numberOfDigits + field?.buildTextFieldsView(forSize: size) + return field + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + func buildTextFieldsView(forSize size: CGFloat) { + + // Remove all current UI. + if let textFields = textFields, !textFields.isEmpt { + StackableViewController.removeUIViews(self.textFields) + } + + if numberOfDigits > 0 { + + // Create text boxes. + var textFields = [AnyHashable](repeating: 0, count: numberOfDigits) + + for i in 0.. 0 { + self.labelToTextFieldPin.constant = 10 + + } else { + self.labelToTextFieldPin.constant = 0 + } + } + } +} diff --git a/MVMCoreUI/Atoms/TextFields/MdnTextField.swift b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift new file mode 100644 index 00000000..5cbf814d --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/MdnTextField.swift @@ -0,0 +1,253 @@ +// +// MdnTextField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/14/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import AddressBookUI +import ContactsUI +import UIKit +import MVMCore + + +@objcMembers class MdnTextField: TextField, UITextFieldDelegate, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public weak var customDelegate: UITextFieldDelegate? + public var isNationalMdn = false + public var shouldValidateMDN = false + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: .zero) + setup() + } + + public convenience init() { + self.init(frame: .zero) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + + private func setup() { + + textField?.delegate = self + customDelegate = uiTextFieldDelegate + isNationalMdn = true + textField?.keyboardType = .numberPad + + let toolbar = MVMCoreUICommonViewsUtility.makeEmptyToolbar() + 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(_:))) + let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFieldInput(_:))) + toolbar.items = [contacts, space, dismissButton] + textField?.inputAccessoryView = toolbar + } + + // If you're using a MFViewController, you must set this to it + public override weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { + return textField?.delegate + } + set { + super.uiTextFieldDelegate = newValue + customDelegate = uiTextFieldDelegate + + if newValue != nil { + textField?.delegate = self + } + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + func hasValidMdn() -> Bool { + + guard let mdn = getMdn() else { return true } + + if mdn.isEmpty { + return true + + } else if isNationalMdn { + return MVMCoreUIUtility.validateMDNString(mdn) + } + + return MVMCoreUIUtility.validateInternationalMDNString(mdn) + } + + func validateAndColor() -> Bool { + + if !shouldValidateMDN { + let isValid = hasValidMdn() + + if isValid { + hideError() + } else { + self.errorMessage = getErrorMessage() ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") + UIAccessibility.post(notification: UIAccessibility.Notification.layoutChanged, argument: textField) + } + + return isValid + } + + return true + } + + func getErrorMessage() -> String? { + + return nil + } + + func getMdn() -> String? { + + return MVMCoreUIUtility.removeMdnFormat(text! as String) + } + + func setMdn(_ mdn: String?) { + + guard let MDN = mdn else { return } + + text = MVMCoreUIUtility.formatMdn(MDN) + } + + @objc func dismissFieldInput(_ sender: Any?) { + + if let delegate = uiTextFieldDelegate { + delegate.perform(#selector(dismissFieldInput(_:)), with: textField) + } else { + textField?.resignFirstResponder() + } + } + + func getContacts(_ sender: Any?) { + + let picker = CNContactPickerViewController() + picker.delegate = self + picker.displayedPropertyKeys = ["phoneNumbers"] + picker.predicateForEnablingContact = NSPredicate(format: "phoneNumbers.@count > 0") + picker.predicateForSelectionOfProperty = NSPredicate(format: "key == 'phoneNumbers'") + MVMCoreNavigationHandler.shared()?.present(picker, animated: true) + } + + //-------------------------------------------------- + // MARK: - ContactPicker Delegate + //-------------------------------------------------- + + func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { + + if contactProperty.value != nil && (contactProperty.value is CNPhoneNumber) { + + 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" { + + unformattedMDN = (unformedMDN as NSString).substring(from: 1) + } + + text = unformattedMDN + + if let textField = textField { + textFieldShouldReturn(textField) + textFieldDidEndEditing(textField) + } + } + } + + //-------------------------------------------------- + // MARK: - ImplementedTextField Delegate + //-------------------------------------------------- + + @discardableResult + @objc func textFieldShouldReturn(_ textField: UITextField) -> Bool { + + textField.resignFirstResponder() + + if let customDelegate = customDelegate { + return customDelegate.textFieldShouldReturn?(textField) ?? false + } + + return true + } + + @objc func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { + return false + } + + if let customDelegate = customDelegate { + return customDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true + } + + return true + } + + func textFieldDidBeginEditing(_ textField: UITextField) { + + textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) + customDelegate?.textFieldDidBeginEditing?(textField) + } + + func textFieldDidEndEditing(_ textField: UITextField) { + + customDelegate?.textFieldDidEndEditing?(textField) + + if validateAndColor() && isNationalMdn { + textField.text = MVMCoreUIUtility.formatMdn(textField.text) + } + } + + //-------------------------------------------------- + // MARK: - Passed Along TextField delegate + //-------------------------------------------------- + + @objc func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + + if let customDelegate = customDelegate { + return customDelegate.textFieldShouldBeginEditing?(textField) ?? true + } + + return true + } + + @objc func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + + if let customDelegate = customDelegate { + return customDelegate.textFieldShouldEndEditing?(textField) ?? true + } + + return true + } + + @objc func textFieldShouldClear(_ textField: UITextField) -> Bool { + + if let customDelegate = customDelegate { + return customDelegate.textFieldShouldClear?(textField) ?? true + } + + return true + } + +} diff --git a/MVMCoreUI/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index a7f3294a..20df71c5 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -35,19 +35,12 @@ import UIKit private(set) weak var toolbar: UIToolbar? //-------------------------------------------------- - // MARK: - Properties + // MARK: - Delegate Properties //-------------------------------------------------- - private var _mfTextFieldDelegate: TextFieldDelegate? - private var _uiTextFieldDelegate: UITextFieldDelegate? - /// The delegate and block for validation. Validates if the text that the user has entered is valid or not. Checked after each change if there is a delegate. public weak var mfTextFieldDelegate: TextFieldDelegate? { - get { - return _mfTextFieldDelegate - } - set { - _mfTextFieldDelegate = newValue + didSet { if mfTextFieldDelegate != nil && !observingForChanges { observingForChanges = true NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextField.textDidChangeNotification, object: textField) @@ -68,16 +61,22 @@ import UIKit return textField?.delegate } set { - _uiTextFieldDelegate = newValue - textField?.delegate = uiTextFieldDelegate + textField?.delegate = newValue } } - public weak var pickerView: UIPickerView? + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + private var borderPath: UIBezierPath? + private var calendar: Calendar? + public var pickerView: UIPickerView? public var observingForChanges = false public var errorShowing = false public var hasDropDown = false + public var enabled = true + public var hideBorder = false public var text: String? { get { @@ -99,9 +98,6 @@ import UIKit } } - - public var fieldKey: String? - public var placeholderTextColor: UIColor = .black public var placeholder: String? { @@ -110,7 +106,10 @@ import UIKit return attributedPlaceholder.string } set { - guard let newPlaceholderText = newValue else { return } + guard let newPlaceholderText = newValue else { + textField?.attributedPlaceholder = nil + return + } textField?.attributedPlaceholder = NSAttributedString(string: newPlaceholderText, attributes: [NSAttributedString.Key.foregroundColor: placeholderTextColor]) @@ -122,10 +121,6 @@ import UIKit } } - public var enabled = false - - public var hideBorder = false - public var formatter: DateFormatter? { let formatter = DateFormatter() @@ -138,14 +133,9 @@ import UIKit } public var isValid = false - - private var _validationBlock: ((_ enteredValue: String?) -> Bool)? + public var fieldKey: String? public var validationBlock: ((_ enteredValue: String?) -> Bool)? { - get { - return _validationBlock - } - set { - _validationBlock = newValue + didSet { valueChanged() } } @@ -153,10 +143,27 @@ import UIKit public var enabledTextColor: UIColor? public var disabledTextColor: UIColor? - public var errMessage: String? - - private var borderPath: UIBezierPath? - private var calendar: Calendar? + public var errorMessage: String? { + didSet { + DispatchQueue.main.async { [weak self] in + + guard let errorMessage = self?.errorMessage else { return } + + if let enabled = self?.enabled, enabled { + self?.separatorHeightConstraint?.constant = 4 + self?.errorShowing = true + self?.separatorView?.backgroundColor = UIColor.mfPumpkin() + self?.placeholderErrorLabel?.text = errorMessage + self?.placeholderErrorLabel?.numberOfLines = 0 + self?.textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", + self?.textField?.text ?? "", + errorMessage) + self?.setNeedsDisplay() + self?.layoutIfNeeded() + } + } + } + } //-------------------------------------------------- // MARK: - Constraints @@ -192,35 +199,31 @@ import UIKit fatalError("init(coder:) has not been implemented") } + /// Basic initializer. public convenience init() { self.init(frame: .zero) - - hasDropDown = false } - public convenience init(with bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. + public convenience init(bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { self.init(frame: .zero) - mfTextFieldDelegate = bothDelegates - uiTextFieldDelegate = bothDelegates + setBothTextFieldDelegates(bothDelegates) } - public convenience init(map: [AnyHashable: Any]?, with bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + /// - parameter hasDropDown: tbd + /// - parameter map: Dictionary of values to setup this TextField + /// - parameter bothDelegate: Sets both MF/UI Text Field Delegates. + public convenience init(hasDropDown: Bool = false, map: [AnyHashable: Any]?, bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { self.init(frame: .zero) - setWithMap(map, bothDelegates: bothDelegates) - } - - public convenience init(dropDownWithMap map: [AnyHashable: Any]?, bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { - self.init(frame: .zero) - - dropDownCarrotLabel?.isHidden = false - hasDropDown = true + dropDownCarrotLabel?.isHidden = hasDropDown + self.hasDropDown = !hasDropDown setWithMap(map, bothDelegates: bothDelegates) } //-------------------------------------------------- - // MARK: - Setup + // MARK: - Lifecycle //-------------------------------------------------- override open func setupView() { @@ -231,12 +234,13 @@ import UIKit setContentHuggingPriority(.required, for: .vertical) setContentCompressionResistancePriority(.required, for: .vertical) backgroundColor = .clear - enabled = true let formLabel = Label() self.formLabel = formLabel formLabel.font = MFStyler.fontB3() formLabel.textColor = UIColor.mfBattleshipGrey() + formLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + formLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) addSubview(formLabel) @@ -251,7 +255,7 @@ import UIKit textFieldContainerView.translatesAutoresizingMaskIntoConstraints = false addSubview(textFieldContainerView) - constrainTextContainerContent() + constrainContent(textFieldContainerView) textFieldContainerView.topAnchor.constraint(equalTo: formLabel.bottomAnchor, constant: 4).isActive = true textContainerLeftPin = textFieldContainerView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) @@ -263,6 +267,9 @@ import UIKit self.placeholderErrorLabel = placeholderErrorLabel placeholderErrorLabel.font = MFStyler.fontForTextFieldUnderLabel() placeholderErrorLabel.textColor = .black + placeholderErrorLabel.setContentHuggingPriority(.required, for: .vertical) + placeholderErrorLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + placeholderErrorLabel.setContentCompressionResistancePriority(.required, for: .vertical) addSubview(placeholderErrorLabel) @@ -275,9 +282,7 @@ import UIKit layoutMarginsGuide.bottomAnchor.constraint(equalTo: placeholderErrorLabel.bottomAnchor).isActive = true } - private func constrainTextContainerContent() { - - guard let parentView = textFieldContainerView else { return } + private func constrainContent(_ parentView: UIView) { let backgroundView = UIView(frame: .zero) self.backgroundView = backgroundView @@ -294,6 +299,7 @@ import UIKit let textField = UITextField(frame: .zero) self.textField = textField textField.translatesAutoresizingMaskIntoConstraints = false + textField.setContentHuggingPriority(.required, for: .vertical) textField.font = MFStyler.fontForTextField() textField.smartQuotesType = .no textField.smartDashesType = .no @@ -302,12 +308,16 @@ import UIKit addSubview(textField) - textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10).isActive = true - textField.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 16).isActive = true - parentView.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 10).isActive = true + NSLayoutConstraint.activate([ + textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10), + textField.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 16), + parentView.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 10)]) let dropDownCarrotLabel = Label() self.dropDownCarrotLabel = dropDownCarrotLabel + dropDownCarrotLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) + dropDownCarrotLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + dropDownCarrotLabel.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal) dropDownCarrotLabel.isHidden = true dropDownCarrotLabel.isUserInteractionEnabled = true let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) @@ -341,11 +351,12 @@ import UIKit addSubview(dashLine) - dashLine.centerYAnchor.constraint(equalTo: separatorView.centerYAnchor).isActive = true - dashLine.centerXAnchor.constraint(equalTo: separatorView.centerXAnchor).isActive = true - dashLine.topAnchor.constraint(equalTo: separatorView.topAnchor).isActive = true - dashLine.leadingAnchor.constraint(equalTo: separatorView.leadingAnchor).isActive = true - separatorView.bottomAnchor.constraint(equalTo: dashLine.bottomAnchor).isActive = true + NSLayoutConstraint.activate([ + dashLine.centerYAnchor.constraint(equalTo: separatorView.centerYAnchor), + dashLine.centerXAnchor.constraint(equalTo: separatorView.centerXAnchor), + dashLine.topAnchor.constraint(equalTo: separatorView.topAnchor), + dashLine.leadingAnchor.constraint(equalTo: separatorView.leadingAnchor), + separatorView.bottomAnchor.constraint(equalTo: dashLine.bottomAnchor)]) } open override func updateView(_ size: CGFloat) { @@ -389,10 +400,10 @@ import UIKit } //-------------------------------------------------- - // MARK: - Methods + // MARK: - Date Picker //-------------------------------------------------- - func createDatePicker() { + private func createDatePicker() { guard let textField = textField else { return } @@ -404,7 +415,7 @@ import UIKit self.calendar = calendar } - func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { + public func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { createDatePicker() @@ -420,13 +431,13 @@ import UIKit datePicker?.maximumDate = toDate } - func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) { + public func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) { if let fromDate = fromDate { if let calendar = calendar, calendar.isDate(fromDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - self.text = formatter?.string(from: fromDate) + text = formatter?.string(from: fromDate) } } @@ -435,7 +446,7 @@ import UIKit datePicker?.timeZone = NSTimeZone.system } - func dismissDatePicker() -> Date? { + public func dismissDatePicker() -> Date? { let pickedDate = datePicker?.date @@ -443,7 +454,7 @@ import UIKit if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - self.text = formatter?.string(from: pickedDate) + text = formatter?.string(from: pickedDate) } } @@ -451,37 +462,16 @@ import UIKit return pickedDate } - func dismissPicker() { + public func dismissPicker() { + textField?.resignFirstResponder() } - func setErrorMessage(_ errorMessage: String?) { - - DispatchQueue.main.async { [weak self] in - - if let enabled = self?.enabled, enabled { - self?.separatorHeightConstraint?.constant = 4 - self?.errorShowing = true - self?.separatorView?.backgroundColor = UIColor.mfPumpkin() - self?.placeholderErrorLabel?.text = errorMessage - self?.placeholderErrorLabel?.numberOfLines = 0 - self?.textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", - self?.textField?.text ?? "", - errorMessage ?? "") - self?.setNeedsDisplay() - self?.layoutIfNeeded() - } - } - } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- - func pushAccessibilityNotification() { - - DispatchQueue.main.async { [weak self] in - UIAccessibility.post(notification: .layoutChanged, argument: self?.textField) - } - } - - func hideError() { + public func hideError() { DispatchQueue.main.async { [weak self] in self?.separatorHeightConstraint?.constant = 1 @@ -496,31 +486,13 @@ import UIKit } } - /** - Adding missing accessibilityLabel value - if we have some value in accessibilityLabel, - then only can append regular and picker item - */ - func setAccessibilityString(_ accessibilityString: String?) { + public func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { - guard let textField = textField else { return } - var accessibilityString = accessibilityString ?? "" - - if hasDropDown, let txtPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { - accessibilityString += txtPickerItem - } else if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") { - accessibilityString += txtRegular - } - - textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" - } - - func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { mfTextFieldDelegate = delegate uiTextFieldDelegate = delegate } - func setWithMap(_ map: [AnyHashable: Any]?) { + public func setWithMap(_ map: [AnyHashable: Any]?) { guard let map = map, !map.isEmpty else { return } @@ -537,7 +509,7 @@ import UIKit } if let errMessage = map[KeyErrorMessage] as? String { - self.errMessage = errMessage + self.errorMessage = errMessage } // Key used to send text value to server @@ -575,17 +547,16 @@ import UIKit } } - func setWithMap(_ map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + public func setWithMap(_ map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?) { guard let textField = textField else { return } MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) - mfTextFieldDelegate = delegate - uiTextFieldDelegate = delegate + setBothTextFieldDelegates(delegate) setWithMap(map) } - func defaultValidationBlock() { + public func defaultValidationBlock() { validationBlock = { enteredValue in return (enteredValue?.count ?? 0) > 0 @@ -606,7 +577,7 @@ import UIKit formLabelRightPin?.constant = constant } - func showDropDown(_ show: Bool) { + public func showDropDown(_ show: Bool) { if hasDropDown { dropDownCarrotLabel?.isHidden = !show @@ -616,7 +587,7 @@ import UIKit } } - func enable(_ enable: Bool) { + public func enable(_ enable: Bool) { // Set outside the dispatch so that registerAnimations can know about it enabled = enable @@ -645,12 +616,12 @@ import UIKit } } - func showLabel(_ show: Bool) { + public func showLabel(_ show: Bool) { placeholderErrorLabel?.isHidden = !show } - func dashSeperatorView(_ dash: Bool) { + public func dashSeperatorView(_ dash: Bool) { // Never hide seperator view because it could be possiblely used by other classes for positioning dashLine?.isHidden = !dash @@ -674,8 +645,8 @@ import UIKit isValid = validationBlock?(text) ?? true if previousValidity && !isValid { - if let errMessage = errMessage { - setErrorMessage(errMessage) + if let errMessage = errorMessage { + self.errorMessage = errMessage } if let mfTextFieldDelegate = mfTextFieldDelegate { @@ -695,8 +666,8 @@ import UIKit if isValid { hideError() separatorView?.backgroundColor = .black - } else if let errMessage = errMessage { - setErrorMessage(errMessage) + } else if let errMessage = errorMessage { + self.errorMessage = errMessage } } @@ -754,9 +725,37 @@ extension TextField { // MARK: - Accessibility extension TextField { -// open override class func accessibilityElements() -> [Any]? { -// return [self.textField] -// } + // open override class func accessibilityElements() -> [Any]? { + // return [self.textField] + // } + + func pushAccessibilityNotification() { + + DispatchQueue.main.async { [weak self] in + UIAccessibility.post(notification: .layoutChanged, argument: self?.textField) + } + } + + /** + Adding missing accessibilityLabel value + if we have some value in accessibilityLabel, + then only can append regular and picker item + */ + func setAccessibilityString(_ accessibilityString: String?) { + + guard let textField = textField else { return } + + var accessibilityString = accessibilityString ?? "" + + if hasDropDown, let txtPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { + accessibilityString += txtPickerItem + + } else if let txtRegular = MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular") { + accessibilityString += txtRegular + } + + textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" + } } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift index b2b18ebe..27d6c18a 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift @@ -9,13 +9,16 @@ import Foundation @objc extension FormValidator: MFTextFieldDelegate { + public func dismissFieldInput(_ sender: Any?) { + if let delegate = delegate as? MFTextFieldDelegate { delegate.dismissFieldInput?(sender) } } public func entryIsValid(_ textfield: MFTextField?) { + MVMCoreDispatchUtility.performBlock(onMainThread: { self.enableByValidation() if let delegate = self.delegate as? MFTextFieldDelegate { @@ -25,6 +28,7 @@ import Foundation } public func entryIsInvalid(_ textfield: MFTextField?) { + MVMCoreDispatchUtility.performBlock(onMainThread: { self.enableByValidation() if let delegate = self.delegate as? MFTextFieldDelegate { diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 1b546d69..5275b633 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -6,7 +6,6 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import Foundation import UIKit @objcMembers public class FormValidator: NSObject {