diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index d5abeb54..0cd70ea2 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -87,10 +87,14 @@ 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */; }; 0A6682A22434DB4F00AD3CA1 /* ListLeftVariableRadioButtonBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6682A12434DB4F00AD3CA1 /* ListLeftVariableRadioButtonBodyText.swift */; }; 0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6682A32434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift */; }; + 0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6682A92435125F00AD3CA1 /* Styler.swift */; }; + 0A6682AC243531C300AD3CA1 /* Padding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6682AB243531C300AD3CA1 /* Padding.swift */; }; 0A69F611241BDEA700F7231B /* RuleAnyRequiredModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */; }; 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 0A7ECC5D243CE85300C828E8 /* DoughnutChartItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7ECC5C243CE85300C828E8 /* DoughnutChartItemModel.swift */; }; + 0A7ECC5F243CEB1200C828E8 /* ColorViewWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7ECC5E243CEB1200C828E8 /* ColorViewWithLabel.swift */; }; 0A7EF85B23D8A52800B2AAD1 /* EntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */; }; 0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */; }; 0A7EF85F23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */; }; @@ -137,6 +141,8 @@ 8D4687E4242E2DF300802879 /* ListFourColumnDataUsageListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D4687E3242E2DF300802879 /* ListFourColumnDataUsageListItem.swift */; }; 8DD1E36E243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DD1E36D243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift */; }; 8DD1E370243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DD1E36F243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift */; }; + 8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DEFA95B243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift */; }; + 8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DEFA95D243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift */; }; 942C372E241149170066E45E /* NHaasGroteskDSStd-75Bd.otf in Resources */ = {isa = PBXBuildFile; fileRef = 942C372C241149170066E45E /* NHaasGroteskDSStd-75Bd.otf */; }; 942C372F241149170066E45E /* NHaasGroteskDSStd-55Rg.otf in Resources */ = {isa = PBXBuildFile; fileRef = 942C372D241149170066E45E /* NHaasGroteskDSStd-55Rg.otf */; }; 942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */; }; @@ -220,6 +226,10 @@ D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; D20FB165241A5D75004AFC3A /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; + D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */; }; + D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */; }; + D21B7F75243BAC8900051ABF /* CarouselItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F74243BAC8900051ABF /* CarouselItem.swift */; }; + D21B7F77243BB70700051ABF /* MoleculeCollectionItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F76243BB70700051ABF /* MoleculeCollectionItemModel.swift */; }; D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */; }; D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479892314445E003FCCF9 /* LabelToggle.swift */; }; D224798C231450C8003FCCF9 /* HeadlineBodyToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224798B231450C8003FCCF9 /* HeadlineBodyToggle.swift */; }; @@ -252,6 +262,12 @@ D260D7B122D65BDD007E7233 /* MVMCoreUIPageControl.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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, ); }; }; + D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */; }; + D264FA8E243BCD9A00D98315 /* CollectionTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */; }; + D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */; }; + D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */; }; + D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */; }; + D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */; }; D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */; }; @@ -493,11 +509,15 @@ 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleGuidelinesProtocol.swift; sourceTree = ""; }; 0A6682A12434DB4F00AD3CA1 /* ListLeftVariableRadioButtonBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonBodyText.swift; sourceTree = ""; }; 0A6682A32434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonBodyTextModel.swift; sourceTree = ""; }; + 0A6682A92435125F00AD3CA1 /* Styler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Styler.swift; sourceTree = ""; }; + 0A6682AB243531C300AD3CA1 /* Padding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Padding.swift; sourceTree = ""; }; 0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleAnyRequiredModel.swift; sourceTree = ""; }; 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.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 /* CheckboxLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxLabel.swift; sourceTree = ""; }; + 0A7ECC5C243CE85300C828E8 /* DoughnutChartItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoughnutChartItemModel.swift; sourceTree = ""; }; + 0A7ECC5E243CEB1200C828E8 /* ColorViewWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorViewWithLabel.swift; sourceTree = ""; }; 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldModel.swift; sourceTree = ""; }; 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryFieldModel.swift; sourceTree = ""; }; 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryFieldModel.swift; sourceTree = ""; }; @@ -546,6 +566,8 @@ 8D4687E3242E2DF300802879 /* ListFourColumnDataUsageListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListFourColumnDataUsageListItem.swift; sourceTree = ""; }; 8DD1E36D243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnInternationalDataModel.swift; sourceTree = ""; }; 8DD1E36F243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnInternationalData.swift; sourceTree = ""; }; + 8DEFA95B243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageDividerModel.swift; sourceTree = ""; }; + 8DEFA95D243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageDivider.swift; sourceTree = ""; }; 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftRightLabelModel.swift; sourceTree = ""; }; 942C372C241149170066E45E /* NHaasGroteskDSStd-75Bd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NHaasGroteskDSStd-75Bd.otf"; sourceTree = ""; }; 942C372D241149170066E45E /* NHaasGroteskDSStd-55Rg.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NHaasGroteskDSStd-55Rg.otf"; sourceTree = ""; }; @@ -628,6 +650,10 @@ D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; + D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = ""; }; + D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemModelProtocol.swift; sourceTree = ""; }; + D21B7F74243BAC8900051ABF /* CarouselItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselItem.swift; sourceTree = ""; }; + D21B7F76243BB70700051ABF /* MoleculeCollectionItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeCollectionItemModel.swift; sourceTree = ""; }; D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraintAxis+Extension.swift"; sourceTree = ""; }; D22479892314445E003FCCF9 /* LabelToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelToggle.swift; sourceTree = ""; }; D224798B231450C8003FCCF9 /* HeadlineBodyToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyToggle.swift; sourceTree = ""; }; @@ -660,6 +686,12 @@ D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPageControl.h; sourceTree = ""; }; 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 = ""; }; + D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplateModel.swift; sourceTree = ""; }; + D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplate.swift; sourceTree = ""; }; + D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCollectionViewController.swift; sourceTree = ""; }; + D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerCollectionReusableView.swift; sourceTree = ""; }; + D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgrammaticCollectionViewController.swift; sourceTree = ""; }; + D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplateItemProtocol.swift; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; @@ -916,6 +948,7 @@ 0A5D59C323AD488600EFD9E9 /* Protocols */ = { isa = PBXGroup; children = ( + D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */, 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */, ); path = Protocols; @@ -1258,8 +1291,6 @@ D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */, 01EB368923609801006832FA /* MoleculeListItemModel.swift */, 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */, - 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */, - D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, D28A838023CCB0D800DFE4FC /* AccordionListItemModel.swift */, D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, D28A837C23CCA86A00DFE4FC /* TabsListItemModel.swift */, @@ -1271,6 +1302,10 @@ D260105E23D0BFFC00764D80 /* StackItem.swift */, 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */, D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */, + D21B7F76243BB70700051ABF /* MoleculeCollectionItemModel.swift */, + D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, + 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */, + D21B7F74243BAC8900051ABF /* CarouselItem.swift */, ); path = Items; sourceTree = ""; @@ -1336,6 +1371,8 @@ 8D3BA9BE2433789900D341BA /* ListThreeColumnInternationalDataDivider.swift */, AA1EC59624373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift */, AA1EC59824373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift */, + 8DEFA95B243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift */, + 8DEFA95D243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift */, ); path = ThreeColumn; sourceTree = ""; @@ -1366,9 +1403,11 @@ D260105723CF9CC500764D80 /* Doughnut */ = { isa = PBXGroup; children = ( + 0A7ECC5C243CE85300C828E8 /* DoughnutChartItemModel.swift */, C695A69323C9909000BFB94E /* DoughnutChartModel.swift */, C695A69523C990BC00BFB94E /* DoughnutChart.swift */, C695A69723C990C200BFB94E /* DoughnutChartView.swift */, + 0A7ECC5E243CEB1200C828E8 /* ColorViewWithLabel.swift */, ); path = Doughnut; sourceTree = ""; @@ -1429,6 +1468,9 @@ 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */, 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */, D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */, + D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */, + D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */, + D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */, ); path = Templates; sourceTree = ""; @@ -1484,10 +1526,13 @@ D29DF2CD21E7C104003B2FB9 /* MFLoadingViewController.m */, D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */, D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */, + D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */, + D264FA8F243BCE6800D98315 /* ThreeLayerCollectionViewController.swift */, D2C521A823EDE79E00CA2634 /* ViewController.swift */, D2A92881241AAB67004E01C6 /* ScrollingViewController.swift */, D2A92883241ACB25004E01C6 /* ProgrammaticScrollViewController.swift */, D2A92885241ACD99004E01C6 /* ProgrammaticTableViewController.swift */, + D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */, ); path = BaseControllers; sourceTree = ""; @@ -1539,6 +1584,8 @@ children = ( D29DF13821E68636003B2FB9 /* MFStyler.h */, D29DF13921E68637003B2FB9 /* MFStyler.m */, + 0A6682A92435125F00AD3CA1 /* Styler.swift */, + 0A6682AB243531C300AD3CA1 /* Padding.swift */, ); path = Styles; sourceTree = ""; @@ -1787,6 +1834,7 @@ D2B18B802360945C00A9AEDC /* View.swift */, 0AE14F63238315D2005417F8 /* TextField.swift */, D2755D7A23689C7500485468 /* TableViewCell.swift */, + D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */, 0A5D59C323AD488600EFD9E9 /* Protocols */, ); path = BaseClasses; @@ -2036,8 +2084,10 @@ D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */, + D21B7F77243BB70700051ABF /* MoleculeCollectionItemModel.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, 011D9602240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift in Sources */, + D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */, D260106323D0C05000764D80 /* StackItemModel.swift in Sources */, D2E2A99823D8D63C000B42E6 /* ActionDetailWithImageModel.swift in Sources */, BBBBC87D24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift in Sources */, @@ -2056,6 +2106,7 @@ BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */, + D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, C7F8012323E846C300396FBD /* ListRVWheelModel.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, @@ -2102,6 +2153,7 @@ D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, + 0A7ECC5D243CE85300C828E8 /* DoughnutChartItemModel.swift in Sources */, 0A7EF85F23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift in Sources */, 011D959B240451E3000E3791 /* RuleRequiredModel.swift in Sources */, 526A265C240D1FF700B0D828 /* ListTwoColumnCompareChangesModel.swift in Sources */, @@ -2112,7 +2164,9 @@ D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, 5248BFEC23F12E350059236A /* ListThreeColumnPlanDataDivider.swift in Sources */, 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */, + D264FA8E243BCD9A00D98315 /* CollectionTemplate.swift in Sources */, 0A7EF85B23D8A52800B2AAD1 /* EntryFieldModel.swift in Sources */, + 8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */, 94F217B723E0BF6100A47C06 /* PrimaryButtonView.m in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */, 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, @@ -2139,6 +2193,7 @@ 8DD1E370243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 01EB368F23609801006832FA /* LabelModel.swift in Sources */, + 0A6682AC243531C300AD3CA1 /* Padding.swift in Sources */, AA1EC59924373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift in Sources */, 942C378E2412F5B60066E45E /* ModalMoleculeStackTemplate.swift in Sources */, 8D4687E4242E2DF300802879 /* ListFourColumnDataUsageListItem.swift in Sources */, @@ -2171,6 +2226,7 @@ C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, 011D958524042432000E3791 /* RulesProtocol.swift in Sources */, 94AF4A3F23E9D13900676048 /* MFCaretButton.m in Sources */, + D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */, @@ -2191,6 +2247,7 @@ D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */, 279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */, BB6C6AC0242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift in Sources */, + 8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */, D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */, D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, AAA74A172410C04600080241 /* HeadersH2NoButtonsBodyText.swift in Sources */, @@ -2211,6 +2268,7 @@ C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */, 0AE98BB523FF18D2004C5109 /* Arrow.swift in Sources */, D2D90B442404789000DD6EC9 /* MoleculeContainerProtocol.swift in Sources */, + 0A7ECC5F243CEB1200C828E8 /* ColorViewWithLabel.swift in Sources */, 94C0150A24215643005811A9 /* ActionTopAlertModel.swift in Sources */, 012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, @@ -2221,6 +2279,7 @@ 01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */, + D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 8DD1E36E243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift in Sources */, D243859923A16B1800332775 /* Container.swift in Sources */, @@ -2243,6 +2302,7 @@ D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */, + D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */, 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */, D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */, 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, @@ -2269,10 +2329,12 @@ 52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */, C003506123AA94CD00B6AC29 /* Button.swift in Sources */, DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */, + D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, + D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */, C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, @@ -2302,6 +2364,7 @@ EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */, 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */, 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */, + D21B7F75243BAC8900051ABF /* CarouselItem.swift in Sources */, D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */, C695A69823C990C200BFB94E /* DoughnutChartView.swift in Sources */, 8D3BA9BD2433787000D341BA /* ListThreeColumnInternationalDataDividerModel.swift in Sources */, @@ -2310,6 +2373,7 @@ 011D95AD2406BB57000E3791 /* FormHolderProtocol.swift in Sources */, 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */, D260106523D0CEA700764D80 /* StackModel.swift in Sources */, + 0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */, D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/CaretLink.swift b/MVMCoreUI/Atomic/Atoms/Buttons/CaretLink.swift index 697f4282..b437a56f 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/CaretLink.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/CaretLink.swift @@ -25,11 +25,11 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol { @objc public var rightViewHeight: NSNumber? @objc public var rightViewWidth: NSNumber? - @objc public var enabledColor: UIColor = .black { + @objc public var enabledColor: UIColor = .mvmBlack { didSet { changeCaretColor() } } - @objc public var disabledColor: UIColor = .mfSilver() { + @objc public var disabledColor: UIColor = .mvmCoolGray3 { didSet { changeCaretColor() } } @@ -103,8 +103,7 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol { let width = CGFloat(rightViewWidth?.floatValue ?? CARET_VIEW_WIDTH) let height = CGFloat(rightViewHeight?.floatValue ?? CARET_VIEW_HEIGHT) - let edgeInsets: UIEdgeInsets = contentEdgeInsets - contentEdgeInsets = UIEdgeInsets(top: edgeInsets.top, left: edgeInsets.left, bottom: edgeInsets.bottom, right: 4 + width) + contentEdgeInsets.right = 4 + width let caretView: UIView = rightView ?? createCaretView() rightView = caretView @@ -114,12 +113,12 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol { caretView.widthAnchor.constraint(equalToConstant: width).isActive = true caretView.heightAnchor.constraint(equalToConstant: height).isActive = true - let caretLabelSpacing = NSLayoutConstraint(item: caretView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1.0, constant: 4) + let caretLabelSpacing = NSLayoutConstraint(item: caretView, attribute: .left, relatedBy: .equal, toItem: titleLabel, attribute: .right, multiplier: 1.0, constant: 7) caretLabelSpacing.isActive = true caretSpacingConstraint = caretLabelSpacing - NSLayoutConstraint(item: caretView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true - NSLayoutConstraint(item: self, attribute: .right, relatedBy: .greaterThanOrEqual, toItem: caretView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true + caretView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + trailingAnchor.constraint(greaterThanOrEqualTo: caretView.trailingAnchor).isActive = true caretView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true bottomAnchor.constraint(greaterThanOrEqualTo: caretView.bottomAnchor).isActive = true contentHorizontalAlignment = .left @@ -137,17 +136,21 @@ open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol { // MARK: - Atomization //------------------------------------------------------ public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let caretLinkModel = model as? CaretLinkModel else { return } - if let color = caretLinkModel.backgroundColor { + + guard let model = model as? CaretLinkModel else { return } + + if let color = model.backgroundColor { backgroundColor = color.uiColor } - enabledColor = caretLinkModel.enabledColor.uiColor - if let color = caretLinkModel.disabledColor { + + enabledColor = model.enabledColor.uiColor + if let color = model.disabledColor { disabledColor = color.uiColor } - isEnabled = caretLinkModel.enabled - set(with: caretLinkModel.action, delegateObject: delegateObject, additionalData: additionalData) - setTitle(caretLinkModel.title, for: .normal) + + isEnabled = model.enabled + set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) + setTitle(model.title, for: .normal) } public func needsToBeConstrained() -> Bool { diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift index 9fe48936..ba793000 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift @@ -47,11 +47,11 @@ open class ExternalLink: Link { trailingAnchor.constraint(greaterThanOrEqualTo: exportIcon.trailingAnchor).isActive = true if let titleLabel = titleLabel { - let dimension = round(0.6 * titleLabel.font.pointSize) + let dimension = titleLabel.font.pointSize exportIcon.heightAnchor.constraint(equalToConstant: dimension).isActive = true exportIcon.widthAnchor.constraint(equalToConstant: dimension).isActive = true - exportIcon.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: PaddingOne).isActive = true - exportIcon.bottomAnchor.constraint(equalTo: titleLabel.lastBaselineAnchor).isActive = true + exportIcon.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 4).isActive = true + exportIcon.bottomAnchor.constraint(equalTo: titleLabel.lastBaselineAnchor, constant: 3).isActive = true } } } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift index 98532549..1e520cc2 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift @@ -8,6 +8,7 @@ import UIKit + @objcMembers open class RadioButton: Control { //-------------------------------------------------- // MARK: - Properties @@ -19,18 +20,18 @@ import UIKit } } - public var enabledColor: UIColor = .black - public var disabledColor: UIColor = .mfSilver() + public var enabledColor: UIColor = .mvmBlack + public var disabledColor: UIColor = .mvmCoolGray3 public var delegateObject: MVMCoreUIDelegateObject? public var radioModel: RadioButtonModel? { return model as? RadioButtonModel } - + lazy public var radioGroupName: String? = { return radioModel?.fieldKey }() - + lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = { if let radioGroupName = radioGroupName, let radioButtonModel = delegateObject?.formHolderDelegate?.formValidator?.radioButtonsModelByGroup[radioGroupName] { @@ -40,6 +41,13 @@ import UIKit } }() + public override var isEnabled: Bool { + didSet { + isUserInteractionEnabled = isEnabled + setNeedsDisplay() + } + } + //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- @@ -71,7 +79,7 @@ import UIKit } //-------------------------------------------------- - // MARK: - Methods + // MARK: - Validation //-------------------------------------------------- /// The action performed when tapped. @@ -100,7 +108,7 @@ import UIKit public func formFieldValue() -> AnyHashable? { return radioModel?.fieldValue } - + //-------------------------------------------------- // MARK: - MVMViewProtocol //-------------------------------------------------- @@ -108,7 +116,7 @@ import UIKit open override func setupView() { super.setupView() - backgroundColor = .white + backgroundColor = .mvmWhite clipsToBounds = true widthConstraint = widthAnchor.constraint(equalToConstant: 30) widthConstraint?.isActive = true @@ -120,7 +128,7 @@ import UIKit accessibilityTraits = .button accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint") } - + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) @@ -128,11 +136,12 @@ import UIKit self.delegateObject = delegateObject isSelected = model.state + isEnabled = model.enabled RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject) } - + public override func reset() { - super.reset() - backgroundColor = .white + super.reset() + backgroundColor = .mvmWhite } } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift index 579c0ed9..ee408bb4 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift @@ -51,7 +51,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol { } //-------------------------------------------------- - // MARK: - Method + // MARK: - Validation //-------------------------------------------------- public func formFieldValue() -> AnyHashable? { diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift index 3decadb5..8730a9c3 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift @@ -7,29 +7,41 @@ // import Foundation -import UIKit + @objcMembers public class RadioButtonSelectionHelper: FormFieldProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName private var selectedRadioButton: RadioButton? private var fieldGroupName: String? public var baseValue: AnyHashable? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + init(_ fieldKey: String?) { self.fieldKey = fieldKey } + //-------------------------------------------------- + // MARK: - Functions + //-------------------------------------------------- + public static func setupForRadioButtonGroup(_ radioButtonModel: RadioButtonModel, _ radioButton: RadioButton, delegateObject: MVMCoreUIDelegateObject?) { + guard let groupName = radioButtonModel.fieldKey, - let formValidator = delegateObject?.formHolderDelegate?.formValidator else { - return - } + let formValidator = delegateObject?.formHolderDelegate?.formValidator + else { return } let radioButtonSelectionHelper = formValidator.radioButtonsModelByGroup[groupName] ?? RadioButtonSelectionHelper(radioButtonModel.fieldKey) radioButtonSelectionHelper.fieldGroupName = radioButtonModel.fieldKey formValidator.radioButtonsModelByGroup[groupName] = radioButtonSelectionHelper - + if radioButtonModel.state { radioButtonSelectionHelper.selectedRadioButton = radioButton } @@ -37,6 +49,7 @@ import UIKit } public func selected(_ radioButton: RadioButton) { + selectedRadioButton?.isSelected = false selectedRadioButton = radioButton selectedRadioButton?.isSelected = true @@ -45,8 +58,9 @@ import UIKit // MARK: - FormValidationFormFieldProtocol extension RadioButtonSelectionHelper { + public func formFieldGroupName() -> String? { - return selectedRadioButton?.formFieldGroupName() ?? self.fieldGroupName + return selectedRadioButton?.formFieldGroupName() ?? fieldGroupName } public func formFieldValue() -> AnyHashable? { diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift index 091f9e51..38adee03 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift @@ -116,7 +116,7 @@ import UIKit for (index, field) in digitBoxes.enumerated() { if index < newValue.count { let indexChar = newValue.index(newValue.startIndex, offsetBy: index) - field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()]) + field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [NSAttributedString.Key.foregroundColor: UIColor.mvmCoolGray6]) } } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift index 7fb3a458..658af357 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift @@ -20,8 +20,8 @@ import UIKit public private(set) var titleLabel: Label = { let label = Label() - label.font = MFStyler.fontB3() - label.textColor = .mvmCoolGray6 + label.font = MFStyler.fontRegularMicro() + label.textColor = .mvmBlack label.setContentCompressionResistancePriority(.required, for: .vertical) return label }() @@ -31,7 +31,7 @@ import UIKit /// Provides contextual information on the TextField. public private(set) var feedbackLabel: Label = { let label = Label() - label.font = MFStyler.fontForTextFieldUnderLabel() + label.font = MFStyler.fontRegularMicro() label.textColor = .mvmBlack label.setContentCompressionResistancePriority(.required, for: .vertical) return label @@ -65,7 +65,8 @@ import UIKit public var isEnabled: Bool { get { return entryFieldContainer.isEnabled } set (enabled) { - self.feedbackLabel.textColor = enabled ? .black : .mfSilver() + self.titleLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3 + self.feedbackLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3 self.entryFieldContainer.isEnabled = enabled } } @@ -233,15 +234,16 @@ import UIKit //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- + @objc open override func reset() { super.reset() backgroundColor = .clear isAccessibilityElement = false - titleLabel.font = MFStyler.fontB3() - titleLabel.textColor = .mfBattleshipGrey() - feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() - feedbackLabel.textColor = .black + titleLabel.font = MFStyler.fontRegularMicro() + titleLabel.textColor = .mvmBlack + feedbackLabel.font = MFStyler.fontRegularMicro() + feedbackLabel.textColor = .mvmBlack entryFieldContainer.reset() } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift index 2e7df5ee..de0d1789 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift @@ -28,7 +28,8 @@ import UIKit let textField = TextField() textField.isAccessibilityElement = true textField.setContentCompressionResistancePriority(.required, for: .vertical) - textField.font = MFStyler.fontForTextField() + textField.font = MFStyler.fontRegularBodyLarge() + textField.textColor = .mvmBlack textField.smartQuotesType = .no textField.smartDashesType = .no textField.smartInsertDeleteType = .no @@ -40,7 +41,7 @@ import UIKit //-------------------------------------------------- /// Set enabled and disabled colors to be utilized when setting this texfield's isEnabled property. - public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.black, .mfSilver()) + public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.mvmBlack, .mvmCoolGray3) private var observingForChange: Bool = false @@ -168,7 +169,7 @@ import UIKit @objc open override func setupFieldContainerContent(_ container: UIView) { - MFStyler.styleTextField(textField) + textField.font = MFStyler.fontRegularBodyLarge() container.addSubview(textField) NSLayoutConstraint.activate([ @@ -193,14 +194,14 @@ import UIKit @objc open override func updateView(_ size: CGFloat) { super.updateView(size) - MFStyler.styleTextField(textField) + textField.font = MFStyler.fontRegularBodyLarge() layoutIfNeeded() } open override func reset() { super.reset() - textField.font = MFStyler.fontForTextField() + textField.font = MFStyler.fontRegularBodyLarge() } @objc deinit { @@ -252,6 +253,7 @@ import UIKit observingTextFieldDelegate?.isValid?(textfield: self) } } + /// Executes on UITextField.textDidBeginEditingNotification @objc func startEditing() { isSelected = true @@ -270,7 +272,7 @@ import UIKit resignFirstResponder() if isValid { showError = false - entryFieldContainer.bottomBar?.backgroundColor = UIColor.black.cgColor + entryFieldContainer.bottomBar?.backgroundColor = UIColor.mvmBlack.cgColor } } @@ -278,6 +280,10 @@ import UIKit resignFirstResponder() } + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 7fec5960..a630f63a 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -760,8 +760,6 @@ public typealias ActionBlock = () -> () */ static func getTextAttachmentImage(name: String = "externalLink", dimension: CGFloat) -> NSTextAttachment { - let dimension = round(dimension * 0.8) - let imageAttachment = NSTextAttachment() imageAttachment.image = MVMCoreUIUtility.imageNamed(name) imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension) @@ -771,23 +769,18 @@ public typealias ActionBlock = () -> () static func getTextAttachmentFrom(url: String, dimension: CGFloat, label: Label) -> NSTextAttachment { - let dimension = round(dimension * 0.8) - let imageAttachment = NSTextAttachment() imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension) DispatchQueue.global(qos: .default).async { - - guard let url = URL(string: url), - let data = try? Data(contentsOf: url) - else { return } - - DispatchQueue.main.sync { - imageAttachment.image = UIImage(data: data) - label.setNeedsDisplay() + MVMCoreCache.shared()?.getImage(url, useWidth: false, widthForS7: 0, useHeight: false, heightForS7: 0, localFallbackImageName: nil) { image, data, _ in + + DispatchQueue.main.sync { + imageAttachment.image = image + label.setNeedsDisplay() + } } } - return imageAttachment } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift index 437bb6f8..862f8f9b 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelAttributeFontModel.swift @@ -18,7 +18,7 @@ import UIKit return "font" } - var style: LabelModel.FontStyle? + var style: Styler.Font? var name: String? var size: CGFloat? @@ -38,7 +38,7 @@ import UIKit required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - style = try typeContainer.decodeIfPresent(LabelModel.FontStyle.self, forKey: .style) + style = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .style) name = try typeContainer.decodeIfPresent(String.self, forKey: .name) size = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .size) try super.init(from: decoder) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift index 74e6efa4..4ec23ad6 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/LabelModel.swift @@ -10,31 +10,6 @@ import Foundation @objcMembers public class LabelModel: MoleculeModelProtocol { - - public enum FontStyle: String, Codable { - case Title2XLarge - case TitleXLarge - case BoldTitleLarge - case RegularTitleLarge - case BoldTitleMedium - case RegularTitleMedium - case BoldBodyLarge - case RegularBodyLarge - case BoldBodySmall - case RegularBodySmall - case BoldMicro - case RegularMicro - // Legacy - case H1 - case H2 - case H3 - case H32 - case B1 - case B2 - case B3 - case B20 - } - //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -44,7 +19,7 @@ import Foundation public var text: String public var accessibilityText: String? public var textColor: Color? - public var fontStyle: FontStyle? + public var fontStyle: Styler.Font? public var fontName: String? public var fontSize: CGFloat? public var textAlignment: NSTextAlignment? @@ -95,7 +70,7 @@ import Foundation accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - fontStyle = try typeContainer.decodeIfPresent(FontStyle.self, forKey: .fontStyle) + fontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .fontStyle) fontName = try typeContainer.decodeIfPresent(String.self, forKey: .fontName) fontSize = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .fontSize) textAlignment = try typeContainer.decodeIfPresent(NSTextAlignment.self, forKey: .textAlignment) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift index 1e072faf..956c2afc 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift @@ -25,13 +25,13 @@ public typealias ActionBlockConfirmation = () -> (Bool) //-------------------------------------------------- /// Holds the on and off colors for the container. - public var containerTintColor: (on: UIColor?, off: UIColor?)? = (on: .mvmGreen, off: .black) + public var containerTintColor: (on: UIColor?, off: UIColor?)? = (on: .mvmGreen, off: .mvmBlack) /// Holds the on and off colors for the knob. - public var knobTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) + public var knobTintColor: (on: UIColor?, off: UIColor?)? = (on: .mvmWhite, off: .mvmWhite) /// Holds the on and off colors for the disabled state.. - public var disabledTintColor: (container: UIColor?, knob: UIColor?)? = (container: .mvmCoolGray3, knob: .white) + public var disabledTintColor: (container: UIColor?, knob: UIColor?)? = (container: .mvmCoolGray3, knob: .mvmWhite) /// Set this flag to false if you do not want to animate state changes. public var isAnimated = true diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index ff717f1e..0f0baac6 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -114,7 +114,9 @@ import Foundation // Other Items MoleculeObjectMapping.shared()?.register(viewClass: MoleculeStackItem.self, viewModelClass: MoleculeStackItemModel.self) MoleculeObjectMapping.shared()?.register(viewClass: StackItem.self, viewModelClass: StackItemModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: MoleculeCollectionViewCell.self, viewModelClass: CarouselItemModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: MoleculeCollectionViewCell.self, viewModelClass: MoleculeCollectionItemModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: CarouselItem.self, viewModelClass: CarouselItemModel.self) + // Other Container Molecules MoleculeObjectMapping.shared()?.register(viewClass: MoleculeHeaderView.self, viewModelClass: MoleculeHeaderModel.self) @@ -156,6 +158,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnInternationalDataDivider.self, viewModelClass: ListThreeColumnInternationalDataDividerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnSpeedTestDivider.self, viewModelClass: ListThreeColumnSpeedTestDividerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnBillChangesDivider.self, viewModelClass: ListThreeColumnBillChangesDividerModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnDataUsageDivider.self, viewModelClass: ListThreeColumnDataUsageDividerModel.self) // Designed Headers MoleculeObjectMapping.shared()?.register(viewClass: HeadersH2NoButtonsBodyText.self, viewModelClass: HeadersH2NoButtonsBodyTextModel.self) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift index 9517acf0..4597a0b9 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift @@ -8,12 +8,13 @@ import UIKit + @objcMembers open class ListLeftVariableRadioButtonAndPaymentMethod: TableViewCell { //----------------------------------------------------- // MARK: - Outlets //----------------------------------------------------- - let radioButton = RadioButton(frame: .zero) + let radioButton = RadioButton() let leftImage = MFLoadImageView(pinnedEdges: .all) let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() var stack: Stack @@ -57,7 +58,7 @@ import UIKit // MARK: - Molecule //---------------------------------------------------- - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? ListLeftVariableRadioButtonAndPaymentMethodModel else { return} radioButton.set(with: model.radioButton, delegateObject, additionalData) @@ -69,7 +70,10 @@ import UIKit return 90 } - public override func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - radioButton.tapAction() + public override func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + + if radioButton.isEnabled { + radioButton.tapAction() + } } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnDataUsageDivider.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnDataUsageDivider.swift new file mode 100644 index 00000000..eab5e8be --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnDataUsageDivider.swift @@ -0,0 +1,61 @@ +// +// ListThreeColumnDataUsageDivider.swift +// MVMCoreUI +// +// Created by Kruthika KP on 20/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class ListThreeColumnDataUsageDivider: TableViewCell { + + //----------------------------------------------------- + // MARK: - Outlets + //------------------------------------------------------- + public let leftLabel = Label.createLabelBoldBodySmall(true) + public let centerLabel = Label.createLabelBoldBodySmall(true) + public let rightLabel = Label.createLabelBoldBodySmall(true) + var stack: Stack + + //------------------------------------------------------ + // MARK: - Initializers + //------------------------------------------------------ + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + stack = Stack.createStack(with: [(view: leftLabel, model: StackItemModel(percent: 40, horizontalAlignment: .leading)), + (view: centerLabel, model: StackItemModel(percent: 37, horizontalAlignment: .leading)), + (view: rightLabel, model: StackItemModel(percent: 23, horizontalAlignment: .leading))], + axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + } + + // MARK: - ModelMoleculeViewProtocol + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListThreeColumnDataUsageDividerModel else { return } + leftLabel.set(with: model.leftLabel, delegateObject, additionalData) + centerLabel.set(with: model.centerLabel, delegateObject, additionalData) + rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 121 + } + + open override func reset() { + super.reset() + leftLabel.styleBoldBodySmall(true) + centerLabel.styleBoldBodySmall(true) + rightLabel.styleBoldBodySmall(true) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnDataUsageDividerModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnDataUsageDividerModel.swift new file mode 100644 index 00000000..f57bdf01 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/SectionDividers/ThreeColumn/ListThreeColumnDataUsageDividerModel.swift @@ -0,0 +1,51 @@ +// +// ListThreeColumnDataUsageDividerModel.swift +// MVMCoreUI +// +// Created by Kruthika KP on 20/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class ListThreeColumnDataUsageDividerModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "list3CDataUsgDiv" + public let leftLabel: LabelModel + public let centerLabel: LabelModel + public let rightLabel: LabelModel + + public init(leftLabel: LabelModel, centerLabel: LabelModel, rightLabel: LabelModel) { + self.leftLabel = leftLabel + self.centerLabel = centerLabel + self.rightLabel = rightLabel + super.init() + } + + override public func setDefaults() { + super.setDefaults() + style = "tallDivider" + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case leftLabel + case centerLabel + case rightLabel + } + + required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + leftLabel = try typeContainer.decode(LabelModel.self, forKey: .leftLabel) + centerLabel = try typeContainer.decode(LabelModel.self, forKey: .centerLabel) + rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) + try super.init(from:decoder) + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(leftLabel, forKey: .leftLabel) + try container.encode(centerLabel, forKey: .centerLabel) + try container.encode(rightLabel, forKey: .rightLabel) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Doughnut/ColorViewWithLabel.swift b/MVMCoreUI/Atomic/Molecules/Doughnut/ColorViewWithLabel.swift new file mode 100644 index 00000000..f8dd4b5c --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/Doughnut/ColorViewWithLabel.swift @@ -0,0 +1,73 @@ +// +// ColorViewWithLabel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 4/7/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + + +open class ColorViewWithLabel: View { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var label = Label.createLabelRegularBodySmall(true) + public var colorView = View() + private var sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: 8)! + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + public var heightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func setupView() { + super.setupView() + + addSubview(colorView) + addSubview(label) + + heightConstraint = colorView.heightAnchor.constraint(equalToConstant: sizeObject.getValueBasedOnApplicationWidth()) + heightConstraint?.isActive = true + colorView.widthAnchor.constraint(equalTo: colorView.heightAnchor).isActive = true + colorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + colorView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + + label.leadingAnchor.constraint(equalTo: colorView.trailingAnchor, constant: PaddingOne).isActive = true + label.topAnchor.constraint(equalTo: topAnchor).isActive = true + trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true + bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + label.updateView(size) + heightConstraint?.constant = sizeObject.getValueBased(onSize: size) + setNeedsDisplay() + } + + open override func reset() { + super.reset() + label.reset() + } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + + guard let chartItemModel = model as? DoughnutChartItemModel else { return } + + label.set(with: chartItemModel.label, delegateObject, additionalData) + colorView.backgroundColor = chartItemModel.color.uiColor + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChart.swift b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChart.swift index d84c7dc0..bad0492d 100644 --- a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChart.swift +++ b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChart.swift @@ -8,23 +8,38 @@ import UIKit + open class DoughnutChart: View { - var doughnutLayer = CALayer() - var titleLabel = Label.commonLabelH3(true) - var subTitleLabel = Label.commonLabelB2(true) - var doughnutChartModel: DoughnutChartModel? { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var doughnutLayer = CALayer() + public var titleLabel = Label.createLabelBoldTitleLarge(true) + public var subTitleLabel = Label.createLabelRegularMicro(true) + public var labelContainer = MVMCoreUICommonViewsUtility.commonView() + public static let heightConstant: CGFloat = 136 + private var sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: heightConstant)! + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + public var labelContainerLeftConstraint: NSLayoutConstraint? + public var labelContainerTopConstraint: NSLayoutConstraint? + public var labelContainerBottomConstraint: NSLayoutConstraint? + public var labelContainerRightConstraint: NSLayoutConstraint? + public var heightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + public var doughnutChartModel: DoughnutChartModel? { get { return model as? DoughnutChartModel } - } - var labelContainer = MVMCoreUICommonViewsUtility.commonView() - var labelContainerLeftConstraint: NSLayoutConstraint? - var labelContainerTopConstraint: NSLayoutConstraint? - var labelContainerBottomConstraint: NSLayoutConstraint? - var labelContainerRightConstraint: NSLayoutConstraint? - var heightConstraint: NSLayoutConstraint? - - static let heightConstant: CGFloat = 150 - private var sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: heightConstant)! - var height: CGFloat = heightConstant { + } + + public var height: CGFloat = heightConstant { didSet { if height != oldValue { sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: height)! @@ -32,69 +47,89 @@ open class DoughnutChart: View { drawGraph() } } - } - - open override func updateView(_ size: CGFloat) { - super.updateView(size) - titleLabel.updateView(size) - subTitleLabel.updateView(size) - updateContainer() - drawGraph() - } - - open override func reset() { - super.reset() - titleLabel.reset() - subTitleLabel.reset() - clearLayers() - } - - open override func setupView() { - super.setupView() - guard labelContainer.superview == nil else { - return - } - addSubview(labelContainer) - - labelContainer.addSubview(titleLabel) - labelContainer.addSubview(subTitleLabel) - titleLabel.textAlignment = .center - subTitleLabel.textAlignment = .center - - //Make label font size to adjust width if label content is high - titleLabel.numberOfLines = 1 - titleLabel.adjustsFontSizeToFitWidth = true - - layer.addSublayer(doughnutLayer) - heightConstraint = heightAnchor.constraint(equalToConstant: - sizeObject.getValueBasedOnApplicationWidth()) - heightConstraint?.isActive = true - widthAnchor.constraint(equalTo: heightAnchor).isActive = true - - labelContainerLeftConstraint = labelContainer.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor, constant: lineWidth()) - labelContainerLeftConstraint?.isActive = true - labelContainerTopConstraint = labelContainer.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: lineWidth()) - labelContainerTopConstraint?.isActive = true - labelContainerRightConstraint = rightAnchor.constraint(greaterThanOrEqualTo: labelContainer.rightAnchor, constant: lineWidth()) - labelContainerRightConstraint?.isActive = true - labelContainerBottomConstraint = bottomAnchor.constraint(greaterThanOrEqualTo: labelContainer.bottomAnchor, constant: lineWidth()) - labelContainerBottomConstraint?.isActive = true - labelContainer.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - labelContainer.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true - - NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true) - NSLayoutConstraint.constraintPinSubview(subTitleLabel, pinTop: false, pinBottom: true, pinLeft: true, pinRight: true) - _ = NSLayoutConstraint(pinFirstView: titleLabel, toSecondView: subTitleLabel, withConstant: 0, directionVertical: true) - //Rotate view for initial draw - doughnutLayer.transform = CATransform3DMakeRotation(1 * .pi, 0.0, 0.0, 1.0) } - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: .zero) + } + + public convenience init() { + self.init(frame: .zero) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + titleLabel.updateView(size) + subTitleLabel.updateView(size) + updateContainer() + drawGraph() + } + + open override func reset() { + super.reset() + titleLabel.reset() + subTitleLabel.reset() + clearLayers() + } + + open override func setupView() { + super.setupView() + + addSubview(labelContainer) + + labelContainer.addSubview(titleLabel) + labelContainer.addSubview(subTitleLabel) + titleLabel.textAlignment = .center + subTitleLabel.textAlignment = .center + + //Make label font size to adjust width if label content is high + titleLabel.numberOfLines = 1 + titleLabel.adjustsFontSizeToFitWidth = true + + layer.addSublayer(doughnutLayer) + heightConstraint = heightAnchor.constraint(equalToConstant: sizeObject.getValueBasedOnApplicationWidth()) + heightConstraint?.isActive = true + widthAnchor.constraint(equalTo: heightAnchor).isActive = true + + labelContainerLeftConstraint = labelContainer.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: lineWidth()) + labelContainerLeftConstraint?.isActive = true + labelContainerTopConstraint = labelContainer.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: lineWidth()) + labelContainerTopConstraint?.isActive = true + labelContainerRightConstraint = trailingAnchor.constraint(greaterThanOrEqualTo: labelContainer.trailingAnchor, constant: lineWidth()) + labelContainerRightConstraint?.isActive = true + labelContainerBottomConstraint = bottomAnchor.constraint(greaterThanOrEqualTo: labelContainer.bottomAnchor, constant: lineWidth()) + labelContainerBottomConstraint?.isActive = true + labelContainer.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + labelContainer.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true + + NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true) + NSLayoutConstraint.constraintPinSubview(subTitleLabel, pinTop: false, pinBottom: true, pinLeft: true, pinRight: true) + _ = NSLayoutConstraint(pinFirstView: titleLabel, toSecondView: subTitleLabel, withConstant: 0, directionVertical: true) + //Rotate view for initial draw + doughnutLayer.transform = CATransform3DMakeRotation(1 * .pi, 0, 0, 1) + } + + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) clearLayers() - guard let doughnutChartModel = doughnutChartModel else { - return - } + guard let doughnutChartModel = doughnutChartModel else { return } + titleLabel.setOptional(with: doughnutChartModel.title, delegateObject, additionalData) subTitleLabel.setOptional(with: doughnutChartModel.subtitle, delegateObject, additionalData) titleLabel.textAlignment = .center @@ -103,78 +138,83 @@ open class DoughnutChart: View { drawGraph() } - func drawGraph() { - clearLayers() - let widthHeight = sizeObject.getValueBasedOnApplicationWidth() - doughnutLayer.frame = CGRect(x: 0, y: 0, - width: widthHeight, - height: widthHeight) - if let doughnutChart = doughnutChartModel { - var prevPercent: CGFloat = 0.0 - for model in doughnutChart.sections { + //-------------------------------------------------- + // MARK: - Draw Graph + //-------------------------------------------------- + + private func drawGraph() { + clearLayers() + let widthHeight = sizeObject.getValueBasedOnApplicationWidth() + doughnutLayer.frame = CGRect(x: 0, y: 0, width: widthHeight, height: widthHeight) + + if let doughnutChart = doughnutChartModel { + var prevPercent: CGFloat = 0.0 + for model in doughnutChart.sections { prevPercent += drawBar(model, prevPercent) - } - } - } - - func drawBar(_ chartModel: DoughnutChartItemModel, _ prevPercent: CGFloat) -> CGFloat { - - let shapeLayer = CAShapeLayer() - shapeLayer.frame = doughnutLayer.frame - shapeLayer.lineWidth = lineWidth() - shapeLayer.fillColor = nil - shapeLayer.strokeColor = chartModel.color.uiColor.cgColor - - let arcCenter = shapeLayer.position - let radius = shapeLayer.bounds.size.width / 2.0 - lineWidth()/2.0 - - let value: CGFloat = chartModel.percent - let gap: CGFloat = spaceRequired() ? lineGap() : 0.0 - let startAngle = ((prevPercent / 100.0) * 2 * .pi) - (0.5 * .pi) - let endAngle = ((value / 100.0) * 2 * .pi) + startAngle - gap - let circlePath = UIBezierPath(arcCenter: arcCenter, - radius: radius, - startAngle: startAngle, - endAngle: endAngle, - clockwise: true) - - shapeLayer.path = circlePath.cgPath - doughnutLayer.addSublayer(shapeLayer) - return value - } - - func clearLayers() { - doughnutLayer.sublayers?.forEach({ $0.removeFromSuperlayer() }) - } + } + } + } - func updateContainer() { - heightConstraint?.constant = sizeObject.getValueBasedOnApplicationWidth() - updateLabelContainer() - setNeedsDisplay() - } + private func drawBar(_ chartModel: DoughnutChartItemModel, _ prevPercent: CGFloat) -> CGFloat { + + let shapeLayer = CAShapeLayer() + shapeLayer.frame = doughnutLayer.frame + shapeLayer.lineWidth = lineWidth() + shapeLayer.fillColor = nil + shapeLayer.strokeColor = chartModel.color.uiColor.cgColor + + let arcCenter = shapeLayer.position + let radius = shapeLayer.bounds.size.width / 2.0 - lineWidth() / 2.0 + + let value: CGFloat = chartModel.percent + let gap: CGFloat = spaceRequired() ? lineGap() : 0.0 + let startAngle = ((prevPercent / 100.0) * 2 * .pi) - (0.5 * .pi) + let endAngle = ((value / 100.0) * 2 * .pi) + startAngle - gap + let circlePath = UIBezierPath(arcCenter: arcCenter, + radius: radius, + startAngle: startAngle, + endAngle: endAngle, + clockwise: true) + + shapeLayer.path = circlePath.cgPath + doughnutLayer.addSublayer(shapeLayer) + return value + } - func lineWidth() -> CGFloat { - return 30 - } + private func clearLayers() { + doughnutLayer.sublayers?.forEach{ $0.removeFromSuperlayer() } + } - func lineGap() -> CGFloat { + public func updateContainer() { + + heightConstraint?.constant = sizeObject.getValueBasedOnApplicationWidth() + updateLabelContainer() + setNeedsDisplay() + } + + public func lineWidth() -> CGFloat { + return 12 + } + + public func lineGap() -> CGFloat { //If array is having the single item then no space required return doughnutChartModel?.sections.count == 1 ? 0.0 : 0.05 - } + } - func spaceRequired() -> Bool { + public func spaceRequired() -> Bool { return doughnutChartModel?.spaceRequired ?? true - } + } - func updateLabelContainer() { + public func updateLabelContainer() { + labelContainer.setNeedsDisplay() labelContainer.layoutIfNeeded() - let radius = sizeObject.getValueBasedOnApplicationWidth()/2 - lineWidth() - let labelheight = labelContainer.frame.height/2 - let padding = sizeObject.getValueBasedOnApplicationWidth()/2 - sqrt(max(0, pow(radius, 2) - pow(labelheight, 2))) + let radius = sizeObject.getValueBasedOnApplicationWidth() / 2 - lineWidth() + let labelheight = labelContainer.frame.height / 2 + let padding = sizeObject.getValueBasedOnApplicationWidth() / 2 - sqrt(max(0, pow(radius, 2) - pow(labelheight, 2))) - labelContainerLeftConstraint?.constant = padding - labelContainerRightConstraint?.constant = padding + labelContainerLeftConstraint?.constant = round(padding) + labelContainerRightConstraint?.constant = round(padding) labelContainerTopConstraint?.constant = max(radius - labelheight, labelheight) labelContainerBottomConstraint?.constant = max(radius - labelheight, labelheight) } diff --git a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartItemModel.swift b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartItemModel.swift new file mode 100644 index 00000000..ee4e664a --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartItemModel.swift @@ -0,0 +1,44 @@ +// +// DoughnutChartItemModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 4/7/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +@objcMembers public class DoughnutChartItemModel: MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var backgroundColor: Color? + public static var identifier: String = "doughnutChartItem" + public var moleculeName: String = DoughnutChartItemModel.identifier + public var label: LabelModel + @Percent public var percent: CGFloat + public var color: Color + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case backgroundColor + case label + case percent + case color + } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(percent: CGFloat, color: Color, label: LabelModel) { + self.percent = percent + self.color = color + self.label = label + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift index ff9b1bf6..fbb034c6 100644 --- a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartModel.swift @@ -8,7 +8,12 @@ import Foundation + @objcMembers public class DoughnutChartModel: MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var backgroundColor: Color? public static var identifier: String = "doughnutChart" public var moleculeName: String = DoughnutChartModel.identifier @@ -17,22 +22,11 @@ import Foundation public var sections: [DoughnutChartItemModel] public var spaceRequired: Bool? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public init(sections: [DoughnutChartItemModel]) { self.sections = sections } } - -@objcMembers public class DoughnutChartItemModel: MoleculeModelProtocol { - public var backgroundColor: Color? - public static var identifier: String = "doughnutChartItem" - public var moleculeName: String = DoughnutChartItemModel.identifier - public var label: LabelModel - @Percent public var percent: CGFloat - public var color: Color - - public init(percent: CGFloat, color: Color, label: LabelModel) { - self.percent = percent - self.color = color - self.label = label - } -} diff --git a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartView.swift b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartView.swift index 280385cb..1c0ea963 100644 --- a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartView.swift +++ b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartView.swift @@ -8,47 +8,53 @@ import Foundation + @objcMembers open class DoughnutChartView: View { - var doughnutChart = DoughnutChart(frame: CGRect.zero) + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var doughnutChart = DoughnutChart() var colorLablesStack = ColorViewLabelsStack() - var doughnutChartModel: DoughnutChartModel? { - get { return model as? DoughnutChartModel } + + public var doughnutChartModel: DoughnutChartModel? { + get { return model as? DoughnutChartModel } } + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func setupView() { super.setupView() - guard doughnutChart.superview == nil else { - return - } - doughnutChart.translatesAutoresizingMaskIntoConstraints = false + addSubview(doughnutChart) - colorLablesStack.translatesAutoresizingMaskIntoConstraints = false addSubview(colorLablesStack) doughnutChart.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - doughnutChart.topAnchor.constraint(equalTo: topAnchor).isActive = true + doughnutChart.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: doughnutChart.bottomAnchor).isActive = true let doughnutBottomAnchor = bottomAnchor.constraint(equalTo: doughnutChart.bottomAnchor) doughnutBottomAnchor.priority = UILayoutPriority(rawValue: 200) doughnutBottomAnchor.isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: colorLablesStack.bottomAnchor).isActive = true + let colorLablesBottomAnchor = bottomAnchor.constraint(equalTo: colorLablesStack.bottomAnchor) colorLablesBottomAnchor.priority = UILayoutPriority(rawValue: 204) colorLablesBottomAnchor.isActive = true - let colorLablesTopAnchor = colorLablesStack.topAnchor.constraint(equalTo: doughnutChart.topAnchor) + colorLablesStack.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + + let colorLablesTopAnchor = colorLablesStack.topAnchor.constraint(equalTo: topAnchor) colorLablesTopAnchor.priority = .defaultLow colorLablesTopAnchor.isActive = true - colorLablesStack.topAnchor.constraint(greaterThanOrEqualTo: doughnutChart.topAnchor).isActive = true - bottomAnchor.constraint(greaterThanOrEqualTo: colorLablesStack.bottomAnchor).isActive = true trailingAnchor.constraint(equalTo: colorLablesStack.trailingAnchor).isActive = true - - let centerY = colorLablesStack.centerYAnchor.constraint(equalTo: doughnutChart.centerYAnchor) - centerY.priority = UILayoutPriority(rawValue: 500) - centerY.isActive = true - + doughnutChart.centerYAnchor.constraint(equalTo: colorLablesStack.centerYAnchor).isActive = true + colorLablesStack.leadingAnchor.constraint(equalTo: doughnutChart.trailingAnchor, constant: PaddingThree).isActive = true colorLablesStack.backgroundColor = .clear } @@ -65,12 +71,16 @@ import Foundation colorLablesStack.reset() } - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = doughnutChartModel else { return } doughnutChart.set(with: model, delegateObject, additionalData) - + // Create the stack model var stackItems: [MoleculeStackItemModel] = [] for item in model.sections { @@ -82,14 +92,17 @@ import Foundation } } +// MARK: - MVMCoreUIViewConstrainingProtocol extension DoughnutChartView: MVMCoreUIViewConstrainingProtocol { + open func horizontalAlignment() -> UIStackView.Alignment { return .leading } } class ColorViewLabelsStack: MoleculeStackView { - override func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + + override func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let stackItemModels = stackModel?.molecules else { return } for model in stackItemModels { let view = ColorViewWithLabel() @@ -99,53 +112,3 @@ class ColorViewLabelsStack: MoleculeStackView { } } } - -class ColorViewWithLabel: View { - - var label = Label.commonLabelB2(true) - var colorView = MVMCoreUICommonViewsUtility.commonView() - private var sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: 8)! - var heightConstraint: NSLayoutConstraint? - - override func setupView() { - super.setupView() - guard colorView.superview == nil else { - return - } - translatesAutoresizingMaskIntoConstraints = false - addSubview(colorView) - addSubview(label) - - heightConstraint = colorView.heightAnchor.constraint(equalToConstant: sizeObject.getValueBasedOnApplicationWidth()) - heightConstraint?.isActive = true - colorView.widthAnchor.constraint(equalTo: colorView.heightAnchor).isActive = true - colorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - colorView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - - label.leadingAnchor.constraint(equalTo: colorView.trailingAnchor, constant: PaddingOne).isActive = true - label.topAnchor.constraint(equalTo: topAnchor).isActive = true - trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true - bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true - } - - override func updateView(_ size: CGFloat) { - super.updateView(size) - label.updateView(size) - heightConstraint?.constant = sizeObject.getValueBased(onSize: size) - setNeedsDisplay() - } - - override func reset() { - super.reset() - label.reset() - } - - override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { - super.set(with: model, delegateObject, additionalData) - guard let chartItemModel = model as? DoughnutChartItemModel else { - return - } - label.set(with: chartItemModel.label, delegateObject, additionalData) - colorView.backgroundColor = chartItemModel.color.uiColor - } -} diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift new file mode 100644 index 00000000..3089596a --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift @@ -0,0 +1,78 @@ +// +// CarouselItem.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class CarouselItem: MoleculeCollectionViewCell { + + open var allowsPeaking = false + var peakingLeftArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) + var peakingRightArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) + var peakingCover = MVMCoreUICommonViewsUtility.commonView() + + open override func addMolecule(_ molecule: MoleculeViewProtocol) { + super.addMolecule(molecule) + contentView.sendSubviewToBack(molecule) + } + + open override func setupView() { + super.setupView() + + // Covers the card when peaking. + peakingCover.backgroundColor = .white + peakingCover.alpha = 0 + contentView.addSubview(peakingCover) + NSLayoutConstraint.constraintPinSubview(toSuperview: peakingCover) + + // A small arrow on the next card for when peaking. + let ratio: CGFloat = 0.015 + peakingLeftArrow.translatesAutoresizingMaskIntoConstraints = false + peakingLeftArrow.alpha = 0 + peakingLeftArrow.tintColor = .black + contentView.addSubview(peakingLeftArrow) + peakingLeftArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + NSLayoutConstraint.scalingPinViewLeft(toSuper: peakingLeftArrow, ratio: ratio, anchor: contentView.widthAnchor) + + peakingRightArrow.translatesAutoresizingMaskIntoConstraints = false + peakingRightArrow.transform = CGAffineTransform(scaleX: -1, y: 1) // Flip + peakingRightArrow.alpha = 0 + peakingRightArrow.tintColor = .black + contentView.addSubview(peakingRightArrow) + peakingRightArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + NSLayoutConstraint.scalingPinViewRight(toSuper: peakingRightArrow, ratio: ratio, anchor: contentView.widthAnchor) + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let collectionModel = model as? CarouselItemModel else { return } + + // Handles peaking. + allowsPeaking = collectionModel.peakingUI ?? false + if let peakingArrowColor = collectionModel.peakingArrowColor { + let color = peakingArrowColor.uiColor + peakingLeftArrow.tintColor = color + peakingRightArrow.tintColor = color + } + } + + public func setPeaking(_ peaking: Bool, animated: Bool) { + guard allowsPeaking else { + return + } + let animation = {() in + self.peakingRightArrow.alpha = peaking ? 1 : 0 + self.peakingLeftArrow.alpha = peaking ? 1 : 0 + self.peakingCover.alpha = peaking ? 0.5 : 0 + } + if animated { + UIView.animate(withDuration: 0.4, animations: animation) + } else { + animation() + } + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift index 9fc218a7..ab6a1cbd 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItemModel.swift @@ -9,22 +9,20 @@ import Foundation -@objcMembers public class CarouselItemModel: MoleculeContainerModel, CarouselItemModelProtocol { - public static var identifier: String = "carouselItem" - public var backgroundColor: Color? +@objcMembers public class CarouselItemModel: MoleculeCollectionItemModel, CarouselItemModelProtocol { + public override class var identifier: String { + return "carouselItem" + } public var peakingUI: Bool? public var peakingArrowColor: Color? private enum CodingKeys: String, CodingKey { - case moleculeName - case backgroundColor case peakingUI case peakingArrowColor } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) peakingUI = try typeContainer.decodeIfPresent(Bool.self, forKey: .peakingUI) peakingArrowColor = try typeContainer.decodeIfPresent(Color.self, forKey: .peakingArrowColor) try super.init(from: decoder) @@ -33,8 +31,6 @@ import Foundation public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(peakingUI, forKey: .peakingUI) try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor) } diff --git a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift index 9365125a..9a00a94d 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift @@ -9,6 +9,7 @@ import Foundation + @objcMembers open class ListItemModel: ContainerModel, ListItemModelProtocol { //-------------------------------------------------- // MARK: - Properties diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift new file mode 100644 index 00000000..ed9467fd --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -0,0 +1,57 @@ +// +// MoleculeCollectionItemModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// A model for a collection item that is a container for any molecule. +@objcMembers public class MoleculeCollectionItemModel: MoleculeContainerModel, CollectionItemModelProtocol, MoleculeModelProtocol { + open class var identifier: String { + return "collectionItem" + } + public var backgroundColor: Color? + + /// Defaults to set + public func setDefaults() { + if useHorizontalMargins == nil { + useHorizontalMargins = true + } + if useVerticalMargins == nil { + useVerticalMargins = true + } + if topMarginPadding == nil { + topMarginPadding = PaddingDefault + } + if bottomMarginPadding == nil { + bottomMarginPadding = PaddingDefault + } + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + } + + public override init(with moleculeModel: MoleculeModelProtocol) { + super.init(with: moleculeModel) + setDefaults() + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + try super.init(from: decoder) + setDefaults() + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift index 219617ec..42af1a53 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift @@ -8,90 +8,15 @@ import UIKit -open class MoleculeCollectionViewCell: UICollectionViewCell, MoleculeViewProtocol { - - open var molecule: MoleculeViewProtocol? - public let containerHelper = ContainerHelper() - - // In updateView, will set padding to default. - open var updateViewHorizontalDefaults = true - open var updateViewVerticalDefaults = true - - open var allowsPeaking = false - var peakingLeftArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) - var peakingRightArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) - var peakingCover = MVMCoreUICommonViewsUtility.commonView() - - public override init(frame: CGRect) { - super.init(frame: .zero) - setupView() - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupView() - } - - public func setupView() { - guard peakingCover.superview == nil else { - return - } - isAccessibilityElement = false - contentView.isAccessibilityElement = false - insetsLayoutMarginsFromSafeArea = false - contentView.insetsLayoutMarginsFromSafeArea = false - contentView.preservesSuperviewLayoutMargins = false - - // Covers the card when peaking. - peakingCover.backgroundColor = .white - peakingCover.alpha = 0 - contentView.addSubview(peakingCover) - NSLayoutConstraint.constraintPinSubview(toSuperview: peakingCover) - - // A small arrow on the next card for when peaking. - let ratio: CGFloat = 0.015 - peakingLeftArrow.translatesAutoresizingMaskIntoConstraints = false - peakingLeftArrow.alpha = 0 - peakingLeftArrow.tintColor = .black - contentView.addSubview(peakingLeftArrow) - peakingLeftArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true - NSLayoutConstraint.scalingPinViewLeft(toSuper: peakingLeftArrow, ratio: ratio, anchor: contentView.widthAnchor) - - peakingRightArrow.translatesAutoresizingMaskIntoConstraints = false - peakingRightArrow.transform = CGAffineTransform(scaleX: -1, y: 1) // Flip - peakingRightArrow.alpha = 0 - peakingRightArrow.tintColor = .black - contentView.addSubview(peakingRightArrow) - peakingRightArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true - NSLayoutConstraint.scalingPinViewRight(toSuper: peakingRightArrow, ratio: ratio, anchor: contentView.widthAnchor) - } - - - public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let collectionModel = model as? CarouselItemModel else { return } - if let useHorizontalMargins = collectionModel.useHorizontalMargins { - updateViewHorizontalDefaults = useHorizontalMargins - } - if let useVerticalMargins = collectionModel.useVerticalMargins { - updateViewVerticalDefaults = useVerticalMargins - } - // Handles peaking. - allowsPeaking = collectionModel.peakingUI ?? false - if let peakingArrowColor = collectionModel.peakingArrowColor { - let color = peakingArrowColor.uiColor - peakingLeftArrow.tintColor = color - peakingRightArrow.tintColor = color - } - - if let backgroundColor = collectionModel.backgroundColor { - self.backgroundColor = backgroundColor.uiColor - } +/// A collection item that is a container for any molecule. +open class MoleculeCollectionViewCell: CollectionViewCell { + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let collectionModel = model as? MoleculeCollectionItemModel else { return } if molecule == nil { if let moleculeView = MoleculeObjectMapping.shared()?.createMolecule(collectionModel.molecule, delegateObject: delegateObject, additionalData: additionalData) { - contentView.insertSubview(moleculeView, at: 0) - containerHelper.constrainView(moleculeView) - molecule = moleculeView + addMolecule(moleculeView) } } else { molecule?.set(with: collectionModel.molecule, delegateObject, additionalData) @@ -102,39 +27,33 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MoleculeViewProtoco accessibilityElements = molecule.subviews } - public func reset() { + open override func reset() { + super.reset() molecule?.reset() - updateViewVerticalDefaults = true - updateViewHorizontalDefaults = true backgroundColor = .white } - public class func nameForReuse(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { - guard let molecule = (model as? CarouselItemModel)?.molecule, - let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(molecule) else { - return nil - } - return moleculeClass.nameForReuse(with: molecule, delegateObject) ?? molecule.moleculeName + open class func nameForReuse(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let moleculeModel = (model as? MoleculeCollectionItemModel)?.molecule else { return "\(self)<>" } + let className = MoleculeObjectMapping.shared()?.getMoleculeClass(moleculeModel) + let moleculeName = className?.nameForReuse(with: moleculeModel, delegateObject) ?? moleculeModel.moleculeName + return "\(self)<\(moleculeName)>" } - public func updateView(_ size: CGFloat) { - (molecule as? MVMCoreViewProtocol)?.updateView(size) - MFStyler.setDefaultMarginsFor(contentView, size: size, horizontal: updateViewHorizontalDefaults, vertical: updateViewVerticalDefaults) + open class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + guard let moleculeModel = (model as? MoleculeCollectionItemModel)?.molecule, + let theClass = MoleculeObjectMapping.shared()?.getMoleculeClass(moleculeModel) + else { return nil } + + return theClass.requiredModules(with: moleculeModel, delegateObject, error: error) } - public func setPeaking(_ peaking: Bool, animated: Bool) { - guard allowsPeaking else { - return - } - let animation = {() in - self.peakingRightArrow.alpha = peaking ? 1 : 0 - self.peakingLeftArrow.alpha = peaking ? 1 : 0 - self.peakingCover.alpha = peaking ? 0.5 : 0 - } - if animated { - UIView.animate(withDuration: 0.4, animations: animation) - } else { - animation() - } + open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + guard let model = model as? MoleculeCollectionItemModel, + let classType = MoleculeObjectMapping.shared()?.getMoleculeClass(model.molecule), + let height = classType.estimatedHeight(with: model.molecule, delegateObject) + else { return 100 } + + return height + (model.topMarginPadding ?? 0) + (model.bottomMarginPadding ?? 0) } } diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index a7117e33..1ed2ecd2 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -52,12 +52,37 @@ open class Carousel: View { public var delegateObject: MVMCoreUIDelegateObject? + private var size: CGFloat? + + // Updates the model and index. + public func updateModelIndex() { + (model as? CarouselModel)?.index = pageIndex + } + + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + DispatchQueue.main.async { + self.layoutCollection() + } + } + + /// Invalidates the layout and ensures we are paged to the correct cell. + open func layoutCollection() { + collectionView.collectionViewLayout.invalidateLayout() + showPeaking(false) + + // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. + DispatchQueue.main.async { + self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) + self.collectionView.layoutIfNeeded() + self.showPeaking(true) + } + } + // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() - guard collectionView.superview == nil else { - return - } collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.dataSource = self collectionView.delegate = self @@ -73,15 +98,13 @@ open class Carousel: View { open override func updateView(_ size: CGFloat) { super.updateView(size) - collectionView.collectionViewLayout.invalidateLayout() - showPeaking(false) - - // Go to current cell. layoutIfNeeded is needed otherwise cellForItem returns nil for peaking logic. The dispatch is a sad way to ensure the collection view is ready to be scrolled. - DispatchQueue.main.async { - self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: false) - self.collectionView.layoutIfNeeded() - self.showPeaking(true) + self.size = size + + // Update cells and re-layout. + for cell in collectionView.visibleCells { + (cell as? MVMCoreViewProtocol)?.updateView(size) } + layoutCollection() } // MARK: - MoleculeViewProtocol @@ -108,6 +131,9 @@ open class Carousel: View { } setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject) + + pageIndex = carouselModel.index + pagingView?.setPage(carouselModel.index) collectionView.reloadData() } @@ -143,7 +169,7 @@ open class Carousel: View { } /// Sets up the paging molecule - open func setupPagingMolecule(_ molecule: CarouselPagingModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) { + open func setupPagingMolecule(_ molecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)?, delegateObject: MVMCoreUIDelegateObject?) { var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil if let molecule = molecule { pagingView = MoleculeObjectMapping.shared()?.createMolecule(molecule, delegateObject: delegateObject) as? (UIView & MVMCoreUIPagingProtocol) @@ -197,6 +223,7 @@ open class Carousel: View { } let currentPage = pager.currentPage() localSelf.pageIndex = currentPage + localSelf.updateModelIndex() localSelf.goTo(localSelf.currentIndex, animated: !UIAccessibility.isVoiceOverRunning) }) } @@ -208,15 +235,15 @@ open class Carousel: View { // Show overlay and arrow in peaking Cell let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row } if let firstItem = visibleItemsPaths.first, firstItem.row != currentIndex { - (collectionView.cellForItem(at: firstItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true) + (collectionView.cellForItem(at: firstItem) as? CarouselItem)?.setPeaking(true, animated: true) } if let lastItem = visibleItemsPaths.last, lastItem.row != currentIndex { - (collectionView.cellForItem(at: lastItem) as? MoleculeCollectionViewCell)?.setPeaking(true, animated: true) + (collectionView.cellForItem(at: lastItem) as? CarouselItem)?.setPeaking(true, animated: true) } } else { // Hide peaking. for item in collectionView.visibleCells { - (item as? MoleculeCollectionViewCell)?.setPeaking(false, animated: true) + (item as? CarouselItem)?.setPeaking(false, animated: true) } } } @@ -229,10 +256,12 @@ open class Carousel: View { cell.accessibilityElementsHidden = false var array = cell.accessibilityElements - if let acc = pagingView?.accessibilityElements { - array?.append(contentsOf: acc) - } else { - array?.append(pagingView!) + if let pagingView = pagingView { + if let acc = pagingView.accessibilityElements { + array?.append(contentsOf: acc) + } else { + array?.append(pagingView) + } } self.accessibilityElements = array @@ -249,7 +278,7 @@ extension Carousel: UICollectionViewDelegateFlowLayout { } open func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - (cell as? MoleculeCollectionViewCell)?.setPeaking(false, animated: false) + (cell as? CarouselItem)?.setPeaking(false, animated: false) } } @@ -268,7 +297,7 @@ extension Carousel: UICollectionViewDataSource { protocolCell.reset() protocolCell.set(with: moleculeInfo.molecule, delegateObject, nil) } - (cell as? MVMCoreViewProtocol)?.updateView(collectionView.bounds.width) + (cell as? MVMCoreViewProtocol)?.updateView(size ?? collectionView.bounds.width) setAccessiblity(cell, index: indexPath.row) return cell } @@ -278,19 +307,18 @@ extension Carousel: UIScrollViewDelegate { func goTo(_ index: Int, animated: Bool) { showPeaking(false) - setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) - self.currentIndex = index - self.collectionView.scrollToItem(at: IndexPath(row: self.currentIndex, section: 0), at: self.itemAlignment, animated: animated) - if let cell = collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)) { - setAccessiblity(collectionView.cellForItem(at: IndexPath(row: self.currentIndex, section: 0)), index: index) + setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index) + currentIndex = index + updateModelIndex() + collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: itemAlignment, animated: animated) + if let cell = collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)) { + setAccessiblity(collectionView.cellForItem(at: IndexPath(row: currentIndex, section: 0)), index: index) UIAccessibility.post(notification: .layoutChanged, argument: cell) } } func handleUserOnBufferCell() { - guard loop else { - return - } + guard loop else { return } let lastPageIndex = numberOfPages + 1 let goToIndex = {(index: Int) in @@ -318,9 +346,11 @@ extension Carousel: UIScrollViewDelegate { let index = scrollView.contentOffset.x / (itemWidth + separatorWidth) let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 if index < 1 { - self.currentIndex = 0 + currentIndex = 0 + updateModelIndex() } else if index > CGFloat(lastCellIndex - 1) { - self.currentIndex = lastCellIndex + currentIndex = lastCellIndex + updateModelIndex() } } @@ -330,7 +360,7 @@ extension Carousel: UIScrollViewDelegate { open func scrollViewDidScroll(_ scrollView: UIScrollView) { // Check if the user is dragging the card even further past the next card. - checkForDraggingOutOfBounds(scrollView) + //checkForDraggingOutOfBounds(scrollView) // Let the pager know our progress if needed. pagingView?.scrollViewDidScroll?(collectionView) @@ -346,9 +376,7 @@ extension Carousel: UIScrollViewDelegate { targetContentOffset.pointee = scrollView.contentOffset // This is for setting up smooth custom paging. (Since UICollectionView only handles paging based on collection view size and not cell size). - guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { - return - } + guard let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing else { return } // We switch cards if we pass the velocity threshold or position threshold (currently 50%). let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) diff --git a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift index 8b987a73..c5f73a01 100644 --- a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift @@ -12,14 +12,14 @@ import UIKit public static var identifier: String = "carousel" public var backgroundColor: Color? public var molecules: [CarouselItemModel] - + public var index: Int = 0 public var spacing: Float? public var border: Bool? public var loop: Bool? public var height: Float? public var itemWidthPercent: Float? public var itemAlignment: UICollectionView.ScrollPosition? - public var pagingMolecule: CarouselPagingModelProtocol? + public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)? public init(molecules: [CarouselItemModel]){ self.molecules = molecules @@ -29,6 +29,7 @@ import UIKit case moleculeName case backgroundColor case molecules + case index case spacing case border case loop @@ -40,15 +41,16 @@ import UIKit required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - self.molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) - self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - self.spacing = try typeContainer.decode(Float.self, forKey: .spacing) - self.border = try typeContainer.decode(Bool.self, forKey: .border) - self.loop = try typeContainer.decode(Bool.self, forKey: .loop) - self.height = try typeContainer.decode(Float.self, forKey: .height) - self.itemWidthPercent = try typeContainer.decode(Float.self, forKey: .itemWidthPercent) - self.itemAlignment = try typeContainer.decode(UICollectionView.ScrollPosition.self, forKey: .itemAlignment) - self.pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule) + molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) + index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0 + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing) + border = try typeContainer.decodeIfPresent(Bool.self, forKey: .border) + loop = try typeContainer.decodeIfPresent(Bool.self, forKey: .loop) + height = try typeContainer.decodeIfPresent(Float.self, forKey: .height) + itemWidthPercent = try typeContainer.decodeIfPresent(Float.self, forKey: .itemWidthPercent) + itemAlignment = try typeContainer.decodeIfPresent(UICollectionView.ScrollPosition.self, forKey: .itemAlignment) + pagingMolecule = try typeContainer.decodeModelIfPresent(codingKey: .pagingMolecule) } public func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselPagingModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselPagingModelProtocol.swift index 94b1277a..72138290 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselPagingModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselPagingModelProtocol.swift @@ -9,6 +9,6 @@ import Foundation -public protocol CarouselPagingModelProtocol: MoleculeModelProtocol { +public protocol CarouselPagingModelProtocol { var position: Float? { get } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift new file mode 100644 index 00000000..1d2f07a6 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -0,0 +1,195 @@ +// +// CollectionTemplate.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc open class CollectionTemplate: ThreeLayerCollectionViewController, TemplateProtocol { + public typealias TemplateModel = CollectionTemplateModel + public var templateModel: CollectionTemplateModel? + + public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]? + + var observer: NSKeyValueObservation? + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + open override func parsePageJSON() throws { + try parseTemplate(json: loadObject?.pageJSON) + try super.parsePageJSON() + } + + open override var loadObject: MVMCoreLoadObject? { + didSet { + guard loadObject != oldValue else { return } + + updateRequiredModules() + observer?.invalidate() + if let newObject = loadObject { + observer = newObject.observe(\MVMCoreLoadObject.pageJSON, options: [.old, .new]) { [weak self] object, change in + self?.updateRequiredModules() + } + } + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + open override func viewForTop() -> UIView? { + guard let headerModel = templateModel?.header, + let molecule = MoleculeObjectMapping.shared()?.createMolecule(headerModel, delegateObject: delegateObjectIVar) + else { return super.viewForTop() } + + // Temporary, Default the horizontal padding + if var container = templateModel?.header as? ContainerModelProtocol, container.useHorizontalMargins == nil { + container.useHorizontalMargins = true + } + + return molecule + } + + override open func viewForBottom() -> UIView? { + guard let footerModel = templateModel?.footer, + let molecule = MoleculeObjectMapping.shared()?.createMolecule(footerModel, delegateObject: delegateObjectIVar) + else { return super.viewForBottom() } + + return molecule + } + + open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer) -> Bool { + guard super.shouldFinishProcessingLoad(loadObject, error: error) else { return false } + + // This template requires atleast one of the three layers. + if templateModel?.header == nil, + templateModel?.molecules?.count ?? 0 == 0, + templateModel?.footer == nil, + let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), messageToLog: "Collection template requires atleast one of the following: header, footer, molecules", code: CoreUIErrorCode.ErrorCodeListMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) { + error.pointee = errorObject + return false + } + return true + } + + + open override func handleNewData() { + setup() + registerCells() + super.handleNewData() + } + + //-------------------------------------------------- + // MARK: - Collection + //-------------------------------------------------- + + open override func update(cell: UICollectionViewCell, size: CGFloat) { + super.update(cell: cell, size: size) + + // Update the width for columns. + if let collectionView = collectionView, + let columns = templateModel?.columns, columns > 0, + let cell = cell as? CollectionTemplateItemProtocol { + let width = (size - collectionView.adjustedContentInset.left - collectionView.adjustedContentInset.right) / CGFloat(columns) + cell.set(width: width) + } + } + + open override func registerCells() { + super.registerCells() + guard let moleculesInfo = moleculesInfo else { return } + + for moleculeInfo in moleculesInfo { + collectionView?.register(moleculeInfo.class, forCellWithReuseIdentifier: moleculeInfo.identifier) + } + } + + open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return moleculesInfo?.count ?? 0 + } + + open override func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + open override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let moleculeInfo = moleculesInfo?[indexPath.row] + else { return UICollectionViewCell() } + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath) + (cell as? MoleculeViewProtocol)?.reset() + (cell as? MoleculeViewProtocol)?.set(with: moleculeInfo.molecule, delegateObjectIVar, nil) + update(cell: cell, size: view.frame.width) + // Neded to fix an apple defect where the cell is not the correct size on certain devices for certain cells + cell.layoutIfNeeded() + return cell + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + (collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil) + } + + public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + (cell as? CollectionTemplateItemProtocol)?.willDisplay() + } + + //-------------------------------------------------- + // MARK: - Convenience + //-------------------------------------------------- + + /// Returns the (identifier, class) of the molecule for the given map. + open func getMoleculeInfo(with item: (CollectionItemModelProtocol & MoleculeModelProtocol)?) -> (identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)? { + guard let item = item, + let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(item) else { return nil } + let moleculeName = moleculeClass.nameForReuse(with: item, delegateObjectIVar) ?? item.moleculeName + return (moleculeName, moleculeClass, item) + } + + /// Sets up the molecule list and ensures no errors loading all content. + open func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]? { + + var moleculeList: [(identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)] = [] + + if let molecules = templateModel?.molecules { + for molecule in molecules { + if let info = getMoleculeInfo(with: molecule) { + moleculeList.append(info) + } + } + } + + return moleculeList.count > 0 ? moleculeList : nil + } + + /// Sets up the header, footer, molecule list and ensures no errors loading all content. + open func setup() { + moleculesInfo = getMoleculeInfoList() + } + + /// Adds modules from requiredModules() to the MVMCoreViewControllerMapping.requiredModules map. + open func updateRequiredModules() { + if let requiredModules = requiredModules(), let pageType = pageType { + MVMCoreViewControllerMappingObject.shared()?.addRequiredModules(toMapping: requiredModules, forPageType: pageType) + } + } + + /// Gets modules required by the loadObject.pageJSON. + open func requiredModules() -> [Any]? { + var modules: [String]? = [] + var errors: [MVMCoreErrorObject]? = nil + MoleculeObjectMapping.addRequiredModules(for: templateModel?.header, delegateObjectIVar, moduleList: &modules, errorList: &errors) + MoleculeObjectMapping.addRequiredModules(for: templateModel?.footer, delegateObjectIVar, moduleList: &modules, errorList: &errors) + + if let molecules = templateModel?.molecules { + for molecule in molecules { + MoleculeObjectMapping.addRequiredModules(for: molecule, delegateObjectIVar, moduleList: &modules, errorList: &errors) + } + } + + return modules + } +} diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift new file mode 100644 index 00000000..55db2c6d --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateItemProtocol.swift @@ -0,0 +1,29 @@ +// +// CollectionTemplateItemProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/9/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// A protocol that items of the CollectionTemplate must conform to. +public protocol CollectionTemplateItemProtocol: UICollectionViewCell { + + /// Set the width of the item. Used for the columns functionality + func set(width: CGFloat) + + /// Handle action when cell is pressed + func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) + + /// Called when the cell will display. + func willDisplay() +} + +// Default implementation does nothing +extension CollectionTemplateItemProtocol { + public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {} + + public func willDisplay() {} +} diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift new file mode 100644 index 00000000..56c56456 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift @@ -0,0 +1,67 @@ +// +// CollectionTemplateModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class CollectionTemplateModel: TemplateModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public override class var identifier: String { + return "collection" + } + public var header: MoleculeModelProtocol? + public var molecules: [CollectionItemModelProtocol & MoleculeModelProtocol]? + public var footer: MoleculeModelProtocol? + public var columns: Int? + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(pageType: String, screenHeading: String?, molecules: [CollectionItemModelProtocol & MoleculeModelProtocol]) { + super.init(pageType: pageType) + self.screenHeading = screenHeading + self.molecules = molecules + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case molecules + case header + case footer + case columns + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + molecules = try typeContainer.decodeModelsIfPresent(codingKey: .molecules) + header = try typeContainer.decodeModelIfPresent(codingKey: .header) + footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) + columns = try typeContainer.decodeIfPresent(Int.self, forKey: .columns) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeModelsIfPresent(molecules, forKey: .molecules) + try container.encodeModelIfPresent(header, forKey: .header) + try container.encodeModelIfPresent(footer, forKey: .footer) + try container.encodeIfPresent(columns, forKey: .columns) + } +} + diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 7575faf1..b236e332 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -81,9 +81,9 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } open override func handleNewData() { - super.handleNewData() setup() registerWithTable() + super.handleNewData() } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift index 8f963f5d..96e85fad 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift @@ -51,10 +51,6 @@ import UIKit return molecule } - open override func spaceBetweenMiddleAndBottom() -> CGFloat? { - return 0 - } - open override func spaceBetweenTopAndMiddle() -> CGFloat? { return 0 } diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift new file mode 100644 index 00000000..e11b350c --- /dev/null +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -0,0 +1,104 @@ +// +// CollectionViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// A base collection view cell with basic mvm functionality. +open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCoreViewProtocol, CollectionTemplateItemProtocol { + + // Convenience helpers + open var molecule: MoleculeViewProtocol? + public let containerHelper = ContainerHelper() + open var model: CollectionItemModelProtocol? + + /// The width, used for establishing columns + open var width: CGFloat? + + private var initialSetupPerformed = false + + // MARK: - Inits + public override init(frame: CGRect) { + super.init(frame: .zero) + initialSetup() + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initialSetup() + } + + private func initialSetup() { + if !initialSetupPerformed { + initialSetupPerformed = true + setupView() + } + } + + // MARK: - MVMCoreViewProtocol + open func setupView() { + isAccessibilityElement = false + contentView.isAccessibilityElement = false + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false + MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0) + } + + open func updateView(_ size: CGFloat) { + if let model = model as? ContainerModelProtocol { + containerHelper.updateViewMargins(contentView, model: model, size: size) + } + (molecule as? MVMCoreViewProtocol)?.updateView(size) + } + + open func reset() { + molecule?.reset() + backgroundColor = .white + width = nil + } + + // MARK: - MoleculeViewProtocol + open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? CollectionItemModelProtocol else { return } + self.model = model + + if let moleculeModel = model as? MoleculeModelProtocol, + let backgroundColor = moleculeModel.backgroundColor { + self.backgroundColor = backgroundColor.uiColor + } + + // align if needed. + if let model = model as? ContainerModelProtocol { + containerHelper.set(with: model, for: molecule as? MVMCoreUIViewConstrainingProtocol) + } + } + + /// Convenience function. Adds a molecule to the view. + open func addMolecule(_ molecule: MoleculeViewProtocol) { + contentView.addSubview(molecule) + containerHelper.constrainView(molecule) + self.molecule = molecule + } + + // MARK: - CollectionTemplateItemProtocol + public func set(width: CGFloat) { + self.width = width + } + + // Column logic, set width. + override open func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + let autoLayoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes) + guard let width = width else { return autoLayoutAttributes } + + let targetSize = CGSize(width: width, height: 0) + let newSize = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: UILayoutPriority.required, verticalFittingPriority: UILayoutPriority.defaultLow) + let newFrame = CGRect(origin: autoLayoutAttributes.frame.origin, size: newSize) + autoLayoutAttributes.frame = newFrame + return autoLayoutAttributes + } +} diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index ebb88977..d8acb6f6 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -8,6 +8,7 @@ import UIKit + @objcMembers open class Control: UIControl, MoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties diff --git a/MVMCoreUI/BaseClasses/Protocols/CollectionItemModelProtocol.swift b/MVMCoreUI/BaseClasses/Protocols/CollectionItemModelProtocol.swift new file mode 100644 index 00000000..9361ab1e --- /dev/null +++ b/MVMCoreUI/BaseClasses/Protocols/CollectionItemModelProtocol.swift @@ -0,0 +1,13 @@ +// +// CollectionItemModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol CollectionItemModelProtocol { + +} diff --git a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift new file mode 100644 index 00000000..d7fb0ed4 --- /dev/null +++ b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift @@ -0,0 +1,30 @@ +// +// ContainerCollectionReusableView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/7/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// CollectionReusableView that can contains any other view. +public class ContainerCollectionReusableView: UICollectionReusableView { + public var view: UIView? + public var topConstraint: NSLayoutConstraint? + public var bottomConstraint: NSLayoutConstraint? + + public func addAndContain(view: UIView) { + guard self.view != view else { return } + self.view?.removeFromSuperview() + view.setContentCompressionResistancePriority(.required, for: .vertical) + addSubview(view) + self.view = view + topConstraint = view.topAnchor.constraint(equalTo: topAnchor) + topConstraint?.isActive = true + bottomConstraint = bottomAnchor.constraint(equalTo: view.bottomAnchor) + bottomConstraint?.isActive = true + rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + view.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + } +} diff --git a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift new file mode 100644 index 00000000..9d279d79 --- /dev/null +++ b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift @@ -0,0 +1,81 @@ +// +// ProgrammaticCollectionViewController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/8/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// A base view controller with a collection view. +@objc open class ProgrammaticCollectionViewController: ScrollingViewController { + + public var collectionView: UICollectionView? + + open override func loadView() { + let view = UIView() + view.backgroundColor = .white + + let collection = createCollectionView() + view.addSubview(collection) + NSLayoutConstraint.constraintPinSubview(toSuperview: collection) + + collectionView = collection + scrollView = collectionView + self.view = view + } + + /// A place to register cells with the collectionView + open func registerCells() {} + + open override func viewDidLoad() { + super.viewDidLoad() + registerCells() + } + + /// Creates the layout for the collection. + open func createCollectionViewLayout() -> UICollectionViewLayout { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = 0 + layout.minimumInteritemSpacing = 0 + layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + return layout + } + + /// Creates the collection view. + open func createCollectionView() -> UICollectionView { + let collection = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) + collection.translatesAutoresizingMaskIntoConstraints = false + collection.dataSource = self + collection.delegate = self + collection.showsHorizontalScrollIndicator = false + collection.backgroundColor = .white + collection.isAccessibilityElement = false + collection.contentInsetAdjustmentBehavior = .always + return collection + } + + deinit { + collectionView?.delegate = nil + collectionView?.dataSource = nil + } +} + +extension ProgrammaticCollectionViewController: UICollectionViewDataSource { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 0 + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + return UICollectionViewCell() + } + + open func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } +} + +extension ProgrammaticCollectionViewController: UICollectionViewDelegate { +} diff --git a/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift index 4e262398..b27845b3 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift @@ -42,6 +42,11 @@ open class ProgrammaticTableViewController: ProgrammaticScrollViewController, UI self.view = view } + open override func viewDidLoad() { + super.viewDidLoad() + registerWithTable() + } + /// This class should create the table view that will be used here. Subclass this for different table styles. open func createTableView() -> UITableView { let tableView = UITableView(frame: .zero, style: .grouped) diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift new file mode 100644 index 00000000..aa34f2f2 --- /dev/null +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -0,0 +1,223 @@ +// +// ThreeLayerCollectionViewController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// A view controller that has three main layers, a header, collection rows, and a footer. The header is added as a supplement header to the first section, and the footer is added as a supplement footer to the last section. This view controller allows for flexible space between the three layers to fit the screeen. +@objc open class ThreeLayerCollectionViewController: ProgrammaticCollectionViewController, UICollectionViewDelegateFlowLayout { + + private var topView: UIView? + private var bottomView: UIView? + private var headerView: ContainerCollectionReusableView? + private var footerView: ContainerCollectionReusableView? + private let headerID = "header" + private let footerID = "footer" + + /// Updates the padding for flexible space (header or footer) + private func updateFlexibleSpace() { + guard let tableView = collectionView else { return } + + let minimumSpace: CGFloat = minimumFillSpace() + var currentSpace: CGFloat = 0 + var totalMinimumSpace: CGFloat = 0 + + var fillTop = false + if spaceBelowTopView() == nil, headerView != nil { + fillTop = true + currentSpace += headerView?.bottomConstraint?.constant ?? 0 + totalMinimumSpace += minimumSpace + } + + var fillBottom = false + if spaceAboveBottomView() == nil, footerView != nil { + fillBottom = true + currentSpace += footerView?.topConstraint?.constant ?? 0 + totalMinimumSpace += minimumSpace + } + + guard fillTop || fillBottom else { return } + + let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) + + // If the bottom view is outside of the scroll, then only the top view constraint is being used, so we have to double it to account for the bottom constraint not being there when we compare to the new value. + var currentSpaceForCompare: CGFloat = currentSpace + if fillTop { + currentSpaceForCompare = currentSpace * 2; + } + + if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 1) { + if fillTop && fillBottom { + // space both + let half = newSpace / 2 + headerView?.bottomConstraint?.constant = half + footerView?.topConstraint?.constant = half + collectionView?.collectionViewLayout.invalidateLayout() + } else if fillTop { + // Only top is spaced (half the size if the bottom view is out of the scroll because it needs to be sized as if there are two spacers but there is only one. + headerView?.bottomConstraint?.constant = newSpace + collectionView?.collectionViewLayout.invalidateLayout() + } else if fillBottom { + // Only bottom is spaced. + footerView?.topConstraint?.constant = newSpace + collectionView?.collectionViewLayout.invalidateLayout() + } + } + } + + //MARK: - ViewController + open override func updateViews() { + super.updateViews() + // Needed due to dispatch in reloadCollectionData. + DispatchQueue.main.async { + let width = self.view.bounds.width + if let topView = self.topView as? MVMCoreViewProtocol { + topView.updateView(width) + } + if let bottomView = self.bottomView as? MVMCoreViewProtocol { + bottomView.updateView(width) + } + if let cells = self.collectionView?.visibleCells { + for cell in cells { + self.update(cell: cell, size: width) + } + } + self.invalidateCollectionLayout() + } + } + + open override func handleNewData() { + super.handleNewData() + createViewForHeader() + createViewForFooter() + reloadCollectionData() + } + + override open func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view. + } + + //MARK: - Spacing + // If both are subclassed to return a value, then the buttons will not be pinned towards the bottom because neither spacing would try to fill the screen. + /// Space between the top view and the collection rows, nil to fill. 0 default + open func spaceBelowTopView() -> CGFloat? { + return 0 + } + + /// Space between the bottom view and the collection rows, nil to fill. nil default + open func spaceAboveBottomView() -> CGFloat? { + return nil + } + + /// can override to return a minimum fill space. 0 default + open func minimumFillSpace() -> CGFloat { + return 0 + } + + //MARK: - Header Footer + /// Creates the top view. + open func createViewForHeader() { + guard let topView = viewForTop() else { + self.topView = nil + self.headerView = nil + return + } + self.topView = topView + } + + /// Creates the footer + open func createViewForFooter() { + guard let bottomView = viewForBottom() else { + self.bottomView = nil + self.footerView = nil + return + } + self.bottomView = bottomView + } + + //MARK: - Functions to subclass + /// Subclass for a top view. + open func viewForTop() -> UIView? { + return nil + } + + /// Subclass for a bottom view. + open func viewForBottom() -> UIView? { + return nil + } + + //MARK: - Collection + + /// Should be used to refresh the layout of the collection view. Updates flexible padding. + open func invalidateCollectionLayout() { + self.collectionView?.collectionViewLayout.invalidateLayout() + // TODO: Improve this workaround (autolayout for cells happens async so contentSize isn't accurate immediately) + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { + self.updateFlexibleSpace() + }) + } + + /// Should be used to reload the data of the collection view. Updates flexible padding. + open func reloadCollectionData() { + collectionView?.reloadData() + // TODO: Improve this workaround (autolayout for cells happens async so contentSize isn't accurate immediately) + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: { + self.updateFlexibleSpace() + }) + } + + /// Called in updateView, updates the cell. + open func update(cell: UICollectionViewCell, size: CGFloat) { + (cell as? MVMCoreViewProtocol)?.updateView(size) + } + + open override func registerCells() { + super.registerCells() + collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID) + collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: footerID) + } + + open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 0 + } + + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { + guard let _ = topView, + section == 0 else { return .zero } + + // Calculate the height of the header since apple doesn't support autolayout. Width is fixed, height is tall as content. + let header = headerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: section)) + return header.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + } + + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { + guard let _ = bottomView, + section == numberOfSections(in: collectionView) - 1 else { return .zero } + + // Calculate the height of the footr since apple doesn't support autolayout. Width is fixed, height is tall as content. + let footer = footerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionFooter, at: IndexPath(row: 0, section: section)) + return footer.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + } + + open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + if kind == UICollectionView.elementKindSectionFooter, + let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footerID, for: indexPath) as? ContainerCollectionReusableView { + footerView.addAndContain(view: bottomView!) + footerView.topConstraint?.constant = spaceAboveBottomView() ?? 0 + self.footerView = footerView + return footerView + } else if kind == UICollectionView.elementKindSectionHeader, + let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? ContainerCollectionReusableView { + headerView.addAndContain(view: topView!) + headerView.bottomConstraint?.constant = spaceBelowTopView() ?? 0 + self.headerView = headerView + return headerView + } + fatalError() + } +} diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index c0f267fc..000f2e7f 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -33,7 +33,7 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { bottomView.updateView(width) showFooter(width) } - self.tableView?.reloadData() + tableView?.reloadData() } open override func handleNewData() { diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index ab143b14..eb4be908 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -24,7 +24,7 @@ import UIKit public var formValidator: FormValidator? - public var needsUpdateUI = true + public var needsUpdateUI = false private var observingForResponses = false private var initialLoadFinished = false private var previousScreenSize = CGSize.zero @@ -151,8 +151,8 @@ import UIKit /// Calls processNewData and then sets the ui to update with updateView open func handleNewDataAndUpdateUI() { handleNewData() - self.needsUpdateUI = true - self.view.setNeedsLayout() + needsUpdateUI = true + view.setNeedsLayout() } /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, @@ -166,9 +166,9 @@ import UIKit navigationModel.line = LineModel(type: .none) } pageModel?.navigationItem = navigationModel - if self.formValidator == nil { + if formValidator == nil { let rules = pageModel?.formRules - self.formValidator = FormValidator(rules) + formValidator = FormValidator(rules) } } @@ -265,10 +265,10 @@ import UIKit initialLoad() } - // Handle data on load - handleNewData() - - view.setNeedsLayout() + // Handle data for first load. Dispatched to allow subclasses to finish their view did load implementations. + DispatchQueue.main.async { + self.handleNewDataAndUpdateUI() + } } open override func viewDidLayoutSubviews() { @@ -278,7 +278,8 @@ import UIKit return } - if needsUpdateUI || screenSizeChanged() { + // First update should be explicit (hence the zero check) + if needsUpdateUI || (previousScreenSize != .zero && screenSizeChanged()) { updateViews() needsUpdateUI = false } diff --git a/MVMCoreUI/Categories/UIColor+MFConvenience.h b/MVMCoreUI/Categories/UIColor+MFConvenience.h index c3c78c7a..92b28a36 100644 --- a/MVMCoreUI/Categories/UIColor+MFConvenience.h +++ b/MVMCoreUI/Categories/UIColor+MFConvenience.h @@ -24,6 +24,7 @@ + (nonnull UIColor *)mvmOrange; + (nonnull UIColor *)mfPumpkinColor; + (nonnull UIColor *)mfShamrock; ++ (nonnull UIColor *)mvmGreen; + (nonnull UIColor *)mfCerulean; + (nonnull UIColor *)mfWhiteTwo; diff --git a/MVMCoreUI/Categories/UIColor+MFConvenience.m b/MVMCoreUI/Categories/UIColor+MFConvenience.m index 6ff8f70f..33df3848 100644 --- a/MVMCoreUI/Categories/UIColor+MFConvenience.m +++ b/MVMCoreUI/Categories/UIColor+MFConvenience.m @@ -61,6 +61,10 @@ return [UIColor mfColor8bitsWithRed:0 green:134 blue:49 alpha:1.0]; } ++ (nonnull UIColor *)mvmGreen { + return [UIColor mfColor8bitsWithRed:0 green:134 blue:48 alpha:1.0]; +} + + (nonnull UIColor *)mfCerulean { return [UIColor mfColor8bitsWithRed:0 green:122 blue:184 alpha:1.0]; } diff --git a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift index cf56fb9d..3a8fac64 100644 --- a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift @@ -17,7 +17,7 @@ import UIKit /// The bottom border line. Height is dynamic based on scenario. public var bottomBar: CAShapeLayer? = { let layer = CAShapeLayer() - layer.backgroundColor = UIColor.black.cgColor + layer.backgroundColor = UIColor.mvmBlack.cgColor layer.drawsAsynchronously = true layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); return layer @@ -46,7 +46,7 @@ import UIKit /// Determines if the top, left, and right borders should be drawn. private var hideBorders = false - public var borderStrokeColor: UIColor = .mfSilver() + public var borderStrokeColor: UIColor = .mvmCoolGray3 private var borderPath: UIBezierPath = UIBezierPath() //-------------------------------------------------- @@ -210,8 +210,8 @@ import UIKit isUserInteractionEnabled = true hideBorders = false - borderStrokeColor = .mfSilver() - bottomBar?.backgroundColor = UIColor.black.cgColor + borderStrokeColor = .mvmCoolGray3 + bottomBar?.backgroundColor = UIColor.mvmBlack.cgColor refreshUI(bottomBarSize: 1) } @@ -219,8 +219,8 @@ import UIKit isUserInteractionEnabled = true hideBorders = false - borderStrokeColor = .mfPumpkin() - bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor + borderStrokeColor = .mvmOrange + bottomBar?.backgroundColor = UIColor.mvmOrange.cgColor refreshUI(bottomBarSize: 4) } @@ -228,8 +228,8 @@ import UIKit isUserInteractionEnabled = true hideBorders = false - borderStrokeColor = .black - bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor + borderStrokeColor = .mvmBlack + bottomBar?.backgroundColor = UIColor.mvmOrange.cgColor refreshUI(bottomBarSize: 4) } @@ -237,8 +237,8 @@ import UIKit isUserInteractionEnabled = true hideBorders = false - borderStrokeColor = .black - bottomBar?.backgroundColor = UIColor.black.cgColor + borderStrokeColor = .mvmBlack + bottomBar?.backgroundColor = UIColor.mvmBlack.cgColor refreshUI(bottomBarSize: 1) } @@ -255,7 +255,7 @@ import UIKit isUserInteractionEnabled = false hideBorders = false - borderStrokeColor = .mfSilver() + borderStrokeColor = .mvmCoolGray3 bottomBar?.backgroundColor = UIColor.mvmCoolGray3.cgColor refreshUI(bottomBarSize: 1) } diff --git a/MVMCoreUI/FormUIHelpers/FormValidator.swift b/MVMCoreUI/FormUIHelpers/FormValidator.swift index 592132f5..06c8cdf1 100644 --- a/MVMCoreUI/FormUIHelpers/FormValidator.swift +++ b/MVMCoreUI/FormUIHelpers/FormValidator.swift @@ -9,8 +9,12 @@ import UIKit import MVMCore -@objcMembers public class FormValidator: NSObject { +@objcMembers public class FormValidator: NSObject { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + static var defaultGroupName: String = "default" var formRules: [FormGroupRule]? weak var delegate: FormHolderProtocol? @@ -18,10 +22,18 @@ import MVMCore var groupWatchers: [FormGroupWatcherFieldProtocol] = [] var radioButtonsModelByGroup: [String: RadioButtonSelectionHelper] = [:] + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public init(_ formRules: [FormGroupRule]?) { self.formRules = formRules } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + /// Adds the form field to the validator. public func add(_ field: FormFieldProtocol) { if let fieldKey = field.fieldKey { diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift index 042243db..7cf24cd5 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleAnyValueChangedModel.swift @@ -8,25 +8,34 @@ import Foundation -public class RuleAnyValueChangedModel: RulesProtocol { +public class RuleAnyValueChangedModel: RulesProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "anyValueChanged" public var type: String = RuleAnyValueChangedModel.identifier public var fields: [String] + //-------------------------------------------------- + // MARK: - Validation + //-------------------------------------------------- + public func isValid(_ formField: FormFieldProtocol) -> Bool { return formField.baseValue != formField.formFieldValue() } public func isValid(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { + for formKey in fields { - guard let formField = fieldMolecules[formKey] else { - continue - } + guard let formField = fieldMolecules[formKey] else { continue } + if isValid(formField) { return true } } + return false } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m index d3db2a2d..20fb924e 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m @@ -24,7 +24,8 @@ @"list" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]], @"threeLayer" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ThreeLayerTemplate class]], @"modalStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeStackTemplate class]], - @"modalList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeListTemplate class]] + @"modalList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeListTemplate class]], + @"collection" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[CollectionTemplate class]] } mutableCopy]; }); return viewControllerMapping; diff --git a/MVMCoreUI/Styles/Padding.swift b/MVMCoreUI/Styles/Padding.swift new file mode 100644 index 00000000..0dd9ff6e --- /dev/null +++ b/MVMCoreUI/Styles/Padding.swift @@ -0,0 +1,48 @@ +// +// Padding.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 4/1/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +/// Padding is a multiple based on the number 4. +public struct Padding { + + public static let OneHalf: CGFloat = 2 + public static let One: CGFloat = 4 + public static let Two: CGFloat = 8 + public static let Three: CGFloat = 12 + public static let Four: CGFloat = 16 + public static let Five: CGFloat = 24 + public static let Eight: CGFloat = 32 + public static let Ten: CGFloat = 40 + public static let Twelve: CGFloat = 48 + public static let Eighteen: CGFloat = 72 + + public struct Component { + public static let Standard: CGFloat = 24 + public static let HorizontalMarginSpacing: CGFloat = 32 + public static let LargeVerticalMarginSpacing: CGFloat = 32 + public static let VerticalMarginSpacing: CGFloat = 24 + + public static var horizontalPaddingForApplicationWidth: CGFloat { + return MFSizeObject(scalingStandardSize: HorizontalMarginSpacing)?.getValueBasedOnApplicationWidth() ?? HorizontalMarginSpacing + } + + public static var verticalPaddingForApplicationWidth: CGFloat { + return MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBasedOnApplicationWidth() ?? VerticalMarginSpacing + } + + public static func horizontalPaddingForSize(_ size: CGFloat) -> CGFloat { + return MFSizeObject(scalingStandardSize: HorizontalMarginSpacing)?.getValueBased(onSize: size) ?? HorizontalMarginSpacing + } + + public static func verticalPaddingForSize(_ size: CGFloat) -> CGFloat { + return MFSizeObject(scalingStandardSize: VerticalMarginSpacing)?.getValueBased(onSize: size) ?? VerticalMarginSpacing + } + } +} diff --git a/MVMCoreUI/Styles/Styler.swift b/MVMCoreUI/Styles/Styler.swift new file mode 100644 index 00000000..b5d6627c --- /dev/null +++ b/MVMCoreUI/Styles/Styler.swift @@ -0,0 +1,228 @@ +// +// Styler.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 4/1/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +open class Styler { + //-------------------------------------------------- + // MARK: - Enums + //-------------------------------------------------- + + public enum Font: String, Codable { + case Title2XLarge + case TitleXLarge + case BoldTitleLarge + case RegularTitleLarge + case BoldTitleMedium + case RegularTitleMedium + case BoldBodyLarge + case RegularBodyLarge + case BoldBodySmall + case RegularBodySmall + case BoldMicro + case RegularMicro + + // Legacy Fonts + case H1 + case H2 + case H3 + case H32 + case B1 + case B2 + case B3 + case B20 + + /// Returns the font size of the current enum case. + public func pointSize() -> CGFloat { + switch self { + case .H1: + return 40 + + case .Title2XLarge: + return 36 + + case .TitleXLarge, + .H32: + return 32 + + case .H2: + return 25 + + case .BoldTitleLarge, + .RegularTitleLarge: + return 24 + + case .BoldTitleMedium, + .RegularTitleMedium, + .B20: + return 20 + + case .H3: + return 18 + + case .BoldBodyLarge, + .RegularBodyLarge: + return 16 + + case .BoldBodySmall, + .RegularBodySmall, + .B1, + .B2: + return 13 + + case .BoldMicro, + .RegularMicro, + .B3: + return 11 + } + } + + /// Determines if the selected font case is bold or regular. + public func isBold() -> Bool { + + switch self { + case .Title2XLarge, + .TitleXLarge, + .RegularTitleLarge, + .RegularTitleMedium, + .RegularBodyLarge, + .RegularBodySmall, + .RegularMicro, + .B2, + .B3, + .B20: + return false + + case .BoldTitleLarge, + .BoldTitleMedium, + .BoldBodyLarge, + .BoldBodySmall, + .BoldMicro, + .H1, + .H2, + .H3, + .H32, + .B1: + return true + } + } + + /// Determines if the current enum is a legacy or modern font. + public func isLegacyFont() -> Bool { + + switch self { + case .Title2XLarge, + .TitleXLarge, + .RegularTitleLarge, + .RegularTitleMedium, + .RegularBodyLarge, + .RegularBodySmall, + .RegularMicro, + .BoldTitleLarge, + .BoldTitleMedium, + .BoldBodyLarge, + .BoldBodySmall, + .BoldMicro: + return false + + case .H1, + .H2, + .H3, + .H32, + .B1, + .B2, + .B3, + .B20: + return true + } + } + + /// Returns the font based on the declared enum case. + public func getFont(_ genericScaling: Bool = true) -> UIFont? { + + let size = genericScaling ? sizeFontGeneric(forCurrentDevice: pointSize()) : pointSize() + + if isLegacyFont() { + switch self { + case .B2, .B3, .B20: + return MFFonts.mfFont55Rg(size) + + default: + return MFFonts.mfFont75Bd(size) + } + } else { + if isBold() { + return size >= 15 ? MFFonts.mfFontDSBold(size) : MFFonts.mfFontTXBold(size) + + } else { + return size >= 15 ? MFFonts.mfFontDSRegular(size) : MFFonts.mfFontTXRegular(size) + } + } + } + + /// Styles the provided label to the declared enum Font case. + public func styleLabel(_ label: UILabel, textColor: UIColor = .mvmBlack, genericScaling: Bool = true) { + + label.font = getFont(genericScaling) + label.textColor = textColor + } + } + + //-------------------------------------------------- + // MARK: - Functions + //-------------------------------------------------- + + open class func sizeObjectGeneric(forCurrentDevice size: CGFloat) -> MFSizeObject? { + + let sizeObject = MFSizeObject(standardSize: size, standardiPadPortraitSize: size * 1.3) + sizeObject?.addLargerThanCustomSize(size * 1.4, forThreshold: MFSizeStandardiPadLandscapeThreshold) + sizeObject?.addLargerThanCustomSize(size * 1.5, forThreshold: MFSizeiPadProLandscapeThreshold) + + return sizeObject + } + + open class func sizeFontGeneric(forCurrentDevice size: CGFloat) -> CGFloat { + return sizeObjectGeneric(forCurrentDevice: size)?.getValueBasedOnApplicationWidth() ?? size + } + + //-------------------------------------------------- + // MARK: - Spacing + //-------------------------------------------------- + + open class func setDefaultMarginsFor(_ view: UIView?, size: CGFloat?, horizontal: Bool = true, vertical: Bool = false) { + + var horizontalPadding: CGFloat = Padding.Component.HorizontalMarginSpacing + let verticalPadding: CGFloat = vertical ? Padding.Component.VerticalMarginSpacing : 0 + + if let size = size { + horizontalPadding = horizontal ? Padding.Component.horizontalPaddingForSize(size) : 0 + } + + DispatchQueue.main.async { + MVMCoreUIUtility.setMarginsFor(view, + leading: horizontalPadding, + top: verticalPadding, + trailing: horizontalPadding, + bottom: verticalPadding) + } + } + + open class func setMarginsFor(_ view: UIView?, size: CGFloat, horizontal: CGFloat?, top: CGFloat, bottom: CGFloat) { + + let horizontalPadding: CGFloat = horizontal ?? Padding.Component.horizontalPaddingForSize(size) + + DispatchQueue.main.async { + MVMCoreUIUtility.setMarginsFor(view, + leading: horizontalPadding, + top: top, + trailing: horizontalPadding, + bottom: bottom) + } + } +} diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m index 930828f6..4e79ee6f 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertView.m @@ -117,7 +117,7 @@ NSString * const MFAccTopAlertClosed = @"Top alert notification is closed."; if ([type isEqualToString:ValueTypeError]) { return [UIColor mvmOrange]; } else { - return [UIColor mfShamrock]; + return [UIColor mvmGreen]; } } diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index bcead7fe..0841e248 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -164,11 +164,10 @@ CGFloat topInset = scrollview.contentInset.top; CGFloat bottomInset = scrollview.contentInset.bottom; - if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { + if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic || scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAlways) { topInset = scrollview.adjustedContentInset.top; bottomInset = scrollview.adjustedContentInset.bottom; } - CGFloat remainingSpace = frameHeight - contentSizeHeight - topInset - bottomInset; return remainingSpace - 1; }