diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 42ea7c83..0973ba4b 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -23,15 +23,38 @@ 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 */; }; + 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* EntryField.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 */; }; + 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */; }; + 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; + 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; + 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; }; 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Toggle.swift */; }; + 0ABD136B237B193A0081388D /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */; }; + 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; + 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; + 0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; }; 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; + C003506123AA94CD00B6AC29 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C003506023AA94CD00B6AC29 /* Button.swift */; }; + C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479892314445E003FCCF9 /* LabelSwitch.swift */; }; @@ -104,17 +127,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 */; }; - 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 */; }; @@ -223,16 +235,28 @@ 0198F7A02256A80A0066C936 /* MFRadioButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFRadioButton.h; sourceTree = ""; }; 0198F7A22256A80A0066C936 /* MFRadioButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFRadioButton.m; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; + 0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = ""; }; + 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryField.swift; sourceTree = ""; }; + 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryField.swift; sourceTree = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; + 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; + 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = ""; }; 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleGuidelinesProtocol.swift; sourceTree = ""; }; 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.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 = ""; }; 0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; + 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; + 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; + 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; + 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; + 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.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 = ""; }; + C003506023AA94CD00B6AC29 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; + C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadLineBodyCaretLinkImage.swift; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; D22479892314445E003FCCF9 /* LabelSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelSwitch.swift; sourceTree = ""; }; @@ -438,6 +462,14 @@ path = FormUIHelpers; sourceTree = ""; }; + 0ABD1369237B18EE0081388D /* views */ = { + isa = PBXGroup; + children = ( + 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */, + ); + path = views; + sourceTree = ""; + }; 0A5D59C323AD488600EFD9E9 /* Protocols */ = { isa = PBXGroup; children = ( @@ -494,6 +526,7 @@ D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */, D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */, + C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */, ); path = VerticalCombinationViews; sourceTree = ""; @@ -680,6 +713,7 @@ D29DF11921E68467003B2FB9 /* Containers */ = { isa = PBXGroup; children = ( + 0ABD1369237B18EE0081388D /* views */, D29DF2B721E7BE79003B2FB9 /* TabBarController */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, D2B18B93236214AD00A9AEDC /* NavigationController.swift */, @@ -813,6 +847,14 @@ D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */, D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, + 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, + 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */, + 0A21DB7E235DECC500C160A2 /* EntryField.swift */, + 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, + 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, + 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */, + 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */, + 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */, ); path = TextFields; sourceTree = ""; @@ -915,8 +957,10 @@ D2B18B7D236090D500A9AEDC /* BaseClasses */ = { isa = PBXGroup; children = ( + C003506023AA94CD00B6AC29 /* Button.swift */, D2B18B7E2360913400A9AEDC /* Control.swift */, D2B18B802360945C00A9AEDC /* View.swift */, + 0AE14F63238315D2005417F8 /* TextField.swift */, 0A5D59C323AD488600EFD9E9 /* Protocols */, ); path = BaseClasses; @@ -936,7 +980,6 @@ 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 */, @@ -946,24 +989,25 @@ 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 */, 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 */, - D29DF25721E6A177003B2FB9 /* MFTextField.h in Headers */, D29DF17521E69E1F003B2FB9 /* ButtonDelegateProtocol.h in Headers */, D29DF18221E69E54003B2FB9 /* SeparatorView.h in Headers */, D29DF26E21E6AA0B003B2FB9 /* FLAnimatedImage.h in Headers */, @@ -971,6 +1015,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 */, @@ -979,7 +1024,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 */, @@ -1061,12 +1105,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 */, @@ -1099,7 +1143,6 @@ D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, - D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */, D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, @@ -1108,17 +1151,18 @@ D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, + 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, + 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, - D29DF24D21E6A177003B2FB9 /* MFTextField.m in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */, - D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */, + 0A21DB85235E06EF00C160A2 /* MFTextField.m in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D213347723843825008E41B3 /* Line.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, @@ -1126,7 +1170,9 @@ D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, + 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, + 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D2B18B812360945C00A9AEDC /* View.swift in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, @@ -1139,6 +1185,7 @@ 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */, D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, + 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */, 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */, @@ -1156,11 +1203,14 @@ D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */, D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */, D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, + 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift 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 */, + 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, @@ -1169,8 +1219,11 @@ D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */, + 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, + 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, + 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, @@ -1178,17 +1231,21 @@ D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, + C003506123AA94CD00B6AC29 /* Button.swift in Sources */, D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, + 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, D268C712238D6699007F2C1C /* DropDown.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, + C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, + 0ABD136B237B193A0081388D /* EntryFieldContainer.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretButton.swift index 6c9de088..887c4c5f 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretButton.swift @@ -9,7 +9,6 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { - //------------------------------------------------------ // MARK: - Constants //------------------------------------------------------ @@ -17,7 +16,6 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI private let CARET_VIEW_HEIGHT: Float = 10.5 private let CARET_VIEW_WIDTH: Float = 6.5 - //------------------------------------------------------ // MARK: - Properties //------------------------------------------------------ @@ -46,25 +44,23 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI super.layoutSubviews() } - public func setEnabled(_ enabled: Bool) { - super.isEnabled = enabled - - changeCaretColor() + override public var isEnabled: Bool { + didSet { changeCaretColor() } } - public func updateView(_ size: CGFloat) { - } + public func updateView(_ size: CGFloat) { } //------------------------------------------------------ - // MARK: - Functions + // MARK: - Methods //------------------------------------------------------ private func changeCaretColor() { + setTitleColor(enabledColor, for: .normal) setTitleColor(disabledColor, for: .disabled) if let rightCaretView = rightView as? CaretView { - rightCaretView.setLineColor(isEnabled ? enabledColor : disabledColor) + rightCaretView.isEnabled = isEnabled } } @@ -83,16 +79,17 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI rightView?.translatesAutoresizingMaskIntoConstraints = false addSubview(caretView) - NSLayoutConstraint(item: caretView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: width).isActive = true - NSLayoutConstraint(item: caretView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: height).isActive = true + caretView.widthAnchor.constraint(equalToConstant: width).isActive = true + caretView.heightAnchor.constraint(equalToConstant: height).isActive = true - let caretLabelSpacing = NSLayoutConstraint(item: caretView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1.0, constant: 4.0) + let caretLabelSpacing = NSLayoutConstraint(item: caretView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1.0, constant: 4) caretLabelSpacing.isActive = true caretSpacingConstraint = caretLabelSpacing NSLayoutConstraint(item: caretView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true NSLayoutConstraint(item: self, attribute: .right, relatedBy: .greaterThanOrEqual, toItem: caretView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true contentHorizontalAlignment = .left + //set correct color after layout changeCaretColor() } @@ -119,6 +116,14 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI guard let dictionary = json else { return } + if let title = dictionary.optionalStringForKey(KeyTitle) { + setTitle(title, for: .normal) + } + + if let disableButtonAsAny = dictionary[KeyDisableButton], let isDisabled = disableButtonAsAny as? Bool { + isEnabled = !isDisabled + } + if let backgroundColorHex = dictionary[KeyBackgroundColor] as? String { backgroundColor = UIColor.mfGet(forHex: backgroundColorHex) } @@ -137,10 +142,10 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI } open func horizontalAlignment() -> UIStackView.Alignment { - return UIStackView.Alignment.leading; + return .leading } - public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 10 } } diff --git a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift new file mode 100644 index 00000000..05e64f70 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift @@ -0,0 +1,83 @@ +// +// BaseDropdownEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/23/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +/** + This class is intended to be subclassed. + See ItemDropdownEntryField and DateDropdownEntryField. + */ +@objcMembers open class BaseDropdownEntryField: TextEntryField { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + let dropDownCaretView: CaretView = { + let caret = CaretView() + caret.direction = .down + caret.lineWidth = 1.5 + caret.isUserInteractionEnabled = true + caret.heightAnchor.constraint(equalToConstant: 9).isActive = true + caret.widthAnchor.constraint(equalToConstant: 16).isActive = true + return caret + }() + + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + + @objc public override var isEnabled: Bool { + get { super.isEnabled } + set (enabled) { + dropDownCaretView.isEnabled = enabled + super.isEnabled = enabled + } + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + @objc public override init(frame: CGRect) { + super.init(frame: frame) + } + + @objc required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("DropdownEntryField does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + + @objc public override func setupFieldContainerContent(_ container: UIView) { + super.setupFieldContainerContent(container) + + container.addSubview(dropDownCaretView) + + textFieldTrailingConstraint?.isActive = false + textFieldTrailingConstraint = dropDownCaretView.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6) + textFieldTrailingConstraint?.isActive = true + + container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true + dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension BaseDropdownEntryField { + + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json, !dictionary.isEmpty else { return } + + dropDownCaretView.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift new file mode 100644 index 00000000..feddfd6b --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -0,0 +1,129 @@ +// +// DateDropdownEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objcMembers open class DateDropdownEntryField: BaseDropdownEntryField { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public weak var datePicker: UIDatePicker? + + private var calendar: Calendar = { + var calendar: Calendar = .current + calendar.timeZone = NSTimeZone.system + return calendar + }() + + public var dateFormat: String? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + @objc public override init(frame: CGRect) { + super.init(frame: frame) + } + + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc public convenience init(startDate: Date, endDate: Date, showStartDate: Bool = true) { + self.init(frame: .zero) + setDatePickerDuration(from: startDate, to: endDate, showStartDate: showStartDate) + } + + @objc required public init?(coder: NSCoder) { + fatalError("DateDropdownEntryField init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + public override func setupFieldContainerContent(_ container: UIView) { + super.setupFieldContainerContent(container) + + datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + datePicker?.addTarget(self, action: #selector(pickerValueChanged), for: .valueChanged) + datePicker?.timeZone = NSTimeZone.system + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: self) + } + + @objc public func setDatePickerDuration(from startDate: Date?, to endDate: Date?, showStartDate: Bool = true) { + + datePicker?.minimumDate = startDate + datePicker?.maximumDate = endDate + + if showStartDate { + setTextWith(date: startDate) + } + } + + @objc public func dismissDatePicker() -> Date? { + + let pickedDate = datePicker?.date + setTextWith(date: pickedDate) + resignFirstResponder() + return pickedDate + } + + @objc private func setTextWith(date: Date?) { + + guard let date = date else { return } + + if calendar.isDate(date, inSameDayAs: Date()) { + text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") + } else { + text = dateFormatter().string(from: date) + } + } + + @objc override func dismissFieldInput(_ sender: Any?) { + + setTextWith(date: datePicker?.date) + super.dismissFieldInput(sender) + } + + @objc func pickerValueChanged(_ sender: UIDatePicker) { + + setTextWith(date: datePicker?.date) + } + + public func dateFormatter() -> DateFormatter { + + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeZone = NSTimeZone.system + formatter.locale = .current + formatter.formatterBehavior = .default + + if let dateFormat = dateFormat { + formatter.dateFormat = dateFormat + } + + return formatter + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension DateDropdownEntryField { + + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json, !dictionary.isEmpty else { return } + + if let dateFormat = dictionary["dateFormat"] as? String { + self.dateFormat = dateFormat + } + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitBox.swift b/MVMCoreUI/Atoms/TextFields/DigitBox.swift new file mode 100644 index 00000000..1134a80f --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DigitBox.swift @@ -0,0 +1,195 @@ +// +// DigitBox.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/15/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objc protocol DigitBoxProtocol { + @objc optional func digitFieldDidDelete(_ textField: TextField?) +} + + +@objcMembers open class DigitBox: EntryFieldContainer, UITextFieldDelegate, TextFieldDidDeleteProtocol { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + let digitField: TextField = { + let textField = TextField() + textField.isAccessibilityElement = true + textField.setContentCompressionResistancePriority(.required, for: .vertical) + textField.setContentCompressionResistancePriority(.required, for: .horizontal) + textField.textAlignment = .center + textField.font = MFStyler.fontForTextField() + textField.keyboardType = .numberPad + return textField + }() + + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- + + private var previousSize: CGFloat = 0.0 + + // Default dimensions of the DigitBox + static let size: CGSize = CGSize(width: 39, height: 44) + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + public override var showError: Bool { + get { return super.showError } + set (error) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.borderStrokeColor = error ? .mfPumpkin() : .mfSilver() + + let barHeight: CGFloat = self.showError ? 4 : 1 + self.bottomBar?.frame = CGRect(x: 0, y: self.bounds.height - barHeight, width: self.bounds.width, height: barHeight) + self.bottomBar?.backgroundColor = self.showError ? UIColor.mfPumpkin().cgColor : UIColor.black.cgColor + + self.setNeedsDisplay() + self.layoutIfNeeded() + } + super.showError = error + } + } + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + weak var digitBoxDelegate: DigitBoxProtocol? + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private weak var widthConstraint: NSLayoutConstraint? + private weak var heightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + @objc public override init(frame: CGRect) { + super.init(frame: frame) + } + + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("DigitBox does not support xibs.") + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setupView() { + super.setupView() + + guard constraints.isEmpty else { return } + + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .clear + + addSubview(digitField) + digitField.delegate = self + digitField.didDeleteDelegate = self + + NSLayoutConstraint.activate([ + digitField.topAnchor.constraint(equalTo: topAnchor, constant: PaddingOne), + digitField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: PaddingOne), + bottomAnchor.constraint(equalTo: digitField.bottomAnchor, constant: PaddingOne), + trailingAnchor.constraint(equalTo: digitField.trailingAnchor, constant: PaddingOne), + digitField.centerYAnchor.constraint(equalTo: centerYAnchor), + digitField.centerXAnchor.constraint(equalTo: centerXAnchor)]) + + widthConstraint = widthAnchor.constraint(equalToConstant: DigitBox.size.width) + widthConstraint?.isActive = true + heightConstraint = heightAnchor.constraint(equalToConstant: DigitBox.size.height) + heightConstraint?.isActive = true + + if let bottomBar = bottomBar { + layer.addSublayer(bottomBar) + } + + let tap = UITapGestureRecognizer(target: self, action: #selector(callTextField)) + addGestureRecognizer(tap) + + updateView(MVMCoreUIUtility.getWidth()) + } + + @objc public func textFieldDidDelete() { + + digitBoxDelegate?.digitFieldDidDelete?(digitField) + } + + @objc open override func layoutSubviews() { + super.layoutSubviews() + + let barHeight: CGFloat = showError ? 4 : 1 + bottomBar?.frame = CGRect(x: 0, y: bounds.height - barHeight, width: bounds.width, height: barHeight) + } + + @objc open override func reset() { + super.reset() + + backgroundColor = .clear + digitField.font = MFStyler.fontForTextField() + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + @objc public func callTextField() { + + digitField.becomeFirstResponder() + } + + @objc public override func updateView(_ size: CGFloat) { + super.updateView(size) + + if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(previousSize)) { + + var width: CGFloat = 0 + var height: CGFloat = 0 + var pointSize: CGFloat = 13 + + let sizeObject = MFSizeObject(standardBlock: { + width = 39 + height = 44 + pointSize = 16 + + }, smalliPhone: { + width = 35 + height = 38 + pointSize = 13 + + }, standardiPadPortraitBlock: { + width = 59 + height = 74 + pointSize = 32 + }) + + sizeObject?.performBlockBase(onSize: size) + widthConstraint?.constant = width + heightConstraint?.constant = height + digitField.font = MFFonts.mfFont55Rg(pointSize) + previousSize = size + } + + setNeedsLayout() + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift new file mode 100644 index 00000000..4cd9b67a --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -0,0 +1,452 @@ +// +// DigitEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +/** + * Subclass of TextEntryField as it is to use similar logic as a singular textField but appear separate.. + */ +@objcMembers open class DigitEntryField: TextEntryField, DigitBoxProtocol { + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- + + private(set) var numberOfDigits = 4 { + didSet { + guard entryFieldContainer.subviews.isEmpty || oldValue != numberOfDigits else { return } + + var accessibleElements: [Any] = [titleLabel] + + if numberOfDigits > 0 { + var digitBoxes = [DigitBox]() + + let ordinalFormatter = NumberFormatter() + ordinalFormatter.numberStyle = .ordinal + + for i in 0.. DigitBox { + + let digitBox = DigitBox() + digitBox.isAccessibilityElement = true + MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: self) + digitBox.digitField.delegate = self + digitBox.digitBoxDelegate = self + return digitBox + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + @objc open override func updateView(_ size: CGFloat) { + super.updateView(size) + + entryFieldContainer.disableAllBorders = true + + if !self.digitBoxes.isEmpty { + self.digitBoxes.forEach { $0.updateView(size) } + } + layoutIfNeeded() + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + @objc public func setAsSecureTextEntry(_ secureEntry: Bool) { + + for (index, field) in self.digitBoxes.enumerated() { + field.digitField.isSecureTextEntry = secureEntry + + // Accessibility - 33704 fix voice over will read what pin user is filling + field.accessibilityLabel = String(format: "PIN %lu of %lu", UInt(index) + 1, UInt(self.digitBoxes.count)) + } + } + + @objc public override func defaultValidationBlock() { + + validationBlock = { enteredValue in + guard let enteredValue = enteredValue else { return false } + + return enteredValue.count > 0 && enteredValue.count == self.digitBoxes.count + } + } + + @objc public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { + + var selectPreviousField = false + + for (index, box) in Array(digitBoxes.reversed()).enumerated() { + if box.digitField === currentTextField { + if index == digitBoxes.count - 1 { + return + } else { + selectPreviousField = true + } + } else if selectPreviousField { + if !clear { + switchFieldsAutomatically = true + } + box.digitField.becomeFirstResponder() + switchFieldsAutomatically = false + UIAccessibility.post(notification: .layoutChanged, argument: box.digitField) + return + } + } + } + + @objc public func selectNextDigitField(_ currentTextField: UITextField?, clear: Bool) { + + var selectNextField = false + + for (index, box) in digitBoxes.enumerated() { + if box.digitField === currentTextField { + if index == digitBoxes.count - 1 { + return + } else { + selectNextField = true + } + } else if selectNextField { + if !clear { + switchFieldsAutomatically = true + } + box.digitField.becomeFirstResponder() + switchFieldsAutomatically = false + UIAccessibility.post(notification: .layoutChanged, argument: box.digitField) + return + } + } + } + + //-------------------------------------------------- + // MARK: - Observing TextField Changes + //-------------------------------------------------- + + @objc override func startEditing() { + + selectedDigitBox?.isSelected = true + selectedDigitBox?.digitField.becomeFirstResponder() + } + + @objc override open func resignFirstResponder() -> Bool { + + if validateWhenDoneEditing { + validateTextField() + } + + selectedDigitBox?.isSelected = false + selectedDigitBox?.digitField.resignFirstResponder() + return true + } + + @objc override func dismissFieldInput(_ sender: Any?) { + + digitBoxes.forEach { + if $0.isSelected { + $0.digitField.resignFirstResponder() + $0.isSelected = false + } + } + } +} + +// MARK: - TextField Delegate +extension DigitEntryField { + + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + + textField.resignFirstResponder() + + return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true + } + + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { + return false + } + + if (textField.text?.count ?? 0) >= range.length + range.location { + + let oldLength = textField.text?.count ?? 0 + let replacementLength = string.count + if replacementLength > 1 { + // Too long (Check with AKQA if they want to allow pasting the digits. + return false + + } else if replacementLength == 1 && (oldLength == 1 || oldLength == 0) { + + // One character, switch old value with new, select next textfield + textField.text = string + selectNextDigitField(textField, clear: false) + return false + + } else if replacementLength == 0 && oldLength == 1 { + // Non empty cell, clear and stay. + textField.text = "" + return false + } + + return true + } + + return false + } + + @objc func digitFieldDidDelete(_ textField: TextField?) { + + selectPreviousDigitField(textField, clear: false) + } + + @objc public func textFieldDidBeginEditing(_ textField: UITextField) { + + digitBoxes.forEach { + if $0.digitField === textField { + selectedDigitBox = $0 + $0.isSelected = true + return + } + } + + if !switchFieldsAutomatically { + textField.text = "" + } + + proprietorTextDelegate?.textFieldDidBeginEditing?(textField) + } + + @objc public func textFieldDidEndEditing(_ textField: UITextField) { + + // There should only be one digitBox to deselect. + selectedDigitBox?.isSelected = false + selectedDigitBox = nil + + if !switchFieldsAutomatically && validateWhenDoneEditing { + validateTextField() + } + + proprietorTextDelegate?.textFieldDidEndEditing?(textField) + } + + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + + selectPreviousDigitField(textField, clear: true) + + return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true + } + + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + + return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true + } + + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + + return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension DigitEntryField { + + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json else { return } + + numberOfDigits = dictionary["digits"] as? Int ?? 4 + + if let _ = dictionary["secureEntry"] as? Bool { + setAsSecureTextEntry(true) + } + + if !dictionary.isEmpty{ + for digitBox in digitBoxes { + MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate) + } + } + } + + @objc open override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 115 + } +} diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift new file mode 100644 index 00000000..7e40feba --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -0,0 +1,320 @@ +// +// EntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +/** + * The EntryForm provides the base logic for the description label, placeholder/error label and field container. + * + * When subclassing, be sure to override setupFieldContainerContent(). In this method you will setup all the content bound to the field container. + */ +@objcMembers open class EntryField: View { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public private(set) var titleLabel: Label = { + let label = Label() + label.font = MFStyler.fontB3() + label.textColor = .mfBattleshipGrey() + label.setContentCompressionResistancePriority(.required, for: .vertical) + return label + }() + + public private(set) var entryFieldContainer = EntryFieldContainer() + + /// Provides contextual information on the TextField. + public private(set) var feedbackLabel: Label = { + let label = Label() + label.font = MFStyler.fontForTextFieldUnderLabel() + label.textColor = .black + label.setContentCompressionResistancePriority(.required, for: .vertical) + return label + }() + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + weak var delegateObject: MVMCoreUIDelegateObject? + + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- + + public var isValid: Bool = false + public var fieldKey: String? + + public var errorMessage: String? + public var standardMessage: String? { + didSet { + if !showError { + feedback = standardMessage + } + } + } + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + /// Toggles enabled (original) or disabled UI. + public var isEnabled: Bool { + get { return entryFieldContainer.isEnabled } + set (enabled) { + self.feedbackLabel.textColor = enabled ? .black : .mfSilver() + self.entryFieldContainer.isEnabled = enabled + } + } + + /// Toggles error or original UI. + public var showError: Bool { + get { return entryFieldContainer.showError } + set (error) { + self.feedback = error ? self.errorMessage : self.standardMessage + self.entryFieldContainer.showError = error + } + } + + /// Toggles original or locked UI. + public var isLocked: Bool { + get { return entryFieldContainer.isLocked } + set (locked) { + self.entryFieldContainer.isLocked = locked + } + } + + /// Toggles selected or original (unselected) UI. + public var isSelected: Bool { + get { return entryFieldContainer.isSelected } + set (selected) { + self.entryFieldContainer.isSelected = selected + } + } + + /// Sets the text of titleLabel + public var title: String? { + get { return titleLabel.text } + set (newText) { + titleLabel.text = newText + setAccessibilityString(newText) + } + } + + /// Override this to conveniently get/set the textfield(s). + public var text: String? { + get { return nil } + set { fatalError("You MUST override EntryField's 'text' variable in your subclass.") } + } + + /// Sets feedback text in the textField. + public var feedback: String? { + get { return feedbackLabel.text } + set (newFeedback) { + feedbackLabel.text = newFeedback + feedbackLabel.accessibilityElementsHidden = feedbackLabel.text?.isEmpty ?? true + entryFieldContainer.refreshUI() + } + } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + public var entryFieldContainerLeading: NSLayoutConstraint? + public var entryFieldContainerTrailing: NSLayoutConstraint? + + public var feedbackLabelTrailing: NSLayoutConstraint? + public var feedbackLabelLeading: NSLayoutConstraint? + + public var titleLabelLeading: NSLayoutConstraint? + public var titleLabelTrailing: NSLayoutConstraint? + + public var titleContainerDistance: NSLayoutConstraint? + public var feedbackContainerDistance: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + /// This must be overriden by a subclass. + @objc public override init(frame: CGRect) { + super.init(frame: frame) + } + + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc public init(title: String) { + super.init(frame: .zero) + + titleLabel.text = title + } + + @objc required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("EntryField does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + /// Initial configuration of class and view. + @objc final public override func setupView() { + super.setupView() + + guard subviews.isEmpty else { return } + + isAccessibilityElement = false + setContentCompressionResistancePriority(.required, for: .vertical) + accessibilityElements = [titleLabel, feedbackLabel] + backgroundColor = .clear + + addSubview(titleLabel) + + titleLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + titleLabelLeading = titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + titleLabelLeading?.isActive = true + titleLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor) + titleLabelLeading?.isActive = true + + addSubview(entryFieldContainer) + entryFieldContainer.setContentCompressionResistancePriority(.required, for: .vertical) + setupFieldContainerContent(entryFieldContainer) + + titleContainerDistance = entryFieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4) + titleContainerDistance?.isActive = true + entryFieldContainerLeading = entryFieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + entryFieldContainerLeading?.isActive = true + entryFieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: entryFieldContainer.trailingAnchor) + entryFieldContainerTrailing?.isActive = true + + addSubview(feedbackLabel) + + feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryFieldContainer.bottomAnchor, constant: PaddingOne) + feedbackContainerDistance?.isActive = true + feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + feedbackLabelLeading?.isActive = true + feedbackLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) + feedbackLabelTrailing?.isActive = true + layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true + } + + @objc open override func layoutSubviews() { + super.layoutSubviews() + + entryFieldContainer.refreshUI() + } + + /// Method to override. + /// Intended to add the interactive content (i.e. textField) to the entryFieldContainer. + @objc open func setupFieldContainerContent(_ container: UIView) { + // To be overridden by subclass. + } + + @objc open override func updateView(_ size: CGFloat) { + super.updateView(size) + + titleLabel.updateView(size) + feedbackLabel.updateView(size) + entryFieldContainer.updateView(size) + } + + @objc open override func reset() { + super.reset() + + backgroundColor = .clear + isAccessibilityElement = false + titleLabel.font = MFStyler.fontB3() + titleLabel.textColor = .mfBattleshipGrey() + feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() + feedbackLabel.textColor = .black + entryFieldContainer.reset() + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension EntryField { + + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + + guard let dictionary = json, !dictionary.isEmpty else { return } + + entryFieldContainer.setWithJSON(dictionary, delegateObject: delegateObject, additionalData: additionalData) + + if let titleText = dictionary[KeyTitle] as? String { + title = titleText + } + + if let disable = dictionary[KeyDisable] as? String, disable.isEqual(StringY) || dictionary.boolForKey(KeyDisable) { + isEnabled = false + } + + if let feedback = dictionary["feedback"] as? String { + self.standardMessage = feedback + } + + if let errMessage = dictionary[KeyErrorMessage] as? String { + errorMessage = errMessage + } + + if let isLocked = dictionary["isLocked"] as? Bool { + self.isLocked = isLocked + } + + if let isSelected = dictionary["isSelected"] as? Bool { + self.isSelected = isSelected + } + + // Key used to send text value to server + if let fieldKey = dictionary[KeyFieldKey] as? String { + self.fieldKey = fieldKey + } + } + + @objc open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 115 + } +} + +// MARK: - Form Validation +extension EntryField: FormValidationProtocol { + + @objc public func isValidField() -> Bool { + return isValid + } + + @objc public func formFieldName() -> String? { + return fieldKey + } + + @objc public func formFieldValue() -> Any? { + return text + } +} + +// MARK: - Accessibility +extension EntryField { + + @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/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift new file mode 100644 index 00000000..7eaf6c5c --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -0,0 +1,144 @@ +// +// ItemDropdownEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/14/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +open class ItemDropdownEntryField: BaseDropdownEntryField { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + open var pickerData: [String] = [] + open var pickerView: UIPickerView? + + /// When selecting for first responder, allow initial selected value to appear in empty text field. + public var setInitialValueInTextField = true + + /// Closure passed here will run as picker changes items. + public var observeDropdownChange: ((String)->())? + + /// Closure passed here will run upon dismissing the selection picker. + public var observeDropdownSelection: ((String)->())? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + @objc public override init(frame: CGRect) { + super.init(frame: frame) + } + + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc public convenience init(pickerData: [String]) { + self.init(frame: .zero) + self.pickerData = pickerData + } + + @objc required public init?(coder: NSCoder) { + fatalError("ItemDropdownEntryField init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + @objc open override func setupFieldContainerContent(_ container: UIView) { + super.setupFieldContainerContent(container) + + pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) + textField.hideBlinkingCaret = true + textField.autocorrectionType = .no + uiTextFieldDelegate = self + } + + @objc public func setPickerDelegates(delegate: UIPickerViewDelegate & UIPickerViewDataSource) { + + pickerView?.delegate = delegate + pickerView?.dataSource = delegate + } + + @objc private func setInitialValueFromPicker() { + + guard !pickerData.isEmpty else { return } + + if setInitialValueInTextField, let pickerIndex = pickerView?.selectedRow(inComponent: 0) { + text = pickerData[pickerIndex] + } + } + + @objc override func startEditing() { + super.startEditing() + + setInitialValueFromPicker() + } + + @objc override func endInputing() { + super.endInputing() + + guard !pickerData.isEmpty else { return } + + if let pickerIndex = pickerView?.selectedRow(inComponent: 0) { + observeDropdownSelection?(pickerData[pickerIndex]) + } + } +} + +// MARK:- Base Picker Delegate +extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { + + @objc public func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + @objc public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return pickerData.count + } + + @objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return pickerData[row] + } + + @objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + text = pickerData[row] + observeDropdownChange?(pickerData[row]) + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension ItemDropdownEntryField { + + @objc override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json, !dictionary.isEmpty else { return } + + if let options = dictionary["options"] as? [String] { + pickerData = options + setPickerDelegates(delegate: self) + } + } +} + +// MARK: - Accessibility +extension ItemDropdownEntryField { + + @objc open override func setAccessibilityString(_ accessibilityString: String?) { + + var accessibilityString = accessibilityString ?? "" + + if let textPickerItem = MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item") { + accessibilityString += textPickerItem + } + + textField.accessibilityLabel = "\(accessibilityString) \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state") ?? "")" + } +} diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift new file mode 100644 index 00000000..a0a893a6 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryField.swift @@ -0,0 +1,208 @@ +// +// 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 + +/** + This class provides the convenience of formatting the MDN entered/displayer for the user. + */ +@objcMembers open class MdnEntryField: TextEntryField, ABPeoplePickerNavigationControllerDelegate, CNContactPickerDelegate { + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- + + public var isNationalMDN = true + public var shouldValidateMDN = false + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. + private weak var proprietorTextDelegate: UITextFieldDelegate? + + /// If you're using a MFViewController, you must set this to it + public override weak var uiTextFieldDelegate: UITextFieldDelegate? { + get { return textField.delegate } + set { + textField.delegate = self + proprietorTextDelegate = newValue + } + } + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + /// Formats the MDN when setting and removes format of MDN when reading. + public var mdn: String? { + get { return MVMCoreUIUtility.removeMdnFormat(text) } + set { text = MVMCoreUIUtility.formatMdn(newValue) } + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + @objc public override init(frame: CGRect) { + super.init(frame: .zero) + } + + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("MdnEntryField xib not supported.") + } + + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + + @objc public override func setupFieldContainerContent(_ container: UIView) { + super.setupFieldContainerContent(container) + + 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 + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + @objc public func hasValidMDN() -> Bool { + + guard let MDN = mdn, !MDN.isEmpty else { return false } + + if isNationalMDN { + return MVMCoreUIUtility.validateMDNString(MDN) + } + + return MVMCoreUIUtility.validateInternationalMDNString(MDN) + } + + @objc public override func validateTextField() -> Bool { + + guard !shouldValidateMDN, let MDN = mdn, !MDN.isEmpty else { + isValid = true + return true + } + + let isValid = hasValidMDN() + + if isValid { + showError = false + + } else { + errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") + showError = true + UIAccessibility.post(notification: .layoutChanged, argument: textField) + } + + return isValid + } + + @objc public func getContacts(_ sender: Any?) { + + let picker = CNContactPickerViewController() + 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: - Contact Picker Delegate + //-------------------------------------------------- + + @objc public func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { + + if let phoneNumber = contactProperty.value as? CNPhoneNumber { + + let MDN = phoneNumber.stringValue + var unformattedMDN = MVMCoreUIUtility.removeMdnFormat(MDN) + + // Sometimes user add extra 1 in front of mdn in their address book + if isNationalMDN, + let unformedMDN = unformattedMDN, + unformedMDN.count == 11, + unformedMDN[(unformedMDN.index(unformedMDN.startIndex, offsetBy: 0))] == "1" { + + let startIndex = unformedMDN.index(unformedMDN.startIndex, offsetBy: 1) + unformattedMDN = String(unformedMDN[startIndex...]) + } + + text = unformattedMDN + textFieldShouldReturn(textField) + textFieldDidEndEditing(textField) + } + } + + //-------------------------------------------------- + // MARK: - Implemented TextField Delegate + //-------------------------------------------------- + + @discardableResult + @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + + textField.resignFirstResponder() + + return proprietorTextDelegate?.textFieldShouldReturn?(textField) ?? true + } + + @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + + if !MVMCoreUIUtility.validate(string, withRegularExpression: RegularExpressionDigitOnly) { + return false + } + + return proprietorTextDelegate?.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) ?? true + } + + @objc public func textFieldDidBeginEditing(_ textField: UITextField) { + + textField.text = MVMCoreUIUtility.removeMdnFormat(textField.text) + proprietorTextDelegate?.textFieldDidBeginEditing?(textField) + } + + @objc public func textFieldDidEndEditing(_ textField: UITextField) { + + proprietorTextDelegate?.textFieldDidEndEditing?(textField) + + if validateTextField() && isNationalMDN { + textField.text = MVMCoreUIUtility.formatMdn(textField.text) + } + } + + @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + + return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true + } + + @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + + return proprietorTextDelegate?.textFieldShouldEndEditing?(textField) ?? true + } + + @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { + + return proprietorTextDelegate?.textFieldShouldClear?(textField) ?? true + } +} diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift new file mode 100644 index 00000000..5ff12679 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -0,0 +1,359 @@ +// +// TextEntryField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/2/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objc public protocol ObservingTextFieldDelegate: 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 dismissFieldInput(sender: Any?) +} + + +@objcMembers open class TextEntryField: EntryField, UITextFieldDelegate { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + open private(set) var textField: TextField = { + let textField = TextField(frame: .zero) + textField.isAccessibilityElement = true + textField.setContentCompressionResistancePriority(.required, for: .vertical) + textField.font = MFStyler.fontForTextField() + textField.smartQuotesType = .no + textField.smartDashesType = .no + textField.smartInsertDeleteType = .no + return textField + }() + + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- + + /// Set enabled and disabled colors to be utilized when setting this texfield's isEnabled property. + public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.black, .mfSilver()) + + private var observingForChange: Bool = false + + /// Validate on each entry in the textField. Default: false + public var validateEachCharacter: Bool = false + + /// Validate when user resigns editing. Default: true + public var validateWhenDoneEditing: Bool = true + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + public override var isEnabled: Bool { + get { return super.isEnabled } + set (enabled) { + super.isEnabled = enabled + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.textField.isEnabled = enabled + self.textField.textColor = enabled ? self.textColor.enabled : self.textColor.disabled + } + } + } + + public override var showError: Bool { + get { return super.showError } + set (error) { + + if error { + textField.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField.text ?? "", errorMessage ?? "") + } else { + textField.accessibilityValue = nil + } + + super.showError = error + } + } + + /// The text of this TextField. + open override var text: String? { + get { return textField.text } + set { textField.text = newValue } + } + + /// Placeholder access for the TextField. + public var placeholder: String? { + get { return textField.placeholder } + set { textField.placeholder = newValue } + } + + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + + public var validationBlock: ((_ value: String?) -> Bool)? + + //-------------------------------------------------- + // MARK: - Delegate Properties + //-------------------------------------------------- + + /// The delegate and block for validation. Validates if the text that the user has entered. + public weak var observingTextFieldDelegate: ObservingTextFieldDelegate? { + didSet { + if observingTextFieldDelegate != nil && !observingForChange { + observingForChange = true + NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextField.textDidChangeNotification, object: textField) + NotificationCenter.default.addObserver(self, selector: #selector(endInputing), name: UITextField.textDidEndEditingNotification, object: textField) + NotificationCenter.default.addObserver(self, selector: #selector(startEditing), name: UITextField.textDidBeginEditingNotification, object: textField) + + } else if observingTextFieldDelegate == nil && observingForChange { + observingForChange = false + NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: textField) + NotificationCenter.default.removeObserver(self, name: UITextField.textDidEndEditingNotification, object: textField) + NotificationCenter.default.removeObserver(self, name: UITextField.textDidBeginEditingNotification, object: textField) + } + } + } + + /// 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: - Constraints + //-------------------------------------------------- + + public var textFieldTrailingConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + @objc public override init(frame: CGRect) { + super.init(frame: frame) + } + + @objc public convenience init() { + self.init(frame: .zero) + } + + @objc required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("TextEntryField does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + @objc open override func setupFieldContainerContent(_ container: UIView) { + + MFStyler.styleTextField(textField) + container.addSubview(textField) + + NSLayoutConstraint.activate([ + textField.heightAnchor.constraint(equalToConstant: 24), + textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 12), + textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), + container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 12)]) + + textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16) + textFieldTrailingConstraint?.isActive = true + + let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) + entryFieldContainer.addGestureRecognizer(tap) + + accessibilityElements = [titleLabel, textField, feedbackLabel] + } + + @objc open override func updateView(_ size: CGFloat) { + super.updateView(size) + + MFStyler.styleTextField(textField) + layoutIfNeeded() + } + + open override func reset() { + super.reset() + + textField.font = MFStyler.fontForTextField() + } + + @objc deinit { + setBothTextDelegates(to: nil) + } + + @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { + + observingTextFieldDelegate = delegate + uiTextFieldDelegate = delegate + } + + //-------------------------------------------------- + // MARK: - Observing for Change (TextFieldDelegate) + //-------------------------------------------------- + + public func defaultValidationBlock() { + + validationBlock = { enteredValue in + guard let enteredValue = enteredValue else { return false } + return enteredValue.count > 0 + } + } + + @discardableResult + @objc override open func resignFirstResponder() -> Bool { + + if validateWhenDoneEditing { + validateTextField() + } + + textField.resignFirstResponder() + isSelected = false + return true + } + + /// Validates the text of the entry field. + @discardableResult + @objc public func validateTextField() -> Bool { + + isValid = validationBlock?(text) ?? true + + if isValid { + showError = false + observingTextFieldDelegate?.isValid?(textfield: self) + + } else { + showError = true + observingTextFieldDelegate?.isInvalid?(textfield: self) + } + + return isValid + } + + /// Executes on UITextField.textDidBeginEditingNotification + @objc func startEditing() { + + isSelected = true + textField.becomeFirstResponder() + } + + /// Executes on UITextField.textDidChangeNotification (each character entry) + @objc func valueChanged() { + + guard validateEachCharacter else { return } + + validateTextField() + } + + /// Executes on UITextField.textDidEndEditingNotification + @objc func endInputing() { + + if isValid { + showError = false + entryFieldContainer.bottomBar?.backgroundColor = UIColor.black.cgColor + } + + resignFirstResponder() + } + + @objc func dismissFieldInput(_ sender: Any?) { + + resignFirstResponder() + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension TextEntryField { + + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let delegateObject = delegateObject, + let dictionary = json + else { return } + + FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol) + + if let enabledTextColorHex = dictionary["enabledTextColor"] as? String { + textColor.enabled = UIColor.mfGet(forHex: enabledTextColorHex) + } + + if let disabledTextColorHex = dictionary["disabledTextColor"] as? String { + textColor.disabled = UIColor.mfGet(forHex: disabledTextColorHex) + } + + if let text = dictionary[KeyText] as? String { + self.text = text + } + + if let placeholder = dictionary[placeholder] as? String { + self.placeholder = placeholder + } + + switch dictionary.stringForkey(KeyType) { + case "password": + textField.isSecureTextEntry = true + + case "number": + textField.keyboardType = .numberPad + + case "email": + textField.keyboardType = .emailAddress + + default: + break + } + + let regex = dictionary.stringForkey("regex") + + if !regex.isEmpty { + validationBlock = { enteredValue in + guard let value = enteredValue else { return false } + return MVMCoreUIUtility.validate(value, withRegularExpression: regex) + } + } else { + defaultValidationBlock() + } + + if let formValidationProtocol = delegateObject.formValidationProtocol { + observingTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) + } + + uiTextFieldDelegate = delegateObject.uiTextFieldDelegate + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) + } +} + +// MARK: - Accessibility +extension TextEntryField { + + @objc open override func pushAccessibilityNotification() { + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + UIAccessibility.post(notification: .layoutChanged, argument: self.textField) + } + } + + @objc open override func setAccessibilityString(_ accessibilityString: String?) { + + var accessibilityString = accessibilityString ?? "" + + 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/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index 9b500cdc..2534c346 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -2,8 +2,7 @@ // CaretView.swift // MobileFirstFramework // -// Created by Kolli, Praneeth on 1/5/18. -// Converted by Christiano, Kevin on 1/5/18. +// Created by Christiano, Kevin on 1/5/18. // Copyright © 2018 Verizon Wireless. All rights reserved. // @@ -13,87 +12,167 @@ open class CaretView: View { // MARK: - Properties //------------------------------------------------------ - // Objc can't use float enum. - @objc public static let thin: CGFloat = 6.0 - @objc public static let standard: CGFloat = 2.6 - @objc public static let thick: CGFloat = 1.5 + private var caretPath: UIBezierPath = UIBezierPath() + public var strokeColor: UIColor = .black + public var lineWidth: CGFloat = 1 + + public var direction: Direction = .right + public var size: CaretSize? + + public var enabledColor: UIColor = .black + public var disabledColor: UIColor = .mfSilver() + + //------------------------------------------------------ + // MARK: - Property Observer + //------------------------------------------------------ + + public var isEnabled: Bool = true { + didSet { + strokeColor = isEnabled ? enabledColor : disabledColor + setNeedsDisplay() + } + } + + //------------------------------------------------------ + // MARK: - Constraints + //------------------------------------------------------ + + /// Sizes of CaretView are derived from InVision design specs. They are provided for convenience. + public enum CaretSize { + case small(Orientation) + case medium(Orientation) + case large(Orientation) + + /// Orientation based on the longest line of the view. + public enum Orientation { + case vertical + case horizontal + } + + // Dimensions of container; provided by InVision design. + func dimensions() -> CGSize { + + switch self { + case .small(let o): + return o == .vertical ? CGSize(width: 6, height: 10) : CGSize(width: 10, height: 6) + + case .medium(let o): + return o == .vertical ? CGSize(width: 9, height: 16) : CGSize(width: 16, height: 9) + + case .large(let o): + return o == .vertical ? CGSize(width: 14, height: 24) : CGSize(width: 24, height: 14) + } + } + } - private(set) var strokeColor: UIColor? - private var lineWidth: CGFloat? - private var lineThickness: CGFloat? - //------------------------------------------------------ // MARK: - Initialization //------------------------------------------------------ - @objc public override init() { - super.init(frame: .zero) - } - @objc public override init(frame: CGRect) { super.init(frame: frame) } - @objc required public init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) + @objc public convenience init() { + self.init(frame: .zero) } - /// Can init with a specific line width. - @objc public init(lineWidth: CGFloat) { - super.init(frame: CGRect()) + @objc public convenience init(lineWidth: CGFloat) { + self.init(frame: .zero) self.lineWidth = lineWidth } - /// Can init with a specific line thickness, scales based on width and height. - @objc public init(lineThickness: CGFloat) { - super.init(frame: CGRect()) - self.lineThickness = lineThickness + @objc required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + fatalError("CaretView xib not supported.") } + //------------------------------------------------------ + // MARK: - Setup + //------------------------------------------------------ + @objc override open func setupView() { + defaultState() } - //------------------------------------------------------ - // MARK: - Private Function - //------------------------------------------------------ - - private func defaultState() { - isOpaque = false - isHidden = false - backgroundColor = .clear - strokeColor = .black - } - //------------------------------------------------------ // MARK: - Drawing //------------------------------------------------------ - @objc override open func draw(_ rect: CGRect) { - // Drawing Caret - let context = UIGraphicsGetCurrentContext() - context?.clear(rect) - - let lineWidthToDraw: CGFloat = lineWidth ?? frame.size.width / (lineThickness ?? 2.6) - - let path = UIBezierPath() - path.move(to: CGPoint(x: lineWidthToDraw / 2.0, y: 0.0)) - path.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height / 2.0)) - path.addLine(to: CGPoint(x: lineWidthToDraw / 2.0, y: frame.size.height)) - path.addLine(to: CGPoint(x: 0.0, y: frame.size.height - lineWidthToDraw / 2.0)) - path.addLine(to: CGPoint(x: frame.size.width - lineWidthToDraw, y: frame.size.height / 2.0)) - path.addLine(to: CGPoint(x: 0.0, y: lineWidthToDraw / 2.0)) - path.addLine(to: CGPoint(x: lineWidthToDraw / 2.0, y: 0.0)) - strokeColor?.setFill() - path.fill() - path.close() + /// The direction the caret will be pointing to. + @objc public enum Direction: Int { + case left + case right + case down + case up } - @objc public func setLineColor(_ color: UIColor?) { + @objc override open func draw(_ rect: CGRect) { + super.draw(rect) + + caretPath.removeAllPoints() + caretPath.lineJoinStyle = .miter + caretPath.lineWidth = lineWidth + + let inset = lineWidth / 2 + let halfWidth = frame.size.width / 2 + let halfHeight = frame.size.height / 2 + + switch direction { + case .up: + caretPath.move(to: CGPoint(x: inset, y: frame.size.height - inset)) + caretPath.addLine(to: CGPoint(x: halfWidth, y: lineWidth)) + caretPath.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height)) + + case .right: + caretPath.move(to: CGPoint(x: inset, y: inset)) + caretPath.addLine(to: CGPoint(x: frame.size.width - lineWidth, y: halfHeight)) + caretPath.addLine(to: CGPoint(x: inset, y: frame.size.height - inset)) + + case .down: + caretPath.move(to: CGPoint(x: inset, y: inset)) + caretPath.addLine(to: CGPoint(x: halfWidth, y: frame.size.height - lineWidth)) + caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: inset)) + + case .left: + caretPath.move(to: CGPoint(x: frame.size.width - inset, y: inset)) + caretPath.addLine(to: CGPoint(x: lineWidth, y: halfHeight)) + caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: frame.size.height - inset)) + } + + strokeColor.setStroke() + caretPath.stroke() + } + + //------------------------------------------------------ + // MARK: - Methods + //------------------------------------------------------ + + @objc public func setLineColor(_ color: UIColor) { + strokeColor = color setNeedsDisplay() } + @objc public func defaultState() { + + translatesAutoresizingMaskIntoConstraints = false + isOpaque = false + isHidden = false + backgroundColor = .clear + strokeColor = .black + } + + /// Ensure you have defined a CaretSize with Orientation before calling. + @objc public func setConstraints() { + + guard let dimensions = size?.dimensions() else { return } + + heightAnchor.constraint(equalToConstant: dimensions.height).isActive = true + widthAnchor.constraint(equalToConstant: dimensions.width).isActive = true + } + //------------------------------------------------------ // MARK: - Atomization //------------------------------------------------------ @@ -102,26 +181,26 @@ open class CaretView: View { @objc open func setAsMolecule() { defaultState() } - - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - // Configure class properties with JSON values + guard let dictionary = json else { return } if let strokeColorHex = dictionary["strokeColor"] as? String { strokeColor = UIColor.mfGet(forHex: strokeColorHex) } - if let isHiddenValue = dictionary[KeyIsHidden] as? Bool { - isHidden = isHiddenValue - } - - if let isOpaqueValue = dictionary[KeyIsOpaque] as? Bool { - isOpaque = isOpaqueValue + if let isHidden = dictionary[KeyIsHidden] as? Bool { + self.isHidden = isHidden } - if let lineWidthValue = dictionary["lineWidth"] as? CGFloat { - lineWidth = lineWidthValue + if let isOpaque = dictionary[KeyIsOpaque] as? Bool { + self.isOpaque = isOpaque + } + + if let lineWidth = dictionary["lineWidth"] as? CGFloat { + self.lineWidth = lineWidth } } } @@ -132,6 +211,6 @@ extension CaretView: MVMCoreUIViewConstrainingProtocol { } open func horizontalAlignment() -> UIStackView.Alignment { - return UIStackView.Alignment.leading; + return .leading } } diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index 6991adef..229efe2a 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -80,7 +80,7 @@ setupView() } - public convenience override init() { + public convenience init() { self.init(frame: .zero) } diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index 25e39b1b..c66d68b7 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -17,14 +17,31 @@ open class DashLine: View { @objc public var dashColor: UIColor? + //------------------------------------------------------ + // MARK: - Initializer + //------------------------------------------------------ + + public override init(frame: CGRect) { + super.init(frame: .zero) + } + + public convenience init() { + self.init(frame: .zero) + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + } + //------------------------------------------------------ // MARK: - Lifecycle //------------------------------------------------------ @objc override open func updateView(_ size: CGFloat) { - MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + + DispatchQueue.main.async { [weak self] in self?.setNeedsDisplay() - }) + } } @objc override open func draw(_ rect: CGRect) { @@ -33,12 +50,7 @@ open class DashLine: View { dashLayer.backgroundColor = UIColor.clear.cgColor dashLayer.frame = bounds - if let sublayers = layer.sublayers { - for sublayer in sublayers { - sublayer.removeFromSuperlayer() - } - } - + layer.sublayers?.forEach { $0.removeFromSuperlayer() } layer.addSublayer(dashLayer) let path = UIBezierPath() diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index 56a0c38b..30101710 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -91,6 +91,7 @@ public typealias ActionBlock = () -> () translatesAutoresizingMaskIntoConstraints = false clauses = [] accessibilityCustomActions = [] + accessibilityTraits = .staticText let tapGesture = UITapGestureRecognizer(target: self, action: #selector(textLinkTapped(_:))) tapGesture.numberOfTapsRequired = 1 @@ -522,6 +523,7 @@ public typealias ActionBlock = () -> () func appendActionableClause(range: NSRange, actionBlock: @escaping ActionBlock) { + accessibilityTraits = .button let accessibleAction = customAccessibilityAction(range: range) clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1)) } @@ -582,6 +584,7 @@ extension Label { styleB2(true) accessibilityCustomActions = [] clauses = [] + accessibilityTraits = .staticText } @objc public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { @@ -721,7 +724,6 @@ extension UITapGestureRecognizer { if label.makeWholeViewClickable { return true } - guard let abstractContainer = label.abstractTextContainer() else { return false } let textContainer = abstractContainer.0 let layoutManager = abstractContainer.1 @@ -775,7 +777,7 @@ extension Label { } @objc public func accessibilityCustomAction(_ action: UIAccessibilityCustomAction) { - + for clause in clauses { if action.hash == clause.accessibilityID { clause.performAction() @@ -783,4 +785,19 @@ extension Label { } } } + + open override func accessibilityActivate() -> Bool { + + guard let accessibleActions = accessibilityCustomActions else { return false } + + for clause in clauses { + for action in accessibleActions { + if action.hash == clause.accessibilityID { + clause.performAction() + return true + } + } + } + return false + } } diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift new file mode 100644 index 00000000..1d5d743e --- /dev/null +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -0,0 +1,133 @@ +// +// Button.swift +// MVMCoreUI +// +// Created by Robinson, Blake on 12/18/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +public typealias ButtonBlock = (Button) -> () + + +@objcMembers open class Button: UIButton, MFButtonProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var json: [AnyHashable: Any]? + public var actionMap: [AnyHashable: Any]? + + private var initialSetupPerformed = false + + private var buttonBlock: ButtonBlock? + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + public weak var buttonDelegate: ButtonDelegateProtocol? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: .zero) + initialSetup() + } + + public convenience init() { + self.init(frame: .zero) + initialSetup() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("Button does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + + public func initialSetup() { + + if !initialSetupPerformed { + initialSetupPerformed = true + setupView() + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + public func addBlock( event: Event, _ buttonBlock: @escaping ButtonBlock) { + self.buttonBlock = buttonBlock + addTarget(self, action: #selector(callBlock(_:)), for: event) + } + + func callBlock(_ sender: Button) { + buttonBlock?(self) + } + + public func setWithActionMap(_ actionMap: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.actionMap = actionMap + + buttonDelegate = delegateObject?.buttonDelegate + + addBlock(event: .touchUpInside) { [weak self] sender in + guard let self = self else { return } + + if self.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + } + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension Button: MVMCoreUIMoleculeViewProtocol { + + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.json = json + + guard let dictionary = json else { return } + + if let backgroundColorString = dictionary[KeyBackgroundColor] as? String { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + + if let title = dictionary[KeyTitle] as? String { + setTitle(title, for: .normal) + } + } + + public func reset() { + backgroundColor = .clear + } +} + + +// MARK: - MVMCoreViewProtocol +extension Button: MVMCoreViewProtocol { + + public func updateView(_ size: CGFloat) {} + + /// Will be called only once. + public func setupView() { + + translatesAutoresizingMaskIntoConstraints = false + insetsLayoutMarginsFromSafeArea = false + titleLabel?.numberOfLines = 0 + titleLabel?.lineBreakMode = .byWordWrapping + } +} + +// MARK: AppleGuidelinesProtocol +extension Button: AppleGuidelinesProtocol { + + override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return Self.acceptablyOutsideBounds(point: point, bounds: bounds) + } +} diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift new file mode 100644 index 00000000..b1a13854 --- /dev/null +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -0,0 +1,110 @@ +// +// TextField.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/18/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +public protocol TextFieldDidDeleteProtocol: class { + func textFieldDidDelete() +} + + +@objcMembers open class TextField: UITextField { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + open var json: [AnyHashable: Any]? + + private var initialSetupPerformed = false + + /// Set to true to hide the blinking textField cursor. + public var hideBlinkingCaret = false + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds a reference to the delegating class so this class can internally influence the TextField behavior as well. + public weak var didDeleteDelegate: TextFieldDidDeleteProtocol? + + //-------------------------------------------------- + // MARK: - Initialization + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: .zero) + initialSetup() + } + + public convenience init() { + self.init(frame: .zero) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + initialSetup() + } + + public func initialSetup() { + + if !initialSetupPerformed { + tintColor = .black + initialSetupPerformed = true + setupView() + } + } + + open override func caretRect(for position: UITextPosition) -> CGRect { + + if hideBlinkingCaret { + return .zero + } + + let caretRect = super.caretRect(for: position) + return CGRect(origin: caretRect.origin, size: CGSize(width: 1, height: caretRect.height)) + } + + open override func deleteBackward() { + super.deleteBackward() + didDeleteDelegate?.textFieldDidDelete() + } +} + +/// MARK:- MVMCoreViewProtocol +extension TextField: MVMCoreViewProtocol { + + open func updateView(_ size: CGFloat) {} + + /// Will be called only once. + open func setupView() { + translatesAutoresizingMaskIntoConstraints = false + insetsLayoutMarginsFromSafeArea = false + } +} + +/// MARK:- MVMCoreUIMoleculeViewProtocol +extension TextField: MVMCoreUIMoleculeViewProtocol { + + open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.json = json + + guard let dictionary = json else { return } + + if let backgroundColorString = dictionary.optionalStringForKey(KeyBackgroundColor) { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + + if let text = dictionary[KeyText] as? String { + self.text = text + } + } + + open func reset() { + backgroundColor = .clear + } +} diff --git a/MVMCoreUI/BaseClasses/View.swift b/MVMCoreUI/BaseClasses/View.swift index bc6e7634..515dc0eb 100644 --- a/MVMCoreUI/BaseClasses/View.swift +++ b/MVMCoreUI/BaseClasses/View.swift @@ -9,18 +9,25 @@ import UIKit @objcMembers open class View: UIView { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + open var json: [AnyHashable: Any]? private var initialSetupPerformed = false + //-------------------------------------------------- + // MARK: - Initialization + //-------------------------------------------------- + public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() } - public init() { - super.init(frame: .zero) - initialSetup() + public convenience init() { + self.init(frame: .zero) } public required init?(coder: NSCoder) { @@ -29,6 +36,7 @@ import UIKit } public func initialSetup() { + if !initialSetupPerformed { initialSetupPerformed = true setupView() @@ -36,7 +44,9 @@ import UIKit } } +// MARK:- MVMCoreViewProtocol extension View: MVMCoreViewProtocol { + open func updateView(_ size: CGFloat) {} /// Will be called only once. @@ -46,8 +56,10 @@ extension View: MVMCoreViewProtocol { } } +// MARK:- MVMCoreUIMoleculeViewProtocol extension View: MVMCoreUIMoleculeViewProtocol { - open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + + open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { diff --git a/MVMCoreUI/Containers/views/EntryFieldContainer.swift b/MVMCoreUI/Containers/views/EntryFieldContainer.swift new file mode 100644 index 00000000..dd7c79f2 --- /dev/null +++ b/MVMCoreUI/Containers/views/EntryFieldContainer.swift @@ -0,0 +1,285 @@ +// +// EntryFieldContainer.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objcMembers open class EntryFieldContainer: View { + //-------------------------------------------------- + // MARK: - Drawing Properties + //-------------------------------------------------- + + /// The bottom border line. Height is dynamic based on scenario. + public var bottomBar: CAShapeLayer? = { + let layer = CAShapeLayer() + layer.backgroundColor = UIColor.black.cgColor + layer.drawsAsynchronously = true + layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); + return layer + }() + + /// Total control over the drawn top, bottom, left and right borders. + public var disableAllBorders = false { + didSet { + bottomBar?.isHidden = disableAllBorders + } + } + + private(set) var fieldState: FieldState = .original { + didSet (oldState) { + // Will not update if new state is the same as old. + if fieldState != oldState { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.fieldState.setStateUI(for: self) + } + } + } + } + + /// Determines if the top, left, and right borders should be drawn. + private var hideBorders = false + + public var borderStrokeColor: UIColor = .mfSilver() + private var borderPath: UIBezierPath = UIBezierPath() + + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + + private var _isEnabled: Bool = true + private var _showError: Bool = false + private var _isLocked: Bool = false + private var _isSelected: Bool = false + + public var isEnabled: Bool { + get { return _isEnabled } + set (enabled) { + + _isEnabled = enabled + _isLocked = false + _isSelected = false + _showError = false + + fieldState = enabled ? .original : .disabled + } + } + + public var showError: Bool { + get { return _showError } + set (error) { + + _showError = error + _isEnabled = true + _isLocked = false + _isSelected = false + + fieldState = error ? .error : .original + } + } + + public var isLocked: Bool { + get { return _isLocked } + set (locked) { + + _isLocked = locked + _isEnabled = true + _isSelected = false + _showError = false + + fieldState = locked ? .locked : .original + } + } + + public var isSelected: Bool { + get { return _isSelected } + set (selected) { + + _isSelected = selected + _isLocked = false + _isEnabled = true + + if _showError { + fieldState = selected ? .selectedError : .error + } else { + fieldState = selected ? .selected : .original + } + } + } + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds reference to delegateObject to inform molecular tableView of an update. + weak var delegateObject: MVMCoreUIDelegateObject? + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func layoutSubviews() { + super.layoutSubviews() + + refreshUI(bottomBarSize: showError ? 4 : 1) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + refreshUI() + } + + /// This handles the top, left, and right border lines. + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath.removeAllPoints() + + if !disableAllBorders && !hideBorders { + // Brings the other half of the line inside the view to prevent cropping. + let origin = bounds.origin + let size = frame.size + let insetLean: CGFloat = 0.5 + borderPath.lineWidth = 1 + + borderPath.move(to: CGPoint(x: origin.x + insetLean, y: origin.y + size.height)) + borderPath.addLine(to: CGPoint(x: origin.x + insetLean, y: origin.y + insetLean)) + borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + insetLean)) + borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + size.height)) + + borderStrokeColor.setStroke() + borderPath.stroke() + } + } + + override open func setupView() { + super.setupView() + + isAccessibilityElement = false + isOpaque = false + + if let bottomBar = bottomBar { + layer.addSublayer(bottomBar) + } + } + + //-------------------------------------------------- + // MARK: - Draw States + //-------------------------------------------------- + + public enum FieldState { + case original + case error + case selectedError + case selected + case locked + case disabled + + public func setStateUI(for formField: EntryFieldContainer) { + + switch self { + case .original: + formField.originalUI() + + case .error: + formField.errorUI() + + case .selectedError: + formField.selectedErrorUI() + + case .selected: + formField.selectedUI() + + case .locked: + formField.lockedUI() + + case .disabled: + formField.disabledUI() + } + } + } + + open func originalUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = UIColor.black.cgColor + refreshUI(bottomBarSize: 1) + } + + open func errorUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .mfPumpkin() + bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor + refreshUI(bottomBarSize: 4) + } + + open func selectedErrorUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .black + bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor + refreshUI(bottomBarSize: 4) + } + + open func selectedUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .black + bottomBar?.backgroundColor = UIColor.black.cgColor + refreshUI(bottomBarSize: 1) + } + + open func lockedUI() { + + isUserInteractionEnabled = false + hideBorders = true + borderStrokeColor = .clear + bottomBar?.backgroundColor = UIColor.clear.cgColor + refreshUI(bottomBarSize: 1) + } + + open func disabledUI() { + + isUserInteractionEnabled = false + hideBorders = false + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = UIColor.mfSilver().cgColor + refreshUI(bottomBarSize: 1) + } + + open func refreshUI(bottomBarSize: CGFloat? = nil) { + + if !disableAllBorders { + let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) + bottomBar?.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) + + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) + setNeedsDisplay() + layoutIfNeeded() + } + } +} + +// MARK:- MVMCoreUIMoleculeViewProtocol +extension EntryFieldContainer { + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + + guard let dictionary = json, !dictionary.isEmpty else { return } + } +} diff --git a/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift b/MVMCoreUI/FormUIHelpers/FormValidator+TextFields.swift index b2b18ebe..21599abf 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,38 @@ import Foundation } public func entryIsInvalid(_ textfield: MFTextField?) { + + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.enableByValidation() + if let delegate = self.delegate as? MFTextFieldDelegate { + delegate.entryIsInvalid?(textfield) + } + }) + } +} + +// Temporary: Looking to either combine or separate entirely with MFTextFieldDelegate. +extension FormValidator: ObservingTextFieldDelegate { + + public func dismissField(_ sender: Any?) { + + if let delegate = delegate as? MFTextFieldDelegate { + delegate.dismissFieldInput?(sender) + } + } + + @nonobjc public func isValid(_ textfield: MFTextField?) { + + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.enableByValidation() + if let delegate = self.delegate as? MFTextFieldDelegate { + delegate.entryIsValid?(textfield) + } + }) + } + + public func isInvalid(_ 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 77c52249..8cb3d774 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 import MVMCore diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index b1aa92a5..91721544 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -122,7 +122,7 @@ import UIKit } // MARK: - MFViewProtocol - public func updateView(_ size: CGFloat) { + open func updateView(_ size: CGFloat) { MFStyler.setMarginsFor(self, size: size, defaultHorizontal: updateViewHorizontalDefaults, top: topMarginPadding, bottom: bottomMarginPadding) if accessoryView != nil { @@ -145,7 +145,7 @@ import UIKit molecule?.updateView(size) } - public func setupView() { + open func setupView() { selectionStyle = .none insetsLayoutMarginsFromSafeArea = false contentView.insetsLayoutMarginsFromSafeArea = false @@ -153,7 +153,7 @@ import UIKit } // MARK: - MVMCoreUIMoleculeViewProtocol - public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json guard let json = json else { return } @@ -192,7 +192,7 @@ import UIKit containerHelper.set(with: json, for: molecule) } - public func reset() { + open func reset() { molecule?.reset?() updateViewHorizontalDefaults = true styleStandard() @@ -214,12 +214,14 @@ import UIKit /// Adds the standard mvm style caret to the accessory view @objc public func addCaretViewAccessory() { guard accessoryView == nil else { return } - let width: CGFloat = 6 - let height: CGFloat = 10 - caretView = CaretView(lineThickness: CaretView.thin) - caretView?.frame = CGRect(x: 0, y: 0, width: width, height: height) - caretViewWidthSizeObject = MFSizeObject(standardSize: width, standardiPadPortraitSize: 9) - caretViewHeightSizeObject = MFSizeObject(standardSize: height, standardiPadPortraitSize: 16) + caretView = CaretView(lineWidth: 1) + caretView?.size = .small(.vertical) + caretView?.setConstraints() + + if let size = caretView?.size?.dimensions() { + caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9) + caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16) + } accessoryView = caretView } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift new file mode 100644 index 00000000..9c3dba8a --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -0,0 +1,84 @@ +// +// HeadLineBodyCaretLinkImage.swift +// MVMCoreUI +// +// Created by Arora, Prateek on 06/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers public class HeadLineBodyCaretLinkImage: ViewConstrainingView { + + let headlineBody = HeadlineBody(frame: .zero) + let caretButton = CaretButton(frame: .zero) + let backgroundImageView = MFLoadImageView() + var spaceBetweenConstant: CGFloat = 104.0 + var leftConstraintHeadline : NSLayoutConstraint? + var leftConstraintCaretView : NSLayoutConstraint? + let padding = MFStyler.defaultHorizontalPaddingForApplicationWidth() + let maxWidth : CGFloat = 350.0 + // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + caretButton.updateView(size) + backgroundImageView.updateView(size) + leftConstraintHeadline?.constant = MFStyler.defaultHorizontalPadding(forSize: size) + leftConstraintCaretView?.constant = MFStyler.defaultHorizontalPadding(forSize: size) + } + + open override func setupView() { + super.setupView() + guard subviews.count == 0 else { + return + } + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + view.addSubview(headlineBody) + view.addSubview(caretButton) + + //Headline view + leftConstraintHeadline = headlineBody.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) + leftConstraintHeadline?.isActive = true + headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: PaddingDefault).isActive = true + + let headLineBodyWidth = headlineBody.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85) + headLineBodyWidth.priority = .defaultHigh + headLineBodyWidth.isActive = true + headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true + + //Caret view + caretButton.translatesAutoresizingMaskIntoConstraints = false + leftConstraintCaretView = caretButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) + leftConstraintCaretView?.isActive = true + view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor, constant: PaddingDefault).isActive = true + + caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant).isActive = true + + //Background image view + backgroundImageView.translatesAutoresizingMaskIntoConstraints = false + backgroundImageView.imageView.contentMode = .scaleAspectFill + view.addSubview(backgroundImageView) + NSLayoutConstraint.constraintPinSubview(toSuperview: backgroundImageView) + view.sendSubviewToBack(backgroundImageView) + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + backgroundImageView.setWithJSON(json?.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) + headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) + caretButton.setWithJSON(json?.optionalDictionaryForKey("caretLink"), delegateObject: delegateObject, additionalData: additionalData) + } + + open override func reset() { + super.reset() + headlineBody.reset() + backgroundImageView.reset() + } + + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 320 + } +} diff --git a/MVMCoreUI/Organisms/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift index 4b37b66d..45e002be 100644 --- a/MVMCoreUI/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -63,10 +63,6 @@ open class MoleculeStackView: Container { } // MARK: - Inits - public override init() { - super.init() - } - public override init(frame: CGRect) { super.init(frame: frame) } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 1b695032..6140ed18 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -38,6 +38,10 @@ @"textField": MFTextField.class, @"dropDown": DropDown.class, @"digitTextField": MFDigitTextField.class, + @"digitEntryField": DigitEntryField.class, + @"textEntryField": TextEntryField.class, + @"itemDropdownEntryField": ItemDropdownEntryField.class, + @"dateDropdownEntryField": DateDropdownEntryField.class, @"checkbox": Checkbox.class, @"checkboxWithLabel": CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, @@ -66,7 +70,8 @@ @"tabsListItem": TabsTableViewCell.class, @"dropDownListItem": DropDownFilterTableViewCell.class, @"headlineBodyButton": HeadlineBodyButton.class, - @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class + @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class, + @"headLineBodyCaretLinkImage" : HeadLineBodyCaretLinkImage.class } mutableCopy]; }); return mapping;