diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 3feedb73..ae599266 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -108,6 +108,14 @@ 94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */; }; 94C661DA23CCF4FB00D9FE5B /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B33239813C50067DD0F /* UIColor+Extension.swift */; }; C003506123AA94CD00B6AC29 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C003506023AA94CD00B6AC29 /* Button.swift */; }; + C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */; }; + C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A68023C9830D00BFB94E /* NumberedListModel.swift */; }; + C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A69323C9909000BFB94E /* DoughnutChartModel.swift */; }; + C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A69523C990BC00BFB94E /* DoughnutChart.swift */; }; + C695A69823C990C200BFB94E /* DoughnutChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A69723C990C200BFB94E /* DoughnutChartView.swift */; }; + C6FA7D5223C77A4A00A3614A /* UnOrderedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FA7D4F23C77A4700A3614A /* UnOrderedList.swift */; }; + C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FA7D5023C77A4800A3614A /* StringAndMoleculeStack.swift */; }; + C6FA7D5423C77A4A00A3614A /* NumberedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FA7D5123C77A4900A3614A /* NumberedList.swift */; }; C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; @@ -128,13 +136,20 @@ D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; }; D260105323CEA61600764D80 /* ToggleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105223CEA61600764D80 /* ToggleModel.swift */; }; D260105523CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */; }; + D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105823D0A92900764D80 /* ContainerProtocol.swift */; }; + D260105B23D0BB7100764D80 /* StackModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105A23D0BB7100764D80 /* StackModelProtocol.swift */; }; + D260105D23D0BCD400764D80 /* Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105C23D0BCD400764D80 /* Stack.swift */; }; + D260105F23D0BFFC00764D80 /* StackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105E23D0BFFC00764D80 /* StackItem.swift */; }; + D260106123D0C02A00764D80 /* StackItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106023D0C02A00764D80 /* StackItemModelProtocol.swift */; }; + D260106323D0C05000764D80 /* StackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106223D0C05000764D80 /* StackItemModel.swift */; }; + D260106523D0CEA700764D80 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.swift */; }; 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, ); }; }; - D268C70C2386DFFD007F2C1C /* StackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* StackItemModel.swift */; }; + D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D268C712238D6699007F2C1C /* DropDown.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C711238D6699007F2C1C /* DropDown.swift */; }; - D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; }; + D274CA332236A78900B01B62 /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* FooterView.swift */; }; D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2755D7A23689C7500485468 /* TableViewCell.swift */; }; D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */; }; D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */; }; @@ -258,13 +273,16 @@ D29DF32521ED0DA2003B2FB9 /* TextButtonView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF32321ED0DA2003B2FB9 /* TextButtonView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF32C21EE8736003B2FB9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D29DF32821EE8736003B2FB9 /* Localizable.strings */; }; D29DF32E21EE8C3D003B2FB9 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D29DF32D21EE8C3D003B2FB9 /* Media.xcassets */; }; + D29E28D823D21AB800ACEA85 /* StringAndMoleculeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29E28D723D21AB800ACEA85 /* StringAndMoleculeView.swift */; }; + D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29E28D923D21AFA00ACEA85 /* StringAndMoleculeModel.swift */; }; + D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29E28DC23D7404C00ACEA85 /* ContainerHelper.swift */; }; D2A514582211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */; }; D2A5145D2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */; }; D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */; }; D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */; }; - D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514662213885800345BFB /* StandardHeaderView.swift */; }; + D2A514672213885800345BFB /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514662213885800345BFB /* HeaderView.swift */; }; D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; }; D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A638FC22CA98280052ED1F /* HeadlineBody.swift */; }; D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390022CBB1820052ED1F /* Carousel.swift */; }; @@ -282,7 +300,7 @@ D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */; }; D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */; }; D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */; }; - D2FB151D23A40F1500C20E10 /* StackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151C23A40F1500C20E10 /* StackItem.swift */; }; + D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */; }; DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; DBC4391822442197001AB423 /* CaretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391622442196001AB423 /* CaretView.swift */; }; DBC4391922442197001AB423 /* DashLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391722442197001AB423 /* DashLine.swift */; }; @@ -339,7 +357,7 @@ 01EB3683236097C0006832FA /* MoleculeProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeProtocol.swift; sourceTree = ""; }; 01EB368823609801006832FA /* LabelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelModel.swift; sourceTree = ""; }; 01EB368923609801006832FA /* ListItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; - 01EB368A23609801006832FA /* StackItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackItemModel.swift; sourceTree = ""; }; + 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeStackItemModel.swift; sourceTree = ""; }; 01EB368B23609801006832FA /* MoleculeStackModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeStackModel.swift; sourceTree = ""; }; 01EB368C23609801006832FA /* HeaderModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderModel.swift; sourceTree = ""; }; 01EB368D23609801006832FA /* HeadlineBodyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyModel.swift; sourceTree = ""; }; @@ -356,9 +374,9 @@ 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; - 0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; 0AA33B33239813C50067DD0F /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; + 0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; @@ -383,6 +401,14 @@ 94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeImageModel.swift; sourceTree = ""; }; 94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeActionModel.swift; sourceTree = ""; }; C003506023AA94CD00B6AC29 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; + C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnOrderedListModel.swift; sourceTree = ""; }; + C695A68023C9830D00BFB94E /* NumberedListModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberedListModel.swift; sourceTree = ""; }; + C695A69323C9909000BFB94E /* DoughnutChartModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoughnutChartModel.swift; sourceTree = ""; }; + C695A69523C990BC00BFB94E /* DoughnutChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoughnutChart.swift; sourceTree = ""; }; + C695A69723C990C200BFB94E /* DoughnutChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoughnutChartView.swift; sourceTree = ""; }; + C6FA7D4F23C77A4700A3614A /* UnOrderedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnOrderedList.swift; sourceTree = ""; }; + C6FA7D5023C77A4800A3614A /* StringAndMoleculeStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringAndMoleculeStack.swift; sourceTree = ""; }; + C6FA7D5123C77A4900A3614A /* NumberedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberedList.swift; sourceTree = ""; }; C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadLineBodyCaretLinkImage.swift; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; @@ -403,12 +429,19 @@ D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; D260105223CEA61600764D80 /* ToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleModel.swift; sourceTree = ""; }; D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUISwitch+Model.swift"; sourceTree = ""; }; + D260105823D0A92900764D80 /* ContainerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerProtocol.swift; sourceTree = ""; }; + D260105A23D0BB7100764D80 /* StackModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackModelProtocol.swift; sourceTree = ""; }; + D260105C23D0BCD400764D80 /* Stack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stack.swift; sourceTree = ""; }; + D260105E23D0BFFC00764D80 /* StackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItem.swift; sourceTree = ""; }; + D260106023D0C02A00764D80 /* StackItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItemModelProtocol.swift; sourceTree = ""; }; + D260106223D0C05000764D80 /* StackItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItemModel.swift; sourceTree = ""; }; + D260106423D0CEA700764D80 /* StackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackModel.swift; sourceTree = ""; }; 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 = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D268C711238D6699007F2C1C /* DropDown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDown.swift; sourceTree = ""; }; - D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = ""; }; + D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; D2755D7A23689C7500485468 /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; }; D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewCell.swift; sourceTree = ""; }; D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EyebrowHeadlineBodyLink.swift; sourceTree = ""; }; @@ -547,13 +580,16 @@ D29DF32A21EE8736003B2FB9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; D29DF32B21EE8736003B2FB9 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; D29DF32D21EE8C3D003B2FB9 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; + D29E28D723D21AB800ACEA85 /* StringAndMoleculeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAndMoleculeView.swift; sourceTree = ""; }; + D29E28D923D21AFA00ACEA85 /* StringAndMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAndMoleculeModel.swift; sourceTree = ""; }; + D29E28DC23D7404C00ACEA85 /* ContainerHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerHelper.swift; sourceTree = ""; }; D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIMoleculeMappingObject.h; sourceTree = ""; }; D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIMoleculeMappingObject.m; sourceTree = ""; }; D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIMoleculeViewProtocol.h; sourceTree = ""; }; D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackView.swift; sourceTree = ""; }; D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackTemplate.swift; sourceTree = ""; }; D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackCenteredTemplate.swift; sourceTree = ""; }; - D2A514662213885800345BFB /* StandardHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardHeaderView.swift; sourceTree = ""; }; + D2A514662213885800345BFB /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerViewController.swift; sourceTree = ""; }; D2A638FC22CA98280052ED1F /* HeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBody.swift; sourceTree = ""; }; D2A6390022CBB1820052ED1F /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = ""; }; @@ -572,7 +608,7 @@ D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListTemplate.swift; sourceTree = ""; }; D2F4DDE52371A4CB00CD28BB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeContainer.swift; sourceTree = ""; }; - D2FB151C23A40F1500C20E10 /* StackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItem.swift; sourceTree = ""; }; + D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackItem.swift; sourceTree = ""; }; DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftRightLabelView.swift; sourceTree = ""; }; DB891E822253FA8500022516 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; DBC4391622442196001AB423 /* CaretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretView.swift; sourceTree = ""; }; @@ -597,6 +633,7 @@ 011B58EE23A2AA850085F53C /* ModelProtocols */ = { isa = PBXGroup; children = ( + 014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */, 017BEB4123620AD20024EF95 /* FormModelProtocol.swift */, 012A88C3238D86E600FE3DA1 /* CollectionCellMoleculeProtocol.swift */, 012A88B0238C880100FE3DA1 /* PagingMoleculeProtocol.swift */, @@ -609,42 +646,18 @@ path = ModelProtocols; sourceTree = ""; }; - 012A88EF23985E0100FE3DA1 /* Primitive Models */ = { + 012A88EF23985E0100FE3DA1 /* CustomPrimitives */ = { isa = PBXGroup; children = ( 012A88F023985E0100FE3DA1 /* Color.swift */, ); - path = "Primitive Models"; - sourceTree = ""; - }; - 014AA72023C501E2006F3E93 /* Container */ = { - isa = PBXGroup; - children = ( - 014AA72123C501E2006F3E93 /* MoleculeContainerModel.swift */, - 014AA72223C501E2006F3E93 /* ContainerModel.swift */, - 014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */, - ); - path = Container; - sourceTree = ""; - }; - 014AA72723C5059B006F3E93 /* Template */ = { - isa = PBXGroup; - children = ( - 014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */, - 014AA72923C5059B006F3E93 /* StackCenteredPageTemplateModel.swift */, - 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */, - 014AA72C23C5059B006F3E93 /* ListPageTemplateModel.swift */, - ); - path = Template; + path = CustomPrimitives; sourceTree = ""; }; 01509D96232803B200EF99AA /* Models */ = { isa = PBXGroup; children = ( - 014AA72723C5059B006F3E93 /* Template */, - 014AA72023C501E2006F3E93 /* Container */, 011B58EE23A2AA850085F53C /* ModelProtocols */, - 012A88EF23985E0100FE3DA1 /* Primitive Models */, 946EE1B5237B663A0036751F /* Extensions */, 01EB368723609801006832FA /* Molecules */, ); @@ -665,11 +678,6 @@ 01EB368723609801006832FA /* Molecules */ = { isa = PBXGroup; children = ( - 011B58F323A2CCC80085F53C /* DropDownModel.swift */, - 01EB368C23609801006832FA /* HeaderModel.swift */, - 012A88EB238F084D00FE3DA1 /* FooterModel.swift */, - 017BEB3F23620A230024EF95 /* TextFieldModel.swift */, - 017BEB7A236763000024EF95 /* LineModel.swift */, 012A88AE238C626E00FE3DA1 /* CarouselModel.swift */, 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */, 012A88C5238DA34000FE3DA1 /* ModuleMoleculeModel.swift */, @@ -685,12 +693,15 @@ path = Protocols; sourceTree = ""; }; - 0ABD1369237B18EE0081388D /* views */ = { + 0ABD1369237B18EE0081388D /* Views */ = { isa = PBXGroup; children = ( + D29E28DE23D740FC00ACEA85 /* Container */, 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */, + 014AA72123C501E2006F3E93 /* MoleculeContainerModel.swift */, + D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */, ); - path = views; + path = Views; sourceTree = ""; }; 946EE1B5237B663A0036751F /* Extensions */ = { @@ -701,7 +712,7 @@ path = Extensions; sourceTree = ""; }; - 94C2D9822386F3E30006CF46 /* LabelModel */ = { + 94C2D9822386F3E30006CF46 /* Label */ = { isa = PBXGroup; children = ( 01EB368823609801006832FA /* LabelModel.swift */, @@ -712,8 +723,9 @@ 94C2D9A623872DA90006CF46 /* LabelAttributeColorModel.swift */, 94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */, 94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */, + DB891E822253FA8500022516 /* Label.swift */, ); - path = LabelModel; + path = Label; sourceTree = ""; }; 94FB5B83238D892800EB2193 /* Recovered References */ = { @@ -751,6 +763,44 @@ D22D1F19220341F50077CEC0 /* MVMCoreUICheckBox.m */, 0198F7A02256A80A0066C936 /* MFRadioButton.h */, 0198F7A22256A80A0066C936 /* MFRadioButton.m */, + D29DF28721E7AC2B003B2FB9 /* ViewConstrainingView.h */, + D29DF28821E7AC2B003B2FB9 /* ViewConstrainingView.m */, + D29DF2AD21E7B3A4003B2FB9 /* MFTextView.h */, + D29DF2AB21E7B3A4003B2FB9 /* MFTextView.m */, + D29DF2AC21E7B3A4003B2FB9 /* MFTextView.xib */, + D29DF2B121E7B76C003B2FB9 /* MFLoadingSpinner.h */, + D29DF2B221E7B76D003B2FB9 /* MFLoadingSpinner.m */, + D29DF32321ED0DA2003B2FB9 /* TextButtonView.h */, + D29DF32221ED0DA2003B2FB9 /* TextButtonView.m */, + D29770FB21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h */, + D29770FA21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m */, + D22D1F44220496A30077CEC0 /* MVMCoreUISwitch.h */, + D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */, + D29DF16A21E69E1F003B2FB9 /* MFCustomButton.h */, + D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */, + D29DF16D21E69E1F003B2FB9 /* MFTextButton.h */, + D29DF17221E69E1F003B2FB9 /* MFTextButton.m */, + D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */, + D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */, + D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */, + D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */, + D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, + D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, + D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, + D29DF24C21E6A177003B2FB9 /* MFTextField.h */, + D29DF24221E6A176003B2FB9 /* MFTextField.m */, + D29DF24421E6A176003B2FB9 /* MFTextField.xib */, + D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */, + D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */, + D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */, + D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */, + D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */, + D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */, + D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, + D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, + D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, + D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */, + D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, ); path = Views; sourceTree = ""; @@ -759,8 +809,8 @@ isa = PBXGroup; children = ( 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */, - D22479892314445E003FCCF9 /* LabelSwitch.swift */, 01C851D223CF9E740021F976 /* LabelToggleModel.swift */, + D22479892314445E003FCCF9 /* LabelSwitch.swift */, D224798B231450C8003FCCF9 /* HeadlineBodySwitch.swift */, ); path = SwitchMolecules; @@ -769,8 +819,9 @@ D224798D2316A988003FCCF9 /* VerticalCombinationViews */ = { isa = PBXGroup; children = ( - D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, + D29E28D423D1FFFA00ACEA85 /* Lists */, 01EB368D23609801006832FA /* HeadlineBodyModel.swift */, + D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */, D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */, D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */, @@ -803,6 +854,9 @@ D22479902316A9CB003FCCF9 /* Organisms */ = { isa = PBXGroup; children = ( + D260105A23D0BB7100764D80 /* StackModelProtocol.swift */, + D260106423D0CEA700764D80 /* StackModel.swift */, + D260105C23D0BCD400764D80 /* Stack.swift */, 01EB368B23609801006832FA /* MoleculeStackModel.swift */, D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */, D2A6390022CBB1820052ED1F /* Carousel.swift */, @@ -814,8 +868,8 @@ isa = PBXGroup; children = ( D2755D7A23689C7500485468 /* TableViewCell.swift */, - 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */, 01EB368923609801006832FA /* ListItemModel.swift */, + 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */, D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, D28A838023CCB0D800DFE4FC /* AccordionListItemModel.swift */, D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, @@ -823,8 +877,11 @@ D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */, 011B58F123A2AE2C0085F53C /* DropDownListItemModel.swift */, D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */, - 01EB368A23609801006832FA /* StackItemModel.swift */, - D2FB151C23A40F1500C20E10 /* StackItem.swift */, + D260106023D0C02A00764D80 /* StackItemModelProtocol.swift */, + D260106223D0C05000764D80 /* StackItemModel.swift */, + D260105E23D0BFFC00764D80 /* StackItem.swift */, + 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */, + D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */, ); path = Items; sourceTree = ""; @@ -838,6 +895,16 @@ path = Legacy; sourceTree = ""; }; + D260105723CF9CC500764D80 /* Doughnut */ = { + isa = PBXGroup; + children = ( + C695A69323C9909000BFB94E /* DoughnutChartModel.swift */, + C695A69523C990BC00BFB94E /* DoughnutChart.swift */, + C695A69723C990C200BFB94E /* DoughnutChartView.swift */, + ); + path = Doughnut; + sourceTree = ""; + }; D29DF0C221E404D4003B2FB9 = { isa = PBXGroup; children = ( @@ -860,6 +927,7 @@ isa = PBXGroup; children = ( 01509D96232803B200EF99AA /* Models */, + 012A88EF23985E0100FE3DA1 /* CustomPrimitives */, D2B18B7D236090D500A9AEDC /* BaseClasses */, 01C74D87224298E2009C25A3 /* FormUIHelpers */, D29DF31421ECECA7003B2FB9 /* SupportingFiles */, @@ -884,10 +952,14 @@ D29DF0DF21E418B2003B2FB9 /* Templates */ = { isa = PBXGroup; children = ( + 014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */, D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */, + 014AA72923C5059B006F3E93 /* StackCenteredPageTemplateModel.swift */, D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */, D28A837A23C928DA00DFE4FC /* MoleculeListCellProtocol.swift */, + 014AA72C23C5059B006F3E93 /* ListPageTemplateModel.swift */, D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */, + 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */, D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */, ); path = Templates; @@ -919,21 +991,20 @@ D224798F2316A99F003FCCF9 /* LeftRightViews */, D224798E2316A995003FCCF9 /* HorizontalCombinationViews */, D224798D2316A988003FCCF9 /* VerticalCombinationViews */, - D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */, - D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */, - D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, - D2A514662213885800345BFB /* StandardHeaderView.swift */, - D274CA322236A78900B01B62 /* StandardFooterView.swift */, + 01EB368C23609801006832FA /* HeaderModel.swift */, + D2A514662213885800345BFB /* HeaderView.swift */, + 012A88EB238F084D00FE3DA1 /* FooterModel.swift */, + D274CA322236A78900B01B62 /* FooterView.swift */, 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */, D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */, D28A838423CCCA8900DFE4FC /* ScrollerModel.swift */, D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */, 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */, 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */, - D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */, 017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */, 017BEB49236235BA0024EF95 /* ModelMoleculeViewProtocol.swift */, + D260105723CF9CC500764D80 /* Doughnut */, ); path = Molecules; sourceTree = ""; @@ -977,11 +1048,10 @@ D29DF11921E68467003B2FB9 /* Containers */ = { isa = PBXGroup; children = ( - 0ABD1369237B18EE0081388D /* views */, + 0ABD1369237B18EE0081388D /* Views */, D29DF2B721E7BE79003B2FB9 /* TabBarController */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, D2B18B93236214AD00A9AEDC /* NavigationController.swift */, - D243859823A16B1800332775 /* Container.swift */, ); path = Containers; sourceTree = ""; @@ -1045,20 +1115,14 @@ D29DF16821E69E1F003B2FB9 /* Buttons */ = { isa = PBXGroup; children = ( - DBC4391A224421A0001AB423 /* CaretButton.swift */, 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */, - D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */, - D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */, - D29DF16A21E69E1F003B2FB9 /* MFCustomButton.h */, - D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */, - D28A837623C79FC600DFE4FC /* MFCustomButton+ActionModel.swift */, + DBC4391A224421A0001AB423 /* CaretButton.swift */, D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */, - D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */, - D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */, - D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */, D282AACA2243C61700C46919 /* ButtonView.swift */, D28A838823CCCFCB00DFE4FC /* LinkModel.swift */, + D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */, D28A838623CCCF6500DFE4FC /* MFTextButton+ModelExtension.swift */, + D28A837623C79FC600DFE4FC /* MFCustomButton+ActionModel.swift */, D29DF16D21E69E1F003B2FB9 /* MFTextButton.h */, D29DF17221E69E1F003B2FB9 /* MFTextButton.m */, C07065C32395677300FBF997 /* Link.swift */, @@ -1069,50 +1133,35 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( - 01509D922327ECFB00EF99AA /* ProgressBar.swift */, 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, - D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, - D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, - D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, - 948DB67D2326DCD90011F916 /* MultiProgress.swift */, + 01509D922327ECFB00EF99AA /* ProgressBar.swift */, 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */, - DBC4391622442196001AB423 /* CaretView.swift */, + 948DB67D2326DCD90011F916 /* MultiProgress.swift */, 9445891E2385D2E900DE9FD4 /* CaretViewModel.swift */, - DBC4391722442197001AB423 /* DashLine.swift */, + DBC4391622442196001AB423 /* CaretView.swift */, 944589202385D6E900DE9FD4 /* DashLineModel.swift */, - DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */, + DBC4391722442197001AB423 /* DashLine.swift */, 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */, - 012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */, - D29DF28721E7AC2B003B2FB9 /* ViewConstrainingView.h */, - D29DF28821E7AC2B003B2FB9 /* ViewConstrainingView.m */, + DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */, D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */, - D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */, 944589222385DA9500DE9FD4 /* ImageViewModel.swift */, + D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */, + 017BEB7A236763000024EF95 /* LineModel.swift */, D213347623843825008E41B3 /* Line.swift */, - D29DF2AD21E7B3A4003B2FB9 /* MFTextView.h */, - D29DF2AB21E7B3A4003B2FB9 /* MFTextView.m */, - D29DF2AC21E7B3A4003B2FB9 /* MFTextView.xib */, + 011B58F323A2CCC80085F53C /* DropDownModel.swift */, D268C711238D6699007F2C1C /* DropDown.swift */, - D29DF2B121E7B76C003B2FB9 /* MFLoadingSpinner.h */, - D29DF2B221E7B76D003B2FB9 /* MFLoadingSpinner.m */, - D29DF32321ED0DA2003B2FB9 /* TextButtonView.h */, - D29DF32221ED0DA2003B2FB9 /* TextButtonView.m */, - D29770FB21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h */, - D29770FA21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m */, - D260105223CEA61600764D80 /* ToggleModel.swift */, - D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */, - D22D1F44220496A30077CEC0 /* MVMCoreUISwitch.h */, - D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */, DBC4391C2245232D001AB423 /* LabelWithInternalButton.swift */, - DB891E822253FA8500022516 /* Label.swift */, - 94C2D9822386F3E30006CF46 /* LabelModel */, + 94C2D9822386F3E30006CF46 /* Label */, 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */, 01004F2F22721C3800991ECC /* RadioButton.swift */, D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */, 943784F3236B77BB006A1E82 /* GraphView.swift */, 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */, + D260105223CEA61600764D80 /* ToggleModel.swift */, 0AA33B392398524F0067DD0F /* Toggle.swift */, + D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */, + 012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */, ); path = Views; sourceTree = ""; @@ -1120,18 +1169,7 @@ D29DF22B21E6A0FA003B2FB9 /* TextFields */ = { isa = PBXGroup; children = ( - D29DF24C21E6A177003B2FB9 /* MFTextField.h */, - D29DF24221E6A176003B2FB9 /* MFTextField.m */, - 012CA9992384A687003F810F /* MFTextField+ModelExtension.swift */, - D29DF24421E6A176003B2FB9 /* MFTextField.xib */, - D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */, - D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */, - D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */, - D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */, - D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */, - D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */, - D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, - D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, + 017BEB3F23620A230024EF95 /* TextFieldModel.swift */, 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */, 0A21DB7E235DECC500C160A2 /* EntryField.swift */, @@ -1140,6 +1178,7 @@ 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */, 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */, 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */, + 012CA9992384A687003F810F /* MFTextField+ModelExtension.swift */, ); path = TextFields; sourceTree = ""; @@ -1241,6 +1280,39 @@ path = Strings; sourceTree = ""; }; + D29E28D423D1FFFA00ACEA85 /* Lists */ = { + isa = PBXGroup; + children = ( + D29E28DB23D21B0A00ACEA85 /* StringAndMoleculeStack */, + C695A68023C9830D00BFB94E /* NumberedListModel.swift */, + C6FA7D5123C77A4900A3614A /* NumberedList.swift */, + C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */, + C6FA7D4F23C77A4700A3614A /* UnOrderedList.swift */, + ); + path = Lists; + sourceTree = ""; + }; + D29E28DB23D21B0A00ACEA85 /* StringAndMoleculeStack */ = { + isa = PBXGroup; + children = ( + D29E28D923D21AFA00ACEA85 /* StringAndMoleculeModel.swift */, + D29E28D723D21AB800ACEA85 /* StringAndMoleculeView.swift */, + C6FA7D5023C77A4800A3614A /* StringAndMoleculeStack.swift */, + ); + path = StringAndMoleculeStack; + sourceTree = ""; + }; + D29E28DE23D740FC00ACEA85 /* Container */ = { + isa = PBXGroup; + children = ( + 014AA72223C501E2006F3E93 /* ContainerModel.swift */, + D260105823D0A92900764D80 /* ContainerProtocol.swift */, + D243859823A16B1800332775 /* Container.swift */, + D29E28DC23D7404C00ACEA85 /* ContainerHelper.swift */, + ); + path = Container; + sourceTree = ""; + }; D2B18B7D236090D500A9AEDC /* BaseClasses */ = { isa = PBXGroup; children = ( @@ -1422,7 +1494,7 @@ 94C2D9A923872E5E0006CF46 /* LabelAttributeImageModel.swift in Sources */, DBC4391922442197001AB423 /* DashLine.swift in Sources */, 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */, - D2FB151D23A40F1500C20E10 /* StackItem.swift in Sources */, + D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */, 017BEB48236230DB0024EF95 /* MoleculeViewProtocol.swift in Sources */, @@ -1449,6 +1521,7 @@ D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, + D260106323D0C05000764D80 /* StackItemModel.swift in Sources */, 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */, 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, @@ -1463,7 +1536,9 @@ D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, D28A838723CCCF6500DFE4FC /* MFTextButton+ModelExtension.swift in Sources */, + C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */, 014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */, + D260106123D0C02A00764D80 /* StackItemModelProtocol.swift in Sources */, 012A88C4238D86E600FE3DA1 /* CollectionCellMoleculeProtocol.swift in Sources */, 94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */, 014AA73123C5059B006F3E93 /* ListPageTemplateModel.swift in Sources */, @@ -1482,10 +1557,11 @@ D28A837723C79FC600DFE4FC /* MFCustomButton+ActionModel.swift in Sources */, D28A837F23CCA96400DFE4FC /* TabsModel.swift in Sources */, 012A88EC238F084D00FE3DA1 /* FooterModel.swift in Sources */, - D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, + D2A514672213885800345BFB /* HeaderView.swift in Sources */, + D29E28D823D21AB800ACEA85 /* StringAndMoleculeView.swift in Sources */, 01EB369023609801006832FA /* ListItemModel.swift in Sources */, D28A838323CCBD3F00DFE4FC /* CircleProgressModel.swift in Sources */, - D268C70C2386DFFD007F2C1C /* StackItemModel.swift in Sources */, + D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */, @@ -1496,13 +1572,16 @@ 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D2B18B812360945C00A9AEDC /* View.swift in Sources */, + C6FA7D5423C77A4A00A3614A /* NumberedList.swift in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D260105323CEA61600764D80 /* ToggleModel.swift in Sources */, 014AA72523C501E2006F3E93 /* ContainerModel.swift in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */, D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */, + D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, + D260105D23D0BCD400764D80 /* Stack.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 01EB368F23609801006832FA /* LabelModel.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, @@ -1520,13 +1599,14 @@ 017BEB7B236763000024EF95 /* LineModel.swift in Sources */, 94C2D9A523872C350006CF46 /* LabelAttributeFontModel.swift in Sources */, 017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */, - D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */, + D274CA332236A78900B01B62 /* FooterView.swift in Sources */, D29DF2BF21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.m in Sources */, 014AA72423C501E2006F3E93 /* MoleculeContainerModel.swift in Sources */, D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */, 011B58F223A2AE2C0085F53C /* DropDownListItemModel.swift in Sources */, 94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */, 944589212385D6E900DE9FD4 /* DashLineModel.swift in Sources */, + C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */, @@ -1546,21 +1626,25 @@ 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */, D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */, D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, + C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */, 017BEB4223620AD20024EF95 /* FormModelProtocol.swift in Sources */, 012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, 017BEB4A236235BA0024EF95 /* ModelMoleculeViewProtocol.swift in Sources */, + C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */, 012CA99A2384A687003F810F /* MFTextField+ModelExtension.swift in Sources */, 01EB3684236097C0006832FA /* MoleculeProtocol.swift in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, D243859923A16B1800332775 /* Container.swift in Sources */, + D260105B23D0BB7100764D80 /* StackModelProtocol.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, D28A839323CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift in Sources */, D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, + D260105F23D0BFFC00764D80 /* StackItem.swift in Sources */, 01EB369323609801006832FA /* HeaderModel.swift in Sources */, D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */, 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */, @@ -1600,19 +1684,25 @@ 014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */, 0ABD136B237B193A0081388D /* EntryFieldContainer.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, + D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */, + C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, + D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */, 012A88C2238D7BCA00FE3DA1 /* CarouselItemModel.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, 94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */, D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */, + C6FA7D5223C77A4A00A3614A /* UnOrderedList.swift in Sources */, 01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */, 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */, 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */, D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */, + C695A69823C990C200BFB94E /* DoughnutChartView.swift in Sources */, D29DF2CB21E7BFCC003B2FB9 /* MFSizeThreshold.m in Sources */, 946EE1BA237B66D80036751F /* MoleculeModelHelper.swift in Sources */, 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */, + D260106523D0CEA700764D80 /* StackModel.swift in Sources */, D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/MVMCoreUI/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift index 4329ccff..26efc82e 100644 --- a/MVMCoreUI/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift @@ -23,20 +23,24 @@ public class ButtonModel: MoleculeProtocol { public var backgroundColor: Color? public var title: String public var action: ActionProtocol - public var style: ButtonStyle? = .primary + public var style: ButtonStyle? public var size: ButtonSize? = .standard + public var required: Bool? + public var requiredGroups: [String]? init(with title: String, action: ActionProtocol) { self.title = title self.action = action } - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case backgroundColor case title case action case style case size + case required + case requiredGroups } required public init(from decoder: Decoder) throws { @@ -44,6 +48,8 @@ public class ButtonModel: MoleculeProtocol { backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) title = try typeContainer.decode(String.self, forKey: .title) action = try typeContainer.decodeModel(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) + required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) + requiredGroups = try typeContainer.decodeIfPresent([String].self, forKey: .requiredGroups) if let style = try typeContainer.decodeIfPresent(ButtonStyle.self, forKey: .style) { self.style = style } @@ -59,5 +65,6 @@ public class ButtonModel: MoleculeProtocol { try container.encodeModel(action, forKey: .action) try container.encodeIfPresent(style, forKey: .style) try container.encodeIfPresent(size, forKey: .size) + try container.encodeIfPresent(required, forKey: .required) } } diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretButton.swift index 6c8e4d31..1574a9e8 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretButton.swift @@ -89,6 +89,8 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI 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.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: caretView.bottomAnchor).isActive = true contentHorizontalAlignment = .left //set correct color after layout @@ -108,6 +110,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI @objc open func setAsMolecule() { backgroundColor = .clear + translatesAutoresizingMaskIntoConstraints = false setTitleColor(enabledColor, for: .normal) setTitleColor(disabledColor, for: .disabled) } @@ -149,7 +152,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI } isEnabled = caretLinkModel.enabled set(with: caretLinkModel.action, delegateObject: delegateObject, additionalData: additionalData) - setTitle(caretLinkModel.label.text, for: .normal) + setTitle(caretLinkModel.title, for: .normal) } public func needsToBeConstrained() -> Bool { diff --git a/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift b/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift index db2de2b0..6daf2740 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift @@ -12,20 +12,20 @@ import MVMCore public class CaretLinkModel: MoleculeProtocol { public static var identifier: String = "caretLink" public var backgroundColor: Color? - public var label: LabelModel + public var title: String public var action: ActionProtocol public var enabledColor: Color = Color(uiColor: .black) public var disabledColor: Color? = Color(uiColor: .mfSilver()) public var enabled: Bool = true - public init(label: LabelModel, action: ActionProtocol) { - self.label = label + public init(title: String, action: ActionProtocol) { + self.title = title self.action = action } - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case backgroundColor - case label + case title case action case enabledColor case disabledColor @@ -35,7 +35,7 @@ public class CaretLinkModel: MoleculeProtocol { required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - label = try typeContainer.decode(LabelModel.self, forKey: .label) + title = try typeContainer.decode(String.self, forKey: .title) if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor) { enabledColor = color } @@ -50,7 +50,7 @@ public class CaretLinkModel: MoleculeProtocol { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(label, forKey: .label) + try container.encode(title, forKey: .title) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeModel(action, forKey: .action) try container.encode(enabled, forKey: .enabledColor) diff --git a/MVMCoreUI/Atoms/Buttons/LinkModel.swift b/MVMCoreUI/Atoms/Buttons/LinkModel.swift index e2fca66c..fd0b17b9 100644 --- a/MVMCoreUI/Atoms/Buttons/LinkModel.swift +++ b/MVMCoreUI/Atoms/Buttons/LinkModel.swift @@ -21,7 +21,7 @@ public class LinkModel: MoleculeProtocol { self.action = action } - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case backgroundColor case title case action diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift b/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift index 2d7ecf40..09c94bdf 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift @@ -14,6 +14,16 @@ extension PrimaryButton: ModelMoleculeViewProtocol { guard let model = model as? ButtonModel else { return } setTitle(model.title, for: .normal) backgroundColor = model.backgroundColor?.uiColor + + self.validationRequired = model.required ?? false + self.requiredGroupsList = model.requiredGroups + + if self.validationRequired, + let selfForm = self as? FormValidationEnableDisableProtocol { + FormValidator.setupValidation(molecule: selfForm, delegate: delegateObject?.formValidationProtocol) + } + + if let style = model.style { switch style { case .primary: diff --git a/MVMCoreUI/Models/Molecules/TextFieldModel.swift b/MVMCoreUI/Atoms/TextFields/TextFieldModel.swift similarity index 100% rename from MVMCoreUI/Models/Molecules/TextFieldModel.swift rename to MVMCoreUI/Atoms/TextFields/TextFieldModel.swift diff --git a/MVMCoreUI/Atoms/Views/CaretViewModel.swift b/MVMCoreUI/Atoms/Views/CaretViewModel.swift index f26a150f..cee09a40 100644 --- a/MVMCoreUI/Atoms/Views/CaretViewModel.swift +++ b/MVMCoreUI/Atoms/Views/CaretViewModel.swift @@ -17,7 +17,7 @@ import Foundation public var isOpaque: Bool? public var lineWidth: CGFloat? - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case backgroundColor case strokeColor case isHidden diff --git a/MVMCoreUI/Atoms/Views/CircleProgressModel.swift b/MVMCoreUI/Atoms/Views/CircleProgressModel.swift index 20f49dc0..aae99612 100644 --- a/MVMCoreUI/Atoms/Views/CircleProgressModel.swift +++ b/MVMCoreUI/Atoms/Views/CircleProgressModel.swift @@ -38,7 +38,7 @@ public class CircleProgressModel: MoleculeProtocol { public init() {} - enum CircleProgressCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case style case size case diameter @@ -50,7 +50,7 @@ public class CircleProgressModel: MoleculeProtocol { } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: CircleProgressCodingKeys.self) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) if let style = try typeContainer.decodeIfPresent(GraphStyle.self, forKey: .style) { self.style = style } @@ -76,7 +76,7 @@ public class CircleProgressModel: MoleculeProtocol { } public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CircleProgressCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(style, forKey: .style) try container.encode(size, forKey: .size) try container.encode(diameter, forKey: .diameter) diff --git a/MVMCoreUI/Atoms/Views/DashLineModel.swift b/MVMCoreUI/Atoms/Views/DashLineModel.swift index ec6b8a15..662a7cf4 100644 --- a/MVMCoreUI/Atoms/Views/DashLineModel.swift +++ b/MVMCoreUI/Atoms/Views/DashLineModel.swift @@ -19,7 +19,7 @@ import Foundation self.dashColor = dashColor } - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case backgroundColor case dashColor case isHidden diff --git a/MVMCoreUI/Models/Molecules/DropDownModel.swift b/MVMCoreUI/Atoms/Views/DropDownModel.swift similarity index 100% rename from MVMCoreUI/Models/Molecules/DropDownModel.swift rename to MVMCoreUI/Atoms/Views/DropDownModel.swift diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label/Label.swift similarity index 97% rename from MVMCoreUI/Atoms/Views/Label.swift rename to MVMCoreUI/Atoms/Views/Label/Label.swift index e5491baf..2188b2a2 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label/Label.swift @@ -244,10 +244,15 @@ public typealias ActionBlock = () -> () } if let fontStyle = labelModel.fontStyle { MFStyler.styleLabel(self, withStyle: fontStyle) + MFStyler.styleLabel(self, withStyle: fontStyle, genericScaling: false) + standardFontSize = font.pointSize } else { let fontSize = labelModel.fontSize + if let fontSize = fontSize { + standardFontSize = fontSize + } if let fontName = labelModel.fontName { - font = MFFonts.mfFont(withName: fontName, size: fontSize ?? font.pointSize) + font = MFFonts.mfFont(withName: fontName, size: fontSize ?? standardFontSize) } else if let fontSize = fontSize { font = font.withSize(fontSize) } @@ -258,13 +263,13 @@ public typealias ActionBlock = () -> () } if let attributes = labelModel.attributes, let labelText = text { - let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font as UIFont, NSAttributedString.Key.foregroundColor: textColor as UIColor]) + let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font.withSize(standardFontSize), NSAttributedString.Key.foregroundColor: textColor as UIColor]) for attribute in attributes { let range = NSRange(location: attribute.location, length: attribute.length) switch attribute { - case let underLineAtt as LabelAttributeUnderlineModel: + case _ as LabelAttributeUnderlineModel: attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range) - case let strikeAtt as LabelAttributeStrikeThroughModel: + case _ as LabelAttributeStrikeThroughModel: attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) attributedString.addAttribute(.baselineOffset, value: 0, range: range) case let colorAtt as LabelAttributeColorModel: @@ -309,11 +314,17 @@ public typealias ActionBlock = () -> () } } case let actionAtt as LabelAttributeActionModel: + addTappableLinkAttribute(range: NSRange(location: range.location, length: range.length)) { + if let data = try? actionAtt.encode(using: JSONEncoder()), let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any] { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + } addActionAttributes(range: range, string: attributedString) default: continue } } + attributedText = attributedString originalAttributedString = attributedText hero = labelModel.hero } diff --git a/MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeActionModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeActionModel.swift similarity index 55% rename from MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeActionModel.swift rename to MVMCoreUI/Atoms/Views/Label/LabelAttributeActionModel.swift index eb5a3cd7..8dd5c562 100644 --- a/MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeActionModel.swift +++ b/MVMCoreUI/Atoms/Views/Label/LabelAttributeActionModel.swift @@ -12,12 +12,21 @@ class LabelAttributeActionModel: LabelAttributeModel { override public class var identifier: String { return "action" } - + var action: ActionProtocol + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + action = try typeContainer.decodeModel(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) 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.encodeModel(action, forKey: .action) + } + + private enum CodingKeys: String, CodingKey { + case action } } diff --git a/MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeColorModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeColorModel.swift similarity index 100% rename from MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeColorModel.swift rename to MVMCoreUI/Atoms/Views/Label/LabelAttributeColorModel.swift diff --git a/MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeFontModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeFontModel.swift similarity index 100% rename from MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeFontModel.swift rename to MVMCoreUI/Atoms/Views/Label/LabelAttributeFontModel.swift diff --git a/MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeImageModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeImageModel.swift similarity index 100% rename from MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeImageModel.swift rename to MVMCoreUI/Atoms/Views/Label/LabelAttributeImageModel.swift diff --git a/MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeModel.swift similarity index 100% rename from MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeModel.swift rename to MVMCoreUI/Atoms/Views/Label/LabelAttributeModel.swift diff --git a/MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeStrikeThroughModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeStrikeThroughModel.swift similarity index 100% rename from MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeStrikeThroughModel.swift rename to MVMCoreUI/Atoms/Views/Label/LabelAttributeStrikeThroughModel.swift diff --git a/MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeUnderlineModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeUnderlineModel.swift similarity index 100% rename from MVMCoreUI/Atoms/Views/LabelModel/LabelAttributeUnderlineModel.swift rename to MVMCoreUI/Atoms/Views/Label/LabelAttributeUnderlineModel.swift diff --git a/MVMCoreUI/Atoms/Views/LabelModel/LabelModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelModel.swift similarity index 98% rename from MVMCoreUI/Atoms/Views/LabelModel/LabelModel.swift rename to MVMCoreUI/Atoms/Views/Label/LabelModel.swift index 90c694a0..faf19c22 100644 --- a/MVMCoreUI/Atoms/Views/LabelModel/LabelModel.swift +++ b/MVMCoreUI/Atoms/Views/Label/LabelModel.swift @@ -26,7 +26,7 @@ import Foundation public var hero: Int? public var makeWholeViewClickable: Bool? - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case moleculeName case text case accessibilityText diff --git a/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift b/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift index 5e091446..73abb4d6 100644 --- a/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift +++ b/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift @@ -9,8 +9,8 @@ import UIKit @objcMembers public class LeftRightLabelModel: MoleculeProtocol { - public static var identifier: String = "leftRightLabel" + public static var identifier: String = "leftRightLabelView" public var backgroundColor: Color? public var leftText: LabelModel - public var rightText: LabelModel + public var rightText: LabelModel? } diff --git a/MVMCoreUI/Models/Molecules/LineModel.swift b/MVMCoreUI/Atoms/Views/LineModel.swift similarity index 51% rename from MVMCoreUI/Models/Molecules/LineModel.swift rename to MVMCoreUI/Atoms/Views/LineModel.swift index 88e09be0..cf671996 100644 --- a/MVMCoreUI/Models/Molecules/LineModel.swift +++ b/MVMCoreUI/Atoms/Views/LineModel.swift @@ -41,11 +41,37 @@ import UIKit } public static var identifier: String = "line" - public var type: Style? = .standard + public var type: Style = .standard public var frequency: Frequency? = .allExceptTop public var backgroundColor: Color? public init(type: Style) { self.type = type } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case type + case backgroundColor + case frequency + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let type = try typeContainer.decodeIfPresent(Style.self, forKey: .type) { + self.type = type + } + if let frequency = try typeContainer.decodeIfPresent(Frequency.self, forKey: .frequency) { + self.frequency = frequency + } + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(type, forKey: .type) + try container.encodeIfPresent(frequency, forKey: .frequency) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } } diff --git a/MVMCoreUI/Atoms/Views/MultiProgressModel.swift b/MVMCoreUI/Atoms/Views/MultiProgressModel.swift index 497fc9d5..abcbd5fd 100644 --- a/MVMCoreUI/Atoms/Views/MultiProgressModel.swift +++ b/MVMCoreUI/Atoms/Views/MultiProgressModel.swift @@ -25,7 +25,7 @@ import Foundation public var thickness: CGFloat? public var roundedRect: Bool? - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case progressList case thickness case roundedRect diff --git a/MVMCoreUI/Atoms/Views/ProgressBarModel.swift b/MVMCoreUI/Atoms/Views/ProgressBarModel.swift index 1850c411..396ab8e9 100644 --- a/MVMCoreUI/Atoms/Views/ProgressBarModel.swift +++ b/MVMCoreUI/Atoms/Views/ProgressBarModel.swift @@ -16,7 +16,7 @@ import Foundation public var isRounded: Bool? public var thickness: CGFloat? - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case moleculeName case isRounded = "roundRect" case thickness diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index 4e855532..366b6b89 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -398,7 +398,7 @@ extension Toggle { changeStateNoAnimation(state) } - if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { + if let actionMap = dictionary.optionalDictionaryForKey("action") { didToggleAction = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } diff --git a/MVMCoreUI/Atoms/Views/ToggleModel.swift b/MVMCoreUI/Atoms/Views/ToggleModel.swift index 81602c2a..8aa9500b 100644 --- a/MVMCoreUI/Atoms/Views/ToggleModel.swift +++ b/MVMCoreUI/Atoms/Views/ToggleModel.swift @@ -17,7 +17,7 @@ public class ToggleModel: MoleculeProtocol { public var required: Bool? public var fieldKey: String? - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case moleculeName case state case action diff --git a/MVMCoreUI/Containers/Views/Container/Container.swift b/MVMCoreUI/Containers/Views/Container/Container.swift new file mode 100644 index 00000000..065490a0 --- /dev/null +++ b/MVMCoreUI/Containers/Views/Container/Container.swift @@ -0,0 +1,82 @@ +// +// Container.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/11/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class Container: View, ContainerProtocol { + public var view: UIView? + let containerHelper = ContainerHelper() + var containerModel: ContainerModelProtocol? { + get { return model as? ContainerModelProtocol } + } + + // MARK:- ModelMoleculeViewProtocol + override open func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let containerModel = model as? ContainerModelProtocol else { return } + containerHelper.set(with: containerModel, for: view as? MVMCoreUIViewConstrainingProtocol) + } + + // MARK:- ContainerProtocol + public func alignHorizontal(_ alignment: UIStackView.Alignment) { + containerHelper.alignHorizontal(alignment) + } + + public func alignVertical(_ alignment: UIStackView.Alignment) { + containerHelper.alignVertical(alignment) + } + + public func constrainView(_ view: UIView) { + containerHelper.constrainView(view) + } +} + +// MARK: - MVMCoreViewProtocol +public extension Container { + override func updateView(_ size: CGFloat) { + super.updateView(size) + (view as? MVMCoreViewProtocol)?.updateView(size) + containerHelper.updateViewMargins(self, model: containerModel, size: size) + } + + /// Will be called only once. + override func setupView() { + super.setupView() + backgroundColor = .clear + } + + func addAndContain(_ view: UIView) { + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + containerHelper.constrainView(view) + self.view = view + } + + convenience init(andContain view: UIView) { + self.init() + addAndContain(view) + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +public extension Container { + override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + guard let view = view else { return } + containerHelper.set(with: json, for: view) + } + + override func reset() { + super.reset() + (view as? MVMCoreUIMoleculeViewProtocol)?.reset?() + } + + override func setAsMolecule() { + (view as? MVMCoreUIMoleculeViewProtocol)?.setAsMolecule?() + } +} diff --git a/MVMCoreUI/Containers/Container.swift b/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift similarity index 79% rename from MVMCoreUI/Containers/Container.swift rename to MVMCoreUI/Containers/Views/Container/ContainerHelper.swift index ab495b81..77226341 100644 --- a/MVMCoreUI/Containers/Container.swift +++ b/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift @@ -1,12 +1,13 @@ // -// Container.swift +// ContainerHelper.swift // MVMCoreUI // -// Created by Scott Pfeil on 12/11/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. +// Created by Scott Pfeil on 1/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. // +// A Helper that consolidates some common container logic -import UIKit +import Foundation public class ContainerHelper: NSObject { var leftConstraint: NSLayoutConstraint? @@ -195,62 +196,3 @@ public class ContainerHelper: NSObject { } } } - -open class Container: View { - var view: UIView? - let containerHelper = ContainerHelper() - var containerModel: ContainerModelProtocol? { - get { return model as? ContainerModelProtocol } - } - - override open func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.setWithModel(model, delegateObject, additionalData) - guard let containerModel = model as? ContainerModelProtocol else { return } - containerHelper.set(with: containerModel, for: view as? MVMCoreUIViewConstrainingProtocol) - } -} - -// MARK: - MVMCoreViewProtocol -public extension Container { - override func updateView(_ size: CGFloat) { - super.updateView(size) - (view as? MVMCoreViewProtocol)?.updateView(size) - containerHelper.updateViewMargins(self, model: containerModel, size: size) - } - - /// Will be called only once. - override func setupView() { - super.setupView() - backgroundColor = .clear - } - - func addAndContain(_ view: UIView) { - view.translatesAutoresizingMaskIntoConstraints = false - addSubview(view) - containerHelper.constrainView(view) - self.view = view - } - - convenience init(andContain view: UIView) { - self.init() - addAndContain(view) - } -} - -// MARK: - MVMCoreUIMoleculeViewProtocol -public extension Container { - override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - guard let view = view else { return } - containerHelper.set(with: json, for: view) - } - - override func reset() { - super.reset() - (view as? MVMCoreUIMoleculeViewProtocol)?.reset?() - } - - override func setAsMolecule() { - (view as? MVMCoreUIMoleculeViewProtocol)?.setAsMolecule?() - } -} diff --git a/MVMCoreUI/Models/Container/ContainerModel.swift b/MVMCoreUI/Containers/Views/Container/ContainerModel.swift similarity index 98% rename from MVMCoreUI/Models/Container/ContainerModel.swift rename to MVMCoreUI/Containers/Views/Container/ContainerModel.swift index 9930ab5c..1b721183 100644 --- a/MVMCoreUI/Models/Container/ContainerModel.swift +++ b/MVMCoreUI/Containers/Views/Container/ContainerModel.swift @@ -17,7 +17,7 @@ public class ContainerModel: ContainerModelProtocol, Codable { public var topMarginPadding: CGFloat? public var bottomMarginPadding: CGFloat? - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case verticalAlignment case horizontalAlignment case useHorizontalMargins diff --git a/MVMCoreUI/Containers/Views/Container/ContainerProtocol.swift b/MVMCoreUI/Containers/Views/Container/ContainerProtocol.swift new file mode 100644 index 00000000..10bac27c --- /dev/null +++ b/MVMCoreUI/Containers/Views/Container/ContainerProtocol.swift @@ -0,0 +1,16 @@ +// +// ContainerProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ContainerProtocol { + var view: UIView? { get set } + func alignHorizontal(_ alignment: UIStackView.Alignment) + func alignVertical(_ alignment: UIStackView.Alignment) + func constrainView(_ view: UIView) +} diff --git a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift new file mode 100644 index 00000000..96a51a8f --- /dev/null +++ b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift @@ -0,0 +1,285 @@ +// +// EntryFieldContainer.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objcMembers open class EntryFieldContainer: View { + //-------------------------------------------------- + // MARK: - Drawing Properties + //-------------------------------------------------- + + /// The bottom border line. Height is dynamic based on scenario. + public var bottomBar: CAShapeLayer? = { + let layer = CAShapeLayer() + layer.backgroundColor = UIColor.black.cgColor + layer.drawsAsynchronously = true + layer.anchorPoint = CGPoint(x: 0.5, y: 1.0); + return layer + }() + + /// Total control over the drawn top, bottom, left and right borders. + public var disableAllBorders = false { + didSet { + bottomBar?.isHidden = disableAllBorders + } + } + + private(set) var fieldState: FieldState = .original { + didSet (oldState) { + // Will not update if new state is the same as old. + if fieldState != oldState { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.fieldState.setStateUI(for: self) + } + } + } + } + + /// Determines if the top, left, and right borders should be drawn. + private var hideBorders = false + + public var borderStrokeColor: UIColor = .mfSilver() + private var borderPath: UIBezierPath = UIBezierPath() + + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + + private var _isEnabled: Bool = true + private var _showError: Bool = false + private var _isLocked: Bool = false + private var _isSelected: Bool = false + + public var isEnabled: Bool { + get { return _isEnabled } + set (enabled) { + + _isEnabled = enabled + _isLocked = false + _isSelected = false + _showError = false + + fieldState = enabled ? .original : .disabled + } + } + + public var showError: Bool { + get { return _showError } + set (error) { + + _showError = error + _isEnabled = true + _isLocked = false + _isSelected = false + + fieldState = error ? .error : .original + } + } + + public var isLocked: Bool { + get { return _isLocked } + set (locked) { + + _isLocked = locked + _isEnabled = true + _isSelected = false + _showError = false + + fieldState = locked ? .locked : .original + } + } + + public var isSelected: Bool { + get { return _isSelected } + set (selected) { + + _isSelected = selected + _isLocked = false + _isEnabled = true + + if _showError { + fieldState = selected ? .selectedError : .error + } else { + fieldState = selected ? .selected : .original + } + } + } + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + /// Holds reference to delegateObject to inform molecular tableView of an update. + weak var delegateObject: MVMCoreUIDelegateObject? + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func layoutSubviews() { + super.layoutSubviews() + + refreshUI(bottomBarSize: showError ? 4 : 1) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + + refreshUI() + } + + /// This handles the top, left, and right border lines. + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath.removeAllPoints() + + if !disableAllBorders && !hideBorders { + // Brings the other half of the line inside the view to prevent cropping. + let origin = bounds.origin + let size = frame.size + let insetLean: CGFloat = 0.5 + borderPath.lineWidth = 1 + + borderPath.move(to: CGPoint(x: origin.x + insetLean, y: origin.y + size.height)) + borderPath.addLine(to: CGPoint(x: origin.x + insetLean, y: origin.y + insetLean)) + borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + insetLean)) + borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + size.height)) + + borderStrokeColor.setStroke() + borderPath.stroke() + } + } + + override open func setupView() { + super.setupView() + + isAccessibilityElement = false + isOpaque = false + + if let bottomBar = bottomBar { + layer.addSublayer(bottomBar) + } + } + + //-------------------------------------------------- + // MARK: - Draw States + //-------------------------------------------------- + + public enum FieldState { + case original + case error + case selectedError + case selected + case locked + case disabled + + public func setStateUI(for formField: EntryFieldContainer) { + + switch self { + case .original: + formField.originalUI() + + case .error: + formField.errorUI() + + case .selectedError: + formField.selectedErrorUI() + + case .selected: + formField.selectedUI() + + case .locked: + formField.lockedUI() + + case .disabled: + formField.disabledUI() + } + } + } + + open func originalUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = UIColor.black.cgColor + refreshUI(bottomBarSize: 1) + } + + open func errorUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .mfPumpkin() + bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor + refreshUI(bottomBarSize: 4) + } + + open func selectedErrorUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .black + bottomBar?.backgroundColor = UIColor.mfPumpkin().cgColor + refreshUI(bottomBarSize: 4) + } + + open func selectedUI() { + + isUserInteractionEnabled = true + hideBorders = false + borderStrokeColor = .black + bottomBar?.backgroundColor = UIColor.black.cgColor + refreshUI(bottomBarSize: 1) + } + + open func lockedUI() { + + isUserInteractionEnabled = false + hideBorders = true + borderStrokeColor = .clear + bottomBar?.backgroundColor = UIColor.clear.cgColor + refreshUI(bottomBarSize: 1) + } + + open func disabledUI() { + + isUserInteractionEnabled = false + hideBorders = false + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = UIColor.mfSilver().cgColor + refreshUI(bottomBarSize: 1) + } + + open func refreshUI(bottomBarSize: CGFloat? = nil) { + + if !disableAllBorders { + let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) + bottomBar?.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) + + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + setNeedsDisplay() + layoutIfNeeded() + } + } +} + +// MARK:- MVMCoreUIMoleculeViewProtocol +extension EntryFieldContainer { + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + + guard let dictionary = json, !dictionary.isEmpty else { return } + } +} diff --git a/MVMCoreUI/Containers/Views/MoleculeContainer.swift b/MVMCoreUI/Containers/Views/MoleculeContainer.swift new file mode 100644 index 00000000..dc70e38d --- /dev/null +++ b/MVMCoreUI/Containers/Views/MoleculeContainer.swift @@ -0,0 +1,69 @@ +// +// MoleculeContainer.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class MoleculeContainer: Container { + + /// Can be overriden to change how the molecule is added to the hierarchy. + public func addMolecule(_ molecule: UIView) { + addAndContain(molecule) + } + + override public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + return + } + if view == nil { + if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: false) { + addAndContain(molecule) + } + } else { + (view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON?(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) + } + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + public override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + if let casteModel = model as? MoleculeContainerModel { + if view != nil { + (view as? ModelMoleculeViewProtocol)?.setWithModel(casteModel.molecule, delegateObject, additionalData) + } else { + if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(casteModel.molecule, delegateObject) { + addMolecule(molecule) + } + } + } + super.setWithModel(model, delegateObject, additionalData) + } + + public override static func nameForReuse(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let containerModel = model as? MoleculeContainerModel, + let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(containerModel.molecule) as? ModelMoleculeViewProtocol.Type, + let moleculeName = moleculeClass.nameForReuse(containerModel.molecule, delegateObject) else { + return "\(model?.moleculeName ?? "moleculeContainer")<>" + } + return "\(model?.moleculeName ?? "moleculeContainer")<\(moleculeName)>" + } + + public override class func estimatedHeight(forRow molecule: MoleculeProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + guard let containerModel = molecule as? MoleculeContainerModel else { return 0 } + guard let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(containerModel.molecule) as? ModelMoleculeViewProtocol.Type, + let moleculeHeight = moleculeClass.estimatedHeight(forRow: containerModel.molecule, delegateObject: delegateObject) else { + return (containerModel.topMarginPadding ?? 0) + (containerModel.bottomMarginPadding ?? 0) + } + return moleculeHeight + (containerModel.topMarginPadding ?? 0) + (containerModel.bottomMarginPadding ?? 0) + } + + public override class func requiredModules(_ molecule: MoleculeProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + guard let containerModel = molecule as? MoleculeContainerModel, + let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(containerModel.molecule) as? ModelMoleculeViewProtocol.Type else { return nil } + return moleculeClass.requiredModules(containerModel.molecule, delegateObject: delegateObject, error: error) + } +} diff --git a/MVMCoreUI/Models/Container/MoleculeContainerModel.swift b/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift similarity index 77% rename from MVMCoreUI/Models/Container/MoleculeContainerModel.swift rename to MVMCoreUI/Containers/Views/MoleculeContainerModel.swift index c29fcd2a..284b91ee 100644 --- a/MVMCoreUI/Models/Container/MoleculeContainerModel.swift +++ b/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift @@ -11,7 +11,7 @@ import Foundation public class MoleculeContainerModel: ContainerModel { public var molecule: MoleculeProtocol - enum MoleculeContainerCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case molecule } @@ -21,14 +21,14 @@ public class MoleculeContainerModel: ContainerModel { } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: MoleculeContainerCodingKeys.self) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecule = try typeContainer.decodeMolecule(codingKey: .molecule) try super.init(from: decoder) } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: MoleculeContainerCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeModel(molecule, forKey: .molecule) } } diff --git a/MVMCoreUI/Models/Primitive Models/Color.swift b/MVMCoreUI/CustomPrimitives/Color.swift similarity index 100% rename from MVMCoreUI/Models/Primitive Models/Color.swift rename to MVMCoreUI/CustomPrimitives/Color.swift diff --git a/MVMCoreUI/Atoms/Buttons/ButtonDelegateProtocol.h b/MVMCoreUI/Legacy/Views/ButtonDelegateProtocol.h similarity index 100% rename from MVMCoreUI/Atoms/Buttons/ButtonDelegateProtocol.h rename to MVMCoreUI/Legacy/Views/ButtonDelegateProtocol.h diff --git a/MVMCoreUI/Atoms/Buttons/MFButtonProtocol.h b/MVMCoreUI/Legacy/Views/MFButtonProtocol.h similarity index 100% rename from MVMCoreUI/Atoms/Buttons/MFButtonProtocol.h rename to MVMCoreUI/Legacy/Views/MFButtonProtocol.h diff --git a/MVMCoreUI/Atoms/Buttons/MFCustomButton.h b/MVMCoreUI/Legacy/Views/MFCustomButton.h similarity index 100% rename from MVMCoreUI/Atoms/Buttons/MFCustomButton.h rename to MVMCoreUI/Legacy/Views/MFCustomButton.h diff --git a/MVMCoreUI/Atoms/Buttons/MFCustomButton.m b/MVMCoreUI/Legacy/Views/MFCustomButton.m similarity index 100% rename from MVMCoreUI/Atoms/Buttons/MFCustomButton.m rename to MVMCoreUI/Legacy/Views/MFCustomButton.m diff --git a/MVMCoreUI/Atoms/TextFields/MFDigitTextBox.h b/MVMCoreUI/Legacy/Views/MFDigitTextBox.h similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFDigitTextBox.h rename to MVMCoreUI/Legacy/Views/MFDigitTextBox.h diff --git a/MVMCoreUI/Atoms/TextFields/MFDigitTextBox.m b/MVMCoreUI/Legacy/Views/MFDigitTextBox.m similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFDigitTextBox.m rename to MVMCoreUI/Legacy/Views/MFDigitTextBox.m diff --git a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.h b/MVMCoreUI/Legacy/Views/MFDigitTextField.h similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFDigitTextField.h rename to MVMCoreUI/Legacy/Views/MFDigitTextField.h diff --git a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.m b/MVMCoreUI/Legacy/Views/MFDigitTextField.m similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFDigitTextField.m rename to MVMCoreUI/Legacy/Views/MFDigitTextField.m diff --git a/MVMCoreUI/Atoms/TextFields/MFDigitTextField.xib b/MVMCoreUI/Legacy/Views/MFDigitTextField.xib similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFDigitTextField.xib rename to MVMCoreUI/Legacy/Views/MFDigitTextField.xib diff --git a/MVMCoreUI/Atoms/Views/MFLoadingSpinner.h b/MVMCoreUI/Legacy/Views/MFLoadingSpinner.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MFLoadingSpinner.h rename to MVMCoreUI/Legacy/Views/MFLoadingSpinner.h diff --git a/MVMCoreUI/Atoms/Views/MFLoadingSpinner.m b/MVMCoreUI/Legacy/Views/MFLoadingSpinner.m similarity index 100% rename from MVMCoreUI/Atoms/Views/MFLoadingSpinner.m rename to MVMCoreUI/Legacy/Views/MFLoadingSpinner.m diff --git a/MVMCoreUI/Atoms/TextFields/MFMdnTextField.h b/MVMCoreUI/Legacy/Views/MFMdnTextField.h similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFMdnTextField.h rename to MVMCoreUI/Legacy/Views/MFMdnTextField.h diff --git a/MVMCoreUI/Atoms/TextFields/MFMdnTextField.m b/MVMCoreUI/Legacy/Views/MFMdnTextField.m similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFMdnTextField.m rename to MVMCoreUI/Legacy/Views/MFMdnTextField.m diff --git a/MVMCoreUI/Atoms/Buttons/MFTextButton.h b/MVMCoreUI/Legacy/Views/MFTextButton.h similarity index 100% rename from MVMCoreUI/Atoms/Buttons/MFTextButton.h rename to MVMCoreUI/Legacy/Views/MFTextButton.h diff --git a/MVMCoreUI/Atoms/Buttons/MFTextButton.m b/MVMCoreUI/Legacy/Views/MFTextButton.m similarity index 100% rename from MVMCoreUI/Atoms/Buttons/MFTextButton.m rename to MVMCoreUI/Legacy/Views/MFTextButton.m diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.h b/MVMCoreUI/Legacy/Views/MFTextField.h similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFTextField.h rename to MVMCoreUI/Legacy/Views/MFTextField.h diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.m b/MVMCoreUI/Legacy/Views/MFTextField.m similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFTextField.m rename to MVMCoreUI/Legacy/Views/MFTextField.m diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.xib b/MVMCoreUI/Legacy/Views/MFTextField.xib similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFTextField.xib rename to MVMCoreUI/Legacy/Views/MFTextField.xib diff --git a/MVMCoreUI/Atoms/TextFields/MFTextFieldSubclassExtension.h b/MVMCoreUI/Legacy/Views/MFTextFieldSubclassExtension.h similarity index 100% rename from MVMCoreUI/Atoms/TextFields/MFTextFieldSubclassExtension.h rename to MVMCoreUI/Legacy/Views/MFTextFieldSubclassExtension.h diff --git a/MVMCoreUI/Atoms/Views/MFTextView.h b/MVMCoreUI/Legacy/Views/MFTextView.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MFTextView.h rename to MVMCoreUI/Legacy/Views/MFTextView.h diff --git a/MVMCoreUI/Atoms/Views/MFTextView.m b/MVMCoreUI/Legacy/Views/MFTextView.m similarity index 100% rename from MVMCoreUI/Atoms/Views/MFTextView.m rename to MVMCoreUI/Legacy/Views/MFTextView.m diff --git a/MVMCoreUI/Atoms/Views/MFTextView.xib b/MVMCoreUI/Legacy/Views/MFTextView.xib similarity index 100% rename from MVMCoreUI/Atoms/Views/MFTextView.xib rename to MVMCoreUI/Legacy/Views/MFTextView.xib diff --git a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h b/MVMCoreUI/Legacy/Views/MVMCoreUIMoleculeViewProtocol.h similarity index 100% rename from MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h rename to MVMCoreUI/Legacy/Views/MVMCoreUIMoleculeViewProtocol.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.h b/MVMCoreUI/Legacy/Views/MVMCoreUIPageControl.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.h rename to MVMCoreUI/Legacy/Views/MVMCoreUIPageControl.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.m b/MVMCoreUI/Legacy/Views/MVMCoreUIPageControl.m similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.m rename to MVMCoreUI/Legacy/Views/MVMCoreUIPageControl.m diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUIPagingProtocol.h b/MVMCoreUI/Legacy/Views/MVMCoreUIPagingProtocol.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUIPagingProtocol.h rename to MVMCoreUI/Legacy/Views/MVMCoreUIPagingProtocol.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h b/MVMCoreUI/Legacy/Views/MVMCoreUISwitch.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h rename to MVMCoreUI/Legacy/Views/MVMCoreUISwitch.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m b/MVMCoreUI/Legacy/Views/MVMCoreUISwitch.m similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m rename to MVMCoreUI/Legacy/Views/MVMCoreUISwitch.m diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.h b/MVMCoreUI/Legacy/Views/MVMCoreUITextFieldView.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.h rename to MVMCoreUI/Legacy/Views/MVMCoreUITextFieldView.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.m b/MVMCoreUI/Legacy/Views/MVMCoreUITextFieldView.m similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.m rename to MVMCoreUI/Legacy/Views/MVMCoreUITextFieldView.m diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.h b/MVMCoreUI/Legacy/Views/PrimaryButton.h similarity index 97% rename from MVMCoreUI/Atoms/Buttons/PrimaryButton.h rename to MVMCoreUI/Legacy/Views/PrimaryButton.h index 99929430..e83484a0 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.h +++ b/MVMCoreUI/Legacy/Views/PrimaryButton.h @@ -46,6 +46,9 @@ static CGFloat const PrimaryButtonSmallHeight = 30.0; // set YES to skip highlight method, for customer color button @property (nonatomic) BOOL skipHighlighted; +@property (nonatomic) BOOL validationRequired; +@property (nullable, nonatomic, strong) NSArray *requiredGroupsList; + // The main creation functions, changed to black button for 2.0 for default + (nullable instancetype)primaryButton:(BOOL)enabled; + (nullable instancetype)primarySmallButton:(BOOL)enabled; diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m b/MVMCoreUI/Legacy/Views/PrimaryButton.m similarity index 99% rename from MVMCoreUI/Atoms/Buttons/PrimaryButton.m rename to MVMCoreUI/Legacy/Views/PrimaryButton.m index 2bc9e026..439d5479 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m +++ b/MVMCoreUI/Legacy/Views/PrimaryButton.m @@ -20,8 +20,6 @@ @interface PrimaryButton() -@property (nonatomic) BOOL validationRequired; -@property (nonatomic, strong) NSArray *requiredGroupsList; @property (nonatomic) BOOL smallButton; @property (assign, nonatomic) BOOL tinyButton; @property (nonatomic) CGFloat sizeForSizing; diff --git a/MVMCoreUI/Atoms/Views/TextButtonView.h b/MVMCoreUI/Legacy/Views/TextButtonView.h similarity index 100% rename from MVMCoreUI/Atoms/Views/TextButtonView.h rename to MVMCoreUI/Legacy/Views/TextButtonView.h diff --git a/MVMCoreUI/Atoms/Views/TextButtonView.m b/MVMCoreUI/Legacy/Views/TextButtonView.m similarity index 100% rename from MVMCoreUI/Atoms/Views/TextButtonView.m rename to MVMCoreUI/Legacy/Views/TextButtonView.m diff --git a/MVMCoreUI/Molecules/TopLabelsView.h b/MVMCoreUI/Legacy/Views/TopLabelsView.h similarity index 100% rename from MVMCoreUI/Molecules/TopLabelsView.h rename to MVMCoreUI/Legacy/Views/TopLabelsView.h diff --git a/MVMCoreUI/Molecules/TopLabelsView.m b/MVMCoreUI/Legacy/Views/TopLabelsView.m similarity index 100% rename from MVMCoreUI/Molecules/TopLabelsView.m rename to MVMCoreUI/Legacy/Views/TopLabelsView.m diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Legacy/Views/ViewConstrainingView.h similarity index 100% rename from MVMCoreUI/Atoms/Views/ViewConstrainingView.h rename to MVMCoreUI/Legacy/Views/ViewConstrainingView.h diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Legacy/Views/ViewConstrainingView.m similarity index 100% rename from MVMCoreUI/Atoms/Views/ViewConstrainingView.m rename to MVMCoreUI/Legacy/Views/ViewConstrainingView.m diff --git a/MVMCoreUI/Models/Container/ContainerModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/ContainerModelProtocol.swift similarity index 100% rename from MVMCoreUI/Models/Container/ContainerModelProtocol.swift rename to MVMCoreUI/Models/ModelProtocols/ContainerModelProtocol.swift diff --git a/MVMCoreUI/Molecules/Doughnut/DoughnutChart.swift b/MVMCoreUI/Molecules/Doughnut/DoughnutChart.swift new file mode 100644 index 00000000..03d87ec9 --- /dev/null +++ b/MVMCoreUI/Molecules/Doughnut/DoughnutChart.swift @@ -0,0 +1,186 @@ +// +// DoughnutChart.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 07/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class DoughnutChart: View { + var doughnutLayer = CALayer() + var titleLabel = Label.commonLabelH3(true) + var subTitleLabel = Label.commonLabelB2(true) + 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 { + didSet { + if height != oldValue { + sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: height)! + updateContainer() + 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() + } + + public override func setAsMolecule() { + titleLabel.setAsMolecule() + subTitleLabel.setAsMolecule() + } + + 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 setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + clearLayers() + guard let doughnutChartModel = doughnutChartModel else { + return + } + titleLabel.setWithModel(doughnutChartModel.title, delegateObject, additionalData) + subTitleLabel.setWithModel(doughnutChartModel.subtitle, delegateObject, additionalData) + titleLabel.textAlignment = .center + subTitleLabel.textAlignment = .center + updateLabelContainer() + 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 { + 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() + } + + func lineWidth() -> CGFloat { + return 30 + } + + 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 { + return doughnutChartModel?.spaceRequired ?? true + } + + 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))) + + labelContainerLeftConstraint?.constant = padding + labelContainerRightConstraint?.constant = padding + labelContainerTopConstraint?.constant = max(radius - labelheight, labelheight) + labelContainerBottomConstraint?.constant = max(radius - labelheight, labelheight) + } +} diff --git a/MVMCoreUI/Molecules/Doughnut/DoughnutChartModel.swift b/MVMCoreUI/Molecules/Doughnut/DoughnutChartModel.swift new file mode 100644 index 00000000..1f4098e2 --- /dev/null +++ b/MVMCoreUI/Molecules/Doughnut/DoughnutChartModel.swift @@ -0,0 +1,36 @@ +// +// DoughnutChartModel.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 10/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class DoughnutChartModel: MoleculeProtocol { + public var backgroundColor: Color? + public static var identifier: String = "doughnutChart" + public var title: LabelModel? + public var subtitle: LabelModel? + public var sections: [DoughnutChartItemModel] + public var spaceRequired: Bool? + + public init(sections: [DoughnutChartItemModel]) { + self.sections = sections + } +} + +@objcMembers public class DoughnutChartItemModel: MoleculeProtocol { + public var backgroundColor: Color? + public static var identifier: String = "doughnutChartItem" + 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/Molecules/Doughnut/DoughnutChartView.swift b/MVMCoreUI/Molecules/Doughnut/DoughnutChartView.swift new file mode 100644 index 00000000..e33601ad --- /dev/null +++ b/MVMCoreUI/Molecules/Doughnut/DoughnutChartView.swift @@ -0,0 +1,160 @@ +// +// DoughnutChartView.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 26/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class DoughnutChartView: View { + var doughnutChart = DoughnutChart(frame: CGRect.zero) + var colorLablesStack = ColorViewLabelsStack() + var doughnutChartModel: DoughnutChartModel? { + get { return model as? DoughnutChartModel } + } + + 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 + bottomAnchor.constraint(greaterThanOrEqualTo: doughnutChart.bottomAnchor).isActive = true + + let doughnutBottomAnchor = bottomAnchor.constraint(equalTo: doughnutChart.bottomAnchor) + doughnutBottomAnchor.priority = UILayoutPriority(rawValue: 200) + doughnutBottomAnchor.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) + 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 + + colorLablesStack.leadingAnchor.constraint(equalTo: doughnutChart.trailingAnchor, constant: PaddingThree).isActive = true + colorLablesStack.backgroundColor = .clear + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + doughnutChart.updateView(size) + colorLablesStack.updateView(size) + } + + open override func reset() { + super.reset() + doughnutChart.reset() + colorLablesStack.reset() + } + + open override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + + guard let model = doughnutChartModel else { return } + doughnutChart.setWithModel(model, delegateObject, additionalData) + + // Create the stack model + var stackItems: [MoleculeStackItemModel] = [] + for item in model.sections { + stackItems.append(MoleculeStackItemModel(with: item)) + } + let stack = MoleculeStackModel(molecules: stackItems) + stack.verticalAlignment = .fill + colorLablesStack.setWithModel(stack, delegateObject, additionalData) + } + + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: DoughnutChartModel.self) else { return } + setWithModel(model, delegateObject, additionalData) + } +} + +extension DoughnutChartView: MVMCoreUIViewConstrainingProtocol { + open func horizontalAlignment() -> UIStackView.Alignment { + return .leading + } +} + +class ColorViewLabelsStack: MoleculeStackView { + override func createStackItemsFromModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let stackItemModels = stackModel?.molecules else { return } + for model in stackItemModels { + let view = ColorViewWithLabel() + let stackItem = MoleculeStackItem(andContain: view) + stackItem.setWithModel(model, delegateObject, additionalData) + stackItems.append(stackItem) + } + } +} + +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 setAsMolecule() { + label.setAsMolecule() + } + + override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let chartItemModel = model as? DoughnutChartItemModel else { + return + } + label.setWithModel(chartItemModel.label, delegateObject, additionalData) + colorView.backgroundColor = chartItemModel.color.uiColor + } +} diff --git a/MVMCoreUI/Models/Molecules/FooterModel.swift b/MVMCoreUI/Molecules/FooterModel.swift similarity index 87% rename from MVMCoreUI/Models/Molecules/FooterModel.swift rename to MVMCoreUI/Molecules/FooterModel.swift index e31ec1fc..a534ed87 100644 --- a/MVMCoreUI/Models/Molecules/FooterModel.swift +++ b/MVMCoreUI/Molecules/FooterModel.swift @@ -13,7 +13,7 @@ import Foundation public static var identifier: String = "footer" public var backgroundColor: Color? - enum FooterCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case backgroundColor } @@ -39,7 +39,7 @@ import Foundation } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: FooterCodingKeys.self) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) try super.init(from: decoder) setDefaults() @@ -47,7 +47,7 @@ import Foundation public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: FooterCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } diff --git a/MVMCoreUI/Molecules/StandardFooterView.swift b/MVMCoreUI/Molecules/FooterView.swift similarity index 88% rename from MVMCoreUI/Molecules/StandardFooterView.swift rename to MVMCoreUI/Molecules/FooterView.swift index e433ec88..b9113d1d 100644 --- a/MVMCoreUI/Molecules/StandardFooterView.swift +++ b/MVMCoreUI/Molecules/FooterView.swift @@ -1,5 +1,5 @@ // -// StandardFooterView.swift +// FooterView.swift // MVMCoreUI // // Created by Scott Pfeil on 3/11/19. @@ -8,7 +8,7 @@ import UIKit -open class StandardFooterView: MoleculeContainer { +open class FooterView: MoleculeContainer { public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { if let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) { return height + PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing diff --git a/MVMCoreUI/Models/Molecules/HeaderModel.swift b/MVMCoreUI/Molecules/HeaderModel.swift similarity index 86% rename from MVMCoreUI/Models/Molecules/HeaderModel.swift rename to MVMCoreUI/Molecules/HeaderModel.swift index 4499d313..ab7d5427 100644 --- a/MVMCoreUI/Models/Molecules/HeaderModel.swift +++ b/MVMCoreUI/Molecules/HeaderModel.swift @@ -13,7 +13,7 @@ import Foundation public var backgroundColor: Color? public var line: LineModel? - enum HeaderCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case moleculeName case line case backgroundColor @@ -43,14 +43,14 @@ import Foundation required public init(from decoder: Decoder) throws { try super.init(from: decoder) - let typeContainer = try decoder.container(keyedBy: HeaderCodingKeys.self) - line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) setDefaults() + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) ?? LineModel(type: .heavy) } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: HeaderCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encode(line, forKey: .line) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/HeaderView.swift similarity index 96% rename from MVMCoreUI/Molecules/StandardHeaderView.swift rename to MVMCoreUI/Molecules/HeaderView.swift index ad231597..f04661e2 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/HeaderView.swift @@ -1,5 +1,5 @@ // -// StandardHeaderView.swift +// HeaderView.swift // MVMCoreUI // // Created by Scott Pfeil on 2/12/19. @@ -8,7 +8,7 @@ import UIKit -public class StandardHeaderView: MoleculeContainer { +public class HeaderView: MoleculeContainer { var line: Line? var headerModel: HeaderModel? { diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TabsModel.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TabsModel.swift index 0e358871..2cf86858 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/TabsModel.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TabsModel.swift @@ -17,7 +17,7 @@ public class TabsModel: MoleculeProtocol { // Must be capped to 0...(tabs.count - 1) public var selectedIndex: Int = 0 - enum TabsCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case tabs case backgroundColor case selectedColor @@ -29,7 +29,7 @@ public class TabsModel: MoleculeProtocol { } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: TabsCodingKeys.self) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) tabs = try typeContainer.decode([LabelModel].self, forKey: .tabs) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedColor) { @@ -41,7 +41,7 @@ public class TabsModel: MoleculeProtocol { } public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: TabsCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(tabs, forKey: .tabs) try container.encode(backgroundColor, forKey: .backgroundColor) try container.encode(selectedColor, forKey: .selectedColor) diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift index 9a7bd952..1559e1a5 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift @@ -84,17 +84,19 @@ import UIKit } open func setupConstraintsForViewWithButtons() { - guard let viewForButtons = viewForButtons, let primaryButton = primaryButton, let secondaryButton = secondaryButton else { - return - } + guard let viewForButtons = viewForButtons, + let primaryButton = primaryButton, + let secondaryButton = secondaryButton + else { return } + viewForButtons.addSubview(primaryButton) viewForButtons.addSubview(secondaryButton) secondaryButton.widthAnchor.constraint(equalTo: primaryButton.widthAnchor, multiplier: 1).isActive = true - secondaryButton.topAnchor.constraint(equalTo: viewForButtons.topAnchor).isActive = true - primaryButton.topAnchor.constraint(equalTo: viewForButtons.topAnchor).isActive = true - viewForButtons.bottomAnchor.constraint(equalTo: secondaryButton.bottomAnchor).isActive = true - viewForButtons.bottomAnchor.constraint(equalTo: primaryButton.bottomAnchor).isActive = true - NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[leftButton]-10-[rightButton]-0-|", options: NSLayoutConstraint.FormatOptions.alignAllCenterY, metrics: nil, views: ["leftButton": secondaryButton, "rightButton": primaryButton])) + NSLayoutConstraint.constraintPinSubview(secondaryButton, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) + NSLayoutConstraint.constraintPinSubview(primaryButton, pinTop: true, pinBottom: true, pinLeft: false, pinRight: true) + let constraint = primaryButton.leadingAnchor.constraint(equalTo: secondaryButton.trailingAnchor, constant: 10) + constraint.priority = UILayoutPriority(900) + constraint.isActive = true } func setupWithTwoButtons() { @@ -107,7 +109,6 @@ import UIKit pinView(toSuperView: viewForButtons) alignCenterHorizontal() - createPrimaryButton() createSecondaryButton() setupConstraintsForViewWithButtons() @@ -256,6 +257,10 @@ import UIKit primaryButton?.isHidden = true secondaryButton?.isHidden = true } + + override open func horizontalAlignment() -> UIStackView.Alignment { + return .center + } } // MARK: - Deprecate diff --git a/MVMCoreUI/Molecules/Items/AccordionListItemModel.swift b/MVMCoreUI/Molecules/Items/AccordionListItemModel.swift index d5519288..5d88b913 100644 --- a/MVMCoreUI/Molecules/Items/AccordionListItemModel.swift +++ b/MVMCoreUI/Molecules/Items/AccordionListItemModel.swift @@ -16,7 +16,7 @@ class AccordionListItemModel: MoleculeContainerModel, ListItemModelProtocol { public var hideArrow: Bool? = true public var line: LineModel? - enum AccordionListItemCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case molecules case backgroundColor case hideLineWhenExpanded @@ -25,7 +25,7 @@ class AccordionListItemModel: MoleculeContainerModel, ListItemModelProtocol { } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: AccordionListItemCodingKeys.self) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeMolecules(codingKey: .molecules) as! [ListItemModelProtocol] backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) @@ -37,7 +37,7 @@ class AccordionListItemModel: MoleculeContainerModel, ListItemModelProtocol { public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: AccordionListItemCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeModels(molecules, forKey: .molecules) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(hideLineWhenExpanded, forKey: .hideLineWhenExpanded) diff --git a/MVMCoreUI/Molecules/Items/DropDownListItemModel.swift b/MVMCoreUI/Molecules/Items/DropDownListItemModel.swift index 64e8c69b..86b0ac82 100644 --- a/MVMCoreUI/Molecules/Items/DropDownListItemModel.swift +++ b/MVMCoreUI/Molecules/Items/DropDownListItemModel.swift @@ -22,7 +22,7 @@ import Foundation super.init(with: molecule) } - enum DropDownCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case molecules case dropDown case line @@ -30,7 +30,7 @@ import Foundation } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: DropDownCodingKeys.self) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeMolecules2D(codingKey: .molecules) as! [[ListItemModelProtocol]] dropDown = try typeContainer.decode(DropDownModel.self, forKey: .dropDown) if let lineModel = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) { @@ -42,7 +42,7 @@ import Foundation public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: DropDownCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeModels2D(molecules, forKey: .molecules) try container.encode(dropDown, forKey: .dropDown) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) diff --git a/MVMCoreUI/Molecules/Items/ListItemModel.swift b/MVMCoreUI/Molecules/Items/ListItemModel.swift index 5e54a2e9..c564c214 100644 --- a/MVMCoreUI/Molecules/Items/ListItemModel.swift +++ b/MVMCoreUI/Molecules/Items/ListItemModel.swift @@ -17,7 +17,7 @@ import MVMCore public var line: LineModel? public var style: String? = "standard" - enum ListItemCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case backgroundColor case action case hideArrow @@ -47,7 +47,7 @@ import MVMCore } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: ListItemCodingKeys.self) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) action = try typeContainer.decodeModelIfPresent(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) hideArrow = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideArrow) @@ -61,7 +61,7 @@ import MVMCore public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: ListItemCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeModelIfPresent(action, forKey: .action) try container.encodeIfPresent(hideArrow, forKey: .hideArrow) diff --git a/MVMCoreUI/Molecules/Items/MoleculeStackItem.swift b/MVMCoreUI/Molecules/Items/MoleculeStackItem.swift new file mode 100644 index 00000000..279e7948 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/MoleculeStackItem.swift @@ -0,0 +1,16 @@ +// +// MoleculeStackItem.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// +// A list item that contains a molecule + +import UIKit + +open class MoleculeStackItem: MoleculeContainer { + var stackItemModel: StackItemModel? { + get { return model as? StackItemModel } + } +} diff --git a/MVMCoreUI/Molecules/Items/MoleculeStackItemModel.swift b/MVMCoreUI/Molecules/Items/MoleculeStackItemModel.swift new file mode 100644 index 00000000..0661e907 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/MoleculeStackItemModel.swift @@ -0,0 +1,45 @@ +// +// MoleculeStackItem.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/4/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + +import Foundation + +@objcMembers public class MoleculeStackItemModel: MoleculeContainerModel, MoleculeProtocol, StackItemModelProtocol { + public static var identifier: String = "stackItem" + public var backgroundColor: Color? + public var spacing: CGFloat? + public var percent: Int? + public var gone: Bool = false + + private enum CodingKeys: String, CodingKey { + case spacing + case percent + case gone + } + + public override init(with moleculeModel: MoleculeProtocol) { + super.init(with: moleculeModel) + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) + percent = try typeContainer.decodeIfPresent(Int.self, forKey: .percent) + if let gone = try typeContainer.decodeIfPresent(Bool.self, forKey: .gone) { + self.gone = gone + } + 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.encodeIfPresent(spacing, forKey: .spacing) + try container.encodeIfPresent(percent, forKey: .percent) + try container.encode(gone, forKey: .gone) + } +} diff --git a/MVMCoreUI/Molecules/Items/StackItem.swift b/MVMCoreUI/Molecules/Items/StackItem.swift index 59c30962..07cae3d0 100644 --- a/MVMCoreUI/Molecules/Items/StackItem.swift +++ b/MVMCoreUI/Molecules/Items/StackItem.swift @@ -2,13 +2,13 @@ // StackItem.swift // MVMCoreUI // -// Created by Scott Pfeil on 12/13/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. // -import UIKit +import Foundation -open class StackItem: MoleculeContainer { +open class StackItem: Container { var stackItemModel: StackItemModel? { get { return model as? StackItemModel } } diff --git a/MVMCoreUI/Molecules/Items/StackItemModel.swift b/MVMCoreUI/Molecules/Items/StackItemModel.swift index d19f2401..9484cd8d 100644 --- a/MVMCoreUI/Molecules/Items/StackItemModel.swift +++ b/MVMCoreUI/Molecules/Items/StackItemModel.swift @@ -1,45 +1,22 @@ // -// MoleculeStackItem.swift +// StackItemModel.swift // MVMCoreUI // -// Created by Suresh, Kamlesh on 10/4/19. -// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. // import Foundation -@objcMembers public class StackItemModel: MoleculeContainerModel, MoleculeProtocol { - public static var identifier: String = "stackItem" +@objcMembers public class StackItemModel: StackItemModelProtocol, MoleculeProtocol { + public static var identifier: String = "simpleStackItem" public var backgroundColor: Color? public var spacing: CGFloat? public var percent: Int? public var gone: Bool = false - enum MoleculeStackItemCodingKeys: String, CodingKey { - case spacing - case percent - case gone - } - - public override init(with moleculeModel: MoleculeProtocol) { - super.init(with: moleculeModel) - } - - required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: MoleculeStackItemCodingKeys.self) - spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) - percent = try typeContainer.decodeIfPresent(Int.self, forKey: .percent) - if let gone = try typeContainer.decodeIfPresent(Bool.self, forKey: .gone) { - self.gone = gone - } - try super.init(from: decoder) - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: MoleculeStackItemCodingKeys.self) - try container.encodeIfPresent(spacing, forKey: .spacing) - try container.encodeIfPresent(percent, forKey: .percent) - try container.encode(gone, forKey: .gone) + public convenience init(gone: Bool) { + self.init() + self.gone = gone } } diff --git a/MVMCoreUI/Molecules/Items/StackItemModelProtocol.swift b/MVMCoreUI/Molecules/Items/StackItemModelProtocol.swift new file mode 100644 index 00000000..165cb1ac --- /dev/null +++ b/MVMCoreUI/Molecules/Items/StackItemModelProtocol.swift @@ -0,0 +1,15 @@ +// +// StackItemModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol StackItemModelProtocol: MoleculeProtocol { + var spacing: CGFloat? { get set } + var percent: Int? { get set } + var gone: Bool { get set } +} diff --git a/MVMCoreUI/Molecules/Items/TabsListItemModel.swift b/MVMCoreUI/Molecules/Items/TabsListItemModel.swift index d27b9097..13dd09e7 100644 --- a/MVMCoreUI/Molecules/Items/TabsListItemModel.swift +++ b/MVMCoreUI/Molecules/Items/TabsListItemModel.swift @@ -17,7 +17,7 @@ public class TabsListItemModel: ContainerModel, ListItemModelProtocol { public var hideArrow: Bool? = true public var line: LineModel? = LineModel(type: .standard) - enum TabsListItemCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case tabs case molecules case backgroundColor @@ -33,7 +33,7 @@ public class TabsListItemModel: ContainerModel, ListItemModelProtocol { } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: TabsListItemCodingKeys.self) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) tabs = try typeContainer.decode(TabsModel.self, forKey: .tabs) molecules = try typeContainer.decodeMolecules2D(codingKey: .molecules) as! [[ListItemModelProtocol]] backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) @@ -45,7 +45,7 @@ public class TabsListItemModel: ContainerModel, ListItemModelProtocol { public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: TabsListItemCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(tabs, forKey: .tabs) try container.encodeModels2D(molecules, forKey: .molecules) try container.encode(backgroundColor, forKey: .backgroundColor) diff --git a/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift b/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift index c61bd408..472be993 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift @@ -21,7 +21,7 @@ public class CornerLabelsModel: MoleculeProtocol { self.molecule = molecule } - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case backgroundColor case topLeftLabel case topRightLabel diff --git a/MVMCoreUI/Molecules/MoleculeContainer.swift b/MVMCoreUI/Molecules/MoleculeContainer.swift deleted file mode 100644 index e3646bcf..00000000 --- a/MVMCoreUI/Molecules/MoleculeContainer.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// MoleculeContainer.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 12/12/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -open class MoleculeContainer: Container { - - /// Can be overriden to change how the molecule is added to the hierarchy. - public func addMolecule(_ molecule: UIView) { - addAndContain(molecule) - } - - override public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - return - } - if view == nil { - if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: false) { - addAndContain(molecule) - } - } else { - (view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON?(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) - } - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - } - - public override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - if let casteModel = model as? MoleculeContainerModel { - if view != nil { - (view as? ModelMoleculeViewProtocol)?.setWithModel(casteModel.molecule, delegateObject, additionalData) - } else { - if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(casteModel.molecule, delegateObject) { - addMolecule(molecule) - } - } - } - super.setWithModel(model, delegateObject, additionalData) - } -} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift index d5557eca..01844e07 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -19,7 +19,7 @@ struct EyebrowHeadlineBodyLinkModel: MoleculeProtocol { } @objcMembers open class EyebrowHeadlineBodyLink: Container { - let stack = MoleculeStackView(frame: .zero) + let stack = Stack(frame: .zero) let eyebrow = Label.commonLabelB3(true) let headline = Label.commonLabelB1(true) let body = Label.commonLabelB2(true) @@ -34,19 +34,7 @@ struct EyebrowHeadlineBodyLinkModel: MoleculeProtocol { guard stack.superview == nil else { return } - let eyebrowStackItem = StackItemModel(with: casteModel!.eyeBrow!) - let headlineStackItem = StackItemModel(with: casteModel!.headline!) - let bodyStackItem = StackItemModel(with: casteModel!.body!) - let linkStackItem = StackItemModel(with: casteModel!.link!) - - // To visually take into account the extra padding in the intrinsic content of a button. - linkStackItem.spacing = -6 - - let stackModel = MoleculeStackModel(molecules: [eyebrowStackItem,headlineStackItem,bodyStackItem,linkStackItem]) - stackModel.spacing = 0 - stack.model = stackModel stack.stackItems = [StackItem(andContain: eyebrow),StackItem(andContain: headline),StackItem(andContain: body),StackItem(andContain: link)] - addSubview(stack) NSLayoutConstraint.constraintPinSubview(toSuperview: stack) } @@ -64,13 +52,14 @@ struct EyebrowHeadlineBodyLinkModel: MoleculeProtocol { headline.setWithModel(casteModel?.headline, delegateObject, additionalData) body.setWithModel(casteModel?.body, delegateObject, additionalData) link.setWithModel(casteModel?.link, delegateObject, additionalData) - - (stack.stackItems[0].model as? StackItemModel)?.gone = !eyebrow.hasText - (stack.stackItems[1].model as? StackItemModel)?.gone = !headline.hasText - (stack.stackItems[2].model as? StackItemModel)?.gone = !body.hasText - (stack.stackItems[3].model as? StackItemModel)?.gone = ((link.titleLabel?.text?.count) ?? 0) == 0 + + // Create a stack model to use for the internal stack. + let stackModel = StackModel(molecules: [StackItemModel(gone: !eyebrow.hasText),StackItemModel(gone: !headline.hasText),StackItemModel(gone: !body.hasText),StackItemModel(gone: (link.titleLabel?.text?.count ?? 0) == 0)]) + stackModel.spacing = 0 + stack.model = stackModel stack.restack() } + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: EyebrowHeadlineBodyLinkModel.self) else { return } setWithModel(model, delegateObject, additionalData) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 3b52e6bf..d1f0dbf5 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -13,18 +13,15 @@ import Foundation let caretButton = CaretButton(frame: .zero) let backgroundImageView = MFLoadImageView() var spaceBetweenConstant: CGFloat = 104.0 - var leftConstraintHeadline : NSLayoutConstraint? - var leftConstraintCaretView : NSLayoutConstraint? - let padding = MFStyler.defaultHorizontalPaddingForApplicationWidth() let maxWidth : CGFloat = 350.0 // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { - super.updateView(size) - headlineBody.updateView(size) - caretButton.updateView(size) - backgroundImageView.updateView(size) - leftConstraintHeadline?.constant = MFStyler.defaultHorizontalPadding(forSize: size) - leftConstraintCaretView?.constant = MFStyler.defaultHorizontalPadding(forSize: size) + super.updateView(size) + headlineBody.updateView(size) + caretButton.updateView(size) + backgroundImageView.updateView(size) + backgroundImageView.alignFillHorizontal() + backgroundImageView.alignFillVertical() } open override func setupView() { @@ -39,8 +36,7 @@ import Foundation view.addSubview(caretButton) //Headline view - leftConstraintHeadline = headlineBody.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) - leftConstraintHeadline?.isActive = true + headlineBody.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: PaddingDefault).isActive = true let headLineBodyWidth = headlineBody.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85) @@ -50,8 +46,7 @@ import Foundation //Caret view caretButton.translatesAutoresizingMaskIntoConstraints = false - leftConstraintCaretView = caretButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) - leftConstraintCaretView?.isActive = true + caretButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor, constant: PaddingDefault).isActive = true caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant).isActive = true @@ -59,6 +54,8 @@ import Foundation //Background image view backgroundImageView.translatesAutoresizingMaskIntoConstraints = false backgroundImageView.imageView.contentMode = .scaleAspectFill + backgroundImageView.alignFillHorizontal() + backgroundImageView.alignFillVertical() view.addSubview(backgroundImageView) NSLayoutConstraint.constraintPinSubview(toSuperview: backgroundImageView) view.sendSubviewToBack(backgroundImageView) @@ -88,6 +85,8 @@ import Foundation headlineBody.setWithModel(model.headlineBody, delegateObject, additionalData) caretButton.setWithModel(model.caretLink, delegateObject, additionalData) backgroundImageView.setWithModel(model.image, delegateObject, additionalData) + backgroundImageView.alignFillHorizontal() + backgroundImageView.alignFillVertical() } } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift index 8052a7db..76333d08 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift @@ -11,7 +11,7 @@ import UIKit public class HeadlineBodyCaretLinkImageModel: MoleculeProtocol { public static var identifier: String = "headlineBodyCaretLinkImage" public var backgroundColor: Color? - public var caretLink: LinkModel? + public var caretLink: CaretLinkModel? public var headlineBody: HeadlineBodyModel public var image: ImageViewModel } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index 6ce51d3e..bc63c12c 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -10,8 +10,8 @@ import Foundation @objcMembers public class HeadlineBodyModel: MoleculeProtocol { public static var identifier: String = "headlineBody" - public var headline: LabelModel - public var body: LabelModel + public var headline: LabelModel? + public var body: LabelModel? public var style: String? public var backgroundColor: Color? diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedList.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedList.swift new file mode 100644 index 00000000..8f977ed7 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedList.swift @@ -0,0 +1,14 @@ +// +// NumberedList.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 03/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class NumberedList: StringAndMoleculeStack { +} + + diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift new file mode 100644 index 00000000..554255d6 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift @@ -0,0 +1,47 @@ +// +// NumberedListModel.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 10/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class NumberedListModel: MoleculeStackModel { + public override class var identifier: String { + return "numberedList" + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case list + } + + // Numbered list model comes in the from of list = [MoleculeProtocol] + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + + let list = try typeContainer.decodeMolecules(codingKey: .list) + var models: [MoleculeStackItemModel] = [] + for (index, molecule) in list.enumerated() { + models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: "\(index+1).", molecule: molecule))) + } + super.init(molecules: models) + spacing = 0 + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + + var models: [MoleculeProtocol] = [] + for molecule in molecules { + models.append(molecule.molecule) + } + try container.encodeModels(models, forKey: .list) + } +} + + diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift new file mode 100644 index 00000000..23e32218 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift @@ -0,0 +1,42 @@ +// +// StringAndMoleculeModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/17/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class StringAndMoleculeModel: MoleculeProtocol { + public static var identifier: String = "stringAndMoleculeModel" + public var backgroundColor: Color? + public var string: String + public var molecule: MoleculeProtocol + + public init(string: String, molecule: MoleculeProtocol) { + self.string = string + self.molecule = molecule + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case string + case molecule + } + + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + string = try typeContainer.decode(String.self, forKey: .string) + molecule = try typeContainer.decodeMolecule(codingKey: .molecule) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(string, forKey: .string) + try container.encodeModel(molecule, forKey: .molecule) + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift new file mode 100644 index 00000000..2606ecf4 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift @@ -0,0 +1,28 @@ +// +// ListMoleculeContainer.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 03/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +// This class is only temporarily necessary. Eventually we will have initWithModel instad of just init for moleculeviews, which will remove this need. +open class StringAndMoleculeStack: MoleculeStackView { + override func createStackItemsFromModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let model = stackModel else { return } + for stackItemModel in model.molecules { + guard let stringAndMoleculeModel = stackItemModel.molecule as? StringAndMoleculeModel, + let moleculeName = stringAndMoleculeModel.molecule.moleculeName, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forName: moleculeName) as? (UIView & ModelMoleculeViewProtocol) else { + // Throw error + return + } + let view = StringAndMoleculeView(string: stringAndMoleculeModel.string, molecule: molecule) + let stackItem = MoleculeStackItem(andContain: view) + stackItem.setWithModel(stackItemModel, delegateObject, nil) + stackItems.append(stackItem) + } + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift new file mode 100644 index 00000000..1874c163 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift @@ -0,0 +1,92 @@ +// +// StringAndMoleculeView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/17/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class StringAndMoleculeView: View { + var label = Label.commonLabelB2(true) + var string: String + var molecule: UIView & ModelMoleculeViewProtocol + + var leftWidthConstraint: NSLayoutConstraint? + @Percent var percentage: CGFloat = 5 + + var constraintBetweenViews: NSLayoutConstraint? + var spaceBetweenViews: CGFloat = 0 { + didSet { + if spaceBetweenViews != oldValue { + constraintBetweenViews?.constant = spaceBetweenViews + setNeedsDisplay() + } + } + } + + // MARK: - Inits + public init(string: String, molecule: UIView & ModelMoleculeViewProtocol) { + self.string = string + self.molecule = molecule + super.init(frame: .zero) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func setupView() { + super.setupView() + guard subviews.count == 0 else { + return + } + + translatesAutoresizingMaskIntoConstraints = false + addSubview(label) + addSubview(molecule) + + NSLayoutConstraint.constraintPinSubview(label, pinTop: true, pinBottom: false, pinLeft: true, pinRight: false) + bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true + + let lowBottomConstraint = bottomAnchor.constraint(equalTo: label.bottomAnchor) + lowBottomConstraint.priority = UILayoutPriority(rawValue: 200) + lowBottomConstraint.isActive = true + + NSLayoutConstraint.constraintPinSubview(molecule, pinTop: true, pinBottom: true, pinLeft: false, pinRight: true) + constraintBetweenViews = molecule.leftAnchor.constraint(equalTo: label.rightAnchor, constant: spaceBetweenViews) + constraintBetweenViews?.priority = .required + constraintBetweenViews?.isActive = true + + setContentHuggingPriority(.defaultHigh, for: .vertical) + setContentHuggingPriority(.defaultHigh, for: .horizontal) + updateLeftViewWidthConstraint(percentage) + } + + override open func updateView(_ size: CGFloat) { + super.updateView(size) + (molecule as? MVMCoreViewProtocol)?.updateView(size) + label.updateView(size) + } + + override open func reset() { + super.reset() + label.reset() + (molecule as? MoleculeViewProtocol)?.reset?() + } + + public override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? StringAndMoleculeModel else { return } + label.text = model.string + molecule.setWithModel(model.molecule, delegateObject, additionalData) + } + + func updateLeftViewWidthConstraint(_ percent: CGFloat) { + percentage = percent + leftWidthConstraint?.isActive = false + leftWidthConstraint = label.widthAnchor.constraint(equalTo: widthAnchor, multiplier: CGFloat(percent/100), constant: 0) + leftWidthConstraint?.isActive = true + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedList.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedList.swift new file mode 100644 index 00000000..b393866c --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedList.swift @@ -0,0 +1,12 @@ +// +// UnOrderedList.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 03/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class UnOrderedList: StringAndMoleculeStack { +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift new file mode 100644 index 00000000..aeb8b34e --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift @@ -0,0 +1,51 @@ +// +// UnOrderedListModel.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 10/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class UnOrderedListModel: MoleculeStackModel { + public override class var identifier: String { + return "unOrderedList" + } + public var bulletChar = "•" + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case list + case bulletChar + } + + // Numbered list model comes in the from of list = [MoleculeProtocol] + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let bulletChar = try typeContainer.decodeIfPresent(String.self, forKey: .bulletChar) { + self.bulletChar = bulletChar + } + + let list = try typeContainer.decodeMolecules(codingKey: .list) + var models: [MoleculeStackItemModel] = [] + for molecule in list { + models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: bulletChar, molecule: molecule))) + } + super.init(molecules: models) + spacing = 0 + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(bulletChar, forKey: .bulletChar) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + + var models: [MoleculeProtocol] = [] + for molecule in molecules { + models.append(molecule.molecule) + } + try container.encodeModels(models, forKey: .list) + } +} diff --git a/MVMCoreUI/Organisms/MoleculeStackModel.swift b/MVMCoreUI/Organisms/MoleculeStackModel.swift index 2a149e73..c8922204 100644 --- a/MVMCoreUI/Organisms/MoleculeStackModel.swift +++ b/MVMCoreUI/Organisms/MoleculeStackModel.swift @@ -5,22 +5,26 @@ // Created by Suresh, Kamlesh on 10/3/19. // Copyright © 2019 Suresh, Kamlesh. All rights reserved. // +// A stack that has a list molecule stack items. import Foundation -@objcMembers public class MoleculeStackModel: ContainerModel, MoleculeProtocol { - public static var identifier: String = "stack" +@objcMembers public class MoleculeStackModel: ContainerModel, MoleculeProtocol, StackModelProtocol { + public class var identifier: String { + return "stack" + } public var backgroundColor: Color? - public var molecules: [StackItemModel] + public var molecules: [MoleculeStackItemModel] public var axis: NSLayoutConstraint.Axis = .vertical public var spacing: CGFloat = 16.0 + public var useStackSpacingBeforeFirstItem = false - public init(molecules: [StackItemModel]) { + public init(molecules: [MoleculeStackItemModel]) { self.molecules = molecules super.init() } - enum StackCodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case moleculeName case molecules case axis @@ -28,8 +32,8 @@ import Foundation } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: StackCodingKeys.self) - molecules = try typeContainer.decode([StackItemModel].self, forKey: .molecules) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + molecules = try typeContainer.decode([MoleculeStackItemModel].self, forKey: .molecules) if let axisString = try typeContainer.decodeIfPresent(String.self, forKey: .axis), let optionalAxis = NSLayoutConstraint.Axis(rawValue: axisString) { axis = optionalAxis } @@ -41,7 +45,7 @@ import Foundation public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: StackCodingKeys.self) + var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(molecules, forKey: .molecules) try container.encodeIfPresent(axis.rawValueString, forKey: .axis) diff --git a/MVMCoreUI/Organisms/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift index 9f2f526f..aa81ff80 100644 --- a/MVMCoreUI/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -8,261 +8,27 @@ import UIKit -open class MoleculeStackView: Container { - var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() - var useStackSpacingBeforeFirstItem = false - var stackModel: MoleculeStackModel? { +open class MoleculeStackView: Stack { + override var stackModel: MoleculeStackModel? { get { return model as? MoleculeStackModel } } - var stackItems: [StackItem] = [] - var moleculesShouldSetHorizontalMargins = false - var moleculesShouldSetVerticalMargins = false - - // MARK: - Helpers - public func pinView(_ view: UIView, toView: UIView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, priority: UILayoutPriority, constant: CGFloat) { - let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: relation, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant) - constraint.priority = priority - constraint.isActive = true - } - - /// Restacks the existing items. - func restack() { - removeAllItemViews() - let stackItems = self.stackItems - self.stackItems = [] - let lastItem = stackItems.last(where: { (item) -> Bool in - return !item.stackItemModel!.gone - }) - for item in stackItems { - addStackItem(item, lastItem: item === lastItem) - } - } - - /// Removes all stack items views from the view. - func removeAllItemViews() { - for item in stackItems { - item.removeFromSuperview() - } + /// Convenience function, adds a molecule to a MoleculeStackItem to the MoleculeStack + func addMolecule(_ view: View, lastItem: Bool) { + guard let model = view.model else { return } + let stackItemModel = MoleculeStackItemModel(with: model) + let stackItem = MoleculeStackItem(andContain: view) + addView(stackItem, stackItemModel, lastItem: lastItem) } - // MARK: - Inits - public override init(frame: CGRect) { - super.init(frame: frame) - } - - public init(withJSON json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.init(frame: CGRect.zero) - setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - } - - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - MFViewProtocol - public override func setupView() { - super.setupView() - guard contentView.superview == nil else { - return - } - MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) - translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .clear - addSubview(contentView) - containerHelper.constrainView(contentView) - contentView.setContentHuggingPriority(.defaultHigh, for: .vertical) - contentView.setContentHuggingPriority(.defaultHigh, for: .horizontal) - } - - public override func updateView(_ size: CGFloat) { - super.updateView(size) - for item in stackItems { - item.updateView(size) - } - } - - // MARK: - MVMCoreUIMoleculeViewProtocol - public override func reset() { - super.reset() - backgroundColor = .clear - for item in stackItems { - item.reset() - } - } - - public override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { - let previousModel = stackModel - super.setWithModel(model, delegateObject, additionalData) - removeAllItemViews() - - // If the items in the stack are different, clear them, create new ones. - if (previousModel == nil) || MoleculeStackView.nameForReuse(previousModel, delegateObject) != MoleculeStackView.nameForReuse(model, delegateObject) { - stackItems = [] - createStackItemsFromModel(with: delegateObject) - } else if let models = stackModel?.molecules { - for (index, element) in models.enumerated() { - stackItems[index].setWithModel(element, delegateObject, additionalData) - } - } - - restack() - stackModel?.useHorizontalMargins = moleculesShouldSetHorizontalMargins - stackModel?.useVerticalMargins = moleculesShouldSetVerticalMargins - } - - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - if model == nil { - let data = try! JSONSerialization.data(withJSONObject: json!) - let decoder = JSONDecoder() - let model = try! decoder.decode(MoleculeStackModel.self, from: data) - setWithModel(model, delegateObject, additionalData) - } else { - setWithModel(model, delegateObject, additionalData) - } - } - - public override class func nameForReuse(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { - // This will aggregate names of molecules to make an id. - guard let model = model as? MoleculeStackModel else { - return "stack<>" - } - var name = "stack<" - for case let item in model.molecules { - if let moleculeName = item.molecule.moleculeName { - if let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping[moleculeName] as? ModelMoleculeViewProtocol.Type, - let nameForReuse = moleculeClass.nameForReuse(item.molecule, delegateObject) { - name.append(nameForReuse + ",") - } else { - name.append(moleculeName + ",") - } - } - } - name.append(">") - return name - } - - public class func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { - // This will aggregate names of molecules to make an id. - guard let molecules = molecule?.optionalArrayForKey(KeyMolecules) else { - return "stack<>" - } - var name = "stack<" - for case let item as [AnyHashable: Any] in molecules { - if let molecule = item.optionalDictionaryForKey(KeyMolecule), let moleculeName = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) { - name.append(moleculeName + ",") - } - } - name.append(">") - return name - } - - public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - guard let items = json?.optionalArrayForKey(KeyMolecules) else { - return 0 - } - let horizontal = json?.optionalStringForKey("axis") == "horizontal" - var estimatedHeight: CGFloat = 0 - for case let item as [AnyHashable: AnyHashable] in items { - if let molecule = item.optionalDictionaryForKey(KeyMolecule) { - let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.estimatedHeight?(forRow: molecule, delegateObject: delegateObject) - if !horizontal { - // Vertical stack aggregates the items - let spacing = item.optionalCGFloatForKey("spacing") ?? (estimatedHeight != 0 ? (json?.optionalCGFloatForKey("spacing") ?? 16) : 0) - estimatedHeight += ((height ?? 0) + spacing) - } else if let height = height { - // Horizontal stack takes the tallest item. - estimatedHeight = max(estimatedHeight, height) - } - } - } - return estimatedHeight - } - - public class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { - guard let items = json?.optionalArrayForKey(KeyMolecules) else { - return nil - } - var modules: [String] = [] - for case let item as [AnyHashable: AnyHashable] in items { - if let molecule = item.optionalDictionaryForKey(KeyMolecule), let modulesForMolecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.requiredModules?(molecule, delegateObject: delegateObject, error: error) { - modules += modulesForMolecule - } - } - return modules.count > 0 ? modules : nil - } - // MARK: - Adding to stack /// Creates all of the stackItems for the stackItemModels - func createStackItemsFromModel(with delegate: MVMCoreUIDelegateObject?) { + override func createStackItemsFromModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { guard let stackItemModels = stackModel?.molecules else { return } for model in stackItemModels { - if let stackItem = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(model, delegate) as? StackItem { + if let stackItem = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(model, delegateObject) as? MoleculeStackItem { stackItems.append(stackItem) } } } - - /// Adds the view to the stack. - func addView(_ view: View, lastItem: Bool) { - guard let model = view.model else { return } - let stackItem = StackItem(andContain: view) - stackItem.model = StackItemModel(with: model) - addStackItem(stackItem, lastItem: lastItem) - } - - /// Adds the stack item view - private func addStackItem(_ stackItem: StackItem, lastItem: Bool) { - let stackModel = self.stackModel! - let model = stackItem.stackItemModel! - guard !model.gone else { - // Gone views do not show - return - } - contentView.addSubview(stackItem) - stackItem.translatesAutoresizingMaskIntoConstraints = false - - let spacing = model.spacing ?? stackModel.spacing - let verticalAlignment = model.verticalAlignment ?? (stackItem.view as? MVMCoreUIViewConstrainingProtocol)?.verticalAlignment?() ?? (model.percent == nil && stackModel.axis == .vertical ? .fill : (stackModel.axis == .vertical ? .leading : .center)) - let horizontalAlignment = model.horizontalAlignment ?? (stackItem.view as? MVMCoreUIViewConstrainingProtocol)?.horizontalAlignment?() ?? (stackModel.axis == .vertical || model.percent == nil ? .fill : .leading) - stackItem.containerHelper.alignHorizontal(horizontalAlignment) - stackItem.containerHelper.alignVertical(verticalAlignment) - - let first = stackItems.first { !($0.stackItemModel?.gone ?? false) } == nil - if stackModel.axis == .vertical { - if first { - pinView(stackItem, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : model.spacing ?? 0) - } else if let previousView = stackItems.last(where: { item in - return !item.stackItemModel!.gone - }) { - stackItem.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: spacing).isActive = true - } - pinView(stackItem, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) - pinView(contentView, toView: stackItem, attribute: .trailing, relation: .equal, priority: .required, constant: 0) - if let percent = model.percent { - stackItem.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: CGFloat(percent)/100.0).isActive = true - } - if lastItem { - pinView(contentView, toView: stackItem, attribute: .bottom, relation: .equal, priority: .required, constant: 0) - } - } else { - if first { - // First horizontal item has no spacing by default unless told otherwise. - pinView(stackItem, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : model.spacing ?? 0) - } else if let previousView = stackItems.last(where: { item in - return !item.stackItemModel!.gone - }) { - stackItem.leftAnchor.constraint(equalTo: previousView.rightAnchor, constant: spacing).isActive = true - } - pinView(stackItem, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) - pinView(contentView, toView: stackItem, attribute: .bottom, relation: .equal, priority: .required, constant: 0) - if let percent = model.percent { - stackItem.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: CGFloat(percent)/100.0).isActive = true - } - if lastItem { - pinView(contentView, toView: stackItem, attribute: .right, relation: .equal, priority: .required, constant: 0) - } - } - stackItems.append(stackItem) - } } diff --git a/MVMCoreUI/Organisms/Stack.swift b/MVMCoreUI/Organisms/Stack.swift new file mode 100644 index 00000000..f67f7a0e --- /dev/null +++ b/MVMCoreUI/Organisms/Stack.swift @@ -0,0 +1,236 @@ +// +// Stack.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class Stack: Container where T: StackModelProtocol { + var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() + var stackModel: T? { + get { return model as? T } + } + var stackItems: [UIView] = [] + + // MARK: - Helpers + public func pinView(_ view: UIView, toView: UIView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, priority: UILayoutPriority, constant: CGFloat) { + let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: relation, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant) + constraint.priority = priority + constraint.isActive = true + } + + /// Restacks the existing items. + func restack() { + removeAllItemViews() + guard let stackModel = stackModel else { return } + let stackItems = self.stackItems + self.stackItems = [] + let lastItemIndex = stackModel.molecules.lastIndex(where: { (item) -> Bool in + return !item.gone + }) + for (index, view) in stackItems.enumerated() { + addView(view, stackModel.molecules[index], lastItem: lastItemIndex == index) + } + } + + /// Removes all stack items views from the view. + func removeAllItemViews() { + for item in stackItems { + item.removeFromSuperview() + } + } + + // MARK: - Inits + public override init(frame: CGRect) { + super.init(frame: frame) + } + + public init(withJSON json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.init(frame: CGRect.zero) + setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - MFViewProtocol + public override func setupView() { + super.setupView() + guard contentView.superview == nil else { + return + } + MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .clear + addSubview(contentView) + containerHelper.constrainView(contentView) + contentView.setContentHuggingPriority(.defaultHigh, for: .vertical) + contentView.setContentHuggingPriority(.defaultHigh, for: .horizontal) + } + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + for item in stackItems { + (item as? MVMCoreViewProtocol)?.updateView(size) + } + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + public override func reset() { + super.reset() + backgroundColor = .clear + for item in stackItems { + (item as? MoleculeViewProtocol)?.reset?() + } + } + + public override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + let previousModel = self.model + super.setWithModel(model, delegateObject, additionalData) + removeAllItemViews() + + // If the items in the stack are different, clear them, create new ones. + if (previousModel == nil) || Self.nameForReuse(previousModel, delegateObject) != Self.nameForReuse(model, delegateObject) { + stackItems = [] + createStackItemsFromModel(model, delegateObject, additionalData) + } else { + setStackItemsFromModel(model, delegateObject, additionalData) + } + + restack() + } + + public override class func nameForReuse(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + // This will aggregate names of molecules to make an id. + guard let model = model as? T else { + return "stack<>" + } + var name = "stack<" + for case let item in model.molecules { + if let moleculeName = item.moleculeName { + if let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping[moleculeName] as? ModelMoleculeViewProtocol.Type, + let nameForReuse = moleculeClass.nameForReuse(item, delegateObject) { + name.append(nameForReuse + ",") + } else { + name.append(moleculeName + ",") + } + } + } + name.append(">") + return name + } + + // Need to update to take into account first spacing flag + public override class func estimatedHeight(forRow molecule: MoleculeProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + guard let model = molecule as? T else { return 0 } + let horizontal = model.axis == .horizontal + var estimatedHeight: CGFloat = 0 + for case let item in model.molecules { + if item.gone { continue } + let height = (MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(item) as? ModelMoleculeViewProtocol.Type)?.estimatedHeight(forRow: item, delegateObject: delegateObject) ?? 0 + if !horizontal { + // Vertical stack aggregates the items + let spacing = item.spacing ?? model.spacing + estimatedHeight += (height + spacing) + } else { + // Horizontal stack takes the tallest item. + estimatedHeight = max(estimatedHeight, height) + } + } + return estimatedHeight + } + + public override class func requiredModules(_ molecule: MoleculeProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + guard let model = molecule as? T else { return nil } + var modules: [String] = [] + for case let item in model.molecules { + if let modulesForMolecule = (MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(item) as? ModelMoleculeViewProtocol.Type)?.requiredModules(item, delegateObject: delegateObject, error: error) { + modules += modulesForMolecule + } + } + return modules.count > 0 ? modules : nil + } + + // MARK: - Subclassables + + /// Can be subclassed to create views when we get stack item models and have no views yet + func createStackItemsFromModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + } + + /// Can be subclassed to set stack items with model when we already have views + func setStackItemsFromModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let models = stackModel?.molecules else { return } + for (index, element) in models.enumerated() { + (stackItems[index] as? ModelMoleculeViewProtocol)?.setWithModel(element, delegateObject, additionalData) + } + } + + // MARK: - Adding to stack + /// Convenience function, adds a view to a StackItem to the Stack + func addViewToItemToStack(_ view: UIView, lastItem: Bool) { + let stackItemModel = StackItemModel() + let stackItem = StackItem(andContain: view) + addView(stackItem, stackItemModel, lastItem: lastItem) + } + + /// Adds the stack item view + func addView(_ view: UIView,_ model: StackItemModelProtocol, lastItem: Bool) { + guard let stackModel = self.stackModel else { return } + guard !model.gone else { + // Gone views do not show + stackItems.append(view) + return + } + contentView.addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false + + let spacing = model.spacing ?? stackModel.spacing + if let container = view as? ContainerProtocol { + let verticalAlignment = (model as? ContainerModelProtocol)?.verticalAlignment ?? (container.view as? MVMCoreUIViewConstrainingProtocol)?.verticalAlignment?() ?? (model.percent == nil && stackModel.axis == .vertical ? .fill : (stackModel.axis == .vertical ? .leading : .center)) + let horizontalAlignment = (model as? ContainerModelProtocol)?.horizontalAlignment ?? (container.view as? MVMCoreUIViewConstrainingProtocol)?.horizontalAlignment?() ?? (stackModel.axis == .vertical || model.percent == nil ? .fill : .leading) + container.alignHorizontal(horizontalAlignment) + container.alignVertical(verticalAlignment) + } + + let first = contentView.subviews.count == 1 + if stackModel.axis == .vertical { + if first { + pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: stackModel.useStackSpacingBeforeFirstItem ? spacing : model.spacing ?? 0) + } else if let previousView = stackItems.last(where: { item in + return !model.gone + }) { + view.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: spacing).isActive = true + } + pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) + pinView(contentView, toView: view, attribute: .trailing, relation: .equal, priority: .required, constant: 0) + if let percent = model.percent { + view.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: CGFloat(percent)/100.0).isActive = true + } + if lastItem { + pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) + } + } else { + if first { + // First horizontal item has no spacing by default unless told otherwise. + pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: stackModel.useStackSpacingBeforeFirstItem ? spacing : model.spacing ?? 0) + } else if let previousView = stackItems.last(where: { item in + return !model.gone + }) { + view.leftAnchor.constraint(equalTo: previousView.rightAnchor, constant: spacing).isActive = true + } + pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) + pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) + if let percent = model.percent { + view.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: CGFloat(percent)/100.0).isActive = true + } + if lastItem { + pinView(contentView, toView: view, attribute: .right, relation: .equal, priority: .required, constant: 0) + } + } + stackItems.append(view) + } +} diff --git a/MVMCoreUI/Organisms/StackModel.swift b/MVMCoreUI/Organisms/StackModel.swift new file mode 100644 index 00000000..8368ce8c --- /dev/null +++ b/MVMCoreUI/Organisms/StackModel.swift @@ -0,0 +1,49 @@ +// +// StackModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class StackModel: StackModelProtocol, MoleculeProtocol { + public static var identifier: String = "simpleStack" + public var backgroundColor: Color? + public var molecules: [StackItemModel] + public var axis: NSLayoutConstraint.Axis = .vertical + public var spacing: CGFloat = 16.0 + public var useStackSpacingBeforeFirstItem = false + + public init(molecules: [StackItemModel]) { + self.molecules = molecules + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case molecules + case axis + case spacing + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + + molecules = try typeContainer.decode([StackItemModel].self, forKey: .molecules) + if let axisString = try typeContainer.decodeIfPresent(String.self, forKey: .axis), let optionalAxis = NSLayoutConstraint.Axis(rawValue: axisString) { + axis = optionalAxis + } + if let spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) { + self.spacing = spacing + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(molecules, forKey: .molecules) + try container.encodeIfPresent(axis.rawValueString, forKey: .axis) + try container.encodeIfPresent(spacing, forKey: .spacing) + } +} diff --git a/MVMCoreUI/Organisms/StackModelProtocol.swift b/MVMCoreUI/Organisms/StackModelProtocol.swift new file mode 100644 index 00000000..b7385466 --- /dev/null +++ b/MVMCoreUI/Organisms/StackModelProtocol.swift @@ -0,0 +1,18 @@ +// +// StackModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol StackModelProtocol { + associatedtype AnyStackItemModel: StackItemModelProtocol + + var molecules: [AnyStackItemModel] { get set } + var axis: NSLayoutConstraint.Axis { get set } + var spacing: CGFloat { get set } + var useStackSpacingBeforeFirstItem: Bool { get set } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index af0f5023..d60cdd6a 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -10,7 +10,7 @@ import UIKit @objcMembers open class CoreUIObject: MVMCoreObject { public var moleculeMap: MVMCoreUIMoleculeMappingObject? - + open override func defaultInitialSetup() { cache = MVMCoreCache() sessionHandler = MVMCoreSessionTimeHandler() @@ -19,5 +19,6 @@ import UIKit viewControllerMapping = MVMCoreUIViewControllerMappingObject() loggingDelegate = MVMCoreUILoggingHandler() moleculeMap = MVMCoreUIMoleculeMappingObject() + MoleculeObjectMapping.registerObjects() } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 5a4bfd5b..e47ed9d8 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -30,10 +30,9 @@ @"line": Line.class, @"link": Link.class, @"button": PrimaryButton.class, - @"header": StandardHeaderView.class, - @"stack": MoleculeStackView.class, + @"header": HeaderView.class, @"twoButtonView": TwoButtonView.class, - @"footer": StandardFooterView.class, + @"footer": FooterView.class, @"caretView": CaretView.class, @"caretLink": CaretButton.class, @"textField": MFTextField.class, @@ -46,7 +45,7 @@ @"checkbox": Checkbox.class, @"checkboxLabel": CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, - @"progressbar": ProgressBar.class, + @"progressBar": ProgressBar.class, @"circleProgress": GraphView.class, @"multiProgressBar": MultiProgress.class, @"radioButton": RadioButton.class, @@ -71,9 +70,11 @@ @"tabsListItem": TabsTableViewCell.class, @"dropDownListItem": DropDownFilterTableViewCell.class, @"headlineBodyButton": HeadlineBodyButton.class, - @"stackItem": StackItem.class, + @"stackItem": MoleculeStackItem.class, @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class, - @"headlineBodyCaretLinkImage" : HeadLineBodyCaretLinkImage.class + @"headlineBodyCaretLinkImage" : HeadLineBodyCaretLinkImage.class, + @"doughnutChart": DoughnutChartView.class, + @"headLineBodyCaretLinkImage" : HeadLineBodyCaretLinkImage.class } mutableCopy]; }); return mapping; @@ -83,13 +84,6 @@ return [MVMCoreActionUtility initializerClassCheck:[CoreUIObject sharedInstance].moleculeMap classToVerify:self]; } -- (instancetype)init { - if (self = [super init]) { - [MoleculeObjectMapping registerObjects]; - } - return self; -} - - (nullable Class)getMoleculeClassWithJSON:(nonnull NSDictionary *)json { NSString *moleculeName = [json string:KeyMoleculeName]; if (moleculeName) { diff --git a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift index d84f48b7..b12145f2 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift +++ b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift @@ -10,12 +10,25 @@ import Foundation @objcMembers public class MoleculeObjectMapping: NSObject { public static func registerObjects() { + let mapping = MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping + ModelRegistry.register(MoleculeStackModel.self) + mapping?.setObject(MoleculeStackView.self, forKey: MoleculeStackModel.identifier as NSString) + ModelRegistry.register(StackModel.self) + mapping?.setObject(Stack.self, forKey: StackModel.identifier as NSString) + ModelRegistry.register(StackItemModel.self) + mapping?.setObject(StackItem.self, forKey: StackItemModel.identifier as NSString) + ModelRegistry.register(StringAndMoleculeModel.self) + mapping?.setObject(StringAndMoleculeView.self, forKey: StringAndMoleculeModel.identifier as NSString) + ModelRegistry.register(UnOrderedListModel.self) + mapping?.setObject(UnOrderedList.self, forKey: UnOrderedListModel.identifier as NSString) + ModelRegistry.register(NumberedListModel.self) + mapping?.setObject(NumberedList.self, forKey: NumberedListModel.identifier as NSString) + ModelRegistry.register(LabelModel.self) ModelRegistry.register(HeaderModel.self) ModelRegistry.register(FooterModel.self) ModelRegistry.register(HeadlineBodyModel.self) - ModelRegistry.register(MoleculeStackModel.self) - ModelRegistry.register(StackItemModel.self) + ModelRegistry.register(MoleculeStackItemModel.self) ModelRegistry.register(TextFieldModel.self) ModelRegistry.register(ProgressBarModel.self) ModelRegistry.register(MultiProgressBarModel.self) @@ -47,11 +60,15 @@ import Foundation ModelRegistry.register(LabelAttributeUnderlineModel.self) ModelRegistry.register(LabelAttributeStrikeThroughModel.self) ModelRegistry.register(LabelAttributeActionModel.self) + // //ModelRegistry.register(ModuleMoleculeModel.self) ModelRegistry.register(LeftRightLabelModel.self) ModelRegistry.register(CaretViewModel.self) ModelRegistry.register(CaretLinkModel.self) ModelRegistry.register(LabelToggleModel.self) + ModelRegistry.register(DoughnutChartModel.self) + ModelRegistry.register(NumberedListModel.self) + ModelRegistry.register(UnOrderedListModel.self) } } diff --git a/MVMCoreUI/Models/Template/ListPageTemplateModel.swift b/MVMCoreUI/Templates/ListPageTemplateModel.swift similarity index 98% rename from MVMCoreUI/Models/Template/ListPageTemplateModel.swift rename to MVMCoreUI/Templates/ListPageTemplateModel.swift index c3b2a120..4a301660 100644 --- a/MVMCoreUI/Models/Template/ListPageTemplateModel.swift +++ b/MVMCoreUI/Templates/ListPageTemplateModel.swift @@ -27,7 +27,7 @@ import Foundation self.molecules = molecules } - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case moleculeName case pageType case screenHeading diff --git a/MVMCoreUI/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Templates/MoleculeStackTemplate.swift index a4f69518..03d21c6b 100644 --- a/MVMCoreUI/Templates/MoleculeStackTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeStackTemplate.swift @@ -49,8 +49,8 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { } let stack = MoleculeStackView(frame: .zero) - stack.useStackSpacingBeforeFirstItem = true - stack.moleculesShouldSetHorizontalMargins = true + moleculeStackModel.useStackSpacingBeforeFirstItem = true + moleculeStackModel.useHorizontalMargins = true stack.setWithModel(moleculeStackModel, delegateObject() as? MVMCoreUIDelegateObject, nil) return stack } diff --git a/MVMCoreUI/Models/Template/StackCenteredPageTemplateModel.swift b/MVMCoreUI/Templates/StackCenteredPageTemplateModel.swift similarity index 99% rename from MVMCoreUI/Models/Template/StackCenteredPageTemplateModel.swift rename to MVMCoreUI/Templates/StackCenteredPageTemplateModel.swift index 804103c4..86c09b6d 100644 --- a/MVMCoreUI/Models/Template/StackCenteredPageTemplateModel.swift +++ b/MVMCoreUI/Templates/StackCenteredPageTemplateModel.swift @@ -11,11 +11,7 @@ import Foundation @objcMembers public class StackCenteredPageTemplateModel: TemplateModelProtocol { public static var identifier: String = "stackCenterTemplate" - public var pageType: String - public var screenHeading: String? - public var isAtomicTabs: Bool? - } diff --git a/MVMCoreUI/Models/Template/StackPageTemplateModel.swift b/MVMCoreUI/Templates/StackPageTemplateModel.swift similarity index 97% rename from MVMCoreUI/Models/Template/StackPageTemplateModel.swift rename to MVMCoreUI/Templates/StackPageTemplateModel.swift index 0e5e4488..db9d632d 100644 --- a/MVMCoreUI/Models/Template/StackPageTemplateModel.swift +++ b/MVMCoreUI/Templates/StackPageTemplateModel.swift @@ -13,9 +13,7 @@ import Foundation public static var identifier: String = "stack" public var pageType: String - public var screenHeading: String? - public var isAtomicTabs: Bool? public var header: MoleculeProtocol? @@ -27,7 +25,7 @@ import Foundation self.moleculeStack = moleculeStack } - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case pageType case screenHeading case header diff --git a/MVMCoreUI/Models/Template/ThreeLayerPageTemplateModel.swift b/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift similarity index 97% rename from MVMCoreUI/Models/Template/ThreeLayerPageTemplateModel.swift rename to MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift index 5d4d1b25..e42ca5f0 100644 --- a/MVMCoreUI/Models/Template/ThreeLayerPageTemplateModel.swift +++ b/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift @@ -12,9 +12,7 @@ import Foundation public static var identifier: String = "threeLayer" public var pageType: String - public var screenHeading: String? - public var isAtomicTabs: Bool? public var header: MoleculeProtocol? @@ -28,7 +26,7 @@ import Foundation self.footer = footer } - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case pageType case screenHeading case header diff --git a/MVMCoreUI/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Templates/ThreeLayerTemplate.swift index 9c963693..785d1055 100644 --- a/MVMCoreUI/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Templates/ThreeLayerTemplate.swift @@ -26,7 +26,6 @@ import UIKit heightConstraint?.isActive = true } - open override func viewForTop() -> UIView? { guard let headerModel = templateModel?.header, let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(headerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { diff --git a/MVMCoreUI/Utility/NSLayoutConstraintAxis+Extension.swift b/MVMCoreUI/Utility/NSLayoutConstraintAxis+Extension.swift index 05d13bcd..f989e17b 100644 --- a/MVMCoreUI/Utility/NSLayoutConstraintAxis+Extension.swift +++ b/MVMCoreUI/Utility/NSLayoutConstraintAxis+Extension.swift @@ -30,6 +30,11 @@ import Foundation try container.encode(axis.rawValueString, forKey: .axis) } */ + +enum AxisError: Error { + case notAnAxis +} + extension NSLayoutConstraint.Axis: RawRepresentable { init?(rawValue: String) { @@ -54,3 +59,29 @@ extension NSLayoutConstraint.Axis: RawRepresentable { } } } + +@propertyWrapper +public struct Axis { + public var wrappedValue: NSLayoutConstraint.Axis + + public init(wrappedValue value: NSLayoutConstraint.Axis) { + self.wrappedValue = value + } +} + +extension Axis: Codable { + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.singleValueContainer() + let string = try typeContainer.decode(String.self) + guard let axis = NSLayoutConstraint.Axis(rawValue: string) else { + throw AxisError.notAnAxis + } + wrappedValue = axis + } + + public func encode(to encoder: Encoder) throws { + let string = wrappedValue.rawValueString + var container = encoder.singleValueContainer() + try container.encode(string) + } +}