diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 376b136e..48ba4e50 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -3,15 +3,21 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 48; objects = { /* Begin PBXBuildFile section */ + 01004F3022721C3800991ECC /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01004F2F22721C3800991ECC /* RadioButton.swift */; }; 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105618A224BBE7700E1557D /* FormValidator.swift */; }; 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105618B224BBE7700E1557D /* FormValidator+TextFields.swift */; }; 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105618C224BBE7700E1557D /* FormValidator+FormParams.swift */; }; - 016A1071228122180009D605 /* HeadlineBodyTextButtonSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016A1070228122180009D605 /* HeadlineBodyTextButtonSwitch.swift */; }; - 017BEB3A2360EEB40024EF95 /* PageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB392360EEB40024EF95 /* PageModel.swift */; }; + 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */; }; + 01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */; }; + 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D902327ECE600EF99AA /* CornerLabels.swift */; }; + 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D922327ECFB00EF99AA /* ProgressBar.swift */; }; + 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */; }; + 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */; }; + 017BEB3A2360EEB40024EF95 /* PageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB392360EEB40024EF95 /* PageModel.swift */; }; 017BEB3C2361EA1D0024EF95 /* MFViewController+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB3B2361EA1D0024EF95 /* MFViewController+Model.swift */; }; 017BEB4023620A230024EF95 /* TextFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB3F23620A230024EF95 /* TextFieldModel.swift */; }; 017BEB4223620AD20024EF95 /* FormModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB4123620AD20024EF95 /* FormModelProtocol.swift */; }; @@ -42,9 +48,12 @@ 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368D23609801006832FA /* HeadlineBodyModel.swift */; }; 01EB369523609801006832FA /* ActionMapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368E23609801006832FA /* ActionMapModel.swift */; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; + 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; + 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; + 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; + 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; - B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; - B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; }; D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; @@ -65,6 +74,7 @@ D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; }; D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; }; + D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2755D7A23689C7500485468 /* TableViewCell.swift */; }; D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */; }; D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */; }; D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */; }; @@ -174,7 +184,6 @@ D29DF2CB21E7BFCC003B2FB9 /* MFSizeThreshold.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF14721E68728003B2FB9 /* MFSizeThreshold.m */; }; D29DF2CE21E7C104003B2FB9 /* MFLoadingViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF2CC21E7C104003B2FB9 /* MFLoadingViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF2CD21E7C104003B2FB9 /* MFLoadingViewController.m */; }; - D29DF2D121E7C1C8003B2FB9 /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29DF2D021E7C1C8003B2FB9 /* MVMAnimationFramework.framework */; }; D29DF2E121E9240B003B2FB9 /* MVMCoreUIPanelProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF2E021E9240B003B2FB9 /* MVMCoreUIPanelProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF2EE21ECEADF003B2FB9 /* MFFonts.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF14D21E693AD003B2FB9 /* MFFonts.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF14C21E693AD003B2FB9 /* MFFonts.m */; }; @@ -199,13 +208,14 @@ D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A638FC22CA98280052ED1F /* HeadlineBody.swift */; }; D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390022CBB1820052ED1F /* Carousel.swift */; }; D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */; }; + D2B18B7F2360913400A9AEDC /* Control.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B18B7E2360913400A9AEDC /* Control.swift */; }; + D2B18B812360945C00A9AEDC /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B18B802360945C00A9AEDC /* View.swift */; }; D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */; }; D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */; }; D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */; }; D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */; }; D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; }; - D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */; }; D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */; }; D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */; }; DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; @@ -217,11 +227,17 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 01004F2F22721C3800991ECC /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; 0105618A224BBE7700E1557D /* FormValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormValidator.swift; sourceTree = ""; }; 0105618B224BBE7700E1557D /* FormValidator+TextFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FormValidator+TextFields.swift"; sourceTree = ""; }; 0105618C224BBE7700E1557D /* FormValidator+FormParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FormValidator+FormParams.swift"; sourceTree = ""; }; - 016A1070228122180009D605 /* HeadlineBodyTextButtonSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyTextButtonSwitch.swift; sourceTree = ""; }; - 017BEB392360EEB40024EF95 /* PageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageModel.swift; sourceTree = ""; }; + 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonModel.swift; sourceTree = ""; }; + 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeTableViewCell.swift; sourceTree = ""; }; + 01509D902327ECE600EF99AA /* CornerLabels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; + 01509D922327ECFB00EF99AA /* ProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; + 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyTextButtonSwitch.swift; sourceTree = ""; }; + 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButtonLabel.swift; sourceTree = ""; }; + 017BEB392360EEB40024EF95 /* PageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageModel.swift; sourceTree = ""; }; 017BEB3B2361EA1D0024EF95 /* MFViewController+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFViewController+Model.swift"; sourceTree = ""; }; 017BEB3F23620A230024EF95 /* TextFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldModel.swift; sourceTree = ""; }; 017BEB4123620AD20024EF95 /* FormModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormModelProtocol.swift; sourceTree = ""; }; @@ -251,9 +267,12 @@ 01EB368D23609801006832FA /* HeadlineBodyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyModel.swift; sourceTree = ""; }; 01EB368E23609801006832FA /* ActionMapModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionMapModel.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; + 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; + 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; + 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; + 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; + 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; - B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; - B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUINavigationController.h; sourceTree = ""; }; D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUINavigationController.m; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; @@ -274,6 +293,7 @@ D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = ""; }; D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = ""; }; D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = ""; }; + D2755D7A23689C7500485468 /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; }; D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewCell.swift; sourceTree = ""; }; D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EyebrowHeadlineBodyLink.swift; sourceTree = ""; }; D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFLoadImageView.swift; sourceTree = ""; }; @@ -387,7 +407,6 @@ D29DF2C321E7BF57003B2FB9 /* MFTabBarInteractor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTabBarInteractor.m; sourceTree = ""; }; D29DF2CC21E7C104003B2FB9 /* MFLoadingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFLoadingViewController.h; sourceTree = ""; }; D29DF2CD21E7C104003B2FB9 /* MFLoadingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFLoadingViewController.m; sourceTree = ""; }; - D29DF2D021E7C1C8003B2FB9 /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; D29DF2E021E9240B003B2FB9 /* MVMCoreUIPanelProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPanelProtocol.h; sourceTree = ""; }; D29DF31621ECECC0003B2FB9 /* NHaasGroteskDSStd-45Lt.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NHaasGroteskDSStd-45Lt.otf"; sourceTree = ""; }; D29DF31721ECECC0003B2FB9 /* OCRAExtended.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = OCRAExtended.ttf; sourceTree = ""; }; @@ -412,13 +431,14 @@ D2A638FC22CA98280052ED1F /* HeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBody.swift; sourceTree = ""; }; D2A6390022CBB1820052ED1F /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = ""; }; D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeCollectionViewCell.swift; sourceTree = ""; }; + D2B18B7E2360913400A9AEDC /* Control.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Control.swift; sourceTree = ""; }; + D2B18B802360945C00A9AEDC /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeadlineBody.swift; sourceTree = ""; }; D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewControllerMappingObject.h; sourceTree = ""; }; D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIViewControllerMappingObject.m; sourceTree = ""; }; D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scroller.swift; sourceTree = ""; }; D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTemplate.swift; sourceTree = ""; }; D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIDelegateObject.swift; sourceTree = ""; }; - D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeTableViewCell.swift; sourceTree = ""; }; D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTableViewController.swift; sourceTree = ""; }; D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListTemplate.swift; sourceTree = ""; }; DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftRightLabelView.swift; sourceTree = ""; }; @@ -435,7 +455,7 @@ buildActionMask = 2147483647; files = ( D29DF0E621E4F3C7003B2FB9 /* MVMCore.framework in Frameworks */, - D29DF2D121E7C1C8003B2FB9 /* MVMAnimationFramework.framework in Frameworks */, + 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -498,9 +518,9 @@ D224798823142BF2003FCCF9 /* SwitchMolecules */ = { isa = PBXGroup; children = ( + 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */, D22479892314445E003FCCF9 /* LabelSwitch.swift */, D224798B231450C8003FCCF9 /* HeadlineBodySwitch.swift */, - 016A1070228122180009D605 /* HeadlineBodyTextButtonSwitch.swift */, ); path = SwitchMolecules; sourceTree = ""; @@ -527,8 +547,8 @@ D224798F2316A99F003FCCF9 /* LeftRightViews */ = { isa = PBXGroup; children = ( + 01509D902327ECE600EF99AA /* CornerLabels.swift */, D224798823142BF2003FCCF9 /* SwitchMolecules */, - B8200E182281DC1A007245F4 /* CornerLabels.swift */, ); path = LeftRightViews; sourceTree = ""; @@ -545,8 +565,9 @@ D22479912316A9EF003FCCF9 /* Items */ = { isa = PBXGroup; children = ( + D2755D7A23689C7500485468 /* TableViewCell.swift */, + 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */, D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, - D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */, D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */, ); @@ -587,6 +608,7 @@ isa = PBXGroup; children = ( 01509D96232803B200EF99AA /* Models */, + D2B18B7D236090D500A9AEDC /* BaseClasses */, 01C74D87224298E2009C25A3 /* FormUIHelpers */, D29DF31421ECECA7003B2FB9 /* SupportingFiles */, D29DF27021E79B2C003B2FB9 /* OtherHandlers */, @@ -623,8 +645,8 @@ D29DF0E421E4F3C7003B2FB9 /* Frameworks */ = { isa = PBXGroup; children = ( - D29DF2D021E7C1C8003B2FB9 /* MVMAnimationFramework.framework */, D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */, + 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */, ); name = Frameworks; sourceTree = ""; @@ -653,8 +675,11 @@ D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, D2A514662213885800345BFB /* StandardHeaderView.swift */, D274CA322236A78900B01B62 /* StandardFooterView.swift */, + 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */, D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */, D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */, + 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */, + 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */, 017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */, 017BEB49236235BA0024EF95 /* ModelMoleculeViewProtocol.swift */, ); @@ -744,6 +769,7 @@ D29DF2A021E7AF4E003B2FB9 /* MVMCoreUIUtility.m */, D29DF2A721E7B2F9003B2FB9 /* MVMCoreUIConstants.h */, D29DF2A821E7B2F9003B2FB9 /* MVMCoreUIConstants.m */, + 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */, ); path = Utility; sourceTree = ""; @@ -779,10 +805,10 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + 01509D922327ECFB00EF99AA /* ProgressBar.swift */, D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, - B8200E142280C4CF007245F4 /* ProgressBar.swift */, 948DB67D2326DCD90011F916 /* MultiProgress.swift */, DBC4391622442196001AB423 /* CaretView.swift */, DBC4391722442197001AB423 /* DashLine.swift */, @@ -816,6 +842,9 @@ DB891E822253FA8500022516 /* Label.swift */, 0198F7A02256A80A0066C936 /* MFRadioButton.h */, 0198F7A22256A80A0066C936 /* MFRadioButton.m */, + 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, + 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */, + 01004F2F22721C3800991ECC /* RadioButton.swift */, ); path = Views; sourceTree = ""; @@ -936,6 +965,15 @@ path = Strings; sourceTree = ""; }; + D2B18B7D236090D500A9AEDC /* BaseClasses */ = { + isa = PBXGroup; + children = ( + D2B18B7E2360913400A9AEDC /* Control.swift */, + D2B18B802360945C00A9AEDC /* View.swift */, + ); + path = BaseClasses; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1049,11 +1087,12 @@ D29DF0CB21E404D4003B2FB9 = { CreatedOnToolsVersion = 10.1; LastSwiftMigration = 1010; + ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = D29DF0C621E404D4003B2FB9 /* Build configuration list for PBXProject "MVMCoreUI" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -1100,17 +1139,21 @@ D29770F221F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.m in Sources */, D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */, DBC4391922442197001AB423 /* DashLine.swift in Sources */, + 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, + 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */, 017BEB48236230DB0024EF95 /* MoleculeViewProtocol.swift in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */, D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, 01EB3686236097F1006832FA /* MoleculeHolder.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, + 01004F3022721C3800991ECC /* RadioButton.swift in Sources */, 017BEB3C2361EA1D0024EF95 /* MFViewController+Model.swift in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */, + D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, 01EB3681236092AE006832FA /* Holder.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, @@ -1133,17 +1176,20 @@ 017BEB4023620A230024EF95 /* TextFieldModel.swift in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, + D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */, 01EB369223609801006832FA /* MoleculeStackModel.swift in Sources */, D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */, - B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, 01EB369023609801006832FA /* ListItemModel.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, + 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, + 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, + D2B18B812360945C00A9AEDC /* View.swift in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, 01EB367923609281006832FA /* JSONValue.swift in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, @@ -1152,9 +1198,10 @@ D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 01EB368F23609801006832FA /* LabelModel.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, + 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */, D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, - D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, + 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */, D29DF17A21E69E1F003B2FB9 /* MFCustomButton.m in Sources */, @@ -1171,7 +1218,6 @@ D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */, D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */, D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */, - 016A1071228122180009D605 /* HeadlineBodyTextButtonSwitch.swift in Sources */, D29DF29521E7ADB8003B2FB9 /* ProgrammaticScrollViewController.m in Sources */, D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */, D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, @@ -1206,6 +1252,7 @@ D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, + 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, @@ -1215,11 +1262,13 @@ D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, - B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, + 01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */, 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */, + 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */, D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */, D29DF2CB21E7BFCC003B2FB9 /* MFSizeThreshold.m in Sources */, + 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */, D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1293,7 +1342,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1352,12 +1401,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1379,12 +1427,8 @@ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks"; INFOPLIST_FILE = MVMCoreUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1409,12 +1453,8 @@ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks"; INFOPLIST_FILE = MVMCoreUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/MVMCoreUI/Atoms/Buttons/ButtonView.swift b/MVMCoreUI/Atoms/Buttons/ButtonView.swift index 0fc0f3c3..088c4a00 100644 --- a/MVMCoreUI/Atoms/Buttons/ButtonView.swift +++ b/MVMCoreUI/Atoms/Buttons/ButtonView.swift @@ -64,7 +64,7 @@ import UIKit primaryButton?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 42 } diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretButton.swift index 4ca750c6..b01d2758 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretButton.swift @@ -140,7 +140,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI return UIStackView.Alignment.leading; } - public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 10 } } diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m b/MVMCoreUI/Atoms/Buttons/PrimaryButton.m index 57470bf9..35395557 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton.m @@ -13,13 +13,15 @@ #import "MFStyler.h" #import "UIColor+MFConvenience.h" #import + @import MVMCore.MVMCoreDispatchUtility; @import MVMCore.MVMCoreGetterUtility; @import MVMCore.NSDictionary_MFConvenience; -@interface PrimaryButton() +@interface PrimaryButton() @property (nonatomic) BOOL validationRequired; +@property (nonatomic, strong) NSArray *requiredGroupsList; @property (nonatomic) BOOL smallButton; @property (assign, nonatomic) BOOL tinyButton; @property (nonatomic) CGFloat sizeForSizing; @@ -666,6 +668,9 @@ } - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { + + self.validationRequired = [json boolForKey:@"validationRequired"]; + self.requiredGroupsList = [json array:@"requiredGroups"]; [FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol]; self.primaryButtonType = PrimaryButtonTypeCustom; @@ -696,7 +701,6 @@ if ((color = [json string:@"disabledBorderColor"])) { self.disabledBorderColor = [UIColor mfGetColorForHex:color]; } - self.validationRequired = [json boolForKey:@"validationRequired"]; NSString *size = [json string:@"size"]; if ([size isEqualToString:@"small"]) { @@ -773,7 +777,15 @@ } } -#pragma mark - FormValidationProtocol +#pragma mark - FormValidationEnableDisableProtocol + +- (BOOL)isValidationRequired { + return self.validationRequired; +} + +- (NSArray *)requiredGroups { + return self.requiredGroupsList; +} - (void)enableField:(BOOL)enable { if (!self.validationRequired) { diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.h b/MVMCoreUI/Atoms/TextFields/MFTextField.h index 6b047c6c..8ccbe22e 100644 --- a/MVMCoreUI/Atoms/TextFields/MFTextField.h +++ b/MVMCoreUI/Atoms/TextFields/MFTextField.h @@ -46,7 +46,8 @@ // To set the placeholder and text @property (nullable, weak, nonatomic) NSString *text; @property (nullable, weak, nonatomic) NSString *formText; -@property (nullable, weak, nonatomic) NSString *fieldKey; +@property (nullable, strong, nonatomic) NSString *fieldKey; +@property (nullable, strong, nonatomic) NSString *groupName; @property (nullable, weak, nonatomic) NSString *placeholder; // will move out in Feb release diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.m b/MVMCoreUI/Atoms/TextFields/MFTextField.m index ba68c0ed..cfef9090 100644 --- a/MVMCoreUI/Atoms/TextFields/MFTextField.m +++ b/MVMCoreUI/Atoms/TextFields/MFTextField.m @@ -18,7 +18,7 @@ @import MVMCore.NSDictionary_MFConvenience; @import MVMCore.MVMCoreJSONConstants; -@interface MFTextField() +@interface MFTextField() @property (strong, nonatomic) UIColor *customPlaceHolderColor; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *separatorHeightConstraint; @@ -83,11 +83,9 @@ self.enabled = YES; // Disable SmartQuotes - if (@available(iOS 11.0, *)) { - self.textField.smartQuotesType = UITextSmartQuotesTypeNo; - self.textField.smartDashesType = UITextSmartDashesTypeNo; - self.textField.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; - } + self.textField.smartQuotesType = UITextSmartQuotesTypeNo; + self.textField.smartDashesType = UITextSmartDashesTypeNo; + self.textField.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; } } @@ -319,12 +317,14 @@ if (string.length > 0) { self.errMessage = string; } - + // key used to send text value to server string = [map string:KeyFieldKey]; if (string.length > 0) { self.fieldKey = string; } + + self.groupName = [map string:@"groupName"]; string = [map string:KeyType]; if ([string isEqualToString:@"dropDown"]) { @@ -588,5 +588,9 @@ - (nullable id)formFieldValue { return self.text; } - + +- (NSString * _Nullable)formFieldGroupName { + return self.groupName; +} + @end diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift new file mode 100644 index 00000000..e5a82b1d --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -0,0 +1,480 @@ +// +// Checkbox.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import MVMCore + +/** + This class expects its height and width to be equal. + */ +@objcMembers open class Checkbox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + // Form Validation + var isRequired = false + var fieldKey: String? + var groupName: String? + var delegateObject: MVMCoreUIDelegateObject? + + public static let defaultHeightWidth: CGFloat = 18.0 + + /// If true the border of this checkbox will be circular. + public var isRound: Bool = false + + /// Determined if the checkbox's UI should animated when selected. + public var isAnimated: Bool = true + + /// Disables all selection logic when setting the value of isSelected, reducing it to a stored property. + public var updateSelectionOnly: Bool = false + + /// The color of the background when checked. + public var checkedBackgroundColor: UIColor = .clear { + didSet { + if isSelected { + backgroundColor = checkedBackgroundColor + } + } + } + + /// The color of the background when unChecked. + public var unCheckedBackgroundColor: UIColor = .clear { + didSet { + if !isSelected { + backgroundColor = unCheckedBackgroundColor + } + } + } + + /// Retrieves ideeal radius value to curve square into a circle. + public var cornerRadiusValue: CGFloat { + return bounds.size.height / 2 + } + + /// Action Block called when the switch is selected. + public var actionBlock: ActionBlock? + + /// Manages the appearance of the checkbox. + private var shapeLayer: CAShapeLayer? + + /// Width of the check mark. + public var checkWidth: CGFloat = 2 { + didSet { + if let shapeLayer = shapeLayer { + CATransaction.withDisabledAnimations { + shapeLayer.lineWidth = checkWidth + } + } + } + } + + /// Color of the check mark. + public var checkColor: UIColor = .black { + didSet { + setShapeLayerStrokeColor(checkColor) + } + } + + /// Border width of the checkbox + public var borderWidth: CGFloat = 1 { + didSet { + layer.borderWidth = borderWidth + } + } + + /// border color of the Checkbox + public var borderColor: UIColor = .black { + didSet { + layer.borderColor = borderColor.cgColor + } + } + + /** + The represented state of the Checkbox. + + Setting updateSelectionOnly to true bypasses the animation logic inherent with setting this property. + */ + override open var isSelected: Bool { + didSet { + if !updateSelectionOnly { + layoutIfNeeded() + shapeLayer?.removeAllAnimations() + + updateCheckboxUI(isSelected: isSelected, isAnimated: isAnimated) + FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) + updateAccessibilityLabel() + } + } + } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private var heightConstraint: NSLayoutConstraint? + private var widthConstraint: NSLayoutConstraint? + + /// Updates the height and width anchors of the Checkbox with the assigned value. + public var heigthWidthConstant: CGFloat = Checkbox.defaultHeightWidth { + didSet { + heightConstraint?.constant = heigthWidthConstant + widthConstraint?.constant = heigthWidthConstant + } + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + override public init(frame: CGRect) { + super.init(frame: frame) + + accessibilityTraits = .button + accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") + updateAccessibilityLabel() + setupView() + } + + /// There is currently no intention on using xib files. + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + fatalError("xib file is not implemented for Checkbox.") + } + + public convenience init() { + self.init(frame:.zero) + } + + public convenience init(isChecked: Bool) { + self.init(frame: .zero) + updateSelectionOnly = true + isSelected = isChecked + updateSelectionOnly = false + } + + public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) { + self.init(frame: .zero) + updateSelectionOnly = true + isSelected = isChecked + updateSelectionOnly = false + self.checkedBackgroundColor = checkedBackgroundColor + self.unCheckedBackgroundColor = unCheckedBackgroundColor + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + override open func layoutSubviews() { + super.layoutSubviews() + + drawShapeLayer() + layer.cornerRadius = isRound ? cornerRadiusValue : 0 + layer.borderWidth = borderWidth + layer.borderColor = borderColor.cgColor + } + + open func setupView() { + + guard constraints.isEmpty else { return } + + isUserInteractionEnabled = true + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .clear + + widthConstraint = widthAnchor.constraint(equalToConstant: Checkbox.defaultHeightWidth) + heightConstraint = heightAnchor.constraint(equalToConstant: Checkbox.defaultHeightWidth) + heightWidthIsActive(true) + } + + //-------------------------------------------------- + // MARK: - Actions + //-------------------------------------------------- + + open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { + super.sendAction(action, to: target, for: event) + toggleAndAction() + } + + open override func sendActions(for controlEvents: UIControl.Event) { + super.sendActions(for: controlEvents) + toggleAndAction() + } + + /// This will toggle the state of the Checkbox and execute the actionBlock if provided. + public func toggleAndAction() { + isSelected.toggle() + actionBlock?() + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + /// Creates the check mark layer. + private func drawShapeLayer() { + + if shapeLayer == nil { + + let shapeLayer = CAShapeLayer() + self.shapeLayer = shapeLayer + shapeLayer.frame = bounds + layer.addSublayer(shapeLayer) + shapeLayer.strokeColor = checkColor.cgColor + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.path = checkMarkPath() + shapeLayer.lineJoin = .miter + shapeLayer.lineWidth = checkWidth + + CATransaction.withDisabledAnimations { + shapeLayer.strokeEnd = isSelected ? 1 : 0 + } + } + } + + /// - returns: The CGPath of a UIBezierPath detailing the path of a checkmark + func checkMarkPath() -> CGPath { + + let length = max(bounds.size.height, bounds.size.width) + let xInsetLeft = length * 0.25 + let yInsetTop = length * 0.3 + let innerWidth = length - (xInsetLeft + length * 0.25) // + Right X Inset + let innerHeight = length - (yInsetTop + length * 0.35) // + Bottom Y Inset + + let startPoint = CGPoint(x: xInsetLeft, y: yInsetTop + (innerHeight / 2)) + let pivotOffSet = CGPoint(x: xInsetLeft + (innerWidth * 0.33), y: yInsetTop + innerHeight) + let endOffset = CGPoint(x: xInsetLeft + innerWidth, y: yInsetTop) + + let bezierPath = UIBezierPath() + bezierPath.move(to: startPoint) + bezierPath.addLine(to: pivotOffSet) + bezierPath.addLine(to: endOffset) + + return bezierPath.cgPath + } + + /// Programmatic means to check/uncheck the box. + /// - parameter selected: state of the check box: true = checked OR false = unchecked. + /// - parameter animated: allows the state of the checkbox to change with or without animation. + public func updateSelection(to selected: Bool, animated: Bool) { + + DispatchQueue.main.async { + + self.updateSelectionOnly = true + self.isSelected = selected + self.updateSelectionOnly = false + self.drawShapeLayer() + self.shapeLayer?.removeAllAnimations() + self.updateCheckboxUI(isSelected: selected, isAnimated: animated) + } + } + + /// updates the visuals of the check mark and background. + /// - parameter isSelected: the check state of the checkbox. + /// - parameter isAnimated: determines of the changes should animate or immediately refelect. + public func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) { + + if isAnimated { + let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") + animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) + animateStrokeEnd.duration = 0.3 + animateStrokeEnd.fillMode = .both + animateStrokeEnd.isRemovedOnCompletion = false + animateStrokeEnd.fromValue = !isSelected ? 1 : 0 + animateStrokeEnd.toValue = isSelected ? 1 : 0 + self.shapeLayer?.add(animateStrokeEnd, forKey: "strokeEnd") + + UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + self.backgroundColor = isSelected ? self.checkedBackgroundColor : self.unCheckedBackgroundColor + }) + } else { + CATransaction.withDisabledAnimations { + self.shapeLayer?.strokeEnd = isSelected ? 1 : 0 + } + + backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor + } + } + + /// Adjust accessibility label based on state of Checkbox. + func updateAccessibilityLabel() { + // Attention: This needs to be addressed with the accessibility team. + // NOTE: Currently emptying description part of MVMCoreUICheckBox accessibility label to avoid crashing! + if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@%@", "", state) + } + } + + func isEnabled(_ enabled: Bool) { + + isUserInteractionEnabled = enabled + + if enabled { + layer.borderColor = borderColor.cgColor + backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor + alpha = 1.0 + setShapeLayerStrokeColor(checkColor) + } else { + layer.borderColor = UIColor.mfSilver().cgColor + backgroundColor = .clear + alpha = DisableOppacity + setShapeLayerStrokeColor(UIColor.mfSilver()) + } + } + + private func setShapeLayerStrokeColor(_ color: UIColor) { + + if let shapeLayer = shapeLayer { + CATransaction.withDisabledAnimations { + shapeLayer.strokeColor = color.cgColor + } + } + } + + public func heightWidthIsActive(_ isActive: Bool) { + + heightConstraint?.isActive = isActive + widthConstraint?.isActive = isActive + } + + //-------------------------------------------------- + // MARK: - UITouch + //-------------------------------------------------- + + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { + + sendActions(for: touchIsAcceptablyOutside(touches.first) ? .touchUpOutside : .touchUpInside) + } + + func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool { + let endLocation = touch?.location(in: self) + let x = endLocation?.x ?? 0.0 + let y = endLocation?.y ?? 0.0 + let faultTolerance: CGFloat = 20.0 + let widthLimit = CGFloat(bounds.size.width + faultTolerance) + let heightLimt = CGFloat(bounds.size.height + faultTolerance) + + return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt + } + + override open func accessibilityActivate() -> Bool { + sendActions(for: .touchUpInside) + return true + } + + //-------------------------------------------------- + // MARK: - Molecular + //-------------------------------------------------- + + open func needsToBeConstrained() -> Bool { + return true + } + + open func reset() { + + isEnabled(true) + shapeLayer?.removeAllAnimations() + shapeLayer?.removeFromSuperlayer() + shapeLayer = nil + backgroundColor = .clear + borderColor = .black + borderWidth = 1.0 + checkColor = .black + checkWidth = 2.0 + updateSelectionOnly = true + isSelected = false + updateSelectionOnly = false + } + + open func setAsMolecule() { + setupView() + } + + public func updateView(_ size: CGFloat) { + + layoutIfNeeded() + } + + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.delegateObject = delegateObject + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + + guard let dictionary = json else { return } + + groupName = dictionary.optionalStringForKey("groupName") + if let fieldKey = dictionary[KeyFieldKey] as? String { + self.fieldKey = fieldKey + } + + if let isRequired = dictionary[KeyRequired] as? Bool { + self.isRequired = isRequired + } + + if let borderColorHex = dictionary["borderColor"] as? String { + layer.borderColor = UIColor.mfGet(forHex: borderColorHex).cgColor + } + + if let borderWidth = dictionary["borderWidth"] as? CGFloat { + layer.borderWidth = borderWidth + } + + if let isChecked = dictionary["isChecked"] as? Bool, isChecked { + updateSelectionOnly = true + isSelected = isChecked + updateSelectionOnly = false + } + + if let checkColorHex = dictionary["checkColor"] as? String { + checkColor = UIColor.mfGet(forHex: checkColorHex) + } + + if let unCheckedBackgroundColorHex = dictionary["unCheckedBackgroundColor"] as? String { + unCheckedBackgroundColor = UIColor.mfGet(forHex: unCheckedBackgroundColorHex) + } + + if let checkedBackgroundColorHex = dictionary["checkedBackgroundColor"] as? String { + checkedBackgroundColor = UIColor.mfGet(forHex: checkedBackgroundColorHex) + } + + if let isAnimated = dictionary["isAnimated"] as? Bool { + self.isAnimated = isAnimated + } + + if let isRound = dictionary["isRound"] as? Bool { + self.isRound = isRound + } + + if let enabled = dictionary["isEnabled"] as? Bool { + isEnabled(enabled) + } + + if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { + actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } + } + } +} + +// MARK:- FormValidationProtocol +extension Checkbox: FormValidationFormFieldProtocol { + + public func formFieldGroupName() -> String? { + return groupName + } + + public func isValidField() -> Bool { + return isRequired ? isSelected : true + } + + public func formFieldName() -> String? { + return fieldKey + } + + public func formFieldValue() -> Any? { + return NSNumber(value: isSelected) + } +} diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift new file mode 100644 index 00000000..f148863b --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -0,0 +1,200 @@ +// +// CheckboxWithLabelView.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + + +@objcMembers open class CheckboxWithLabelView: ViewConstrainingView { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public let checkbox = Checkbox() + public let label = Label.commonLabelB2(true) + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) + + var checkboxPosition: CheckboxPosition = .center + + public enum CheckboxPosition: String { + case center + case top + case bottom + } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + var checkboxWidthConstraint: NSLayoutConstraint? + var checkboxHeightConstraint: NSLayoutConstraint? + var checkboxTopConstraint: NSLayoutConstraint? + var checkboxBottomConstraint: NSLayoutConstraint? + var checkboxCenterYConstraint: NSLayoutConstraint? + var centerLabelCheckboxConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + + override open func setupView() { + super.setupView() + + guard subviews.isEmpty else { return } + + translatesAutoresizingMaskIntoConstraints = false + + addSubview(checkbox) + addSubview(label) + + label.text = "" + + let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth + checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) + checkboxWidthConstraint?.isActive = true + checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) + checkboxHeightConstraint?.isActive = true + + checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor).isActive = true + + let checboxBottom = checkbox.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + checboxBottom.priority = UILayoutPriority(249) + checboxBottom.isActive = true + + // Allows various positions of checkbox. + checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor) + checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) + checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor) + centerLabelCheckboxConstraint = label.centerYAnchor.constraint(equalTo: checkbox.centerYAnchor) + + label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true + label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true + + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true + let bottomLabelConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor) + bottomLabelConstraint.priority = UILayoutPriority(249) + bottomLabelConstraint.isActive = true + + alignCheckbox(.center) + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("xib file is not implemented for CheckboxWithLabelView") + } + + override public init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + public convenience init() { + self.init(frame: .zero) + } + + public convenience init(position: CheckboxPosition) { + self.init(frame: .zero) + + alignCheckbox(position) + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + /// Aligns Checkbox and Label relative to the desired position of the Checkbox. + private func alignCheckbox(_ position: CheckboxPosition) { + checkboxPosition = position + + switch position { + case .center: + checkboxBottomConstraint?.isActive = false + checkboxTopConstraint?.isActive = false + checkboxCenterYConstraint?.isActive = true + centerLabelCheckboxConstraint?.isActive = true + + case .top: + checkboxBottomConstraint?.isActive = false + checkboxTopConstraint?.isActive = true + checkboxCenterYConstraint?.isActive = false + centerLabelCheckboxConstraint?.isActive = false + + case .bottom: + checkboxBottomConstraint?.isActive = true + checkboxTopConstraint?.isActive = false + checkboxCenterYConstraint?.isActive = false + centerLabelCheckboxConstraint?.isActive = false + } + } +} + +/// MARK: - Molecular +extension CheckboxWithLabelView { + + override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return CGFloat(Checkbox.defaultHeightWidth) + } + + @objc override open func updateView(_ size: CGFloat) { + super.updateView(size) + + label.updateView(size) + + if self.checkbox.responds(to: #selector(self.updateView(_:))) { + if let dimension = sizeObject?.getValueBased(onSize: size) { + checkboxWidthConstraint?.constant = dimension + checkboxHeightConstraint?.constant = dimension + checkbox.updateView(size) + } + } + + layoutIfNeeded() + } + + override open func alignment() -> UIStackView.Alignment { + return .leading + } + + open override func resetConstraints() { + super.resetConstraints() + + checkboxCenterYConstraint?.isActive = false + checkboxBottomConstraint?.isActive = false + checkboxTopConstraint?.isActive = false + centerLabelCheckboxConstraint?.isActive = false + } + + open override func reset() { + super.reset() + + label.text = "" + checkbox.reset() + } + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json else { return } + + if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) { + alignCheckbox(position) + } + + checkbox.setWithJSON(dictionary.dictionaryForKey("checkbox"), delegateObject: delegateObject, additionalData: additionalData) + label.setWithJSON(dictionary.dictionaryForKey("label"), delegateObject: delegateObject, additionalData: additionalData) + } +} diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index 459b78cf..6b013f82 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -26,6 +26,9 @@ public typealias ActionBlock = () -> () public var sizeObject: MFSizeObject? public var scaleSize: NSNumber? + /// A specific text index to use as a unique marker. + public var hero: Int? + // Used for scaling the font in updateView. private var originalAttributedString: NSAttributedString? @@ -34,10 +37,15 @@ public typealias ActionBlock = () -> () return !text.isEmpty || !attributedText.string.isEmpty } + public var getRange: NSRange { + return NSRange(location: 0, length: text?.count ?? 0) + } + //------------------------------------------------------ // MARK: - Multi-Action Text //------------------------------------------------------ + /// Data store of the tappable ranges of the text. public var clauses: [ActionableClause] = [] { didSet { isUserInteractionEnabled = !clauses.isEmpty @@ -110,6 +118,15 @@ public typealias ActionBlock = () -> () standardFontSize = size } + /// Convenience to init Label with a link comprised of range, actionMap and delegateObject + @objc convenience public init(text: String, range: NSRange, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + self.init() + self.text = text + if let actionBlock = createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { + setTextLinkState(range: range, actionBlock: actionBlock) + } + } + //------------------------------------------------------ // MARK: - Factory Functions //------------------------------------------------------ @@ -305,8 +322,9 @@ public typealias ActionBlock = () -> () guard let actionLabel = label as? Label else { continue } actionLabel.addActionAttributes(range: range, string: attributedString) - let actionBlock = actionLabel.createActionBlockFrom(actionMap: json, additionalData: additionalData, delegateObject: delegate) - actionLabel.appendActionableClause(range: range, actionBlock: actionBlock) + if let actionBlock = actionLabel.createActionBlockFor(actionMap: attribute, additionalData: additionalData, delegateObject: delegate) { + actionLabel.appendActionableClause(range: range, actionBlock: actionBlock) + } default: continue @@ -481,7 +499,7 @@ public typealias ActionBlock = () -> () } /// Call to detect in the attributedText contains an NSTextAttachment. - func textContainsTextAttachment() -> Bool { + func containsTextAttachment() -> Bool { guard let attributedText = attributedText else { return false } @@ -502,6 +520,50 @@ public typealias ActionBlock = () -> () let accessibleAction = customAccessibilityAction(range: range) clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1)) } + + /** + Provides a text container and layout manager of how the text would appear on screen. + They are used in tandem to derive low-level TextKit results of the label. + */ + public func abstractTextContainer() -> (NSTextContainer, NSLayoutManager, NSTextStorage)? { + + // Must configure the attributed string to translate what would appear on screen to accurately analyze. + guard let attributedText = attributedText else { return nil } + + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = textAlignment + + let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText) + stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) + + let textStorage = NSTextStorage(attributedString: stagedAttributedString) + let layoutManager = NSLayoutManager() + let textContainer = NSTextContainer(size: .zero) + + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) + + textContainer.lineFragmentPadding = 0.0 + textContainer.lineBreakMode = lineBreakMode + textContainer.maximumNumberOfLines = numberOfLines + textContainer.size = bounds.size + + return (textContainer, layoutManager, textStorage) + } + + public static func boundingRect(forCharacterRange range: NSRange, in label: Label) -> CGRect { + + guard let abstractContainer = label.abstractTextContainer() else { return CGRect() } + let textContainer = abstractContainer.0 + let layoutManager = abstractContainer.1 + + var glyphRange = NSRange() + + // Convert the range for glyphs. + layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange) + + return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) + } } // MARK: - Atomization @@ -521,6 +583,8 @@ extension Label { clauses = [] Label.setUILabel(self, withJSON: json, delegate: delegateObject, additionalData: additionalData) originalAttributedString = attributedText + + hero = json?["hero"] as? Int } public func setAsMolecule() { @@ -540,7 +604,7 @@ extension Label { } } -// MARK: - Multi-Action Functionality +// MARK: - Multi-Link Functionality extension Label { /// Applied to existing text. Removes underlines of tappable links and assoated actionable clauses. @@ -559,15 +623,17 @@ extension Label { clauses = [] } - public func createActionBlockFrom(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock { + public func createActionBlockFor(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? { return { [weak self] in - if let wSelf = self, (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(wSelf, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { + guard let self = self else { return } + + if (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } } - func addActionAttributes(range: NSRange, string: NSMutableAttributedString?) { + private func addActionAttributes(range: NSRange, string: NSMutableAttributedString?) { guard let string = string else { return } string.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue], range: range) @@ -591,8 +657,7 @@ extension Label { */ @objc public func addTappableLinkAttribute(range: NSRange, actionBlock: @escaping ActionBlock) { - setActionAttributes(range: range) - appendActionableClause(range: range, actionBlock: actionBlock) + setTextLinkState(range: range, actionBlock: actionBlock) } /** @@ -606,15 +671,35 @@ extension Label { */ @objc public func addTappableLinkAttribute(range: NSRange, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + if let actionBlock = createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { + setTextLinkState(range: range, actionBlock: actionBlock) + } + } + + /// Converts the entire text into a link. All characters will be underlined and the intrinsic bounds will respond to tap. + @objc public func makeTextButton(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + + if let actionBlock = createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { + setTextLinkState(range: getRange, actionBlock: actionBlock) + } + } + + /// Converts the entire text into a link. All characters will be underlined and the intrinsic bounds will respond to tap. + @objc public func makeTextButton(actionBlock: @escaping ActionBlock) { + + setTextLinkState(range: getRange, actionBlock: actionBlock) + } + + /// Underlines the tappable region and stores the tap logic for interation. + private func setTextLinkState(range: NSRange, actionBlock: @escaping ActionBlock) { + setActionAttributes(range: range) - let actionBlock = createActionBlockFrom(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) appendActionableClause(range: range, actionBlock: actionBlock) } @objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) { for clause in clauses { - // This determines if we tapped on the desired range of text. if let range = clause.range, gesture.didTapAttributedTextInLabel(self, inRange: range) { clause.performAction() @@ -634,30 +719,37 @@ extension UITapGestureRecognizer { return true } - // Must configure the attributed string to translate what would appear on screen to accurately analyze. - guard let attributedText = label.attributedText else { return false } + guard let abstractContainer = label.abstractTextContainer() else { return false } + let textContainer = abstractContainer.0 + let layoutManager = abstractContainer.1 - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = label.textAlignment + let tapLocation = location(in: label) + let indexOfGlyph = layoutManager.glyphIndex(for: tapLocation, in: textContainer) + let intrinsicWidth = label.intrinsicContentSize.width - let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText) - stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) + // Assert that tapped occured within acceptable bounds based on alignment. + switch label.textAlignment { + case .right: + if tapLocation.x < label.bounds.width - intrinsicWidth { + return false + } + case .center: + let halfBounds = label.bounds.width / 2 + let halfIntrinsicWidth = intrinsicWidth / 2 + + if tapLocation.x > halfBounds + halfIntrinsicWidth { + return false + } else if tapLocation.x < halfBounds - halfIntrinsicWidth { + return false + } + default: // Left align + if tapLocation.x > intrinsicWidth { + return false + } + } - let textStorage = NSTextStorage(attributedString: stagedAttributedString) - let layoutManager = NSLayoutManager() - let textContainer = NSTextContainer(size: .zero) - - layoutManager.addTextContainer(textContainer) - textStorage.addLayoutManager(layoutManager) - - textContainer.lineFragmentPadding = 0.0 - textContainer.lineBreakMode = label.lineBreakMode - textContainer.maximumNumberOfLines = label.numberOfLines - textContainer.size = label.bounds.size - - let indexOfGlyph = layoutManager.glyphIndex(for: location(in: label), in: textContainer) - - return NSLocationInRange(indexOfGlyph, targetRange) + // Affirms that the tap occured in the desired rect of provided by the target range. + return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(tapLocation) && NSLocationInRange(indexOfGlyph, targetRange) } } diff --git a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift index ff2e751d..070cc0f7 100644 --- a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift +++ b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift @@ -207,7 +207,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt @objc public func setActionMap(_ actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - actionBlock = label?.createActionBlockFrom(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) + actionBlock = label?.createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) } //------------------------------------------------------ @@ -377,7 +377,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt actionText = actionMap?.optionalStringForKey(KeyTitle) backText = actionMap?.optionalStringForKey(KeyTitlePostfix) text = getTextFromStringComponents() - actionBlock = label?.createActionBlockFrom(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) + actionBlock = label?.createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) setLabelAttributes() } diff --git a/MVMCoreUI/Atoms/Views/MFLoadImageView.swift b/MVMCoreUI/Atoms/Views/MFLoadImageView.swift index 21802b38..361c0aa2 100644 --- a/MVMCoreUI/Atoms/Views/MFLoadImageView.swift +++ b/MVMCoreUI/Atoms/Views/MFLoadImageView.swift @@ -215,7 +215,7 @@ import UIKit pinEdges(.all) } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return json?.optionalCGFloatForKey("height") ?? 0 } diff --git a/MVMCoreUI/Atoms/Views/MFTextView.m b/MVMCoreUI/Atoms/Views/MFTextView.m index c120d964..424c8117 100644 --- a/MVMCoreUI/Atoms/Views/MFTextView.m +++ b/MVMCoreUI/Atoms/Views/MFTextView.m @@ -173,11 +173,10 @@ view.placeHolderLabel.textColor = [UIColor mfLightGrayColor]; // Disable SmartQuotes - if (@available(iOS 11.0, *)) { - view.textView.smartQuotesType = UITextSmartQuotesTypeNo; - view.textView.smartDashesType = UITextSmartDashesTypeNo; - view.textView.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; - } + view.textView.smartQuotesType = UITextSmartQuotesTypeNo; + view.textView.smartDashesType = UITextSmartDashesTypeNo; + view.textView.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; + [view didSetFont:view.textView.font]; view.hideBorder = YES; return view; diff --git a/MVMCoreUI/Atoms/Views/MFView.m b/MVMCoreUI/Atoms/Views/MFView.m index 4e0d71ab..6d939fc8 100644 --- a/MVMCoreUI/Atoms/Views/MFView.m +++ b/MVMCoreUI/Atoms/Views/MFView.m @@ -48,9 +48,7 @@ - (void)setAsMolecule { self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 11.0, *)) { - self.insetsLayoutMarginsFromSafeArea = NO; - } + self.insetsLayoutMarginsFromSafeArea = NO; } - (void)reset { diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m index 2cde1bc3..50377b1a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m +++ b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m @@ -21,7 +21,7 @@ static const CGFloat FaultTolerance = 20.f; static const CGFloat CheckBoxHeightWidth = 18.0; -@interface MVMCoreUICheckBox () +@interface MVMCoreUICheckBox () @property (nonatomic, readwrite) BOOL isSelected; @property (weak, nonatomic) UIView *checkedSquare; @@ -42,7 +42,7 @@ static const CGFloat CheckBoxHeightWidth = 18.0; @property (nonatomic) BOOL isRequired; @property (nullable, strong, nonatomic) NSString *fieldKey; -@property (nullable, strong, nonatomic) DelegateObject *delegate; +@property (nullable, strong, nonatomic) MVMCoreUIDelegateObject *delegateObject; @end @@ -61,7 +61,8 @@ static const CGFloat CheckBoxHeightWidth = 18.0; - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { [FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol]; - self.delegate = delegateObject; + + self.delegateObject = delegateObject; self.fieldKey = [json stringForKey:KeyFieldKey]; self.isRequired = [json boolForKey:KeyRequired]; @@ -306,6 +307,8 @@ static const CGFloat CheckBoxHeightWidth = 18.0; self.checkedSquare.layer.cornerRadius = self.isRoundRectCheckMark ? 5.0f : 0; } +// TODO:..................................... + #pragma mark - XIB Helpers - (instancetype)awakeAfterUsingCoder:(NSCoder *)aDecoder { @@ -345,10 +348,7 @@ static const CGFloat CheckBoxHeightWidth = 18.0; [self.checkMark updateCheckSelected:NO animated:animated]; } - if (self.delegate && [self.delegate respondsToSelector:@selector(formValidationProtocol)] && [[self.delegate performSelector:@selector(formValidationProtocol)] respondsToSelector:@selector(formValidatorModel)]) { - FormValidator *formValidator = [[self.delegate performSelector:@selector(formValidationProtocol)] performSelector:@selector(formValidatorModel)]; - [formValidator enableByValidation]; - } + [FormValidator enableByValidationWithDelegate:self.delegateObject.formValidationProtocol]; } - (void)setColor:(nullable UIColor *)color forState:(UIControlState)state { diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h index 32e98f2a..256aabfa 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h @@ -24,6 +24,7 @@ typedef void(^ValueChangeBlock)(void); @property (nonatomic) BOOL shouldTouchToSwitch; @property (nullable, copy, nonatomic) ValueChangeBlock valueChangedBlock; +@property (nullable, copy, nonatomic) ValueChangeBlock actionBlock; + (nonnull instancetype)mvmSwitchDefault; + (nonnull instancetype)mvmSwitchDefaultWithValueChangeBlock:(nullable ValueChangeBlock)block; diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m index f994b320..ae0db4a7 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m @@ -22,7 +22,7 @@ const CGFloat SwitchKnobWidth = 20; const CGFloat SwitchKnobHeight = 20; const CGFloat SwitchShakeIntensity = 2; -@interface MVMCoreUISwitch () +@interface MVMCoreUISwitch () @property (weak, nonatomic) UIView *baseView; @property (weak, nonatomic) UIView *knobView; @@ -145,7 +145,8 @@ const CGFloat SwitchShakeIntensity = 2; - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { self.json = json; - + self.delegate = delegateObject; + [FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol]; NSString *color = [json string:@"onTintColor"]; @@ -169,6 +170,17 @@ const CGFloat SwitchShakeIntensity = 2; } [self setState:[json boolForKey:@"state"] animated:false]; + + NSDictionary *actionMap = [json dict:@"actionMap"]; + if (actionMap) { + [self addTarget:self action:@selector(addCustomAction) forControlEvents:UIControlEventTouchUpInside]; + } +} + +- (void)addCustomAction { + if (self.actionBlock) { + self.actionBlock(); + } } + (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject { diff --git a/MVMCoreUI/Atoms/Views/ProgressBar.swift b/MVMCoreUI/Atoms/Views/ProgressBar.swift index a9333966..1aecd537 100644 --- a/MVMCoreUI/Atoms/Views/ProgressBar.swift +++ b/MVMCoreUI/Atoms/Views/ProgressBar.swift @@ -73,7 +73,7 @@ import Foundation trackTintColor = UIColor.mfLightSilver() } - public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 8 } } diff --git a/MVMCoreUI/Atoms/Views/RadioButton.swift b/MVMCoreUI/Atoms/Views/RadioButton.swift new file mode 100644 index 00000000..23588a4c --- /dev/null +++ b/MVMCoreUI/Atoms/Views/RadioButton.swift @@ -0,0 +1,135 @@ +// +// RadioButton.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 4/25/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class RadioButton: Control, FormValidationFormFieldProtocol { + + var diameter: CGFloat = 30 { + didSet { + widthConstraint?.constant = diameter + } + } + var enabledColor = UIColor.black + var disabledColor = UIColor.mfSilver() + + var delegateObject: MVMCoreUIDelegateObject? + + var fieldKey: String? + var formValue: Bool? + var isRequired: Bool = false + + var widthConstraint: NSLayoutConstraint? + var heightConstraint: NSLayoutConstraint? + + lazy var radioGroupName: String? = { + [unowned self] in + return json?.optionalStringForKey("radioGroupName") ?? json?.optionalStringForKey("fieldKey") + }() + + lazy var radioButtonModel: RadioButtonModel? = { + [unowned self] in + if let radioGroupName = radioGroupName, + let radioButtonModel = delegateObject?.formValidationProtocol?.formValidatorModel?()?.radioButtonsModelByGroup[radioGroupName] { + return radioButtonModel + } else { + return nil + } + }() + + open override func draw(_ rect: CGRect) { + guard let context = UIGraphicsGetCurrentContext() else { return } + let color = isEnabled ? enabledColor.cgColor : disabledColor.cgColor + layer.cornerRadius = bounds.width * 0.5 + layer.borderColor = color + layer.borderWidth = bounds.width * 0.0333 + if isSelected { + // Space around inner circle is 1/5 the size + context.addEllipse(in: CGRect(x: bounds.width*0.2, y: bounds.height*0.2, width: bounds.width*0.6, height: bounds.height*0.6)) + context.setFillColor(color) + context.fillPath() + } + } + + /// The action performed when tapped. + func tapAction() { + if let radioButtonModel = radioButtonModel { + radioButtonModel.selected(self) + } else { + isSelected = !isSelected + } + FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) + setNeedsDisplay() + } + + public func isValidField() -> Bool { + return isSelected + } + + public func formFieldName() -> String? { + return json?.optionalStringForKey("fieldKey") + } + + public func formFieldGroupName() -> String? { + return json?.optionalStringForKey("groupName") + } + + public func formFieldValue() -> Any? { + return isSelected + } + +// MARK: - MVMViewProtocol + open override func setupView() { + super.setupView() + backgroundColor = .white + clipsToBounds = true + widthConstraint = widthAnchor.constraint(equalToConstant: 30) + widthConstraint?.isActive = true + heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) + heightConstraint?.isActive = true + + addTarget(self, action: #selector(tapAction), for: .touchUpInside) + isAccessibilityElement = true + accessibilityTraits = .button + accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint") + } +} + +// MARK: - MVMCoreUIViewConstrainingProtocol +extension RadioButton: MVMCoreUIViewConstrainingProtocol { + public func needsToBeConstrained() -> Bool { + return true + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension RadioButton { + + @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 jsonDictionary = json else { + return + } + + fieldKey = jsonDictionary.optionalStringForKey("fieldKey") + isRequired = jsonDictionary.boolForKey("required") + self.delegateObject = delegateObject + + let radioButtonModel = RadioButtonModel.setupForRadioButtonGroup(radioButton: self, + formValidator: delegateObject?.formValidationProtocol?.formValidatorModel?()) + FormValidator.setupValidation(molecule: radioButtonModel, delegate: delegateObject?.formValidationProtocol) + } + + public override func reset() { + super.reset() + backgroundColor = .white + } +} diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift new file mode 100644 index 00000000..fdf8204f --- /dev/null +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -0,0 +1,62 @@ +// +// Control.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/23/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class Control: UIControl { + var json: [AnyHashable: Any]? + + private var initialSetupPerformed = false + + public override init(frame: CGRect) { + super.init(frame: .zero) + initialSetup() + } + + init() { + super.init(frame: .zero) + initialSetup() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + initialSetup() + } + + public func initialSetup() { + if !initialSetupPerformed { + initialSetupPerformed = true + setupView() + } + } +} + +extension Control: MVMCoreViewProtocol { + public func updateView(_ size: CGFloat) { + } + + /// Will be called only once. + public func setupView() { + translatesAutoresizingMaskIntoConstraints = false + insetsLayoutMarginsFromSafeArea = false + } +} + +extension Control: MVMCoreUIMoleculeViewProtocol { + public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + self.json = json + + if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + } + + public func reset() { + backgroundColor = .clear + } +} diff --git a/MVMCoreUI/BaseClasses/View.swift b/MVMCoreUI/BaseClasses/View.swift new file mode 100644 index 00000000..bc6e7634 --- /dev/null +++ b/MVMCoreUI/BaseClasses/View.swift @@ -0,0 +1,61 @@ +// +// View.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/23/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class View: UIView { + open var json: [AnyHashable: Any]? + + private var initialSetupPerformed = false + + public override init(frame: CGRect) { + super.init(frame: .zero) + initialSetup() + } + + public init() { + super.init(frame: .zero) + initialSetup() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + initialSetup() + } + + public func initialSetup() { + if !initialSetupPerformed { + initialSetupPerformed = true + setupView() + } + } +} + +extension View: MVMCoreViewProtocol { + open func updateView(_ size: CGFloat) {} + + /// Will be called only once. + open func setupView() { + translatesAutoresizingMaskIntoConstraints = false + insetsLayoutMarginsFromSafeArea = false + } +} + +extension View: MVMCoreUIMoleculeViewProtocol { + open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + self.json = json + + if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + } + + open func reset() { + backgroundColor = .clear + } +} diff --git a/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m b/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m index 98d2b5ef..1d0d2a3b 100644 --- a/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m +++ b/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m @@ -61,12 +61,8 @@ tableView.separatorStyle = UITableViewCellSeparatorStyleNone; tableView.delegate = self; tableView.dataSource = self; - if (@available(iOS 11.0, *)) { - tableView.insetsContentViewsToSafeArea = NO; - } - if ([tableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)]) { - tableView.cellLayoutMarginsFollowReadableWidth = NO; - } + tableView.insetsContentViewsToSafeArea = NO; + tableView.cellLayoutMarginsFollowReadableWidth = NO; return tableView; } diff --git a/MVMCoreUI/BaseControllers/MFScrollingViewController.m b/MVMCoreUI/BaseControllers/MFScrollingViewController.m index 5486b54a..56019cb7 100644 --- a/MVMCoreUI/BaseControllers/MFScrollingViewController.m +++ b/MVMCoreUI/BaseControllers/MFScrollingViewController.m @@ -91,10 +91,7 @@ static NSTimeInterval const HandScrollAnimationTiming = 7.f; - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; - BOOL automaticInset = NO; - if (@available(iOS 11.0, *)) { - automaticInset = self.navigationController && self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic; - } + BOOL automaticInset = self.navigationController && self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic; // Takes into account the navigation bar. if (!automaticInset && (self.edgesForExtendedLayout & UIRectEdgeTop)) { diff --git a/MVMCoreUI/BaseControllers/MFViewController.m b/MVMCoreUI/BaseControllers/MFViewController.m index 1cde71f2..2e0f2c73 100644 --- a/MVMCoreUI/BaseControllers/MFViewController.m +++ b/MVMCoreUI/BaseControllers/MFViewController.m @@ -67,11 +67,13 @@ } - (void)dismiss { - if (self.presentingViewController) { - [[MVMCoreNavigationHandler sharedNavigationHandler] dismissViewController:self animated:YES]; - } else if (self.navigationController) { - [[MVMCoreNavigationHandler sharedNavigationHandler] popViewController:self animated:YES]; - } + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + if (self.presentingViewController) { + [[MVMCoreNavigationHandler sharedNavigationHandler] dismissViewController:self animated:YES]; + } else if (self.navigationController) { + [[MVMCoreNavigationHandler sharedNavigationHandler] popViewController:self animated:YES]; + } + }]; } - (BOOL)isVisibleViewController { @@ -520,6 +522,7 @@ // Don't track page state if there is a tab bar page control, it will be handled later. if (!self.manager) { + [MVMCoreUISession sharedGlobal].currentPageType = self.pageType; [self adobeTrackPageState]; } @@ -529,6 +532,13 @@ } } +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + if (self.selectedField) { + [self.selectedField resignFirstResponder]; + } +} + - (void)dealloc { [self stopObservingForResponseJSONUpdates]; MVMCoreLog(@"%@ deallocated", [[self class] description]); @@ -814,6 +824,7 @@ if (self.initialLoadFinished) { [self updateNavigationBarUI:self.manager.navigationController]; } + [MVMCoreUISession sharedGlobal].currentPageType = self.pageType; [self adobeTrackPageState]; } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 0293e860..5045d5a4 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -190,13 +190,9 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true view.rightAnchor.constraint(equalTo: footerView.rightAnchor).isActive = true - if #available(iOS 11.0, *) { - view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true - safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) - safeAreaView?.backgroundColor = bottomView?.backgroundColor - } else { - view.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true - } + view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true + safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) + safeAreaView?.backgroundColor = bottomView?.backgroundColor } else { bottomConstraint?.isActive = true var y: CGFloat? @@ -218,14 +214,16 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { /// Subclass for a top view. open func viewForTop() -> UIView { let view = MVMCoreUICommonViewsUtility.commonView() - view.heightAnchor.constraint(equalToConstant: 0).isActive = true + // Small height is needed to stop apple from adding padding for grouped tables when no header. + view.heightAnchor.constraint(equalToConstant: 1).isActive = true return view } /// Subclass for a bottom view. open func viewForBottom() -> UIView { + // Default spacing is standard when no buttons. let view = MVMCoreUICommonViewsUtility.commonView() - view.heightAnchor.constraint(equalToConstant: 0).isActive = true + view.heightAnchor.constraint(equalToConstant: PaddingDefaultVerticalSpacing).isActive = true return view } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index dae861dd..ea6923c5 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -45,7 +45,7 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { return } - if #available(iOS 11.0, *), scrollView.contentInsetAdjustmentBehavior == UIScrollView.ContentInsetAdjustmentBehavior.automatic { + if scrollView.contentInsetAdjustmentBehavior == UIScrollView.ContentInsetAdjustmentBehavior.automatic { heightConstraint?.constant = -scrollView.adjustedContentInset.top - scrollView.adjustedContentInset.bottom } else { heightConstraint?.constant = -scrollView.contentInset.top - scrollView.contentInset.bottom @@ -233,14 +233,10 @@ extension ThreeLayerViewController { view.topAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true - if #available(iOS 11.0, *) { - parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true - if let safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: parentView) { - safeAreaView.backgroundColor = bottomView?.backgroundColor - self.safeAreaView = safeAreaView - } - } else { - NSLayoutConstraint.pinViewBottom(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + if let safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: parentView) { + safeAreaView.backgroundColor = bottomView?.backgroundColor + self.safeAreaView = safeAreaView } } } diff --git a/MVMCoreUI/Containers/TabBarController/MVMCoreUITabBarPageControlViewController.m b/MVMCoreUI/Containers/TabBarController/MVMCoreUITabBarPageControlViewController.m index 1d8481d6..cb48eb54 100644 --- a/MVMCoreUI/Containers/TabBarController/MVMCoreUITabBarPageControlViewController.m +++ b/MVMCoreUI/Containers/TabBarController/MVMCoreUITabBarPageControlViewController.m @@ -283,16 +283,6 @@ // So we will update titles. [self newDataBuildScreen]; - - // Fix for right bar button item with custom view which disappears when user navigates to top tabbar page controller - if (@available(iOS 11.0, *)) { - } else { - NSMutableArray *buttonItems = [[NSMutableArray alloc] init]; - UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:self action:nil]; - [buttonItems addObject:space]; - [buttonItems addObjectsFromArray:self.navigationItem.rightBarButtonItems]; - self.navigationItem.rightBarButtonItems = buttonItems; - } } - (void)viewWillDisappear:(BOOL)animated { diff --git a/MVMCoreUI/FormUIHelpers/FormValidationProtocol.swift b/MVMCoreUI/FormUIHelpers/FormValidationProtocol.swift index b593a503..49cfacc9 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidationProtocol.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidationProtocol.swift @@ -10,18 +10,32 @@ import Foundation @objc public protocol FormValidationProtocol: NSObjectProtocol { - // Getter method to get the FormValidator form the delegate (Mostly from the parent View Controller) + // Getter method to get the FormValidator from the delegate (Mostly from the parent View Controller) @objc optional func formValidatorModel() -> FormValidator? +} + +@objc public protocol FormValidationFormFieldProtocol: FormValidationProtocol { + // Used to check the validity of the field. For example, to enable/disable the primary button. + @objc func isValidField() -> Bool - // Used to check the validity of the field, to enable/disable the primary button. - @objc optional func isValidField() -> Bool + // The Field name key value pair for sending to server + @objc func formFieldName() -> String? + + // Returns the group name for validation. The class should always return a value. + @objc func formFieldGroupName() -> String? + + // The Field value key value pair for sending to server + @objc func formFieldValue() -> Any? +} + +@objc public protocol FormValidationEnableDisableProtocol: FormValidationProtocol { + + // Returns true if the button needs to be enabled/disabled based on the validation + @objc func isValidationRequired() -> Bool // Based on the isValidField(), the fields which needs to be enabled can call this method @objc optional func enableField(_ enable: Bool) - // The Field name key value pair for sending to server - @objc optional func formFieldName() -> String? - - // The Field value key value pair for sending to server - @objc optional func formFieldValue() -> Any? + // Returns the list of field keys required to enable/disable + @objc optional func requiredGroups() -> [String]? } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator+FormParams.swift b/MVMCoreUI/FormUIHelpers/FormValidator+FormParams.swift index 17da0e72..4c4945a4 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator+FormParams.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator+FormParams.swift @@ -16,13 +16,10 @@ import Foundation @objc func getFormParams() -> [String: Any] { var extraParam: [String: Any] = [:] MVMCoreDispatchUtility.performSyncBlock(onMainThread: { - for molecule in self.molecules { - if let formFieldName = molecule.formFieldName, - let formFieldValue = molecule.formFieldValue, - let fieldName = formFieldName(), - let fieldValue = formFieldValue() { - - extraParam[fieldName] = fieldValue + for molecule in self.fieldMolecules { + if let formFieldName = molecule.formFieldName(), + let formFieldValue = molecule.formFieldValue() { + extraParam[formFieldName] = formFieldValue } } }) diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 1b546d69..77c52249 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -8,52 +8,79 @@ import Foundation import UIKit +import MVMCore @objcMembers public class FormValidator: NSObject { - var delegate: FormValidationProtocol? - var molecules: [UIView & FormValidationProtocol] = [] var extraValidationBlock: (() -> Bool)? + var dummyGroupName = "dummyGroupName" + weak var delegate: FormValidationProtocol? + var fieldMolecules: [FormValidationFormFieldProtocol] = [] + var enableDisableMolecules: [FormValidationEnableDisableProtocol] = [] + var radioButtonsModelByGroup: [String: RadioButtonModel] = [:] - public func insertMolecule(_ molecule: UIView & FormValidationProtocol) { - molecules.append(molecule) + public func insertMolecule(_ molecule: FormValidationProtocol) { + if let molecule = molecule as? FormValidationFormFieldProtocol { + fieldMolecules.append(molecule) + } + if let moleculeT = molecule as? FormValidationEnableDisableProtocol, + moleculeT.isValidationRequired() { + enableDisableMolecules.append(moleculeT) + } } - public static func getFormValidatorFor(delegate: FormValidationProtocol) -> FormValidator? { - if let delegateFormValidatorModel = delegate.formValidatorModel, - let validator = delegateFormValidatorModel() { - return validator - } else { - return nil + public static func enableByValidationWith(delegate: FormValidationProtocol?) { + if let delegate = delegate { + let formValidator = FormValidator.getFormValidatorFor(delegate: delegate) + formValidator?.enableByValidation() } } - public static func setupValidation(molecule: UIView & FormValidationProtocol, delegate: FormValidationProtocol?) { - if let delegateFormValidatorModel = delegate?.formValidatorModel, - let validator = delegateFormValidatorModel() { - + public static func getFormValidatorFor(delegate: FormValidationProtocol) -> FormValidator? { + return delegate.formValidatorModel?() + } + + public static func setupValidation(molecule: FormValidationProtocol, delegate: FormValidationProtocol?) { + if let validator = delegate?.formValidatorModel?() { validator.delegate = delegate validator.insertMolecule(molecule) } } public func enableByValidation() { + for molecule in enableDisableMolecules { + var requiredGroups = molecule.requiredGroups?() ?? [dummyGroupName] + if requiredGroups.count == 0 { + requiredGroups = [dummyGroupName] + } + enableWithGroups(requiredGroups, molecule) + } + } + + public func enableWithGroups(_ requiredGroupList: [String], _ enableDisableMolecule: FormValidationEnableDisableProtocol) { + let requiredGroupSet = Set(requiredGroupList) var valid = true - for molecule in molecules { - if let isValidField = molecule.isValidField, - isValidField() == false { - valid = false + for molecule in fieldMolecules { + let groupName = molecule.formFieldGroupName() ?? dummyGroupName + if requiredGroupSet.contains(groupName) { + valid = valid && molecule.isValidField() + if valid == false { + break + } + } + } + enableDisableMolecule.enableField?(valid) + } + + public func enableIgnoreGroupName(_ enableDisableMolecules: FormValidationEnableDisableProtocol) { + var valid = true + for molecule in fieldMolecules { + valid = valid && molecule.isValidField() + if (!valid) { + break } } let enableField = valid && (extraValidationBlock?() ?? true) - shouldEnable(enableField) - } - - public func shouldEnable(_ enable: Bool) { - for molecule in molecules { - if let enableField = molecule.enableField { - enableField(enable) - } - } + enableDisableMolecules.enableField?(enableField) } } diff --git a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m index ca75b778..5d6a6778 100644 --- a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m +++ b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m @@ -325,19 +325,13 @@ NSLayoutConstraint *bottomViewTop = [NSLayoutConstraint constraintWithItem:footerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:tableView attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; bottomViewTop.active = YES; - NSLayoutConstraint *bottomViewBot = nil; - if (@available(iOS 11.0, *)) { - bottomViewBot = [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:footerView.bottomAnchor]; - - UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; - safeAreaView.backgroundColor = footerView.backgroundColor; - self.safeAreaView = safeAreaView; - } else { - // Fallback on earlier versions - bottomViewBot = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:footerView attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; - } - bottomViewBot.priority = 900; - bottomViewBot.active = YES; + NSLayoutConstraint *bottomViewBot = [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:footerView.bottomAnchor]; + bottomViewBot.priority = 900; + bottomViewBot.active = YES; + + UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; + safeAreaView.backgroundColor = self.bottomView.backgroundColor; + self.safeAreaView = safeAreaView; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[footerView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(footerView)]]; } else { diff --git a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m index 77472e11..0c9d1d5d 100644 --- a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m +++ b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m @@ -272,13 +272,8 @@ - (void)updateViewConstraints { [super updateViewConstraints]; - // Updates for ios 11 - if (@available(iOS 11.0, *)) { - if (self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { - self.heightConstraint.constant = -self.scrollView.adjustedContentInset.top - self.scrollView.adjustedContentInset.bottom; - } else { - self.heightConstraint.constant = -self.scrollView.contentInset.top - self.scrollView.contentInset.bottom; - } + if (self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { + self.heightConstraint.constant = -self.scrollView.adjustedContentInset.top - self.scrollView.adjustedContentInset.bottom; } else { self.heightConstraint.constant = -self.scrollView.contentInset.top - self.scrollView.contentInset.bottom; } @@ -322,17 +317,13 @@ [self.view addSubview:bottomView]; UIScrollView *scrollview = self.scrollView; - if (@available(iOS 11.0, *)) { - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]]; - [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:bottomView.bottomAnchor].active = YES; - - UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; - safeAreaView.backgroundColor = bottomView.backgroundColor; - self.safeAreaView = safeAreaView; - } else { - // Fallback on earlier versions - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]]; - } + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]]; + [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:bottomView.bottomAnchor].active = YES; + + UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; + safeAreaView.backgroundColor = bottomView.backgroundColor; + self.safeAreaView = safeAreaView; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]]; } diff --git a/MVMCoreUI/Molecules/ActionDetailWithImage.swift b/MVMCoreUI/Molecules/ActionDetailWithImage.swift index ec91c479..4811f20d 100644 --- a/MVMCoreUI/Molecules/ActionDetailWithImage.swift +++ b/MVMCoreUI/Molecules/ActionDetailWithImage.swift @@ -14,10 +14,8 @@ import UIKit // MARK: - Outlets //------------------------------------------------------ - let header = HeadlineBody(frame: .zero) - let button = PrimaryButton.primaryTinyButton(false)! + let headlineBodyButton = HeadlineBodyButton(frame: .zero) let imageLoader = MFLoadImageView(pinnedEdges: .all) - let leftContainer = ViewConstrainingView.empty() //------------------------------------------------------ // MARK: - Properties @@ -30,7 +28,6 @@ import UIKit //------------------------------------------------------ var imageLeadingConstraint: NSLayoutConstraint? - var buttonTopConstraint: NSLayoutConstraint? //------------------------------------------------------ // MARK: - Initialization @@ -64,33 +61,20 @@ import UIKit setDefaultState() - addSubview(leftContainer) addSubview(imageLoader) - leftContainer.addSubview(header) - leftContainer.addSubview(button) + addSubview(headlineBodyButton) - leftContainer.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true - leftContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + headlineBodyButton.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + headlineBodyButton.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: leftContainer.bottomAnchor).isActive = true - let leftContainerBottom = leftContainer.bottomAnchor.constraint(equalTo: bottomAnchor) + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: headlineBodyButton.bottomAnchor).isActive = true + let leftContainerBottom = headlineBodyButton.bottomAnchor.constraint(equalTo: bottomAnchor) leftContainerBottom.priority = UILayoutPriority(249) leftContainerBottom.isActive = true - header.topAnchor.constraint(equalTo: leftContainer.topAnchor).isActive = true - header.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true - leftContainer.trailingAnchor.constraint(equalTo: header.trailingAnchor).isActive = true - - buttonTopConstraint = button.topAnchor.constraint(equalTo: header.bottomAnchor, constant: buttonHeaderPadding) - buttonTopConstraint?.isActive = true - - button.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true - leftContainer.bottomAnchor.constraint(equalTo: button.bottomAnchor).isActive = true - leftContainer.trailingAnchor.constraint(greaterThanOrEqualTo: button.trailingAnchor).isActive = true - imageLoader.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true layoutMarginsGuide.trailingAnchor.constraint(equalTo: imageLoader.trailingAnchor).isActive = true - imageLeadingConstraint = imageLoader.leadingAnchor.constraint(greaterThanOrEqualTo: leftContainer.trailingAnchor, constant: 16) + imageLeadingConstraint = imageLoader.leadingAnchor.constraint(greaterThanOrEqualTo: headlineBodyButton.trailingAnchor, constant: 16) imageLeadingConstraint?.isActive = true imageLoader.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true @@ -103,15 +87,11 @@ import UIKit override open func updateView(_ size: CGFloat) { super.updateView(size) - header.updateView(size) - button.updateView(size) + headlineBodyButton.updateView(size) imageLoader.updateView(size) - leftContainer.updateView(size) - - buttonTopConstraint?.constant = header.hasText() ? buttonHeaderPadding : 0 } - public override static func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 197 } @@ -121,10 +101,8 @@ import UIKit private func setDefaultState() { - header.headlineLabel.font = MFStyler.fontH3() - header.messageLabel.font = MFStyler.fontB3() - button.setAsSecondaryCustom() - button.isHidden = false + headlineBodyButton.headlineBody.headlineLabel.font = MFStyler.fontH3() + headlineBodyButton.headlineBody.messageLabel.font = MFStyler.fontB3() imageLoader.imageView.contentMode = .scaleAspectFit imageLoader.addSizeConstraintsForAspectRatio = true buttonHeaderPadding = PaddingTwo @@ -133,8 +111,7 @@ import UIKit override open func reset() { super.reset() - header.reset() - button.reset() + headlineBodyButton.reset() imageLeadingConstraint?.constant = 16 imageLoader.reset() setDefaultState() @@ -143,8 +120,7 @@ import UIKit open override func setAsMolecule() { super.setAsMolecule() - header.setAsMolecule() - button.setAsMolecule() + headlineBodyButton.setAsMolecule() imageLoader.setAsMolecule() setDefaultState() } @@ -153,18 +129,8 @@ import UIKit super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json else { return } - - if let padding = dictionary.optionalCGFloatForKey("buttonHeaderPadding") { - buttonHeaderPadding = padding - } - header.setWithJSON(dictionary.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) + headlineBodyButton.setWithJSON(dictionary.optionalDictionaryForKey("headlineBodyButton"), delegateObject: delegateObject, additionalData: additionalData) imageLoader.setWithJSON(dictionary.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) - - if let buttonDictionary = dictionary.optionalDictionaryForKey("button") { - button.setWithJSON(buttonDictionary, delegateObject: delegateObject, additionalData: additionalData) - } else { - button.isHidden = true - } } } diff --git a/MVMCoreUI/Molecules/HeadlineBodyButton.swift b/MVMCoreUI/Molecules/HeadlineBodyButton.swift new file mode 100644 index 00000000..87efc9b6 --- /dev/null +++ b/MVMCoreUI/Molecules/HeadlineBodyButton.swift @@ -0,0 +1,135 @@ +// +// HeadlineBodyButton.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objcMembers open class HeadlineBodyButton: ViewConstrainingView { + //------------------------------------------------------ + // MARK: - Outlets + //------------------------------------------------------ + + let headlineBody = HeadlineBody(frame: .zero) + let button = PrimaryButton.primaryTinyButton(false)! + + //------------------------------------------------------ + // MARK: - Properties + //------------------------------------------------------ + + var buttonHeadlinePadding: CGFloat = 16 + + //------------------------------------------------------ + // MARK: - Constraints + //------------------------------------------------------ + + var buttonTopConstraint: NSLayoutConstraint? + + //------------------------------------------------------ + // MARK: - Initialization + //------------------------------------------------------ + + public init() { + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + public convenience init(json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.init() + setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + //------------------------------------------------------ + // MARK: - View Lifecycle + //------------------------------------------------------ + + override open func setupView() { + super.setupView() + + guard subviews.isEmpty else { return } + + defaultState() + + addSubview(headlineBody) + addSubview(button) + + headlineBody.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + headlineBody.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(equalTo: headlineBody.trailingAnchor).isActive = true + + buttonTopConstraint = button.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: buttonHeadlinePadding) + buttonTopConstraint?.isActive = true + + button.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(greaterThanOrEqualTo: button.trailingAnchor).isActive = true + } + + override open func updateView(_ size: CGFloat) { + super.updateView(size) + + headlineBody.updateView(size) + button.updateView(size) + + buttonTopConstraint?.constant = headlineBody.hasText() && !button.isHidden ? buttonHeadlinePadding : 0 + } + + private func defaultState() { + + headlineBody.headlineLabel.font = MFStyler.fontH3() + headlineBody.messageLabel.font = MFStyler.fontB3() + button.setAsTiny(true) + button.setAsSecondaryCustom() + button.isHidden = false + buttonHeadlinePadding = PaddingTwo + } + + //------------------------------------------------------ + // MARK: - Molecule + //------------------------------------------------------ + + override open func reset() { + super.reset() + + headlineBody.reset() + button.reset() + defaultState() + } + + open override func setAsMolecule() { + super.setAsMolecule() + + headlineBody.setAsMolecule() + button.setAsMolecule() + defaultState() + } + + 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 } + + if let padding = dictionary.optionalCGFloatForKey("buttonHeadlinePadding") { + buttonHeadlinePadding = padding + } + + headlineBody.setWithJSON(dictionary.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) + + if let buttonDictionary = dictionary.optionalDictionaryForKey("button") { + button.setWithJSON(buttonDictionary, delegateObject: delegateObject, additionalData: additionalData) + } else { + button.isHidden = true + } + } +} diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift index aca450aa..6e10a1a0 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift @@ -82,7 +82,7 @@ import UIKit imageView.reset() } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 95 } } diff --git a/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift index 42a5e5cd..75249f7d 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift @@ -37,11 +37,9 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi } isAccessibilityElement = false contentView.isAccessibilityElement = false - if #available(iOS 11.0, *) { - insetsLayoutMarginsFromSafeArea = false - contentView.insetsLayoutMarginsFromSafeArea = false - contentView.preservesSuperviewLayoutMargins = false - } + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false // Covers the card when peaking. peakingCover.backgroundColor = .white @@ -117,7 +115,7 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi backgroundColor = .white } - public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { + public class func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else { return nil } diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 641b3721..4769b7e5 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -8,195 +8,24 @@ import UIKit -@objcMembers open class MoleculeTableViewCell: UITableViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol { - - open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? - open var json: [AnyHashable: Any]? - - // In updateView, will set padding to default. - open var updateViewHorizontalDefaults = true - - // For the accessory view convenience. - public var caretView: CaretView? - private var caretViewWidthSizeObject: MFSizeObject? - private var caretViewHeightSizeObject: MFSizeObject? - - // For separation between cells. - public var topSeparatorView: SeparatorView? - public var bottomSeparatorView: SeparatorView? - public enum SeparatorFrequency: String { - case All = "all" - case AllExceptTop = "allExceptTop" - case AllExceptBottom = "allExceptBottom" - case Between = "between" - } - - /// For subclasses that want to use a custom accessory view. - open var customAccessoryView = false - - public var topMarginPadding: CGFloat = 24 - public var bottomMarginPadding: CGFloat = 24 - - // MARK: - Styling - func style(with styleString: String?) { - guard let styleString = styleString else { - return - } - switch styleString { - case "standard": - styleStandard() - case "header": - styleHeader() - case "none": - styleNone() - default: break - } - } - - func styleStandard() { - topMarginPadding = 24 - bottomMarginPadding = 24 - bottomSeparatorView?.show() - bottomSeparatorView?.setAsLight() - } - - func styleHeader() { - topMarginPadding = 48 - bottomMarginPadding = 16 - bottomSeparatorView?.show() - bottomSeparatorView?.setAsRegular() - } - - func styleNone() { - topMarginPadding = 0 - bottomMarginPadding = 0 - bottomSeparatorView?.hide() - } - - // MARK: - Inits - public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupView() - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupView() - } - - // MARK: - MFViewProtocol - public func updateView(_ size: CGFloat) { - MFStyler.setMarginsFor(self, size: size, defaultHorizontal: updateViewHorizontalDefaults, top: topMarginPadding, bottom: bottomMarginPadding) - if #available(iOS 11.0, *) { - if accessoryView != nil { - // Smaller left margin if accessory view. - var margin = directionalLayoutMargins - margin.trailing = 16 - contentView.directionalLayoutMargins = margin - } else { - contentView.directionalLayoutMargins = directionalLayoutMargins - } - topSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) - bottomSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) - } else { - if accessoryView != nil { - // Smaller left margin if accessory view. - var margin = layoutMargins - margin.right = 16 - contentView.layoutMargins = margin - } else { - contentView.layoutMargins = layoutMargins - } - topSeparatorView?.setLeftAndRightPinConstant(layoutMargins.left) - bottomSeparatorView?.setLeftAndRightPinConstant(layoutMargins.left) - } - - molecule?.updateView(size) - if let _ = accessoryView, let caretView = caretView, let widthObject = caretViewWidthSizeObject, let heightObject = caretViewHeightSizeObject { - caretView.frame = CGRect(x: 0, y: 0, width: widthObject.getValueBased(onSize: size), height: heightObject.getValueBased(onSize: size)) - } - topSeparatorView?.updateView(size) - bottomSeparatorView?.updateView(size) - } - - public func setupView() { - selectionStyle = .none - if #available(iOS 11.0, *) { - insetsLayoutMarginsFromSafeArea = false - contentView.insetsLayoutMarginsFromSafeArea = false - contentView.preservesSuperviewLayoutMargins = false - } - } +@objcMembers open class MoleculeTableViewCell: TableViewCell { // MARK: - MVMCoreUIMoleculeViewProtocol - public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - self.json = json; + public override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - style(with: json?.optionalStringForKey("style")) - - if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { - updateViewHorizontalDefaults = useHorizontalMargins - } - if (json?.optionalBoolForKey("useVerticalMargins") ?? true) == false { - topMarginPadding = 0 - bottomMarginPadding = 0 - } - - if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { - backgroundColor = UIColor.mfGet(forHex: backgroundColorString) - } - - // Add the caret if there is an action and it's not declared hidden. - if !customAccessoryView { - if let _ = json?.optionalDictionaryForKey("actionMap"), !json!.boolForKey("hideArrow") { - addCaretViewAccessory() - } else { - accessoryView = nil - } - } - - // override the separator - if let separator = json?.optionalDictionaryForKey("separator") { - addSeparatorsIfNeeded() - bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData) - } - - guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { - return - } - if molecule == nil { - if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { - contentView.addSubview(moleculeView) - let standardConstraints = (moleculeView as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true - NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: standardConstraints).values)) - molecule = moleculeView - } - } else { - molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) - } - - // This molecule will by default handle margins. - if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { - castView.shouldSetHorizontalMargins?(false) - castView.shouldSetVerticalMargins?(false) - } + guard molecule == nil, let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule), let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) else { return } + addMolecule(moleculeView) } - public func reset() { - molecule?.reset?() - updateViewHorizontalDefaults = true - styleStandard() - backgroundColor = .white - } - - public static func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) else { return 80 } return max(2 * PaddingDefaultVerticalSpacing3, height) } - public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { + public override class func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else { return "\(self)<>" } @@ -204,89 +33,11 @@ import UIKit return "\(self)<\(moleculeName)>" } - public static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + public class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let theClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON) else { return nil } return theClass.requiredModules?(moleculeJSON, delegateObject: delegateObject, error: error) } - - // MARK: - Arrow - /// Adds the standard mvm style caret to the accessory view - 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) - accessoryView = caretView - } - - // MARK: - MoleculeListCellProtocol - /// For when the separator between cells shows using json and frequency. Default is type: standard, frequency: allExceptTop. - public func setSeparatorWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, indexPath: IndexPath) { - addSeparatorsIfNeeded() - if let json = json { - topSeparatorView?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - bottomSeparatorView?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - if let separatorFrequencyString = json.optionalStringForKey("frequency"), let separatorFrequency = SeparatorFrequency(rawValue: separatorFrequencyString) { - setSeparatorFrequency(separatorFrequency, indexPath: indexPath) - } - } else { - topSeparatorView?.hide() - bottomSeparatorView?.setAsLight() - setSeparatorFrequency(MoleculeTableViewCell.SeparatorFrequency.AllExceptTop, indexPath: indexPath) - } - } - - public func didSelectCell(atIndex indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - if let actionMap = json?.optionalDictionaryForKey("actionMap") { - MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) - } - } - - // MARK: - Separator - func addSeparatorsIfNeeded() { - if topSeparatorView == nil { - topSeparatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionTop) - topSeparatorView?.hide() - } - if bottomSeparatorView == nil { - bottomSeparatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot) - bottomSeparatorView?.hide() - } - } - - /// For when the separator between cells shows. - public func setSeparatorFrequency(_ separatorFrequency: SeparatorFrequency, indexPath: IndexPath) { - switch separatorFrequency { - case .All: - if indexPath.row == 0 { - topSeparatorView?.show() - } else { - topSeparatorView?.hide() - } - bottomSeparatorView?.show() - case .AllExceptBottom: - topSeparatorView?.show() - bottomSeparatorView?.hide() - case .Between: - if indexPath.row == 0 { - topSeparatorView?.hide() - } else { - topSeparatorView?.show() - } - bottomSeparatorView?.hide() - case .AllExceptTop: - fallthrough - default: - topSeparatorView?.hide() - bottomSeparatorView?.show() - } - } } diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift new file mode 100644 index 00000000..cc531d75 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -0,0 +1,314 @@ +// +// TableViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/29/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class TableViewCell: UITableViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol { + open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? + open var json: [AnyHashable: Any]? + + // In updateView, will set padding to default. + open var updateViewHorizontalDefaults = true + + // For the accessory view convenience. + private var caretView: CaretView? + private var caretViewWidthSizeObject: MFSizeObject? + private var caretViewHeightSizeObject: MFSizeObject? + + // For separation between cells. + public var topSeparatorView: SeparatorView? + public var bottomSeparatorView: SeparatorView? + public enum SeparatorFrequency: String { + case all + case allExceptTop + case allExceptBottom + case between + } + + /// For subclasses that want to use a custom accessory view. + open var customAccessoryView = false + + open var topMarginPadding: CGFloat = 24 + open var bottomMarginPadding: CGFloat = 24 + + private var heroAccessoryCenter: CGPoint? + + // MARK: - Styling + open func style(with styleString: String?) { + guard let styleString = styleString else { + return + } + switch styleString { + case "standard": + styleStandard() + case "header": + styleHeader() + case "none": + styleNone() + default: break + } + } + + open func styleStandard() { + topMarginPadding = 24 + bottomMarginPadding = 24 + bottomSeparatorView?.show() + bottomSeparatorView?.setAsLight() + } + + open func styleHeader() { + topMarginPadding = 48 + bottomMarginPadding = 16 + bottomSeparatorView?.show() + bottomSeparatorView?.setAsRegular() + } + + open func styleNone() { + topMarginPadding = 0 + bottomMarginPadding = 0 + bottomSeparatorView?.hide() + } + + /// Adds the molecule to the view. + open func addMolecule(_ molecule: UIView & MVMCoreUIMoleculeViewProtocol) { + contentView.addSubview(molecule) + let standardConstraints = (molecule as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: molecule, useMargins: standardConstraints).values)) + + // This molecule will by default handle margins. + if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { + castView.shouldSetHorizontalMargins?(false) + castView.shouldSetVerticalMargins?(false) + } + self.molecule = molecule + } + + open override func layoutSubviews() { + super.layoutSubviews() + + // Ensures accessory view aligns to the center y derived from the + if let center = heroAccessoryCenter { + accessoryView?.center.y = center.y + } + } + + // MARK: - Inits + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupView() + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setupView() + } + + // MARK: - MFViewProtocol + public func updateView(_ size: CGFloat) { + MFStyler.setMarginsFor(self, size: size, defaultHorizontal: updateViewHorizontalDefaults, top: topMarginPadding, bottom: bottomMarginPadding) + if accessoryView != nil { + // Smaller left margin if accessory view. + var margin = directionalLayoutMargins + margin.trailing = 16 + contentView.directionalLayoutMargins = margin + } else { + contentView.directionalLayoutMargins = directionalLayoutMargins + } + topSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) + bottomSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) + + molecule?.updateView(size) + if let _ = accessoryView, let caretView = caretView, let widthObject = caretViewWidthSizeObject, let heightObject = caretViewHeightSizeObject { + caretView.frame = CGRect(x: 0, y: 0, width: widthObject.getValueBased(onSize: size), height: heightObject.getValueBased(onSize: size)) + } + topSeparatorView?.updateView(size) + bottomSeparatorView?.updateView(size) + } + + public func setupView() { + selectionStyle = .none + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.json = json + + guard let json = json else { return } + + style(with: json.optionalStringForKey("style")) + + if let useHorizontalMargins = json.optionalBoolForKey("useHorizontalMargins") { + updateViewHorizontalDefaults = useHorizontalMargins + } + + if (json.optionalBoolForKey("useVerticalMargins") ?? true) == false { + topMarginPadding = 0 + bottomMarginPadding = 0 + } + + if let backgroundColorString = json.optionalStringForKey(KeyBackgroundColor) { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + + // Add the caret if there is an action and it's not declared hidden. + if !customAccessoryView { + if let _ = json.optionalDictionaryForKey("actionMap"), !json.boolForKey("hideArrow") { + addCaretViewAccessory() + } else { + accessoryView = nil + } + } + + // override the separator + if let separator = json.optionalDictionaryForKey("separator") { + addSeparatorsIfNeeded() + bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData) + } + + guard let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { return } + molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) + + // This molecule will by default handle margins. + if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { + castView.shouldSetHorizontalMargins?(false) + castView.shouldSetVerticalMargins?(false) + } + } + + public func reset() { + molecule?.reset?() + updateViewHorizontalDefaults = true + styleStandard() + backgroundColor = .white + } + + public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) else { + return 80 + } + return max(2 * PaddingDefaultVerticalSpacing3, height) + } + + public class func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { + return molecule?.optionalStringForKey(KeyMoleculeName) ?? "" + } + + // MARK: - Arrow + /// 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) + accessoryView = caretView + } + + /// NOTE: Should only be called when displayed or about to be displayed. + public func alignAccessoryToHero() { + + // Layout call required to force draw in memory to get dimensions of subviews. + layoutIfNeeded() + guard let heroLabel = findHeroLabel(views: contentView.subviews), let hero = heroLabel.hero else { return } + let rect = Label.boundingRect(forCharacterRange: NSRange(location: hero, length: 1), in: heroLabel) + accessoryView?.center.y = contentView.convert(UIView(frame: rect).center, from: heroLabel).y + heroAccessoryCenter = accessoryView?.center + } + + /// Traverses the view hierarchy for a 🦸‍♂️heroic Label. + private func findHeroLabel(views: [UIView]) -> Label? { + + if views.isEmpty { + return nil + } + + var queue = [UIView]() + + for view in views { + // Only one Label will have a hero in a table cell. + if let label = view as? Label, label.hero != nil { + return label + } + queue.append(contentsOf: view.subviews) + } + + return findHeroLabel(views: queue) + } + + // MARK: - MoleculeListCellProtocol + /// For when the separator between cells shows using json and frequency. Default is type: standard, frequency: allExceptTop. + public func setSeparatorWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, indexPath: IndexPath) { + addSeparatorsIfNeeded() + if let json = json { + topSeparatorView?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + bottomSeparatorView?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + if let separatorFrequencyString = json.optionalStringForKey("frequency"), let separatorFrequency = SeparatorFrequency(rawValue: separatorFrequencyString) { + setSeparatorFrequency(separatorFrequency, indexPath: indexPath) + } + } else { + topSeparatorView?.hide() + bottomSeparatorView?.setAsLight() + setSeparatorFrequency(TableViewCell.SeparatorFrequency.allExceptTop, indexPath: indexPath) + } + } + + public func didSelectCell(atIndex indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + if let actionMap = json?.optionalDictionaryForKey("actionMap") { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + } + + public func willDisplay() { + alignAccessoryToHero() + } + + // MARK: - Separator + open func addSeparatorsIfNeeded() { + if topSeparatorView == nil { + topSeparatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionTop) + topSeparatorView?.hide() + } + if bottomSeparatorView == nil { + bottomSeparatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot) + bottomSeparatorView?.hide() + } + } + + /// For when the separator between cells shows. + public func setSeparatorFrequency(_ separatorFrequency: SeparatorFrequency, indexPath: IndexPath) { + switch separatorFrequency { + case .all: + if indexPath.row == 0 { + topSeparatorView?.show() + } else { + topSeparatorView?.hide() + } + bottomSeparatorView?.show() + case .allExceptBottom: + topSeparatorView?.show() + bottomSeparatorView?.hide() + case .between: + if indexPath.row == 0 { + topSeparatorView?.hide() + } else { + topSeparatorView?.show() + } + bottomSeparatorView?.hide() + case .allExceptTop: + fallthrough + default: + topSeparatorView?.hide() + bottomSeparatorView?.show() + } + } +} diff --git a/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift index 1c509a8f..1a1796cd 100644 --- a/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift @@ -8,7 +8,7 @@ import UIKit -@objcMembers public class TabsTableViewCell: MoleculeTableViewCell { +@objcMembers public class TabsTableViewCell: TableViewCell { let tabs = TopTabbar(frame: .zero) var delegateObject: MVMCoreUIDelegateObject? var previousTabIndex = 0 diff --git a/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift b/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift index 06917608..9b7dc062 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift @@ -174,7 +174,7 @@ import UIKit bottomRightLabel.styleB3(true) } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 34 } } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift index d373ce87..79110460 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift @@ -8,9 +8,9 @@ import UIKit -@objcMembers public class HeadlineBodySwitch: ViewConstrainingView { - let headlineBody = HeadlineBody(frame: .zero) - let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() +@objcMembers open class HeadlineBodySwitch: ViewConstrainingView { + public let headlineBody = HeadlineBody(frame: .zero) + public let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { @@ -19,7 +19,7 @@ import UIKit mvmSwitch.updateView(size) } - public override func setupView() { + open override func setupView() { super.setupView() guard mvmSwitch.superview == nil else { return @@ -35,24 +35,24 @@ import UIKit } // MARK: - MVMCoreUIMoleculeViewProtocol - public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) mvmSwitch.setWithJSON(json?.optionalDictionaryForKey("switch"), delegateObject: delegateObject, additionalData: additionalData) } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + open override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 30 } - public override func setAsMolecule() { + open override func setAsMolecule() { super.setAsMolecule() headlineBody.setAsMolecule() (mvmSwitch as MVMCoreUIMoleculeViewProtocol).setAsMolecule?() headlineBody.styleListItem() } - public override func reset() { + open override func reset() { super.reset() headlineBody.reset() (mvmSwitch as MVMCoreUIMoleculeViewProtocol).reset?() diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift index 0f184544..b0cf6956 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift @@ -41,7 +41,7 @@ import UIKit mvmSwitch.setWithJSON(json?.optionalDictionaryForKey("switch"), delegateObject: delegateObject, additionalData: additionalData) } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return HeadlineBodyTextButton.estimatedHeight(forRow: json, delegateObject: delegateObject) } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift index d47cf44c..f60202b3 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift @@ -41,7 +41,7 @@ import UIKit mvmSwitch.setWithJSON(json?.optionalDictionaryForKey("switch"), delegateObject: delegateObject, additionalData: additionalData) } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return MVMCoreUISwitch.estimatedHeight(forRow: json, delegateObject: delegateObject) } diff --git a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h index af39df41..e70c750a 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h +++ b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h @@ -31,7 +31,6 @@ /// Resets to default state before set with json is called again. - (void)reset; - /// For the molecule list to load more efficiently. + (CGFloat)estimatedHeightForRow:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject; diff --git a/MVMCoreUI/Molecules/ModuleMolecule.swift b/MVMCoreUI/Molecules/ModuleMolecule.swift index c993b0c2..77acc007 100644 --- a/MVMCoreUI/Molecules/ModuleMolecule.swift +++ b/MVMCoreUI/Molecules/ModuleMolecule.swift @@ -54,7 +54,7 @@ open class ModuleMolecule: ViewConstrainingView { moduleMolecule?.reset?() } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { guard let moduleName = json?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { // Critical error return 0 @@ -62,7 +62,7 @@ open class ModuleMolecule: ViewConstrainingView { return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: module)?.estimatedHeight?(forRow: module, delegateObject: delegateObject) ?? 0 } - public override static func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { + public override class func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { guard let moduleName = molecule?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { // Critical error return "moduleMolecule<>" @@ -70,7 +70,7 @@ open class ModuleMolecule: ViewConstrainingView { return "moduleMolecule<" + (MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: module)?.name?(forReuse: module, delegateObject: delegateObject) ?? module.stringForkey(KeyMoleculeName)) + ">" } - public override static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + public override class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { let moduleName = json?.optionalStringForKey("moduleName") if moduleName == nil || delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) == nil { if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: CoreUIErrorCode.ErrorCodeModuleMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) { diff --git a/MVMCoreUI/Molecules/RadioButtonLabel.swift b/MVMCoreUI/Molecules/RadioButtonLabel.swift new file mode 100644 index 00000000..e889b4f2 --- /dev/null +++ b/MVMCoreUI/Molecules/RadioButtonLabel.swift @@ -0,0 +1,90 @@ +// +// RadioButtonWithLabel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class RadioButtonLabel: ViewConstrainingView { + + public let radioButton = RadioButton() + var delegateObject: MVMCoreUIDelegateObject? + let label = Label() + + // MARK: - Inits + public init() { + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + radioButton.updateView(size) + label.updateView(size) + } + + open override func setupView() { + super.setupView() + guard subviews.count == 0 else { + return + } + + addSubview(radioButton) + radioButton.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor, constant: 0).isActive = true + radioButton.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor, constant: PaddingOne).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: radioButton.bottomAnchor, constant: PaddingOne).isActive = true + radioButton.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor).isActive = true + + if let rightView = createRightView() { + addSubview(rightView) + rightView.leftAnchor.constraint(equalTo: radioButton.rightAnchor, constant: PaddingHorizontalBetweenRelatedItems).isActive = true + rightView.rightAnchor.constraint(equalTo: layoutMarginsGuide.rightAnchor, constant: 0).isActive = true + + var constraint = rightView.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor, constant: PaddingOne) + constraint.priority = .defaultHigh + constraint.isActive = true + + constraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: rightView.bottomAnchor, constant: PaddingOne) + constraint.priority = .defaultHigh + constraint.isActive = true + layoutMarginsGuide.centerYAnchor.constraint(equalTo: rightView.centerYAnchor).isActive = true + } + } + + func createRightView() -> ViewConstrainingView? { + let rightView = ViewConstrainingView(constrainingView: label) + return rightView + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension RadioButtonLabel { + @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + radioButton.setWithJSON(json?.optionalDictionaryForKey("radioButton"), delegateObject: delegateObject, additionalData: additionalData) + label.setWithJSON(json?.optionalDictionaryForKey(KeyLabel), + delegateObject: delegateObject, + additionalData: additionalData) + } + + public override func reset() { + super.reset() + radioButton.reset() + label.reset() + } + + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 45 + } +} diff --git a/MVMCoreUI/Molecules/RadioButtonModel.swift b/MVMCoreUI/Molecules/RadioButtonModel.swift new file mode 100644 index 00000000..bb70dec7 --- /dev/null +++ b/MVMCoreUI/Molecules/RadioButtonModel.swift @@ -0,0 +1,54 @@ +// +// RadioButtonModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 5/14/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation +import UIKit + +@objcMembers public class RadioButtonModel: NSObject { + + private var selectedRadioButton: RadioButton? + private var fieldGroupName: String? + + public static func setupForRadioButtonGroup(radioButton: RadioButton, formValidator: FormValidator?) -> RadioButtonModel { + guard let groupName = radioButton.radioGroupName, + let formValidator = formValidator else { + return RadioButtonModel() + } + + let radioButtonModel = formValidator.radioButtonsModelByGroup[groupName] ?? RadioButtonModel() + radioButtonModel.fieldGroupName = radioButton.formFieldGroupName() + formValidator.radioButtonsModelByGroup[groupName] = radioButtonModel + return radioButtonModel + } + + public func selected(_ radioButton: RadioButton) { + selectedRadioButton?.isSelected = false + selectedRadioButton = radioButton + selectedRadioButton?.isSelected = true + } +} + +// MARK: - FormValidationFormFieldProtocol +extension RadioButtonModel: FormValidationFormFieldProtocol { + public func formFieldGroupName() -> String? { + return selectedRadioButton?.formFieldGroupName() ?? self.fieldGroupName + } + + // Used to check the validity of the field, to enable/disable the primary button. + @objc public func isValidField() -> Bool { + return selectedRadioButton != nil ? true : false + } + // Name of the field to send to server + @objc public func formFieldName() -> String? { + return selectedRadioButton?.fieldKey + } + // The field value key value pair for sending to server + @objc public func formFieldValue() -> Any? { + return selectedRadioButton != nil ? true : false + } +} diff --git a/MVMCoreUI/Molecules/StandardFooterView.swift b/MVMCoreUI/Molecules/StandardFooterView.swift index 7734883e..7c9c9282 100644 --- a/MVMCoreUI/Molecules/StandardFooterView.swift +++ b/MVMCoreUI/Molecules/StandardFooterView.swift @@ -26,7 +26,7 @@ open class StandardFooterView: ViewConstrainingView { (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false) } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { if let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) { return height + PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing } diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/StandardHeaderView.swift index 4af65f2c..91c3cc99 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/StandardHeaderView.swift @@ -80,7 +80,7 @@ public class StandardHeaderView: ViewConstrainingView, ModelMoleculeViewProtocol separatorView?.show() } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { if let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) { return height + PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift index 306cf690..a7d96a34 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -64,7 +64,7 @@ import UIKit body.styleB2(true) } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 65 } } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift index e63eb2ba..d236fc37 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -136,7 +136,7 @@ open class HeadlineBody: ViewConstrainingView { stylePageHeader() } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 58 } } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift index 009189cb..7c24cff4 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift @@ -76,7 +76,7 @@ import UIKit textButton.reset() } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 60 } } diff --git a/MVMCoreUI/Organisms/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift index 14ad9e70..cafe21b0 100644 --- a/MVMCoreUI/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -184,13 +184,13 @@ public class MoleculeStackView: ViewConstrainingView { } } - public override static func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { + public override class func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { // This will aggregate names of molecules to make an id. guard let molecules = molecule?.optionalArrayForKey(KeyMolecules) else { return "stack<>" } var name = "stack<" - for case let item as [AnyHashable: AnyHashable] in molecules { + for case let item as [AnyHashable: Any] in molecules { if let molecule = item.optionalDictionaryForKey(KeyMolecule), let moleculeName = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) { name.append(moleculeName + ",") } @@ -199,7 +199,7 @@ public class MoleculeStackView: ViewConstrainingView { return name } - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { guard let items = json?.optionalArrayForKey(KeyMolecules) else { return 0 } @@ -221,7 +221,7 @@ public class MoleculeStackView: ViewConstrainingView { return estimatedHeight } - public override static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + public override class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { guard let items = json?.optionalArrayForKey(KeyMolecules) else { return nil } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 614251a9..7fcea605 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -28,7 +28,7 @@ dispatch_once(&onceToken, ^{ mapping = [@{ @"label": Label.class, - @"separator": SeparatorView.class, + @"line": SeparatorView.class, @"button": ButtonView.class, @"textButton": MFTextButton.class, @"header": StandardHeaderView.class, @@ -39,11 +39,14 @@ @"caretButton": CaretButton.class, @"textField" : MFTextField.class, @"digitTextField" : MFDigitTextField.class, - @"checkbox" : MVMCoreUICheckBox.class, + @"checkbox" : Checkbox.class, + @"checkboxWithLabelView" : CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, - @"progressBar": ProgressBar.class, + @"progressbar": ProgressBar.class, @"multiProgressBar": MultiProgress.class, @"checkbox": MVMCoreUICheckBox.class, + @"radioButton": RadioButton.class, + @"radioButtonLabel": RadioButtonLabel.class, @"listItem": MoleculeTableViewCell.class, @"accordionListItem": AccordionMoleculeTableViewCell.class, @"switch": MVMCoreUISwitch.class, @@ -62,6 +65,7 @@ @"headlineBodyTextButton": HeadlineBodyTextButton.class, @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class, @"tabsListItem": TabsTableViewCell.class, + @"headlineBodyButton": HeadlineBodyButton.class, @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class } mutableCopy]; }); diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUISession.h b/MVMCoreUI/OtherHandlers/MVMCoreUISession.h index a5ce493c..8adb32cd 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUISession.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUISession.h @@ -22,6 +22,9 @@ NS_ASSUME_NONNULL_BEGIN @property (weak, nonatomic, nullable) MVMCoreUINavigationController *navigationController; @property (weak, nonatomic, nullable) MFLoadingViewController *loadingViewController; +/// Tracks the current page type the user is currently viewing. KVO compliant. +@property (nonatomic, strong, nullable) NSString *currentPageType; + // for handscroll Animation on subclasses of MFScrollingViewController @property (assign, nonatomic) BOOL enableHandScrollAnimation; diff --git a/MVMCoreUI/Styles/MFStyler.h b/MVMCoreUI/Styles/MFStyler.h index 267a0377..5b73c8e2 100644 --- a/MVMCoreUI/Styles/MFStyler.h +++ b/MVMCoreUI/Styles/MFStyler.h @@ -36,6 +36,7 @@ extern CGFloat const PaddingHorizontalLarge; extern CGFloat const PaddingVerticalWhiteGrayView; extern CGFloat const PaddingVerticalHeadlineAlternate; extern CGFloat const PaddingPrimaryButtonTop; +extern CGFloat const PaddingHorizontalBetweenRelatedItems; // These are based on the multiple of 6 rule extern CGFloat const PaddingOne; diff --git a/MVMCoreUI/Styles/MFStyler.m b/MVMCoreUI/Styles/MFStyler.m index abe6c960..18246f7f 100644 --- a/MVMCoreUI/Styles/MFStyler.m +++ b/MVMCoreUI/Styles/MFStyler.m @@ -26,6 +26,7 @@ CGFloat const PaddingVerticalWhiteGrayView = 72; CGFloat const PaddingVerticalHeadlineAlternate = 48; CGFloat const PaddingPrimaryButtonTop = 36; +CGFloat const PaddingHorizontalBetweenRelatedItems = 16; CGFloat const PaddingOne = 6; CGFloat const PaddingTwo = 12; CGFloat const PaddingThree = 18; diff --git a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings index 4812d697..b9d21b67 100644 --- a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings @@ -36,6 +36,13 @@ "checkbox_checked_state" = "Checked"; "checkbox_unchecked_state" = "Unchecked"; "checkbox_desc_state" = "%@ CheckBox %@"; + +// Radio Button +"radio_action_hint" = "Double tap to select"; +"radio_selected_state" = "Selected"; +"radio_not_selected_state" = "Not Selected"; +"radio_desc_state" = "Option"; + // Switch "mfswitch_buttonlabel" = "Switch Button"; "AccOn" = "on"; diff --git a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings index d6505503..93c5dda5 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings @@ -37,6 +37,11 @@ "checkbox_checked_state" = "Verificado"; "checkbox_unchecked_state" = "Sin marcar"; "checkbox_desc_state" = "%@ Casilla %@"; +// Radio Button +"radio_action_hint" = "Toca dos veces para seleccionar."; +"radio_selected_state" = "Seleccionado"; +"radio_not_selected_state" = "No Seleccionado"; +"radio_desc_state" = "Opción"; // Switch "mfswitch_buttonlabel" = "Botón Cambiar"; "AccOn" = "encendido"; diff --git a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings index d6505503..053c4b02 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings @@ -37,6 +37,13 @@ "checkbox_checked_state" = "Verificado"; "checkbox_unchecked_state" = "Sin marcar"; "checkbox_desc_state" = "%@ Casilla %@"; + +// Radio Button +"radio_action_hint" = "Toca dos veces para seleccionar."; +"radio_selected_state" = "Seleccionado"; +"radio_not_selected_state" = "No Seleccionado"; +"radio_desc_state" = "Opción"; + // Switch "mfswitch_buttonlabel" = "Botón Cambiar"; "AccOn" = "encendido"; diff --git a/MVMCoreUI/Templates/MoleculeListCellProtocol.h b/MVMCoreUI/Templates/MoleculeListCellProtocol.h index 7603c820..3fa19a02 100644 --- a/MVMCoreUI/Templates/MoleculeListCellProtocol.h +++ b/MVMCoreUI/Templates/MoleculeListCellProtocol.h @@ -17,4 +17,6 @@ /// Handle action - (void)didSelectCellAtIndex:(nonnull NSIndexPath *)indexPath delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject additionalData:(nullable NSDictionary *)additionalData; +- (void)willDisplay; + @end diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 3b1c31ef..8c97b427 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -55,9 +55,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { // MARK: - table open override func registerWithTable() { super.registerWithTable() - guard let moleculesInfo = moleculesInfo else { - return - } + guard let moleculesInfo = moleculesInfo else { return } + for moleculeInfo in moleculesInfo { tableView?.register(moleculeInfo.class, forCellReuseIdentifier: moleculeInfo.identifier) } @@ -92,6 +91,13 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { return cell } + open override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + + if let protocolCell = cell as? MoleculeListCellProtocol { + protocolCell.willDisplay?() + } + } + open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let cell = tableView.cellForRow(at: indexPath) as? MoleculeListCellProtocol { cell.didSelectCell?(atIndex: indexPath, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil) @@ -124,9 +130,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { open override func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. DispatchQueue.main.async { - guard let cell = sender as? MoleculeTableViewCell, let indexPath = self.tableView?.indexPath(for: cell) else { - return - } + guard let indexPath = self.tableView?.indexPath(for: sender) else { return } var indexPaths: [IndexPath] = [] for molecule in molecules { if let info = self.getMoleculeInfo(with: molecule) { diff --git a/MVMCoreUI/Utility/CATransaction+Extension.swift b/MVMCoreUI/Utility/CATransaction+Extension.swift new file mode 100644 index 00000000..66bb33d3 --- /dev/null +++ b/MVMCoreUI/Utility/CATransaction+Extension.swift @@ -0,0 +1,21 @@ +// +// CATransaction+Extension.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/2/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + + +extension CATransaction { + + /// Performs changes without activating animation actions. + static func withDisabledAnimations(_ actionBlock: ActionBlock) { + CATransaction.begin() + CATransaction.setDisableActions(true) + actionBlock() + CATransaction.commit() + } +} diff --git a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m index 94c6ca85..4a21b1a5 100644 --- a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m @@ -33,6 +33,7 @@ static const CGFloat VertialShadowOffset = 6; UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; view.translatesAutoresizingMaskIntoConstraints = NO; view.backgroundColor = [UIColor clearColor]; + view.directionalLayoutMargins = NSDirectionalEdgeInsetsZero; return view; } @@ -198,13 +199,8 @@ static const CGFloat VertialShadowOffset = 6; [view.rightAnchor constraintEqualToAnchor:button.rightAnchor constant:PaddingTwo].active = YES; [view.centerYAnchor constraintEqualToAnchor:button.centerYAnchor].active = YES; } else { - if (@available(iOS 11.0, *)) { - [button.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.topAnchor constant:PaddingOne].active = YES; - [view.safeAreaLayoutGuide.trailingAnchor constraintEqualToAnchor:button.trailingAnchor constant:PaddingTwo].active = YES; - } else { - [NSLayoutConstraint constraintPinSubview:button pinTop:YES topConstant:PaddingOne pinBottom:NO bottomConstant:0 pinLeft:NO leftConstant:0 pinRight:YES rightConstant:PaddingTwo]; - } - + [button.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.topAnchor constant:PaddingOne].active = YES; + [view.safeAreaLayoutGuide.trailingAnchor constraintEqualToAnchor:button.trailingAnchor constant:PaddingTwo].active = YES; } } return button; @@ -249,16 +245,12 @@ static const CGFloat VertialShadowOffset = 6; } + (nullable UIView *)getAndSetupSafeAreaViewOnView:(nonnull UIView *)view { - if (@available(iOS 11.0, *)) { - UIView *safeAreaView = [MVMCoreUICommonViewsUtility commonView]; - [view addSubview:safeAreaView]; - [safeAreaView.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.bottomAnchor].active = YES; - [view.bottomAnchor constraintEqualToAnchor:safeAreaView.bottomAnchor].active = YES; - [NSLayoutConstraint constraintPinSubview:safeAreaView pinTop:NO topConstant:0 pinBottom:NO bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0]; - return safeAreaView; - } else { - return nil; - } + UIView *safeAreaView = [MVMCoreUICommonViewsUtility commonView]; + [view addSubview:safeAreaView]; + [safeAreaView.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.bottomAnchor].active = YES; + [view.bottomAnchor constraintEqualToAnchor:safeAreaView.bottomAnchor].active = YES; + [NSLayoutConstraint constraintPinSubview:safeAreaView pinTop:NO topConstant:0 pinBottom:NO bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0]; + return safeAreaView; } #pragma mark - shadows diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index 4f150783..48a7a6a1 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -58,21 +58,13 @@ } + (UIEdgeInsets)getMarginsForView:(nullable UIView *)view { - if (@available(iOS 11.0, *)) { - return UIEdgeInsetsMake(view.directionalLayoutMargins.top, view.directionalLayoutMargins.leading, view.directionalLayoutMargins.bottom, view.directionalLayoutMargins.trailing); - } else { - return view.layoutMargins; - } + return UIEdgeInsetsMake(view.directionalLayoutMargins.top, view.directionalLayoutMargins.leading, view.directionalLayoutMargins.bottom, view.directionalLayoutMargins.trailing); } #pragma mark - Setters + (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom { - if (@available(iOS 11.0, *)) { - view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(top, leading, bottom, trailing); - } else { - view.layoutMargins = UIEdgeInsetsMake(top, leading, bottom, trailing); - } + view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(top, leading, bottom, trailing); } #pragma mark - Formatting @@ -152,12 +144,9 @@ CGFloat topInset = scrollview.contentInset.top; CGFloat bottomInset = scrollview.contentInset.bottom; - // Updates for ios 11 - if (@available(iOS 11.0, *)) { - if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { - topInset = scrollview.adjustedContentInset.top; - bottomInset = scrollview.adjustedContentInset.bottom; - } + if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { + topInset = scrollview.adjustedContentInset.top; + bottomInset = scrollview.adjustedContentInset.bottom; } CGFloat remainingSpace = frameHeight - contentSizeHeight - topInset - bottomInset;