From 6c8ced47c9e542cbe259671912b1dc7ad09f361e Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 21 Oct 2019 11:47:13 -0400 Subject: [PATCH] trying out a new subclass. --- MVMCoreUI.xcodeproj/project.pbxproj | 68 +-- .../Atoms/TextFields/DigitTextField.swift | 317 ++++++----- .../Atoms/TextFields/FieldEntryForm.swift | 466 ++++++++++++++++ .../Atoms/TextFields/MdnEntryField.swift | 232 ++++++++ .../Atoms/TextFields/TextEntryField.swift | 528 ++++++++++++++++++ MVMCoreUI/Atoms/TextFields/TextField.swift | 136 ++--- .../MVMCoreUIMoleculeMappingObject.m | 2 +- 7 files changed, 1505 insertions(+), 244 deletions(-) create mode 100644 MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift create mode 100644 MVMCoreUI/Atoms/TextFields/MdnEntryField.swift create mode 100644 MVMCoreUI/Atoms/TextFields/TextEntryField.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index cea24a21..57508395 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -19,16 +19,22 @@ 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; - 0A21DB6E2359EEF800C160A2 /* DigitTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */; }; + 0A21DB7F235DECC500C160A2 /* FieldEntryForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* FieldEntryForm.swift */; }; + 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */; }; + 0A21DB84235E06EF00C160A2 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB85235E06EF00C160A2 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; + 0A21DB86235E06EF00C160A2 /* MFTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24421E6A176003B2FB9 /* MFTextField.xib */; }; + 0A21DB87235E06EF00C160A2 /* MFTextFieldSubclassExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB88235E06EF00C160A2 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */; }; + 0A21DB8A235E06EF00C160A2 /* MFDigitTextBox.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */; }; + 0A21DB8C235E06EF00C160A2 /* MFDigitTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */; }; + 0A21DB8E235E06EF00C160A2 /* MFDigitTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; - 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; }; - 0A52C1492357B5380051AECD /* MdnTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321A72355062F00CB7F00 /* MdnTextField.swift */; }; - 0A52C14A2358B57E0051AECD /* DigitTextBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */; }; + 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; - 0A8321C523563D3500CB7F00 /* MFTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24221E6A176003B2FB9 /* MFTextField.m */; }; - 0A8321C623563D3800CB7F00 /* MFTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24C21E6A177003B2FB9 /* MFTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 0A8321C723563D4000CB7F00 /* MFMdnTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 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 */; }; @@ -104,13 +110,6 @@ 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 */; }; - 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 */; }; - D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF24821E6A177003B2FB9 /* MFDigitTextField.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, ); }; }; 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 */; }; @@ -210,8 +209,11 @@ 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 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 = ""; }; + 0A21DB7E235DECC500C160A2 /* FieldEntryForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldEntryForm.swift; sourceTree = ""; }; + 0A21DB80235DF87300C160A2 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; + 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryField.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 = ""; }; + 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.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 = ""; }; 0A8321A72355062F00CB7F00 /* MdnTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnTextField.swift; sourceTree = ""; }; @@ -757,10 +759,13 @@ D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */, D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, - 0A41BA7E23453A6400D4C0BC /* TextField.swift */, + 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, 0A8321A72355062F00CB7F00 /* MdnTextField.swift */, 0A8321AC2355FC2600CB7F00 /* DigitTextField.swift */, 0A8321AE2355FE9500CB7F00 /* DigitTextBox.swift */, + 0A21DB7E235DECC500C160A2 /* FieldEntryForm.swift */, + 0A21DB80235DF87300C160A2 /* TextField.swift */, + 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, ); path = TextFields; sourceTree = ""; @@ -881,27 +886,27 @@ 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 */, D29DF29A21E7ADB8003B2FB9 /* MFProgrammaticTableViewController.h in Headers */, - D29DF25621E6A177003B2FB9 /* MFTextFieldSubclassExtension.h in Headers */, D29DF11521E6805F003B2FB9 /* UIColor+MFConvenience.h in Headers */, 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 */, + 0A21DB87235E06EF00C160A2 /* MFTextFieldSubclassExtension.h in Headers */, D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */, D29DF2CE21E7C104003B2FB9 /* MFLoadingViewController.h in Headers */, + 0A21DB84235E06EF00C160A2 /* MFTextField.h in Headers */, D29DF12A21E6851E003B2FB9 /* MVMCoreUITopAlertView.h in Headers */, D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */, D29DF28B21E7AC2B003B2FB9 /* ViewConstrainingView.h in Headers */, D29DF2B321E7B76D003B2FB9 /* MFLoadingSpinner.h in Headers */, + 0A21DB8A235E06EF00C160A2 /* MFDigitTextBox.h in Headers */, D29DF32521ED0DA2003B2FB9 /* TextButtonView.h in Headers */, - D29DF25021E6A177003B2FB9 /* MFDigitTextBox.h in Headers */, + 0A21DB8C235E06EF00C160A2 /* MFDigitTextField.h in Headers */, D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */, D29DF2C621E7BF57003B2FB9 /* MFTabBarInteractor.h in Headers */, D29DF17521E69E1F003B2FB9 /* ButtonDelegateProtocol.h in Headers */, @@ -911,6 +916,7 @@ D29DF17721E69E1F003B2FB9 /* MFTextButton.h in Headers */, 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */, D29DF16221E69996003B2FB9 /* MFViewController.h in Headers */, + 0A21DB88235E06EF00C160A2 /* MFMdnTextField.h in Headers */, D29DF13121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h in Headers */, D29DF2C421E7BF57003B2FB9 /* MFTabBarSwipeAnimator.h in Headers */, D2A5145D2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h in Headers */, @@ -920,7 +926,6 @@ D29DF2BD21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.h in Headers */, D29DF2EE21ECEADF003B2FB9 /* MFFonts.h in Headers */, D29DF12D21E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h in Headers */, - D29DF24E21E6A177003B2FB9 /* MFDigitTextField.h in Headers */, D29770F321F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.h in Headers */, D296E1412295EBBA0051EBE7 /* MoleculeDelegateProtocol.h in Headers */, D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */, @@ -1002,12 +1007,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - D29DF25521E6A177003B2FB9 /* MFDigitTextField.xib in Resources */, D29DF2AF21E7B3A4003B2FB9 /* MFTextView.xib in Resources */, D29DF31C21ECECC0003B2FB9 /* NHaasGroteskDSStd-75Bd.otf in Resources */, - D29DF24F21E6A177003B2FB9 /* MFTextField.xib in Resources */, D29DF31D21ECECC0003B2FB9 /* NHaasGroteskDSStd-55Rg.otf in Resources */, + 0A21DB8E235E06EF00C160A2 /* MFDigitTextField.xib in Resources */, D29DF32C21EE8736003B2FB9 /* Localizable.strings in Resources */, + 0A21DB86235E06EF00C160A2 /* MFTextField.xib in Resources */, D29DF31A21ECECC0003B2FB9 /* NHaasGroteskDSStd-45Lt.otf in Resources */, D29DF32E21EE8C3D003B2FB9 /* Media.xcassets in Resources */, D29DF31B21ECECC0003B2FB9 /* OCRAExtended.ttf in Resources */, @@ -1033,13 +1038,13 @@ D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, - D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, D224798C231450C8003FCCF9 /* HeadlineBodySwitch.swift in Sources */, D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, + 0A21DB7F235DECC500C160A2 /* FieldEntryForm.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, @@ -1047,9 +1052,9 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, - 0A8321C823563D4300CB7F00 /* MFMdnTextField.m in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, + 0A21DB85235E06EF00C160A2 /* MFTextField.m in Sources */, B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, @@ -1058,11 +1063,11 @@ D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, + 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, 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 */, @@ -1070,7 +1075,7 @@ D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, - 0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */, + 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */, D29DF17A21E69E1F003B2FB9 /* MFCustomButton.m in Sources */, @@ -1091,6 +1096,7 @@ D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */, + 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */, D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */, D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, @@ -1100,20 +1106,19 @@ 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, - 0A21DB6E2359EEF800C160A2 /* DigitTextField.swift in Sources */, D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */, + 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, + 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, - D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, - 0A52C1492357B5380051AECD /* MdnTextField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, @@ -1122,7 +1127,6 @@ D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, - 0A52C14A2358B57E0051AECD /* 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/DigitTextField.swift b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift index 79ff733e..0f9d5848 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitTextField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitTextField.swift @@ -14,7 +14,7 @@ import UIKit // MARK: - Outlets //-------------------------------------------------- - private weak var textFieldsView: UIView? + private weak var digitFieldsView: UIView? //-------------------------------------------------- // MARK: - Properties @@ -22,7 +22,97 @@ import UIKit private var numberOfDigits = 0 private var switchedAutomatically = false - public var textFields: [DigitTextBox]? + public var digitFields: [DigitTextBox]? + + /// Setgs placeholder text in the textField. + public override var placeholder: String? { + get { + var string = "" + + for digitField in digitFields ?? [] { + if let placeholderText = digitField.attributedPlaceholder?.string { + string += placeholderText + } + } + + return !string.isEmpty ? string : nil + } + set { + guard let placeholderValue = newValue else { return } + + (digitFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + + if idx < (newValue?.count ?? 0) { + + let stringForIndex = (newValue as NSString?)?.substring(with: NSRange(location: idx, length: 1)) + obj.attributedPlaceholder = NSAttributedString(string: stringForIndex ?? "", attributes: [ + NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) + } else if stop != nil { + stop = true + } + }) + } + + // If there is already text in the textfield, set the place holder label below. + if placeholderErrorLabel.length > 0 && !errorShowing { + placeholderErrorLabel.text = newValue + + } else if !errorShowing { + placeholderErrorLabel.text = "" + } + + if label.text.length > 0 { + labelToTextFieldPin?.constant = 10 + } else { + labelToTextFieldPin?.constant = 0 + } + + // adding missing accessibilityLabel value + // if we have some value in accessibilityLabel, + // then only can append regular and picker item + textField.accessibilityLabel() = newValue ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "mfdigittextfield_regular")) + + } + + public override var text: String? { + get { + var string = "" + + for digitField in digitFields ?? [] { + if let digitText = digitField.text { + string += digitText + } + } + + return string + } + set { + (textFields as NSArray?)?.enumerateObjects( { obj, idx, stop in + + if idx < (text?.count ?? 0) { + let stringForIndex = (text as NSString?)?.substring(with: NSRange(location: idx, length: 1)) + obj.text = stringForIndex + } else if stop != nil { + stop = true + } + }) + valueChanged() + } + } + + public override var formText: String? { + get { + return formLabel?.text + } + set { + if let formText = newValue, !formText.isEmpty > 0 { + messageToTextFieldPin?.constant = 10 + } else { + messageToTextFieldPin?.constant = 0 + } + super.formText = newValue + } + } //-------------------------------------------------- // MARK: - Constraints @@ -44,81 +134,84 @@ import UIKit super.init(frame: .zero) self.numberOfDigits = numberOfDigits - buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) + buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } public init(numberOfDigits: Int, bothDelegates delegates: (UITextFieldDelegate & MFTextFieldDelegate)?) { super.init(bothDelegates: delegates as? (TextFieldDelegate & UITextFieldDelegate)) self.numberOfDigits = numberOfDigits - buildTextFieldsView(forSize: MVMCoreUISplitViewController.getDetailViewWidth()) + buildTextFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth()) } - public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFie.ldDelegate & MFTextFieldDelegate)?, size: CGFloat) { + public init(withNumberOfDigits numberOfDigits: Int, withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?, size: CGFloat) { super.init(bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) self.numberOfDigits = numberOfDigits - buildTextFieldsView(forSize: size) + buildTextFieldsView(size: size) + } + + private func setup() { + } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - func createDigitField() -> DigitTextBox? { + func createDigitField() -> DigitTextBox { let textField = DigitTextBox() textField.delegate = self textField.textBoxDelegate = self + return textField } - func buildTextFieldsView(forSize size: CGFloat) { + func buildTextFieldsView(size: CGFloat) { // Remove all current UI. - if let textFields = textFields, !textFields.isEmpty { - StackableViewController.remove(textFields) + if let digitFields = digitFields, !digitFields.isEmpty { + StackableViewController.remove(digitFields) } if numberOfDigits > 0 { - var textFields = [AnyHashable](repeating: 0, count: numberOfDigits) + let digitFields = [DigitTextBox](repeating: createDigitField(), count: numberOfDigits) - for i in 0.. 0 { - self?.labelToTextFieldPin?.constant = 10 - + guard let self = self else { return } + + if let placeholder = self.placeholder, !placeholder.isEmpty { + self.labelToTextFieldPin?.constant = 10 + } else { - self?.labelToTextFieldPin?.constant = 0 + self.labelToTextFieldPin?.constant = 0 } } } @@ -144,24 +238,26 @@ import UIKit super.updateView(size) DispatchQueue.main.async { [weak self] in - self.formLabel!.updateView(size) - - // Remove all current UI. - if (self.textFields?.count ?? 0) > 0 { - StackableViewController.remove(self.textFields) + guard let self = self else { return } + + self.formLabel?.updateView(size) + + if let digitFields = self.digitFields, !digitFields.isEmpty { + + // Remove all current UI. + StackableViewController.remove(digitFields) + + // Update text boxes. + for digitField in digitFields { + digitField.updateView(size) + } } - - // Update text boxes. - for textField in self.textFields ?? [] { - guard let textField = textField as? MFDigitTextBox else { continue } - textField.updateView(size) - } - + // Layout text boxes. self.setupTextFieldsView(forSize: size) } } - + open override func setupView() { super.setupView() @@ -174,14 +270,17 @@ import UIKit // MARK: - Molecule //-------------------------------------------------- - open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - let digitsNumber = json?.optionalNumber(forKey: "digits") - let digits = digitsNumber != nil ? digitsNumber?.intValue ?? 0 : 4 + guard let dictionary = json else { return } + + let digits = dictionary["digits"] as? Int ?? 4 if digits != numberOfDigits { numberOfDigits = digits - buildTextFieldsView(forSize: MVMCoreUIUtility.getWidth()) } + + buildTextFieldsView(size: MVMCoreUIUtility.getWidth()) + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } @@ -190,99 +289,28 @@ import UIKit return 44 } - //-------------------------------------------------- - // MARK: - Getters - //-------------------------------------------------- - - func placeholder() -> String? { - - var string = "" - for textField in textFields ?? [] { - if textField.attributedPlaceholder?.string != nil { - string += textField.attributedPlaceholder?.string ?? "" - } - } - return string.count > 0 ? string : nil - } - - func text() -> String? { - - var string = "" - for textField in textFields ?? [] { - if textField.text != nil { - string += textField.text ?? "" - } - } - return string - } - //-------------------------------------------------- // MARK: - Setters //-------------------------------------------------- - func setFormText(_ formText: String?) { - - if (formText?.count ?? 0) > 0 { - messageToTextFieldPin?.constant = 10 - } else { - messageToTextFieldPin?.constant = 0 - } - super.setFormText(formText) - } - - func setAsSecureTextEntry(_ secureTextEntry: Bool) { + func setAsSecureTextEntry(_ secureEntry: Bool) { DispatchQueue.main.async { [weak self] in - (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in - obj.isSecureTextEntry = secureTextEntry + (self.digitFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + obj.isSecureTextEntry = secureEntry //accessibility - 33704 fix voice over will read what pin user is filling obj.accessibilityLabel() = String(format: "PIN %lu of %lu", UInt(idx) + 1, UInt(self.textFields?.count ?? 0)) }) } } - - func setPlaceholder(_ placeholder: String?) { - if placeholder != nil { - (textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in - - if idx < (placeholder?.count ?? 0) { - - let stringForIndex = (placeholder as NSString?)?.substring(with: NSRange(location: idx, length: 1)) - obj.attributedPlaceholder = NSAttributedString(string: stringForIndex ?? "", attributes: [ - NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey() - ]) - } else if stop != nil { - stop = true - } - }) - } - - // If there is already text in the textfield, set the place holder label below. - if text.length > 0 && !errorShowing { - label.text = placeholder - - } else if !errorShowing { - label.text = "" - } - - if label.text.length > 0 { - labelToTextFieldPin?.constant = 10 - } else { - labelToTextFieldPin?.constant = 0 - } - - // adding missing accessibilityLabel value - // if we have some value in accessibilityLabel, - // then only can append regular and picker item - textField.accessibilityLabel() = placeholder ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "mfdigittextfield_regular")) - } - - func setErrorMessage(_ errorMessage: String?) { + override public func showErrorMessage(_ errorMessage: String?) { + DispatchQueue.main.async { [weak self] in - super.setErrorMessage(errorMessage) + super.showErrorMessage (errorMessage) + if self.errorShowing { self.labelToTextFieldPin?.constant = 10 } @@ -293,28 +321,19 @@ import UIKit } public override func hideError() { + DispatchQueue.main.async { [weak self] in + super.hideError() (self.textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in obj.hideError() }) } } - - func setText(_ text: String?) { - (textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in - if idx < (text?.count ?? 0) { - let stringForIndex = (text as NSString?)?.substring(with: NSRange(location: idx, length: 1)) - obj.text = stringForIndex - } else if stop != nil { - stop = true - } - }) - valueChanged() - } func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) { - super.setWithMap(map, bothDelegates: delegate) + super.setWithMap(map, bothDelegates: delegate as? (TextFieldDelegate & UITextFieldDelegate)) + if (map?.count ?? 0) > 0 { for textField in textFields ?? [] { MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) @@ -325,6 +344,7 @@ import UIKit func setDefaultValidationBlock() { weak var weakSelf = self + self.validationBlock = { enteredValue in if (enteredValue?.count ?? 0) > 0 && (enteredValue?.count ?? 0) == weakSelf?.textFields?.count { return true @@ -346,6 +366,7 @@ import UIKit for textField in textFields ?? [] { textField.isUserInteractionEnabled = enable textField.isEnabled = enable + if enable { textField.textColor = UIColor.black } else { @@ -361,6 +382,7 @@ import UIKit func selectPreviousTextField(_ currentTextField: UITextField?, clear: Bool) { var selectNextField = false + (textFields as NSArray?)?.enumerateObjects(options: .reverse, using: { obj, idx, stop in if obj == currentTextField { selectNextField = true @@ -382,8 +404,10 @@ import UIKit var selectNextField = false (textFields as NSArray?)?.enumerateObjects({ obj, idx, stop in + if obj == currentTextField { selectNextField = true + } else if selectNextField { if !clear { self.switchedAutomatically = true @@ -403,8 +427,9 @@ import UIKit //-------------------------------------------------- open override var accessibilityElements: [Any]? { + if (self.textFields) { - return [self.textFields arrayByAddingObject:(MFDigitTextBox *)self.label]; + return [self.textFields arrayByAddingObject:(DigitTextBox *)self.label]; } else { return @[self.label]; } @@ -457,27 +482,31 @@ import UIKit } func textFieldDidDelete(_ textField: UITextField?) { + // empty cell, go back to previous cell and clear. selectPreviousTextField(textField, clear: true) } @objc public func textFieldDidBeginEditing(_ textField: UITextField) { + if !switchedAutomatically { textField.text = "" valueChanged() } - if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldDidBeginEditing(_:))) { - uiTextFieldDelegate?.textFieldDidBeginEditing(textField) + if (uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldDidBeginEditing(_:))))! { + uiTextFieldDelegate?.textFieldDidBeginEditing!(textField) } } @objc public func textFieldDidEndEditing(_ textField: UITextField) { + if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldDidEndEditing(_:))) { - uiTextFieldDelegate!.textFieldDidEndEditing(textField) + uiTextFieldDelegate!.textFieldDidEndEditing!(textField) } } @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + selectPreviousTextField(textField, clear: false) if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) { @@ -488,6 +517,7 @@ import UIKit // MARK: - Passed Along TextField delegate @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + if uiTextFieldDelegate?.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) { return uiTextFieldDelegate?.textFieldShouldBeginEditing(textField) } @@ -495,6 +525,7 @@ import UIKit } @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + if uiTextFieldDelegate!.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) { return uiTextFieldDelegate?.textFieldShouldEndEditing(textField) } diff --git a/MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift b/MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift new file mode 100644 index 00000000..592051f6 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/FieldEntryForm.swift @@ -0,0 +1,466 @@ +// +// FieldEntryForm.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +/** + * This class is intended to be subclassed by a class that will add views subclassed under UIControl. + * The FieldEntryForm provides the base logic for the description label, placeholder/error label and field container. + */ +open class FieldEntryForm: ViewConstrainingView { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public var backgroundView: UIView? + public var formDescriptionLabel: Label? + public var fieldContainer: UIView? + public var placeholderErrorLabel: Label? + public var separatorView: UIView? + public var dashLine: DashLine? + public var dropDownCaretLabel: UILabel? + + //-------------------------------------------------- + // MARK: - Accessories + //-------------------------------------------------- + + public weak var datePicker: UIDatePicker? + public var pickerView: UIPickerView? + private(set) weak var toolbar: UIToolbar? + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var showError = false + public var hasDropDown = false + private var borderPath: UIBezierPath? + public var isEnabled = true + + /// Determines if a border should be drawn. + public var hideBorder = false { + didSet { setNeedsLayout() } + } + + public var formText: String? { + get { return formDescriptionLabel?.text } + set { + formDescriptionLabel?.text = newValue + setAccessibilityString(newValue) + } + } + + // Override this with logic of the textfield(s) that are of focus in this form. + public var text: String? { + get { return "" } + set { } + } + + public var placeholderTextColor: UIColor = .black + + /// Setgs placeholder text in the textField. + public var placeholder: String? { + get { return placeholderErrorLabel?.text } + set { + guard let newPlaceholderText = newValue else { return } + + if !showError { + placeholderErrorLabel?.text = newPlaceholderText + } + + setAccessibilityString(newPlaceholderText) + } + } + + public var formatter: DateFormatter = { + + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeZone = NSTimeZone.system + formatter.locale = .current + formatter.formatterBehavior = .default + + return formatter + }() + + public var isValid = false + public var fieldKey: String? + + public var enabledTextColor: UIColor? + public var disabledTextColor: UIColor? + + public var errorMessage: String? + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + public var heightConstraint: NSLayoutConstraint? + + public var dropDownCarrotWidth: NSLayoutConstraint? + + public var textContainerLeftPin: NSLayoutConstraint? + public var textContainerRightPin: NSLayoutConstraint? + + public var errorLableRightPin: NSLayoutConstraint? + public var errorLableLeftPin: NSLayoutConstraint? + + public var formDescriptionLabelLeftPin: NSLayoutConstraint? + public var formDescriptionLabelRightPin: NSLayoutConstraint? + + public var separatorHeightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + /// This must be overriden by a subclass. + public override init(frame: CGRect) { + super.init(frame: frame) + setupView() + self.hasDropDown = false + } + + /// This must be overriden by a subclass. + public convenience init() { + self.init(frame: .zero) + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("TextEntryField does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + /// Initial configuration of class and view. + final public override func setupView() { + + guard subviews.isEmpty else { return } + + translatesAutoresizingMaskIntoConstraints = false + setContentHuggingPriority(.required, for: .vertical) + setContentCompressionResistancePriority(.required, for: .vertical) + backgroundColor = .clear + + let formDescriptionLabel = Label() + self.formDescriptionLabel = formDescriptionLabel + formDescriptionLabel.font = MFStyler.fontB3() + formDescriptionLabel.textColor = UIColor.mfBattleshipGrey() + formDescriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + formDescriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + formDescriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) + + addSubview(formDescriptionLabel) + + formDescriptionLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + formDescriptionLabelLeftPin = formDescriptionLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + formDescriptionLabelLeftPin?.isActive = true + formDescriptionLabelRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: formDescriptionLabel.trailingAnchor) + formDescriptionLabelRightPin?.isActive = true + + let fieldContainer = UIView(frame: .zero) + self.fieldContainer = fieldContainer + fieldContainer.translatesAutoresizingMaskIntoConstraints = false + + addSubview(fieldContainer) + setupFieldContainer(fieldContainer) + + fieldContainer.topAnchor.constraint(equalTo: formDescriptionLabel.bottomAnchor, constant: 4).isActive = true + textContainerLeftPin = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + textContainerLeftPin?.isActive = true + textContainerRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) + textContainerRightPin?.isActive = true + + let placeholderErrorLabel = Label() + self.placeholderErrorLabel = placeholderErrorLabel + placeholderErrorLabel.font = MFStyler.fontForTextFieldUnderLabel() + placeholderErrorLabel.textColor = .black + placeholderErrorLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + placeholderErrorLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) + placeholderErrorLabel.setContentCompressionResistancePriority(.required, for: .vertical) + + addSubview(placeholderErrorLabel) + + placeholderErrorLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor).isActive = true + errorLableLeftPin = placeholderErrorLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + errorLableLeftPin?.isActive = true + errorLableRightPin = layoutMarginsGuide.trailingAnchor.constraint(equalTo: placeholderErrorLabel.trailingAnchor) + errorLableRightPin?.isActive = true + layoutMarginsGuide.bottomAnchor.constraint(equalTo: placeholderErrorLabel.bottomAnchor).isActive = true + + setNeedsLayout() + } + + /// Method to override. + /// Intended to add the interactive content (textField) to the fieldContainer. + open func fieldContainerContent(_ container: UIView) { + + } + + /// Configuration logic for the text container view. + private func setupFieldContainer(_ parentView: UIView) { + + let backgroundView = UIView(frame: .zero) + self.backgroundView = backgroundView + backgroundView.translatesAutoresizingMaskIntoConstraints = false + + parentView.addSubview(backgroundView) + + NSLayoutConstraint.activate([ + backgroundView.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 1), + backgroundView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 1), + parentView.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: 1), + parentView.bottomAnchor.constraint(equalTo: backgroundView.bottomAnchor, constant: 1)]) + + fieldContainerContent(parentView) + + let separatorView = UIView(frame: .zero) + self.separatorView = separatorView + separatorView.translatesAutoresizingMaskIntoConstraints = false + separatorView.backgroundColor = .black + + parentView.addSubview(separatorView) + + separatorHeightConstraint = separatorView.heightAnchor.constraint(equalToConstant: 1) + separatorHeightConstraint?.isActive = true + separatorView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true + parentView.trailingAnchor.constraint(equalTo: separatorView.trailingAnchor).isActive = true + parentView.bottomAnchor.constraint(equalTo: separatorView.bottomAnchor).isActive = true + + let dashLine = DashLine() + dashLine.translatesAutoresizingMaskIntoConstraints = false + dashLine.backgroundColor = .white + dashLine.isHidden = true + + parentView.addSubview(dashLine) + + 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)]) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + formDescriptionLabel?.updateView(size) + placeholderErrorLabel?.font = MFStyler.fontForTextFieldUnderLabel() + dashLine?.updateView(size) + + layoutIfNeeded() + } + + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath?.removeAllPoints() + + if !hideBorder, let frame = fieldContainer?.frame { + + borderPath = UIBezierPath() + borderPath?.move(to: CGPoint(x: frame.origin.x, y: frame.origin.y + frame.size.height)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x, y: frame.origin.y)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y + frame.size.height)) + borderPath?.lineWidth = 1 + + let strokeColor = showError ? UIColor.mfPumpkin() : UIColor.mfSilver() + strokeColor.setStroke() + + borderPath?.stroke() + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + open func showErrorDropdown(_ show: Bool) { + + DispatchQueue.main.async { [weak self] in + + self?.showError = show + self?.separatorHeightConstraint?.constant = show ? 4 : 1 + self?.separatorView?.backgroundColor = show ? UIColor.mfPumpkin() : .black + self?.setNeedsDisplay() + self?.layoutIfNeeded() + } + } + + open func showErrorMessage(_ errorMessage: String?) { + + guard isEnabled else { return } + + DispatchQueue.main.async { [weak self] in + + self?.separatorHeightConstraint?.constant = 4 + self?.showError = true + self?.separatorView?.backgroundColor = UIColor.mfPumpkin() + self?.placeholderErrorLabel?.text = errorMessage + self?.placeholderErrorLabel?.numberOfLines = 0 + self?.setNeedsDisplay() + self?.layoutIfNeeded() + self?.showErrorDropdown(self?.showError ?? false) + } + } + + open func hideError() { + + DispatchQueue.main.async { [weak self] in + self?.separatorHeightConstraint?.constant = 1 + self?.separatorView?.backgroundColor = .black + self?.layoutIfNeeded() + self?.showError = false + self?.placeholderErrorLabel?.textColor = .black + self?.placeholderErrorLabel?.text = "" + self?.setNeedsDisplay() + self?.layoutIfNeeded() + } + } + + public func setWithMap(_ map: [AnyHashable: Any]?) { + + guard let map = map, !map.isEmpty else { return } + + if let formText = map[KeyLabel] as? String { + self.formText = formText + } + + if let text = map[KeyDisable] as? String, text.isEqual(StringY) || map.boolForKey(KeyDisable) { + formIsDisabled() + } + + if let errMessage = map[KeyErrorMessage] as? String { + self.errorMessage = errMessage + } + + if let hideBorder = map["hideBorder"] as? Bool { + self.hideBorder = hideBorder + } + + // Key used to send text value to server + if let fieldKey = map[KeyFieldKey] as? String { + self.fieldKey = fieldKey + } + } + + open override func setLeftPinConstant(_ constant: CGFloat) { + + textContainerLeftPin?.constant = constant + errorLableLeftPin?.constant = constant + formDescriptionLabelLeftPin?.constant = constant + } + + open override func setRightPinConstant(_ constant: CGFloat) { + + textContainerRightPin?.constant = constant + errorLableRightPin?.constant = constant + formDescriptionLabelRightPin?.constant = constant + } + + public func showDropDown(_ show: Bool) { + + if hasDropDown { + dropDownCaretLabel?.isHidden = !show + dropDownCarrotWidth?.isActive = !show + setNeedsLayout() + layoutIfNeeded() + } + } + + open func formIsEnabled() { + + // Set outside the dispatch so that registerAnimations can know about it + isEnabled = true + + DispatchQueue.main.async { [weak self] in + self?.isUserInteractionEnabled = true + self?.formDescriptionLabel?.textColor = UIColor.mfBattleshipGrey() + self?.placeholderErrorLabel?.textColor = .black + self?.separatorView?.backgroundColor = (self?.showError ?? false) ? UIColor.mfPumpkin() : .black + self?.showDropDown(true) + } + } + + open func formIsDisabled() { + + // Set outside the dispatch so that registerAnimations can know about it + isEnabled = false + + DispatchQueue.main.async { [weak self] in + self?.isUserInteractionEnabled = false + self?.formDescriptionLabel?.textColor = UIColor.mfSilver() + self?.placeholderErrorLabel?.textColor = UIColor.mfSilver() + self?.showDropDown(false) + self?.hideError() // Should not have error if the field is disabled + self?.separatorView?.backgroundColor = UIColor.mfSilver() + } + } + + open func showPlaceholderErrorLabel(_ show: Bool) { + + placeholderErrorLabel?.isHidden = !show + } + + open func showDashSeperatorView(_ dash: Bool) { + + // Never hide seperator view because it could be possiblely used by other classes for positioning + dashLine?.isHidden = !dash + separatorView?.backgroundColor = dash ? .clear : .black + } +} + +// MARK: - Molecular +extension FieldEntryForm { + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 76 + } +} + +// MARK: - Form Validation +extension FieldEntryForm: FormValidationProtocol { + + public func isValidField() -> Bool { + return isValid + } + + public func formFieldName() -> String? { + return fieldKey + } + + public func formFieldValue() -> Any? { + return text + } +} + +// MARK: - Accessibility +extension FieldEntryForm { + + @objc open func pushAccessibilityNotification() { + // To Be Overriden + } + + /** + Adding missing accessibilityLabel value + if we have some value in accessibilityLabel, + then only can append regular and picker item + */ + @objc open func setAccessibilityString(_ accessibilityString: String?) { + // To Be Overriden + } +} diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift new file mode 100644 index 00000000..e4f68a6a --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -0,0 +1,232 @@ +// +// MdnEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import AddressBookUI +import ContactsUI +import UIKit +import MVMCore + +class MdnEntryField: TextEntryField, UITextFieldDelegate, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { +//-------------------------------------------------- +// MARK: - Properties +//-------------------------------------------------- + +public weak var customDelegate: UITextFieldDelegate? +public var isNationalMdn = false +public var shouldValidateMDN = false + +public var mdn: String? { + get { + guard let text = text else { return nil } + + return MVMCoreUIUtility.removeMdnFormat(text) + } + set { + guard let MDN = newValue else { return } + text = MVMCoreUIUtility.formatMdn(MDN) + } +} + +//-------------------------------------------------- +// MARK: - Initializers +//-------------------------------------------------- + +public override init(frame: CGRect) { + super.init(frame: .zero) + setup() +} + +public convenience init() { + self.init(frame: .zero) +} + +required public 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 = mdn else { return true } + + if MDN.isEmpty { + return true + } + + 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 +} + +@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 +//-------------------------------------------------- + +public 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 public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + + textField.resignFirstResponder() + + return customDelegate?.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 customDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true +} + +public func textFieldDidBeginEditing(_ textField: UITextField) { + + textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) + customDelegate?.textFieldDidBeginEditing?(textField) +} + +public func textFieldDidEndEditing(_ textField: UITextField) { + + customDelegate?.textFieldDidEndEditing?(textField) + + if validateAndColor() && isNationalMdn { + textField.text = MVMCoreUIUtility.formatMdn(textField.text) + } +} + +//-------------------------------------------------- +// MARK: - Passed Along TextField delegate +//-------------------------------------------------- + +@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + + return customDelegate?.textFieldShouldBeginEditing?(textField) ?? true +} + +@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + + return customDelegate?.textFieldShouldEndEditing?(textField) ?? true +} + +@objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + + return customDelegate?.textFieldShouldClear?(textField) ?? true +} +} diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift new file mode 100644 index 00000000..170aefc8 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -0,0 +1,528 @@ +// +// TextField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/2/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objc public protocol TextFieldDelegate: NSObjectProtocol { + /// Called when the entered text becomes valid based on the validation block + @objc optional func isValid(textfield: TextEntryField?) + /// Called when the entered text becomes invalid based on the validation block + @objc optional func isInvalid(textfield: TextEntryField?) + /// Dismisses the keyboard. + @objc optional func dismissField(sender: Any?) +} + + +@objcMembers open class TextEntryField: FieldEntryForm { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public var textField: UITextField? + + private var calendar: Calendar? + + //-------------------------------------------------- + // MARK: - Delegate Properties + //-------------------------------------------------- + + /// 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? { + didSet { + if mfTextFieldDelegate != nil && !observingForChanges { + observingForChanges = 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 mfTextFieldDelegate == nil && observingForChanges { + observingForChanges = 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) + } + } + } + + /// If you're using a MFViewController, you must set this to it + public weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { return textField?.delegate } + set { + textField?.delegate = newValue + } + } + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var observingForChanges = false + + private var borderPath: UIBezierPath? + + // The text of this textField. + public override var text: String? { + get { return textField?.text } + set { + textField?.text = newValue + valueChanged() + } + } + + public override var formText: String? { + get { return formDescriptionLabel?.text } + set { + formDescriptionLabel?.text = newValue + setAccessibilityString(newValue) + } + } + + /// Sets placeholder text in the textField. + public var placeholder: String? { + get { + guard let attributedPlaceholder = textField?.attributedPlaceholder else { return nil } + return attributedPlaceholder.string + } + set { + guard let newPlaceholderText = newValue else { + textField?.attributedPlaceholder = nil + return + } + + textField?.attributedPlaceholder = NSAttributedString(string: newPlaceholderText, attributes: [NSAttributedString.Key.foregroundColor: placeholderTextColor]) + + if !showError { + placeholderErrorLabel?.text = (textField?.text?.count ?? 0) > 0 ? newPlaceholderText : "" + } + + setAccessibilityString(newPlaceholderText) + } + } + + public var validationBlock: ((_ enteredValue: String?) -> Bool)? { + didSet { + valueChanged() + } + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + /// Basic initializer. + public convenience init() { + self.init(frame: .zero) + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("init(coder:) has not been implemented") + } + + /// - parameter bothDelegates: Sets both MF/UI Text Field Delegates. + public init(bothDelegates: (UITextFieldDelegate & TextEntryFieldDelegate)?) { + super.init(frame: .zero) + setupView() + setBothTextFieldDelegates(bothDelegates) + } + + /// - parameter hasDropDown: tbd + /// - parameter map: Dictionary of values to setup this TextField + /// - parameter bothDelegate: Sets both MF/UI Text Field Delegates. + public init(hasDropDown: Bool = false, map: [AnyHashable: Any]?, bothDelegates: (UITextFieldDelegate & TextFieldDelegate)?) { + super.init(frame: .zero) + setupView() + dropDownCaretLabel?.isHidden = hasDropDown + self.hasDropDown = !hasDropDown + setWithMap(map, bothDelegates: bothDelegates) + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func fieldContainerContent(_ container: UIView) { + + let textField = UITextField(frame: .zero) + self.textField = textField + textField.translatesAutoresizingMaskIntoConstraints = false + textField.setContentCompressionResistancePriority(.required, for: .vertical) + textField.heightAnchor.constraint(equalToConstant: 24).isActive = true + textField.font = MFStyler.fontForTextField() + textField.smartQuotesType = .no + textField.smartDashesType = .no + textField.smartInsertDeleteType = .no + MFStyler.styleTextField(textField) + + container.addSubview(textField) + + NSLayoutConstraint.activate([ + textField.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 10), + textField.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 16), + parentView.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 10)]) + + let dropDownCaretLabel = Label() + self.dropDownCaretLabel = dropDownCaretLabel + dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(900), for: .horizontal) + dropDownCaretLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) + dropDownCaretLabel.setContentCompressionResistancePriority(UILayoutPriority(900), for: .horizontal) + dropDownCaretLabel.isHidden = true + dropDownCaretLabel.isUserInteractionEnabled = true + let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + dropDownCaretLabel.addGestureRecognizer(tapOnCarrot) + + container.addSubview(dropDownCaretLabel) + + dropDownCaretLabel.topAnchor.constraint(equalTo: container.topAnchor).isActive = true + dropDownCaretLabel.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6).isActive = true + container.trailingAnchor.constraint(equalTo: dropDownCaretLabel.trailingAnchor, constant: 16).isActive = true + container.bottomAnchor.constraint(equalTo: dropDownCaretLabel.bottomAnchor).isActive = true + dropDownCarrotWidth = dropDownCaretLabel.widthAnchor.constraint(equalToConstant: 0) + dropDownCarrotWidth?.isActive = true + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + if let textField = textField { + MFStyler.styleTextField(textField) + } + + layoutIfNeeded() + } + + deinit { + mfTextFieldDelegate = nil + uiTextFieldDelegate = nil + } + + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath?.removeAllPoints() + + if !hideBorder, let frame = fieldContainer?.frame { + + borderPath = UIBezierPath() + borderPath?.move(to: CGPoint(x: frame.origin.x, y: frame.origin.y + frame.size.height)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x, y: frame.origin.y)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y)) + borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y + frame.size.height)) + borderPath?.lineWidth = 1 + + let strokeColor = showError ? UIColor.mfPumpkin() : UIColor.mfSilver() + strokeColor.setStroke() + + borderPath?.stroke() + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + open override func showErrorMessage(_ errorMessage: String?) { + super.showErrorMessage(errorMessage) + + textField?.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField?.text ?? "", errorMessage ?? "") + } + + open override func hideError() { + super.hideError() + + textField?.accessibilityValue = nil + } + + public func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + + mfTextFieldDelegate = delegate + uiTextFieldDelegate = delegate + } + + public override func setWithMap(_ map: [AnyHashable: Any]?) { + super.setWithMap(map) + + guard let map = map, !map.isEmpty else { return } + + if let formText = map[KeyLabel] as? String { + self.formText = formText + } + + if let text = map[KeyValue] as? String { + self.text = text + } + + if let text = map[KeyDisable] as? String, text.isEqual(StringY) || map.boolForKey(KeyDisable) { + formIsDisabled() + } + + if let dropDown = map[KeyType] as? String { + dropDownCaretLabel?.isHidden = false + self.hasDropDown = true + } + + // Key used to send text value to server + if let fieldKey = map[KeyFieldKey] as? String { + self.fieldKey = fieldKey + } + + switch map.stringForkey(KeyType) { + case "dropDown": + dropDownCaretLabel?.isHidden = false + self.hasDropDown = true + + case "password": + textField?.isSecureTextEntry = true + + case "number": + textField?.keyboardType = .numberPad + + case "email": + textField?.keyboardType = .emailAddress + + default: + break + } + + let regex = map.stringForkey("regex") + + if !regex.isEmpty { + validationBlock = { enteredValue in + guard let value = enteredValue else { return false } + return MVMCoreUIUtility.validate(value, withRegularExpression: regex) + } + } else { + defaultValidationBlock() + } + } + + public func setWithMap(_ map: [AnyHashable: Any]?, bothDelegates delegate: (UITextFieldDelegate & TextFieldDelegate)?) { + + guard let textField = textField else { return } + + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate) + setBothTextFieldDelegates(delegate) + setWithMap(map) + } + + public func defaultValidationBlock() { + + validationBlock = { enteredValue in + return (enteredValue?.count ?? 0) > 0 + } + } + + open override func formIsEnabled() { + super.formIsEnabled() + + textField?.isUserInteractionEnabled = true + textField?.isEnabled = true + } + + open override func formIsDisabled() { + super.formIsDisabled() + + textField?.isUserInteractionEnabled = false + textField?.isEnabled = false + } + + //-------------------------------------------------- + // MARK: - Observing for change + //-------------------------------------------------- + + func valueChanged() { + + // Update label for placeholder + if !showError { + placeholderErrorLabel?.text = "" + } + + let previousValidity = isValid + + // If validation not set, input will always be valid + isValid = validationBlock?(text) ?? true + + if previousValidity && !isValid { + if let errMessage = errorMessage { + showErrorMessage(errMessage) + } + + if let mfTextFieldDelegate = mfTextFieldDelegate { + mfTextFieldDelegate.isInvalid?(textfield: self) + } + } else if !previousValidity && isValid { + hideError() + + if let mfTextFieldDelegate = mfTextFieldDelegate { + mfTextFieldDelegate.isValid?(textfield: self) + } + } + } + + func endInputing() { + + if isValid { + hideError() + separatorView?.backgroundColor = .black + } else if let errMessage = errorMessage { + showErrorMessage(errMessage) + } + } + + func startEditing() { + + textField?.becomeFirstResponder() + showErrorDropdown(!showError) + } + + class func getEnabledTextfields(_ textFieldToDetermine: [TextEntryField]?) -> [AnyHashable]? { + + var enabledTextFields = [AnyHashable]() + + for textfield in textFieldToDetermine ?? [] { + if textfield.isEnabled { + enabledTextFields.append(textfield) + } + } + + return enabledTextFields + } +} + +// MARK: - Date Picker +extension TextEntryField { + + private func createDatePicker() { + + guard let textField = textField else { return } + + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) + datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + + var calendar: Calendar = .current + calendar.timeZone = NSTimeZone.system + self.calendar = calendar + } + + public func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { + + createDatePicker() + + if show, 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) + } + } + + datePicker?.minimumDate = fromDate + datePicker?.maximumDate = toDate + } + + 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 { + text = formatter.string(from: fromDate) + } + } + + datePicker?.minimumDate = fromDate + datePicker?.maximumDate = toDate + datePicker?.timeZone = NSTimeZone.system + } + + public func dismissDatePicker() -> Date? { + + let pickedDate = datePicker?.date + + if let pickedDate = pickedDate { + if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) { + text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") + } else { + text = formatter.string(from: pickedDate) + } + } + + textField?.resignFirstResponder() + return pickedDate + } + + public func dismissPicker() { + + textField?.resignFirstResponder() + } +} + +// MARK: - Molecular +extension TextEntryField { + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + if let delegateObject = delegateObject { + FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol) + setWithMap(json) + + if let formValidationProtocol = delegateObject.formValidationProtocol { + mfTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) + } + + uiTextFieldDelegate = delegateObject.uiTextFieldDelegate + + if let textField = textField { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) + } + } + } + + override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 76 + } +} + +// MARK: - Accessibility +extension TextEntryField { + + open override 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 + */ + open override 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/Atoms/TextFields/TextField.swift b/MVMCoreUI/Atoms/TextFields/TextField.swift index ef22419b..1fcdbdc6 100644 --- a/MVMCoreUI/Atoms/TextFields/TextField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextField.swift @@ -395,74 +395,6 @@ import UIKit } } - //-------------------------------------------------- - // MARK: - Date Picker - //-------------------------------------------------- - - private func createDatePicker() { - - guard let textField = textField else { return } - - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) - datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) - - var calendar: Calendar = .current - calendar.timeZone = NSTimeZone.system - self.calendar = calendar - } - - public func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { - - createDatePicker() - - if show, 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) - } - } - - datePicker?.minimumDate = fromDate - datePicker?.maximumDate = toDate - } - - 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 { - text = formatter.string(from: fromDate) - } - } - - datePicker?.minimumDate = fromDate - datePicker?.maximumDate = toDate - datePicker?.timeZone = NSTimeZone.system - } - - public func dismissDatePicker() -> Date? { - - let pickedDate = datePicker?.date - - if let pickedDate = pickedDate { - if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) { - text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") - } else { - text = formatter.string(from: pickedDate) - } - } - - textField?.resignFirstResponder() - return pickedDate - } - - public func dismissPicker() { - - textField?.resignFirstResponder() - } - //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -721,6 +653,74 @@ import UIKit } } +// MARK: - Date Picker +extension TextField { + + private func createDatePicker() { + + guard let textField = textField else { return } + + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate) + datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + + var calendar: Calendar = .current + calendar.timeZone = NSTimeZone.system + self.calendar = calendar + } + + public func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) { + + createDatePicker() + + if show, 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) + } + } + + datePicker?.minimumDate = fromDate + datePicker?.maximumDate = toDate + } + + 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 { + text = formatter.string(from: fromDate) + } + } + + datePicker?.minimumDate = fromDate + datePicker?.maximumDate = toDate + datePicker?.timeZone = NSTimeZone.system + } + + public func dismissDatePicker() -> Date? { + + let pickedDate = datePicker?.date + + if let pickedDate = pickedDate { + if let calendar = calendar, calendar.isDate(pickedDate, inSameDayAs: Date()) { + text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") + } else { + text = formatter.string(from: pickedDate) + } + } + + textField?.resignFirstResponder() + return pickedDate + } + + public func dismissPicker() { + + textField?.resignFirstResponder() + } +} + // MARK: - Molecular extension TextField { diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index ffafdc82..d368cbfc 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -37,7 +37,7 @@ @"caretView": CaretView.class, @"caretButton": CaretButton.class, @"textField" : TextField.class, - @"digitTextField" : MFDigitTextField.class, + @"digitTextField" : DigitTextField.class, @"checkbox" : Checkbox.class, @"checkboxWithLabelView" : CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class,