Merge branch 'develop' into feature/swiftify_switch
# Conflicts: # MVMCoreUI.xcodeproj/project.pbxproj
This commit is contained in:
commit
aea8bc5f7b
@ -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 = "<group>"; };
|
||||
0198F7A22256A80A0066C936 /* MFRadioButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFRadioButton.m; sourceTree = "<group>"; };
|
||||
0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = "<group>"; };
|
||||
0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = "<group>"; };
|
||||
0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryField.swift; sourceTree = "<group>"; };
|
||||
0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryField.swift; sourceTree = "<group>"; };
|
||||
0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = "<group>"; };
|
||||
0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = "<group>"; };
|
||||
0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = "<group>"; };
|
||||
0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleGuidelinesProtocol.swift; sourceTree = "<group>"; };
|
||||
0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = "<group>"; };
|
||||
0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
|
||||
0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = "<group>"; };
|
||||
0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = "<group>"; };
|
||||
0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = "<group>"; };
|
||||
0ABD136A237B193A0081388D /* EntryFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = "<group>"; };
|
||||
0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = "<group>"; };
|
||||
0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = "<group>"; };
|
||||
0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
|
||||
943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = "<group>"; };
|
||||
943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = "<group>"; };
|
||||
9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = "<group>"; };
|
||||
948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = "<group>"; };
|
||||
C003506023AA94CD00B6AC29 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
||||
C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadLineBodyCaretLinkImage.swift; sourceTree = "<group>"; };
|
||||
D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = "<group>"; };
|
||||
D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
|
||||
D22479892314445E003FCCF9 /* LabelSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelSwitch.swift; sourceTree = "<group>"; };
|
||||
@ -438,6 +462,14 @@
|
||||
path = FormUIHelpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0ABD1369237B18EE0081388D /* views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0ABD136A237B193A0081388D /* EntryFieldContainer.swift */,
|
||||
);
|
||||
path = views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0A5D59C323AD488600EFD9E9 /* Protocols */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -494,6 +526,7 @@
|
||||
D2A638FC22CA98280052ED1F /* HeadlineBody.swift */,
|
||||
D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */,
|
||||
D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */,
|
||||
C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */,
|
||||
);
|
||||
path = VerticalCombinationViews;
|
||||
sourceTree = "<group>";
|
||||
@ -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 = "<group>";
|
||||
@ -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 */,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
83
MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift
Normal file
83
MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
129
MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift
Normal file
129
MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
195
MVMCoreUI/Atoms/TextFields/DigitBox.swift
Normal file
195
MVMCoreUI/Atoms/TextFields/DigitBox.swift
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
452
MVMCoreUI/Atoms/TextFields/DigitEntryField.swift
Normal file
452
MVMCoreUI/Atoms/TextFields/DigitEntryField.swift
Normal file
@ -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..<numberOfDigits {
|
||||
let newDigitBox = createDigitField()
|
||||
let accessibileLabel = ordinalFormatter.string(from: NSNumber(value: i + 1)) ?? ""
|
||||
newDigitBox.digitField.accessibilityLabel = "\(accessibileLabel) field of \(numberOfDigits) digit fields"
|
||||
digitBoxes.append(newDigitBox)
|
||||
}
|
||||
|
||||
self.digitBoxes = digitBoxes
|
||||
guard let space = MFSizeObject(standardSize: 5, smalliPhoneSize: 3)?.getValueBasedOnScreenSize() else { return }
|
||||
|
||||
var prevBox: DigitBox?
|
||||
|
||||
for (index, box) in digitBoxes.enumerated() {
|
||||
accessibleElements.append(box.digitField)
|
||||
entryFieldContainer.addSubview(box)
|
||||
|
||||
box.topAnchor.constraint(equalTo: entryFieldContainer.topAnchor).isActive = true
|
||||
entryFieldContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true
|
||||
|
||||
if index == 0 {
|
||||
box.leadingAnchor.constraint(equalTo: entryFieldContainer.leadingAnchor).isActive = true
|
||||
|
||||
} else if index == digitBoxes.count - 1 {
|
||||
if let prevTrailingAnchor = prevBox?.trailingAnchor {
|
||||
box.leadingAnchor.constraint(equalTo: prevTrailingAnchor, constant: space).isActive = true
|
||||
}
|
||||
entryFieldContainer.trailingAnchor.constraint(equalTo: box.trailingAnchor).isActive = true
|
||||
|
||||
} else {
|
||||
if let prevTrailingAnchor = prevBox?.trailingAnchor {
|
||||
box.leadingAnchor.constraint(equalTo: prevTrailingAnchor, constant: space).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
prevBox = box
|
||||
}
|
||||
}
|
||||
|
||||
accessibilityElements = accessibleElements + [feedbackLabel]
|
||||
}
|
||||
}
|
||||
|
||||
/// Monitors if fields are being selected internally.
|
||||
private var switchFieldsAutomatically = false
|
||||
|
||||
public var digitBoxes: [DigitBox] = []
|
||||
private var selectedDigitBox: DigitBox?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Computed Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public override var isEnabled: Bool {
|
||||
get { return super.isEnabled }
|
||||
set (enabled) {
|
||||
digitBoxes.forEach { $0.isEnabled = enabled }
|
||||
super.isEnabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
public override var showError: Bool {
|
||||
get { return super.showError }
|
||||
set (error) {
|
||||
digitBoxes.forEach { $0.showError = error }
|
||||
super.showError = error
|
||||
}
|
||||
}
|
||||
|
||||
public override var isLocked: Bool {
|
||||
get { return super.isLocked }
|
||||
set (locked) {
|
||||
digitBoxes.forEach { $0.isLocked = locked }
|
||||
super.isLocked = locked
|
||||
}
|
||||
}
|
||||
|
||||
public override var placeholder: String? {
|
||||
get {
|
||||
var string = ""
|
||||
digitBoxes.forEach { string += $0.digitField.attributedPlaceholder?.string ?? "" }
|
||||
return !string.isEmpty ? string : nil
|
||||
}
|
||||
set {
|
||||
guard let newValue = newValue else { return }
|
||||
|
||||
for (index, field) in digitBoxes.enumerated() {
|
||||
if index < newValue.count {
|
||||
let indexChar = newValue.index(newValue.startIndex, offsetBy: index)
|
||||
field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()])
|
||||
}
|
||||
}
|
||||
|
||||
// If there is already text in the textfield, set the placeholder label below.
|
||||
if let text = text, !text.isEmpty && !showError {
|
||||
feedback = placeholder
|
||||
|
||||
} else if !showError {
|
||||
feedback = ""
|
||||
}
|
||||
|
||||
textField.accessibilityLabel = newValue + (MVMCoreUIUtility.hardcodedString(withKey: "mfdigittextfield_regular") ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses each digitbox to retrieve the held value.
|
||||
public override var text: String? {
|
||||
get {
|
||||
var string = ""
|
||||
digitBoxes.forEach { string += $0.digitField.text ?? "" }
|
||||
return string
|
||||
}
|
||||
set {
|
||||
guard let newValue = newValue else { return }
|
||||
|
||||
for (index, field) in digitBoxes.enumerated() {
|
||||
if index < newValue.count {
|
||||
let indexChar = newValue.index(newValue.startIndex, offsetBy: index)
|
||||
field.digitField.text = String(newValue[indexChar])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// 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: - Initializers
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
@objc public convenience init() {
|
||||
self.init(frame: .zero)
|
||||
}
|
||||
|
||||
@objc public convenience init(numberOfDigits: Int, secureDigits: Bool = false) {
|
||||
self.init(frame: .zero)
|
||||
self.numberOfDigits = numberOfDigits
|
||||
|
||||
if secureDigits {
|
||||
setAsSecureTextEntry(true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc required public init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
fatalError("DigitEntryField xib has not been implemented")
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Setup
|
||||
//--------------------------------------------------
|
||||
|
||||
@objc public override func setupFieldContainerContent(_ container: UIView) {
|
||||
|
||||
isAccessibilityElement = false
|
||||
entryFieldContainer.disableAllBorders = true
|
||||
}
|
||||
|
||||
@objc private func createDigitField() -> 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
|
||||
}
|
||||
}
|
||||
320
MVMCoreUI/Atoms/TextFields/EntryField.swift
Normal file
320
MVMCoreUI/Atoms/TextFields/EntryField.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
144
MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift
Normal file
144
MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift
Normal file
@ -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") ?? "")"
|
||||
}
|
||||
}
|
||||
208
MVMCoreUI/Atoms/TextFields/MdnEntryField.swift
Normal file
208
MVMCoreUI/Atoms/TextFields/MdnEntryField.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
359
MVMCoreUI/Atoms/TextFields/TextEntryField.swift
Normal file
359
MVMCoreUI/Atoms/TextFields/TextEntryField.swift
Normal file
@ -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") ?? "")"
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
setupView()
|
||||
}
|
||||
|
||||
public convenience override init() {
|
||||
public convenience init() {
|
||||
self.init(frame: .zero)
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
133
MVMCoreUI/BaseClasses/Button.swift
Normal file
133
MVMCoreUI/BaseClasses/Button.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
110
MVMCoreUI/BaseClasses/TextField.swift
Normal file
110
MVMCoreUI/BaseClasses/TextField.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
285
MVMCoreUI/Containers/views/EntryFieldContainer.swift
Normal file
285
MVMCoreUI/Containers/views/EntryFieldContainer.swift
Normal file
@ -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 }
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import MVMCore
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -63,10 +63,6 @@ open class MoleculeStackView: Container {
|
||||
}
|
||||
|
||||
// MARK: - Inits
|
||||
public override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user