diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 1c0a4c00..31d9e31c 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -61,7 +61,6 @@ 01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */; }; 01EB368F23609801006832FA /* LabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368823609801006832FA /* LabelModel.swift */; }; 01EB369023609801006832FA /* MoleculeListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368923609801006832FA /* MoleculeListItemModel.swift */; }; - 01EB369223609801006832FA /* MoleculeStackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368B23609801006832FA /* MoleculeStackModel.swift */; }; 01EB369323609801006832FA /* HeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368C23609801006832FA /* HeaderModel.swift */; }; 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368D23609801006832FA /* HeadlineBodyModel.swift */; }; 01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */; }; @@ -95,6 +94,7 @@ 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 0A7ECC5D243CE85300C828E8 /* DoughnutChartItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7ECC5C243CE85300C828E8 /* DoughnutChartItemModel.swift */; }; 0A7ECC5F243CEB1200C828E8 /* ColorViewWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7ECC5E243CEB1200C828E8 /* ColorViewWithLabel.swift */; }; + 0A7ECC702441001C00C828E8 /* UIToolbar+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7ECC6F2441001C00C828E8 /* UIToolbar+Extension.swift */; }; 0A7EF85B23D8A52800B2AAD1 /* EntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */; }; 0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */; }; 0A7EF85F23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */; }; @@ -103,6 +103,8 @@ 0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */; }; 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */; }; 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Toggle.swift */; }; + 0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */; }; + 0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */; }; 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; 0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; }; @@ -139,8 +141,12 @@ 8D448E5524050A46006211BB /* ListOneColumnFullWidthTextAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D448E5424050A46006211BB /* ListOneColumnFullWidthTextAllTextAndLinksModel.swift */; }; 8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D4687E1242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift */; }; 8D4687E4242E2DF300802879 /* ListFourColumnDataUsageListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D4687E3242E2DF300802879 /* ListFourColumnDataUsageListItem.swift */; }; + 8D8067D12444472F00203BE8 /* ListRightVariablePriceChangeAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8067D02444472F00203BE8 /* ListRightVariablePriceChangeAllTextAndLinksModel.swift */; }; + 8D8067D32444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8067D22444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift */; }; 8DD1E36E243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DD1E36D243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift */; }; 8DD1E370243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DD1E36F243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift */; }; + 8DDD6C1D244D90B8006A2232 /* ListThreeColumnDataUsage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DDD6C1C244D90B8006A2232 /* ListThreeColumnDataUsage.swift */; }; + 8DDD6C1F244D90E1006A2232 /* ListThreeColumnDataUsageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DDD6C1E244D90E1006A2232 /* ListThreeColumnDataUsageModel.swift */; }; 8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DEFA95B243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift */; }; 8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DEFA95D243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift */; }; 942C372E241149170066E45E /* NHaasGroteskDSStd-75Bd.otf in Resources */ = {isa = PBXBuildFile; fileRef = 942C372C241149170066E45E /* NHaasGroteskDSStd-75Bd.otf */; }; @@ -148,8 +154,8 @@ 942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */; }; 942C378E2412F5B60066E45E /* ModalMoleculeStackTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942C378D2412F5B60066E45E /* ModalMoleculeStackTemplate.swift */; }; 9432A79F23DB47BA00719041 /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */; }; - 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; - 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; + 943784F5236B77BB006A1E82 /* Wheel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* Wheel.swift */; }; + 943784F6236B77BB006A1E82 /* WheelAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* WheelAnimationHandler.swift */; }; 943820842432382400B43AF3 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943820832432382400B43AF3 /* WebView.swift */; }; 94382086243238D100B43AF3 /* WebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94382085243238D100B43AF3 /* WebViewModel.swift */; }; 9445890C2385BCE300DE9FD4 /* ProgressBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */; }; @@ -182,6 +188,7 @@ 94CA227F24058534002D6750 /* VerizonNHGeTX-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 94CA227B24058533002D6750 /* VerizonNHGeTX-Regular.otf */; }; 94F217B623E0BF6100A47C06 /* PrimaryButtonView.h in Headers */ = {isa = PBXBuildFile; fileRef = 94F217B423E0BF6100A47C06 /* PrimaryButtonView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 94F217B723E0BF6100A47C06 /* PrimaryButtonView.m in Sources */ = {isa = PBXBuildFile; fileRef = 94F217B523E0BF6100A47C06 /* PrimaryButtonView.m */; }; + 94F6516D2437954100631BF9 /* Tabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F6516C2437954100631BF9 /* Tabs.swift */; }; 94FB966223D797DA003D482B /* MFTextButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 94FB966023D797DA003D482B /* MFTextButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; 94FB966323D797DA003D482B /* MFTextButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 94FB966123D797DA003D482B /* MFTextButton.m */; }; AA104B1A24474A66004D2810 /* HeadersH2Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA104B1924474A66004D2810 /* HeadersH2Buttons.swift */; }; @@ -190,16 +197,30 @@ AA11A42123F15D7000D7962F /* ListRightVariablePaymentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11A42023F15D7000D7962F /* ListRightVariablePaymentsModel.swift */; }; AA1EC59724373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1EC59624373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift */; }; AA1EC59924373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1EC59824373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift */; }; + AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2AD115244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift */; }; + AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */; }; AA56A20F243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */; }; AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA56A210243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift */; }; + AA617AB02453010A00910B8F /* ListDeviceComplexLinkSmall.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA617AAF2453010A00910B8F /* ListDeviceComplexLinkSmall.swift */; }; + AA617AB22453012400910B8F /* ListDeviceComplexLinkSmallModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA617AB12453012400910B8F /* ListDeviceComplexLinkSmallModel.swift */; }; + AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA69AAF52445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift */; }; + AA69AAF82445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA69AAF72445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift */; }; + AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */; }; AAA74A172410C04600080241 /* HeadersH2NoButtonsBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */; }; AAA74A192410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */; }; + AAB9C10824346F4B00151545 /* RadioSwatches.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB9C10724346F4B00151545 /* RadioSwatches.swift */; }; + AAB9C10A243496DD00151545 /* RadioSwatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB9C109243496DD00151545 /* RadioSwatch.swift */; }; + AAC6F167243332E400F295C1 /* RadioSwatchesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */; }; + BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */; }; + BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */; }; BB2C968F24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2C968D24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift */; }; BB2C969224330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB2C969124330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift */; }; BB47A586241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB47A585241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift */; }; BB47A588241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB47A587241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift */; }; BB54C5202434D92F0038326C /* ListRightVariableButtonAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB54C51E2434D92F0038326C /* ListRightVariableButtonAllTextAndLinks.swift */; }; BB54C5212434D92F0038326C /* ListRightVariableButtonAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB54C51F2434D92F0038326C /* ListRightVariableButtonAllTextAndLinksModel.swift */; }; + BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB55B51C244482C0002001AD /* ListRightVariablePriceChangeBodyText.swift */; }; + BB55B51F244482D2002001AD /* ListRightVariablePriceChangeBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB55B51E244482D2002001AD /* ListRightVariablePriceChangeBodyTextModel.swift */; }; BB6C6AC0242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6ABE242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift */; }; BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */; }; BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */; }; @@ -224,9 +245,17 @@ C7F8012323E846C300396FBD /* ListRVWheelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F8012223E846C300396FBD /* ListRVWheelModel.swift */; }; D202AFE4242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D202AFE3242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift */; }; D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D202AFE5242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift */; }; + D2092349244A51D40044AD09 /* RadioSwatchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092348244A51D40044AD09 /* RadioSwatchModel.swift */; }; + D209234F244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D209234E244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift */; }; + D2092351244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092350244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift */; }; + D2092353244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */; }; + D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */; }; + D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */; }; + D20923592450ECE00044AD09 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20923582450ECE00044AD09 /* TableView.swift */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; D20FB165241A5D75004AFC3A /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; + D21B7F602437C5BC00051ABF /* MoleculeStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F5E2437C5BC00051ABF /* MoleculeStackView.swift */; }; D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */; }; D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */; }; D21B7F75243BAC8900051ABF /* CarouselItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F74243BAC8900051ABF /* CarouselItem.swift */; }; @@ -287,7 +316,7 @@ D28A837D23CCA86A00DFE4FC /* TabsListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837C23CCA86A00DFE4FC /* TabsListItemModel.swift */; }; D28A837F23CCA96400DFE4FC /* TabsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837E23CCA96400DFE4FC /* TabsModel.swift */; }; D28A838123CCB0D800DFE4FC /* AccordionListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838023CCB0D800DFE4FC /* AccordionListItemModel.swift */; }; - D28A838323CCBD3F00DFE4FC /* CircleProgressModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */; }; + D28A838323CCBD3F00DFE4FC /* WheelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838223CCBD3F00DFE4FC /* WheelModel.swift */; }; D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838423CCCA8900DFE4FC /* ScrollerModel.swift */; }; D28A838923CCCFCB00DFE4FC /* LinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838823CCCFCB00DFE4FC /* LinkModel.swift */; }; D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */; }; @@ -395,7 +424,6 @@ 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 */; }; - 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 /* MoleculeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514662213885800345BFB /* MoleculeHeaderView.swift */; }; @@ -499,7 +527,6 @@ 01EB368823609801006832FA /* LabelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelModel.swift; sourceTree = ""; }; 01EB368923609801006832FA /* MoleculeListItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeListItemModel.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 = ""; }; 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLinkModel.swift; sourceTree = ""; }; @@ -522,6 +549,7 @@ 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxLabel.swift; sourceTree = ""; }; 0A7ECC5C243CE85300C828E8 /* DoughnutChartItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoughnutChartItemModel.swift; sourceTree = ""; }; 0A7ECC5E243CEB1200C828E8 /* ColorViewWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorViewWithLabel.swift; sourceTree = ""; }; + 0A7ECC6F2441001C00C828E8 /* UIToolbar+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIToolbar+Extension.swift"; sourceTree = ""; }; 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldModel.swift; sourceTree = ""; }; 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryFieldModel.swift; sourceTree = ""; }; 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryFieldModel.swift; sourceTree = ""; }; @@ -532,6 +560,8 @@ 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 = ""; }; + 0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDatePicker+Extension.swift"; sourceTree = ""; }; + 0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPickerView+Extension.swift"; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; @@ -568,8 +598,12 @@ 8D448E5424050A46006211BB /* ListOneColumnFullWidthTextAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextAllTextAndLinksModel.swift; sourceTree = ""; }; 8D4687E1242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListFourColumnDataUsageListItemModel.swift; sourceTree = ""; }; 8D4687E3242E2DF300802879 /* ListFourColumnDataUsageListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListFourColumnDataUsageListItem.swift; sourceTree = ""; }; + 8D8067D02444472F00203BE8 /* ListRightVariablePriceChangeAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePriceChangeAllTextAndLinksModel.swift; sourceTree = ""; }; + 8D8067D22444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePriceChangeAllTextAndLinks.swift; sourceTree = ""; }; 8DD1E36D243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnInternationalDataModel.swift; sourceTree = ""; }; 8DD1E36F243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnInternationalData.swift; sourceTree = ""; }; + 8DDD6C1C244D90B8006A2232 /* ListThreeColumnDataUsage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsage.swift; sourceTree = ""; }; + 8DDD6C1E244D90E1006A2232 /* ListThreeColumnDataUsageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageModel.swift; sourceTree = ""; }; 8DEFA95B243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageDividerModel.swift; sourceTree = ""; }; 8DEFA95D243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageDivider.swift; sourceTree = ""; }; 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftRightLabelModel.swift; sourceTree = ""; }; @@ -578,8 +612,8 @@ 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalMoleculeListTemplate.swift; sourceTree = ""; }; 942C378D2412F5B60066E45E /* ModalMoleculeStackTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalMoleculeStackTemplate.swift; sourceTree = ""; }; 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; - 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; - 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = ""; }; + 943784F3236B77BB006A1E82 /* Wheel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wheel.swift; sourceTree = ""; }; + 943784F4236B77BB006A1E82 /* WheelAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WheelAnimationHandler.swift; sourceTree = ""; }; 943820832432382400B43AF3 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; 94382085243238D100B43AF3 /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = ""; }; 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBarModel.swift; sourceTree = ""; }; @@ -610,6 +644,7 @@ 94CA227B24058533002D6750 /* VerizonNHGeTX-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeTX-Regular.otf"; sourceTree = ""; }; 94F217B423E0BF6100A47C06 /* PrimaryButtonView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrimaryButtonView.h; sourceTree = ""; }; 94F217B523E0BF6100A47C06 /* PrimaryButtonView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrimaryButtonView.m; sourceTree = ""; }; + 94F6516C2437954100631BF9 /* Tabs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tabs.swift; sourceTree = ""; }; 94FB966023D797DA003D482B /* MFTextButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTextButton.h; sourceTree = ""; }; 94FB966123D797DA003D482B /* MFTextButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTextButton.m; sourceTree = ""; }; AA104B1924474A66004D2810 /* HeadersH2Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2Buttons.swift; sourceTree = ""; }; @@ -618,16 +653,30 @@ AA11A42023F15D7000D7962F /* ListRightVariablePaymentsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePaymentsModel.swift; sourceTree = ""; }; AA1EC59624373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnSpeedTestDividerModel.swift; sourceTree = ""; }; AA1EC59824373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnSpeedTestDivider.swift; sourceTree = ""; }; + AA2AD115244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkMedium.swift; sourceTree = ""; }; + AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkMediumModel.swift; sourceTree = ""; }; AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDividerModel.swift; sourceTree = ""; }; AA56A210243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDivider.swift; sourceTree = ""; }; + AA617AAF2453010A00910B8F /* ListDeviceComplexLinkSmall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkSmall.swift; sourceTree = ""; }; + AA617AB12453012400910B8F /* ListDeviceComplexLinkSmallModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkSmallModel.swift; sourceTree = ""; }; + AA69AAF52445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxBodyText.swift; sourceTree = ""; }; + AA69AAF72445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxBodyTextModel.swift; sourceTree = ""; }; + AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchCollectionViewCell.swift; sourceTree = ""; }; AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2NoButtonsBodyText.swift; sourceTree = ""; }; AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2NoButtonsBodyTextModel.swift; sourceTree = ""; }; + AAB9C10724346F4B00151545 /* RadioSwatches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatches.swift; sourceTree = ""; }; + AAB9C109243496DD00151545 /* RadioSwatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatch.swift; sourceTree = ""; }; + AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchesModel.swift; sourceTree = ""; }; + BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = ""; }; + BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = ""; }; BB2C968D24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListRightVariableTextLinkAllTextAndLinksModel.swift; sourceTree = ""; }; BB2C969124330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListRightVariableTextLinkAllTextAndLinks.swift; sourceTree = ""; }; BB47A585241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextDividerSubsectionModel.swift; sourceTree = ""; }; BB47A587241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextDividerSubsection.swift; sourceTree = ""; }; BB54C51E2434D92F0038326C /* ListRightVariableButtonAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListRightVariableButtonAllTextAndLinks.swift; sourceTree = ""; }; BB54C51F2434D92F0038326C /* ListRightVariableButtonAllTextAndLinksModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListRightVariableButtonAllTextAndLinksModel.swift; sourceTree = ""; }; + BB55B51C244482C0002001AD /* ListRightVariablePriceChangeBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePriceChangeBodyText.swift; sourceTree = ""; }; + BB55B51E244482D2002001AD /* ListRightVariablePriceChangeBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePriceChangeBodyTextModel.swift; sourceTree = ""; }; BB6C6ABE242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerTallModel.swift; sourceTree = ""; }; BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerTall.swift; sourceTree = ""; }; BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShort.swift; sourceTree = ""; }; @@ -652,9 +701,17 @@ C7F8012223E846C300396FBD /* ListRVWheelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRVWheelModel.swift; sourceTree = ""; }; D202AFE3242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextAlignment+Extension.swift"; sourceTree = ""; }; D202AFE5242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewScrollPosition+Extension.swift"; sourceTree = ""; }; + D2092348244A51D40044AD09 /* RadioSwatchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchModel.swift; sourceTree = ""; }; + D209234E244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCenterTemplate.swift; sourceTree = ""; }; + D2092350244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCenterTemplateModel.swift; sourceTree = ""; }; + D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUIViewControllerMappingObject+Extension.swift"; sourceTree = ""; }; + D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTemplateModelProtocol.swift; sourceTree = ""; }; + D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerModelBase.swift; sourceTree = ""; }; + D20923582450ECE00044AD09 /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; + D21B7F5E2437C5BC00051ABF /* MoleculeStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeStackView.swift; sourceTree = ""; }; D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = ""; }; D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemModelProtocol.swift; sourceTree = ""; }; D21B7F74243BAC8900051ABF /* CarouselItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselItem.swift; sourceTree = ""; }; @@ -714,7 +771,7 @@ D28A837C23CCA86A00DFE4FC /* TabsListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsListItemModel.swift; sourceTree = ""; }; D28A837E23CCA96400DFE4FC /* TabsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsModel.swift; sourceTree = ""; }; D28A838023CCB0D800DFE4FC /* AccordionListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccordionListItemModel.swift; sourceTree = ""; }; - D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleProgressModel.swift; sourceTree = ""; }; + D28A838223CCBD3F00DFE4FC /* WheelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WheelModel.swift; sourceTree = ""; }; D28A838423CCCA8900DFE4FC /* ScrollerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollerModel.swift; sourceTree = ""; }; D28A838823CCCFCB00DFE4FC /* LinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkModel.swift; sourceTree = ""; }; D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonModel.swift; sourceTree = ""; }; @@ -837,7 +894,6 @@ 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 = ""; }; - 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 /* MoleculeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeHeaderView.swift; sourceTree = ""; }; @@ -910,6 +966,7 @@ D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */, 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */, D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */, + D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */, ); path = ModelProtocols; sourceTree = ""; @@ -1051,6 +1108,8 @@ children = ( 8DD1E36D243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift */, 8DD1E36F243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift */, + 8DDD6C1E244D90E1006A2232 /* ListThreeColumnDataUsageModel.swift */, + 8DDD6C1C244D90B8006A2232 /* ListThreeColumnDataUsage.swift */, ); path = ThreeColumn; sourceTree = ""; @@ -1099,6 +1158,10 @@ AA11A41E23F15D3100D7962F /* ListRightVariablePayments.swift */, 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */, 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */, + BB55B51E244482D2002001AD /* ListRightVariablePriceChangeBodyTextModel.swift */, + BB55B51C244482C0002001AD /* ListRightVariablePriceChangeBodyText.swift */, + 8D8067D02444472F00203BE8 /* ListRightVariablePriceChangeAllTextAndLinksModel.swift */, + 8D8067D22444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift */, C7F8012223E846C300396FBD /* ListRVWheelModel.swift */, C7F8012023E8303200396FBD /* ListRVWheel.swift */, ); @@ -1122,6 +1185,9 @@ D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */, D202AFE5242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift */, 013F801823FB4A8E00AD8013 /* UIContentMode+Extension.swift */, + 0A7ECC6F2441001C00C828E8 /* UIToolbar+Extension.swift */, + 0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */, + 0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */, ); path = Extensions; sourceTree = ""; @@ -1135,6 +1201,19 @@ path = FourColumn; sourceTree = ""; }; + D20FFFB42451E32100A31DA2 /* Device */ = { + isa = PBXGroup; + children = ( + BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */, + BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */, + AA617AB12453012400910B8F /* ListDeviceComplexLinkSmallModel.swift */, + AA617AAF2453010A00910B8F /* ListDeviceComplexLinkSmall.swift */, + AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */, + AA2AD115244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift */, + ); + path = Device; + sourceTree = ""; + }; D213347423842FE3008E41B3 /* Controllers */ = { isa = PBXGroup; children = ( @@ -1263,6 +1342,7 @@ D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */, D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */, D28A837E23CCA96400DFE4FC /* TabsModel.swift */, + 94F6516C2437954100631BF9 /* Tabs.swift */, 011D9625240EBB16000E3791 /* RadioButtonLabelModel.swift */, 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */, ); @@ -1287,8 +1367,7 @@ D260105A23D0BB7100764D80 /* StackModelProtocol.swift */, D260106423D0CEA700764D80 /* StackModel.swift */, D260105C23D0BCD400764D80 /* Stack.swift */, - 01EB368B23609801006832FA /* MoleculeStackModel.swift */, - D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */, + D21B7F5E2437C5BC00051ABF /* MoleculeStackView.swift */, 012A88AE238C626E00FE3DA1 /* CarouselModel.swift */, D2A6390022CBB1820052ED1F /* Carousel.swift */, ); @@ -1334,6 +1413,7 @@ D22B38EA23F4E08B00490EF6 /* List */ = { isa = PBXGroup; children = ( + D20FFFB42451E32100A31DA2 /* Device */, 52267A0523FFE0A900906CBA /* OneColumn */, D22D8396241FDE4700D3DF69 /* TwoColumn */, 8DD1E36C243B3CD900D8F2DF /* ThreeColumn */, @@ -1349,6 +1429,8 @@ children = ( 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */, 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */, + AA69AAF72445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift */, + AA69AAF52445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift */, 522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */, 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */, 8D24041423E7FC0B009E23BE /* ListLeftVariableIconWithRightCaretModel.swift */, @@ -1435,6 +1517,11 @@ 01004F2F22721C3800991ECC /* RadioButton.swift */, 31BE15CA23D8924C00452370 /* CheckboxModel.swift */, 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, + AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */, + AAB9C10724346F4B00151545 /* RadioSwatches.swift */, + D2092348244A51D40044AD09 /* RadioSwatchModel.swift */, + AAB9C109243496DD00151545 /* RadioSwatch.swift */, + AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */, ); path = Selectors; sourceTree = ""; @@ -1484,6 +1571,7 @@ isa = PBXGroup; children = ( D22D8392241C27B100D3DF69 /* TemplateModel.swift */, + D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */, 014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */, D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */, 942C378D2412F5B60066E45E /* ModalMoleculeStackTemplate.swift */, @@ -1495,6 +1583,8 @@ 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */, 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */, D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */, + D2092350244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift */, + D209234E244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift */, D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */, D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */, D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */, @@ -1682,9 +1772,9 @@ 94C2D9822386F3E30006CF46 /* Label */, 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */, 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */, - D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */, - 943784F3236B77BB006A1E82 /* GraphView.swift */, - 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */, + D28A838223CCBD3F00DFE4FC /* WheelModel.swift */, + 943784F3236B77BB006A1E82 /* Wheel.swift */, + 943784F4236B77BB006A1E82 /* WheelAnimationHandler.swift */, D260105223CEA61600764D80 /* ToggleModel.swift */, 0AA33B392398524F0067DD0F /* Toggle.swift */, 0AE98BB623FF18E9004C5109 /* ArrowModel.swift */, @@ -1747,6 +1837,7 @@ D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */, D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */, D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */, + D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */, D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */, ); path = OtherHandlers; @@ -1852,6 +1943,7 @@ D2B18B7E2360913400A9AEDC /* Control.swift */, D2B18B802360945C00A9AEDC /* View.swift */, 0AE14F63238315D2005417F8 /* TextField.swift */, + D20923582450ECE00044AD09 /* TableView.swift */, D2755D7A23689C7500485468 /* TableViewCell.swift */, D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */, D264FAA92440F97600D98315 /* CollectionView.swift */, @@ -2046,10 +2138,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AAC6F167243332E400F295C1 /* RadioSwatchesModel.swift in Sources */, 5248BFED23F12E350059236A /* ListThreeColumnPlanDataDividerModel.swift in Sources */, 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */, 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */, - 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */, + 943784F5236B77BB006A1E82 /* Wheel.swift in Sources */, 31BE15CC23D8924D00452370 /* CheckboxModel.swift in Sources */, 8D3BA9BF2433789900D341BA /* ListThreeColumnInternationalDataDivider.swift in Sources */, 94C661DA23CCF4FB00D9FE5B /* UIColor+Extension.swift in Sources */, @@ -2062,6 +2155,7 @@ D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */, D28A838923CCCFCB00DFE4FC /* LinkModel.swift in Sources */, AA56A20F243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift in Sources */, + AAB9C10824346F4B00151545 /* RadioSwatches.swift in Sources */, 94C2D9A923872E5E0006CF46 /* LabelAttributeImageModel.swift in Sources */, DBC4391922442197001AB423 /* DashLine.swift in Sources */, D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */, @@ -2070,12 +2164,15 @@ AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, 0116A4E5228B19640094F3ED /* RadioButtonSelectionHelper.swift in Sources */, + D2092353244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift in Sources */, 017BEB48236230DB0024EF95 /* MoleculeViewProtocol.swift in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, 94382086243238D100B43AF3 /* WebViewModel.swift in Sources */, D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */, D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, + D21B7F602437C5BC00051ABF /* MoleculeStackView.swift in Sources */, 0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */, + AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */, 01004F3022721C3800991ECC /* RadioButton.swift in Sources */, @@ -2091,6 +2188,7 @@ 942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */, BB47A588241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift in Sources */, 012A88C8238DB02000FE3DA1 /* MoleculeDelegateProtocol.swift in Sources */, + 8D8067D12444472F00203BE8 /* ListRightVariablePriceChangeAllTextAndLinksModel.swift in Sources */, 0A7EF86123D8AC2500B2AAD1 /* DigitEntryFieldModel.swift in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, D224798C231450C8003FCCF9 /* HeadlineBodyToggle.swift in Sources */, @@ -2105,10 +2203,13 @@ D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */, + D2092351244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift in Sources */, D21B7F77243BB70700051ABF /* MoleculeCollectionItemModel.swift in Sources */, + AAB9C10A243496DD00151545 /* RadioSwatch.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, 011D9602240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift in Sources */, D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */, + AA617AB22453012400910B8F /* ListDeviceComplexLinkSmallModel.swift in Sources */, D260106323D0C05000764D80 /* StackItemModel.swift in Sources */, D2E2A99823D8D63C000B42E6 /* ActionDetailWithImageModel.swift in Sources */, BBBBC87D24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift in Sources */, @@ -2120,6 +2221,7 @@ D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, AA1EC59724373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift in Sources */, + BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */, 014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */, @@ -2128,7 +2230,6 @@ D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */, D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */, - D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, C7F8012323E846C300396FBD /* ListRVWheelModel.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */, @@ -2155,8 +2256,8 @@ 0A21DB85235E06EF00C160A2 /* MFTextField.m in Sources */, 014AA72623C501E2006F3E93 /* ContainerModelProtocol.swift in Sources */, AA11A42123F15D7000D7962F /* ListRightVariablePaymentsModel.swift in Sources */, - 01EB369223609801006832FA /* MoleculeStackModel.swift in Sources */, 011D9626240EBB16000E3791 /* RadioButtonLabelModel.swift in Sources */, + 8DDD6C1D244D90B8006A2232 /* ListThreeColumnDataUsage.swift in Sources */, AAA74A192410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, 944589232385DA9600DE9FD4 /* ImageViewModel.swift in Sources */, @@ -2165,10 +2266,12 @@ 525019DE2406430800EED91C /* ListProgressBarData.swift in Sources */, D28A837F23CCA96400DFE4FC /* TabsModel.swift in Sources */, 012A88EC238F084D00FE3DA1 /* FooterModel.swift in Sources */, + 8DDD6C1F244D90E1006A2232 /* ListThreeColumnDataUsageModel.swift in Sources */, + BB55B51F244482D2002001AD /* ListRightVariablePriceChangeBodyTextModel.swift in Sources */, D2A514672213885800345BFB /* MoleculeHeaderView.swift in Sources */, D29E28D823D21AB800ACEA85 /* StringAndMoleculeView.swift in Sources */, 01EB369023609801006832FA /* MoleculeListItemModel.swift in Sources */, - D28A838323CCBD3F00DFE4FC /* CircleProgressModel.swift in Sources */, + D28A838323CCBD3F00DFE4FC /* WheelModel.swift in Sources */, EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */, D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, @@ -2182,19 +2285,25 @@ 01509D952327ED1900EF99AA /* HeadlineBodyLinkToggle.swift in Sources */, 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, + 94F6516D2437954100631BF9 /* Tabs.swift in Sources */, 5248BFEC23F12E350059236A /* ListThreeColumnPlanDataDivider.swift in Sources */, 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */, D264FA8E243BCD9A00D98315 /* CollectionTemplate.swift in Sources */, 0A7EF85B23D8A52800B2AAD1 /* EntryFieldModel.swift in Sources */, 8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */, 94F217B723E0BF6100A47C06 /* PrimaryButtonView.m in Sources */, + D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */, 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, + D20923592450ECE00044AD09 /* TableView.swift in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, BB47A586241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift in Sources */, + AA69AAF82445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift in Sources */, D2B18B812360945C00A9AEDC /* View.swift in Sources */, C6FA7D5423C77A4A00A3614A /* NumberedList.swift in Sources */, + 0A7ECC702441001C00C828E8 /* UIToolbar+Extension.swift in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, + D209234F244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift in Sources */, 525019E52406852100EED91C /* ListFourColumnDataUsageDividerModel.swift in Sources */, 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */, 94FB966323D797DA003D482B /* MFTextButton.m in Sources */, @@ -2210,12 +2319,14 @@ D260105D23D0BCD400764D80 /* Stack.swift in Sources */, 0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */, BB54C5212434D92F0038326C /* ListRightVariableButtonAllTextAndLinksModel.swift in Sources */, + D2092349244A51D40044AD09 /* RadioSwatchModel.swift in Sources */, 8DD1E370243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 01EB368F23609801006832FA /* LabelModel.swift in Sources */, 0A6682AC243531C300AD3CA1 /* Padding.swift in Sources */, AA1EC59924373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift in Sources */, 942C378E2412F5B60066E45E /* ModalMoleculeStackTemplate.swift in Sources */, + 8D8067D32444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift in Sources */, 8D4687E4242E2DF300802879 /* ListFourColumnDataUsageListItem.swift in Sources */, 01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */, 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */, @@ -2223,6 +2334,7 @@ D22479962316AF6E003FCCF9 /* HeadlineBodyLink.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */, + BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */, 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, D28A837923C7D5BC00DFE4FC /* PageModelProtocol.swift in Sources */, @@ -2246,6 +2358,7 @@ C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, 011D958524042432000E3791 /* RulesProtocol.swift in Sources */, 94AF4A3F23E9D13900676048 /* MFCaretButton.m in Sources */, + AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, @@ -2281,6 +2394,7 @@ 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, D20FB165241A5D75004AFC3A /* NavigationItemModelProtocol.swift in Sources */, + AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */, DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */, 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */, D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */, @@ -2293,9 +2407,12 @@ 94C0150A24215643005811A9 /* ActionTopAlertModel.swift in Sources */, 012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, + D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */, 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, + 0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, D2C78CD224228BBD00B69FDE /* ActionOpenPanelModel.swift in Sources */, + AA617AB02453010A00910B8F /* ListDeviceComplexLinkSmall.swift in Sources */, C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */, 01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, @@ -2324,11 +2441,12 @@ 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */, D264FA8C243BCD8E00D98315 /* CollectionTemplateModel.swift in Sources */, + AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */, 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */, D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */, 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, 94AF4A4323E9D19E00676048 /* MFCaretView.m in Sources */, - 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */, + 943784F6236B77BB006A1E82 /* WheelAnimationHandler.swift in Sources */, 011D95A1240453D0000E3791 /* RuleEqualsModel.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 011D95892404249B000E3791 /* FormHolderModelProtocol.swift in Sources */, @@ -2360,6 +2478,7 @@ AA104B1A24474A66004D2810 /* HeadersH2Buttons.swift in Sources */, C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, + BB1D17E2244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */, 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, @@ -2376,6 +2495,7 @@ 8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */, D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */, 012A88C2238D7BCA00FE3DA1 /* CarouselItemModel.swift in Sources */, + 0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, 94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */, 011D95AB2405C553000E3791 /* FormItemProtocol.swift in Sources */, diff --git a/MVMCoreUI/Actions/ActionCollapseNotificationModel.swift b/MVMCoreUI/Actions/ActionCollapseNotificationModel.swift index 9a6e6793..4663d539 100644 --- a/MVMCoreUI/Actions/ActionCollapseNotificationModel.swift +++ b/MVMCoreUI/Actions/ActionCollapseNotificationModel.swift @@ -9,9 +9,14 @@ import UIKit @objcMembers public class ActionCollapseNotificationModel: ActionModelProtocol { + public static var identifier: String = "collapseNotification" - public var actionType: String + public var actionType: String = ActionCollapseNotificationModel.identifier public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? - public var title: String? + + public init(_ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + self.extraParameters = extraParameters + self.analyticsData = analyticsData + } } diff --git a/MVMCoreUI/Actions/ActionOpenPanelModel.swift b/MVMCoreUI/Actions/ActionOpenPanelModel.swift index 55fd272a..eaad4746 100644 --- a/MVMCoreUI/Actions/ActionOpenPanelModel.swift +++ b/MVMCoreUI/Actions/ActionOpenPanelModel.swift @@ -9,7 +9,7 @@ import Foundation public class ActionOpenPanelModel: ActionModelProtocol { - + public enum Panel: String, Codable { case left case right @@ -22,10 +22,10 @@ public class ActionOpenPanelModel: ActionModelProtocol { public var panel: Panel public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? - // Temporary fix till server changes - public var title: String? - public init(panel: Panel) { + public init(panel: Panel, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.panel = panel + self.extraParameters = extraParameters + self.analyticsData = analyticsData } } diff --git a/MVMCoreUI/Actions/ActionTopAlertModel.swift b/MVMCoreUI/Actions/ActionTopAlertModel.swift index 628633c2..62e496c2 100644 --- a/MVMCoreUI/Actions/ActionTopAlertModel.swift +++ b/MVMCoreUI/Actions/ActionTopAlertModel.swift @@ -9,15 +9,16 @@ import Foundation @objcMembers public class ActionTopAlertModel: ActionModelProtocol { + public static var identifier: String = "topAlert" public var actionType: String = ActionTopAlertModel.identifier public var pageType: String public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? - // Temporary fix till server changes - public var title: String? - public init(pageType: String) { + public init(pageType: String, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.pageType = pageType + self.extraParameters = extraParameters + self.analyticsData = analyticsData } } diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift index ba793000..c65698c3 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/ExternalLink.swift @@ -25,7 +25,7 @@ open class ExternalLink: Link { guard let model = model as? ExternalLinkModel else { return } - exportImageView?.tintColor = model.textColor.uiColor + exportImageView?.tintColor = model.enabledColor.uiColor } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift index d65ce3ce..5f5e6e12 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift @@ -52,7 +52,7 @@ import UIKit guard let model = model as? LinkModel else { return } setTitle(model.title, for: .normal) - setTitleColor(model.textColor.uiColor, for: .normal) + setTitleColor(model.enabledColor.uiColor, for: .normal) setTitleColor(model.disabledColor.uiColor, for: .disabled) isEnabled = model.enabled set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift index 7a4661bd..a54f23e9 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/LinkModel.swift @@ -21,7 +21,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol { public var title: String public var action: ActionModelProtocol public var enabled = true - public var textColor = Color(uiColor: .mvmBlack) + public var enabledColor = Color(uiColor: .mvmBlack) public var disabledColor = Color(uiColor: .mvmCoolGray6) //-------------------------------------------------- @@ -43,7 +43,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol { case title case action case enabled - case textColor + case enabledColor case disabledColor } @@ -60,8 +60,8 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol { if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { self.enabled = enabled } - if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) { - textColor = color + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor) { + enabledColor = color } if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor) { @@ -76,7 +76,7 @@ open class LinkModel: ButtonModelProtocol, MoleculeModelProtocol { try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeModel(action, forKey: .action) try container.encode(enabled, forKey: .enabled) - try container.encode(textColor, forKey: .textColor) + try container.encode(enabledColor, forKey: .enabledColor) try container.encode(disabledColor, forKey: .disabledColor) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift index a5fedef0..2b22fcee 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift @@ -19,13 +19,14 @@ import UIKit widthConstraint?.constant = diameter } } - + public override var isSelected: Bool { didSet { radioModel?.state = isSelected + updateAccessibilityLabel() } } - + public var enabledColor: UIColor = .mvmBlack public var disabledColor: UIColor = .mvmCoolGray3 public var delegateObject: MVMCoreUIDelegateObject? @@ -39,12 +40,12 @@ import UIKit }() lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = { - if let radioGroupName = radioGroupName, - let radioButtonModel = delegateObject?.formHolderDelegate?.formValidator?.radioButtonsModelByGroup[radioGroupName] { - return radioButtonModel - } else { - return nil - } + + guard let radioGroupName = radioGroupName, + let radioButtonModel = delegateObject?.formHolderDelegate?.formValidator?.radioButtonsModelByGroup[radioGroupName] + else { return nil } + + return radioButtonModel }() public override var isEnabled: Bool { @@ -90,6 +91,9 @@ import UIKit /// The action performed when tapped. func tapAction() { + if !isEnabled { + return + } if let radioButtonModel = radioButtonSelectionHelper { radioButtonModel.selected(self) } else { @@ -115,6 +119,18 @@ import UIKit return radioModel?.fieldValue } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + /// Adjust accessibility label based on state of RadioButton. + func updateAccessibilityLabel() { + + if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "radio_selected_state" : "radio_not_selected_state") { + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "radio_desc_state") ?? "%@%@", "", state) + } + } + //-------------------------------------------------- // MARK: - MVMViewProtocol //-------------------------------------------------- @@ -133,14 +149,15 @@ import UIKit isAccessibilityElement = true accessibilityTraits = .button accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint") + updateAccessibilityLabel() } public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject guard let model = model as? RadioButtonModel else { return } - self.delegateObject = delegateObject isSelected = model.state isEnabled = model.enabled RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject) diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift new file mode 100644 index 00000000..35294457 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatch.swift @@ -0,0 +1,166 @@ +// +// RadioSwatch.swift +// MVMCoreUI +// +// Created by Lekshmi S on 01/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +import UIKit + +open class RadioSwatch: Control { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public let bottomText = Label.createLabelRegularMicro(true) + + private var circleLayer: CAShapeLayer? + private var selectedLayer: CALayer? + private var strikeLayer: CALayer? + private var maskLayer: CALayer? + + public var radioSwatchModel: RadioSwatchModel? { + return model as? RadioSwatchModel + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func updateView(_ size: CGFloat) { + super.updateView(size) + bottomText.updateView(size) + layer.setNeedsDisplay() + } + + open override func setupView() { + super.setupView() + + addSubview(bottomText) + bottomText.textAlignment = .center + NSLayoutConstraint.constraintPinSubview(bottomText, pinTop: true, topConstant: 38, pinBottom: true, bottomConstant: 0, pinLeft: true, leftConstant: 0, pinRight: true, rightConstant: 0) + addTarget(self, action: #selector(selectSwatch), for: .touchUpInside) + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? RadioSwatchModel else { return } + isSelected = model.selected + isEnabled = model.enabled + bottomText.text = model.text + } + + public override func reset() { + super.reset() + isSelected = false + isEnabled = true + bottomText.text = nil + } + + //------------------------------------------------------ + // MARK: - State Handling + //------------------------------------------------------ + open override func draw(_ layer: CALayer, in ctx: CGContext) { + //Draw the swatch + circleLayer?.removeFromSuperlayer() + let circle = getCircle(color: isSelected ? .mvmBlack : .mvmCoolGray6, thickness: 1) + layer.addSublayer(circle) + circleLayer = circle + + //Draw the strikethrough + strikeLayer?.removeFromSuperlayer() + if radioSwatchModel?.strikethrough ?? false { + let line = getStrikeThrough(color: isSelected ? .mvmBlack : .mvmCoolGray6, thickness: 1) + layer.addSublayer(line) + strikeLayer = line + } + + //Draw the selected layer + selectedLayer?.removeFromSuperlayer() + if isSelected { + let outerCircle = getSelectedLayer(color: isSelected ? .mvmBlack : .mvmCoolGray6, thickness: 1) + layer.addSublayer(outerCircle) + selectedLayer = outerCircle + bottomText.isHidden = false + } else { + bottomText.isHidden = true + } + + //Handle Mask + maskLayer?.removeFromSuperlayer() + if !isEnabled { + let mask = getMaskLayer() + layer.mask = mask + maskLayer = mask + } + } + + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any size changes + layer.setNeedsDisplay() + } + + @objc open func selectSwatch() { + guard isEnabled else { return } + isSelected = true + radioSwatchModel?.selected = isSelected + layer.setNeedsDisplay() + } + + @objc open func deselectSwatch() { + isSelected = false + radioSwatchModel?.selected = isSelected + layer.setNeedsDisplay() + } + + func getCircle(color: UIColor, thickness: CGFloat) -> CAShapeLayer { + let circle = CAShapeLayer() + circle.name = "innercircle" + circle.fillColor = radioSwatchModel?.color.cgColor ?? UIColor.blue.cgColor + circle.opacity = 1.0 + circle.lineWidth = thickness + circle.strokeColor = color.cgColor + + let circlePath = UIBezierPath(ovalIn: CGRect(x: 12, y: 1, width: 30, height: 30)) + circle.path = circlePath.cgPath + return circle + } + + func getStrikeThrough(color: UIColor, thickness: CGFloat) -> CAShapeLayer { + let strikeThrough = CAShapeLayer() + strikeThrough.name = "strikethrough" + strikeThrough.fillColor = nil + strikeThrough.opacity = 1.0 + strikeThrough.lineWidth = thickness + strikeThrough.strokeColor = color.cgColor + + let linePath = UIBezierPath() + linePath.move(to: CGPoint(x: 12, y: 31)) + linePath.addLine(to: CGPoint(x: 42, y: 1)) + strikeThrough.path = linePath.cgPath + return strikeThrough + } + + func getSelectedLayer(color: UIColor, thickness: CGFloat) -> CAShapeLayer { + circleLayer?.path = UIBezierPath(ovalIn: CGRect(x: 15, y: 4, width: 24, height: 24)).cgPath + let circle = CAShapeLayer() + circle.name = "outercircle" + circle.fillColor = UIColor.clear.cgColor + circle.opacity = 1.0 + circle.lineWidth = thickness + circle.strokeColor = color.cgColor + + let circlePath = UIBezierPath(ovalIn: CGRect(x: 12, y: 1, width: 30, height: 30)) + circle.path = circlePath.cgPath + return circle + } + + func getMaskLayer() -> CALayer { + let mask = CALayer() + mask.backgroundColor = UIColor.white.cgColor + mask.opacity = 0.3 + mask.frame = bounds + return mask + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchCollectionViewCell.swift new file mode 100644 index 00000000..8412dab2 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchCollectionViewCell.swift @@ -0,0 +1,23 @@ +// +// RadioSwatchCollectionViewCell.swift +// MVMCoreUI +// +// Created by Lekshmi S on 13/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +open class RadioSwatchCollectionViewCell: CollectionViewCell { + public let radioSwatch = RadioSwatch() + + open override func setupView() { + super.setupView() + addMolecule(radioSwatch) + MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? RadioSwatchModel else { return } + radioSwatch.set(with: model, delegateObject, additionalData) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift new file mode 100644 index 00000000..3aadd467 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchModel.swift @@ -0,0 +1,64 @@ +// +// RadioSwatchModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/17/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class RadioSwatchModel: MoleculeModelProtocol { + public static var identifier: String = "radioSwatch" + public var backgroundColor: Color? + public var color: Color = Color(uiColor: .mvmBlue) + public var text: String? + public var selected: Bool = false + public var enabled: Bool = true + public var strikethrough: Bool = false + public var fieldValue: String? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case color + case text + case selected + case enabled + case strikethrough + case fieldValue + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) { + self.color = color + } + text = try typeContainer.decodeIfPresent(String.self, forKey: .text) + if let selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { + self.selected = selected + } + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } + if let strikethrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { + self.strikethrough = strikethrough + } + fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(color, forKey: .color) + try container.encodeIfPresent(text, forKey: .text) + try container.encode(selected, forKey: .selected) + try container.encode(enabled, forKey: .enabled) + try container.encode(strikethrough, forKey: .strikethrough) + try container.encodeIfPresent(fieldValue, forKey: .fieldValue) + } +} + + diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift new file mode 100644 index 00000000..8a6b8faf --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift @@ -0,0 +1,152 @@ +// +// RadioSwatches.swift +// MVMCoreUI +// +// Created by Lekshmi S on 01/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class RadioSwatches: View { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var collectionView: CollectionView! + public var swatches: [RadioSwatchModel]? + private var size: CGFloat? + private var delegateObject: MVMCoreUIDelegateObject? + + //------------------------------------------------------ + // MARK: - Constraints + //------------------------------------------------------ + public var collectionViewHeight: NSLayoutConstraint? + private let cellSize: CGFloat = 54.0 + private let itemSpacing: CGFloat = 8.0 + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + setHeight() + DispatchQueue.main.async { + self.collectionView.collectionViewLayout.invalidateLayout() + } + } + + open override func setupView() { + super.setupView() + collectionView = createCollectionView() + addSubview(collectionView) + NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) + collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 100) + collectionViewHeight?.isActive = true + } + + @objc override open func updateView(_ size: CGFloat) { + super.updateView(size) + self.size = size + collectionView.updateView(size) + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject + + guard let radioSwatchesModel = model as? RadioSwatchesModel else { return } + swatches = radioSwatchesModel.swatches + FormValidator.setupValidation(for: radioSwatchesModel, delegate: delegateObject?.formHolderDelegate) + collectionView.reloadData() + } + + //------------------------------------------------------ + // MARK: - Methods + //------------------------------------------------------ + /// Creates the collection view. + open func createCollectionView() -> CollectionView { + let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) + collection.dataSource = self + collection.delegate = self + collection.register(RadioSwatchCollectionViewCell.self, forCellWithReuseIdentifier: "RadioSwatchCollectionViewCell") + return collection + } + + /// Creates the layout for the collection. + open func createCollectionViewLayout() -> UICollectionViewLayout { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = itemSpacing + layout.minimumInteritemSpacing = itemSpacing + return layout + } + + open func setHeight() { + guard let swatches = swatches, swatches.count > 0 else { + collectionViewHeight?.constant = 0 + return + } + // Calculate the height + let swatchesInRow = floor(CGFloat(collectionView.bounds.width/(cellSize + itemSpacing))) + let numberOfRows = ceil(CGFloat(swatches.count)/swatchesInRow) + let height = (numberOfRows * cellSize) + (itemSpacing * (numberOfRows-1)) + + if let oldHeight = collectionViewHeight?.constant, + height != oldHeight { + // Notify delegate of height change, called async to avoid various race conditions caused while happening while laying out initially. + DispatchQueue.main.async { + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + } + } + collectionViewHeight?.constant = CGFloat(height) + } +} + +//------------------------------------------------------ +// MARK: - Delegate methods +//------------------------------------------------------ +extension RadioSwatches: UICollectionViewDelegateFlowLayout { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: cellSize, height: cellSize) + } +} + +extension RadioSwatches: UICollectionViewDataSource { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return swatches?.count ?? 0 + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let molecule = swatches?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioSwatchCollectionViewCell", for: indexPath) as? RadioSwatchCollectionViewCell else { + fatalError() + } + cell.reset() + cell.radioSwatch.isUserInteractionEnabled = false + cell.set(with: molecule, delegateObject, nil) + cell.updateView(size ?? collectionView.bounds.width) + if molecule.selected { + collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically) + } + cell.layoutIfNeeded() + return cell + } +} + +extension RadioSwatches: UICollectionViewDelegate { + open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + guard let molecule = swatches?[indexPath.row] else { return false } + return molecule.enabled + } + + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let cell = collectionView.cellForItem(at: indexPath) as? RadioSwatchCollectionViewCell else { return } + cell.radioSwatch.selectSwatch() + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + } + + open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + guard let cell = collectionView.cellForItem(at: indexPath) as? RadioSwatchCollectionViewCell else { return } + cell.radioSwatch.deselectSwatch() + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift new file mode 100644 index 00000000..db8e5d5d --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatchesModel.swift @@ -0,0 +1,54 @@ +// +// RadioSwatchesModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 31/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class RadioSwatchesModel: MoleculeModelProtocol, FormFieldProtocol { + public static var identifier: String = "radioSwatches" + public var backgroundColor: Color? + public var swatches: [RadioSwatchModel] + public var fieldKey: String? + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? + + /// Returns the fieldValue of the selected swatch, otherwise the text of selected swatch. + public func formFieldValue() -> AnyHashable? { + let selectedSwatch = swatches.first { (swatch) -> Bool in + return swatch.selected + } + return selectedSwatch?.fieldValue ?? selectedSwatch?.text + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case swatches + case fieldKey + case groupName + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + swatches = try typeContainer.decode([RadioSwatchModel].self, forKey: .swatches) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + self.groupName = groupName + } + baseValue = formFieldValue() + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(swatches, forKey: .swatches) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encode(groupName, forKey: .groupName) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/BaseDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/BaseDropdownEntryField.swift index f7083df5..ee697a3e 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/BaseDropdownEntryField.swift @@ -69,7 +69,7 @@ import UIKit textFieldTrailingConstraint = dropDownCaretView.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 6) textFieldTrailingConstraint?.isActive = true - container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true + container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: Padding.Four).isActive = true dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/DateDropdownEntryField.swift index a7a483fc..9860cd5b 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/DateDropdownEntryField.swift @@ -31,10 +31,9 @@ import UIKit return formatter }() + /// Update the property value to alter the format of how the date is presented. public var dateFormat: String = "MMM d, y" { - didSet { - dateFormatter.dateFormat = dateFormat - } + didSet { dateFormatter.dateFormat = dateFormat } } //-------------------------------------------------- @@ -69,10 +68,10 @@ import UIKit public override func setupFieldContainerContent(_ container: UIView) { super.setupFieldContainerContent(container) - datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField) + datePicker = UIDatePicker.addDatePicker(to: textField) datePicker?.addTarget(self, action: #selector(pickerValueChanged), for: .valueChanged) datePicker?.timeZone = NSTimeZone.system - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: self) + UIToolbar.addDismissToolbar(to: textField, delegate: self, action: #selector(dismissFieldInput)) } @objc public func setDatePickerDuration(from startDate: Date?, to endDate: Date?, showStartDate: Bool = true) { @@ -104,7 +103,7 @@ import UIKit } } - @objc override func dismissFieldInput(_ sender: Any?) { + @objc public override func dismissFieldInput(_ sender: Any?) { setTextWith(date: datePicker?.date) super.dismissFieldInput(sender) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/DateDropdownEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/TextFields/DateDropdownEntryFieldModel.swift index cdafc9e9..86c072dd 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/DateDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/DateDropdownEntryFieldModel.swift @@ -22,24 +22,25 @@ //-------------------------------------------------- private enum CodingKeys: String, CodingKey { - case moleculeName case dateFormat } //-------------------------------------------------- - // MARK: - Initializers + // MARK: - Codec //-------------------------------------------------- required public init(from decoder: Decoder) throws { try super.init(from: decoder) let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - dateFormat = try typeContainer.decodeIfPresent(String.self, forKey: .dateFormat) ?? "MMM d, y" + + if let dateFormat = try typeContainer.decodeIfPresent(String.self, forKey: .dateFormat) { + self.dateFormat = dateFormat + } } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) try container.encode(dateFormat, forKey: .dateFormat) } } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift index 38adee03..8ae4365f 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift @@ -211,7 +211,9 @@ import UIKit let digitBox = DigitBox() digitBox.isAccessibilityElement = true - digitBox.digitField.inputAccessoryView = MVMCoreUICommonViewsUtility.getToolbarWithDoneButton(delegate: self) + let observingDelegate = delegateObject?.observingTextFieldDelegate ?? self + digitBox.digitField.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate, + action: #selector(observingDelegate.dismissFieldInput)) digitBox.digitField.delegate = self digitBox.digitBoxDelegate = self return digitBox @@ -246,15 +248,6 @@ import UIKit } } - @objc public override func defaultValidationBlock() { - - validationBlock = { enteredValue in - guard let enteredValue = enteredValue else { return false } - - return enteredValue.count > 0 && enteredValue.count == self.digitBoxes.count - } - } - @objc public func selectPreviousDigitField(_ currentTextField: UITextField?, clear: Bool) { var selectPreviousField = false @@ -322,7 +315,7 @@ import UIKit return true } - @objc override func dismissFieldInput(_ sender: Any?) { + @objc public override func dismissFieldInput(_ sender: Any?) { digitBoxes.forEach { if $0.isSelected { @@ -337,12 +330,18 @@ import UIKit guard let model = model as? DigitEntryFieldModel else { return } numberOfDigits = model.digits - setAsSecureTextEntry(model.secureEntry) - for digitBox in digitBoxes { - digitBox.digitField.inputAccessoryView = MVMCoreUICommonViewsUtility.getToolbarWithDoneButton(delegate: delegateObject?.observingTextFieldDelegate ?? self) + if let entryType = model.type { + setAsSecureTextEntry(entryType == .secure || entryType == .password) } + let observingDelegate = delegateObject?.observingTextFieldDelegate ?? self + + digitBoxes.forEach { + $0.digitField.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate, + action: #selector(observingDelegate.dismissFieldInput)) + } + super.set(with: model, delegateObject, additionalData) } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryFieldModel.swift index 0291066c..b906244d 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryFieldModel.swift @@ -17,34 +17,31 @@ } public var digits: Int = 4 - public var secureEntry: Bool = false //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- private enum CodingKeys: String, CodingKey { - case moleculeName case digits - case secureEntry } //-------------------------------------------------- - // MARK: - Initializers + // MARK: - Codec //-------------------------------------------------- required public init(from decoder: Decoder) throws { try super.init(from: decoder) let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - digits = try typeContainer.decodeIfPresent(Int.self, forKey: .digits) ?? 4 - secureEntry = try typeContainer.decodeIfPresent(Bool.self, forKey: .secureEntry) ?? false + + if let digits = try typeContainer.decodeIfPresent(Int.self, forKey: .digits) { + self.digits = digits + } } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) try container.encode(digits, forKey: .digits) - try container.encode(secureEntry, forKey: .secureEntry) } } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift index 658af357..03454d06 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift @@ -20,7 +20,7 @@ import UIKit public private(set) var titleLabel: Label = { let label = Label() - label.font = MFStyler.fontRegularMicro() + label.font = Styler.Font.RegularMicro.getFont() label.textColor = .mvmBlack label.setContentCompressionResistancePriority(.required, for: .vertical) return label @@ -31,31 +31,23 @@ import UIKit /// Provides contextual information on the TextField. public private(set) var feedbackLabel: Label = { let label = Label() - label.font = MFStyler.fontRegularMicro() + label.font = Styler.Font.RegularMicro.getFont() label.textColor = .mvmBlack label.setContentCompressionResistancePriority(.required, for: .vertical) return label }() - + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- - weak var delegateObject: MVMCoreUIDelegateObject? + var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- // MARK: - Stored Properties //-------------------------------------------------- public var isValid: Bool = false - public var errorMessage: String? - public var standardMessage: String? { - didSet { - if !showError { - feedback = standardMessage - } - } - } //-------------------------------------------------- // MARK: - Computed Properties @@ -75,7 +67,7 @@ import UIKit public var showError: Bool { get { return entryFieldContainer.showError } set (error) { - self.feedback = error ? self.errorMessage : self.standardMessage + self.feedback = error ? entryFieldModel?.errorMessage : entryFieldModel?.feedback self.entryFieldContainer.showError = error } } @@ -121,6 +113,10 @@ import UIKit } } + public var entryFieldModel: EntryFieldModel? { + return model as? EntryFieldModel + } + //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- @@ -174,8 +170,6 @@ import UIKit @objc final public override func setupView() { super.setupView() - guard subviews.isEmpty else { return } - isAccessibilityElement = false setContentCompressionResistancePriority(.required, for: .vertical) accessibilityElements = [titleLabel, feedbackLabel] @@ -193,7 +187,7 @@ import UIKit entryFieldContainer.setContentCompressionResistancePriority(.required, for: .vertical) setupFieldContainerContent(entryFieldContainer) - titleContainerDistance = entryFieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4) + titleContainerDistance = entryFieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: Padding.One) titleContainerDistance?.isActive = true entryFieldContainerLeading = entryFieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) entryFieldContainerLeading?.isActive = true @@ -202,7 +196,7 @@ import UIKit addSubview(feedbackLabel) - feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryFieldContainer.bottomAnchor, constant: PaddingOne) + feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryFieldContainer.bottomAnchor, constant: Padding.Two) feedbackContainerDistance?.isActive = true feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) feedbackLabelLeading?.isActive = true @@ -217,11 +211,11 @@ import UIKit entryFieldContainer.refreshUI() } - /// Method to override. - /// Intended to add the interactive content (i.e. textField) to the entryFieldContainer. - @objc open func setupFieldContainerContent(_ container: UIView) { - // To be overridden by subclass. - } + /** + Method to override. + Intended to add the interactive content (i.e. textField) to the entryFieldContainer. + */ + @objc open func setupFieldContainerContent(_ container: UIView) { } @objc open override func updateView(_ size: CGFloat) { super.updateView(size) @@ -240,10 +234,11 @@ import UIKit backgroundColor = .clear isAccessibilityElement = false - titleLabel.font = MFStyler.fontRegularMicro() + titleLabel.font = Styler.Font.RegularMicro.getFont() titleLabel.textColor = .mvmBlack - feedbackLabel.font = MFStyler.fontRegularMicro() + feedbackLabel.font = Styler.Font.RegularMicro.getFont() feedbackLabel.textColor = .mvmBlack + feedbackLabel.text = nil entryFieldContainer.reset() } @@ -257,13 +252,12 @@ import UIKit title = model.title feedback = model.feedback - errorMessage = model.errorMessage - isEnabled = model.isEnabled + isEnabled = model.enabled - if let isLocked = model.isLocked { + if let isLocked = model.locked { self.isLocked = isLocked - } else if let isSelected = model.isSelected{ + } else if let isSelected = model.selected { self.isSelected = isSelected } } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/EntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/TextFields/EntryFieldModel.swift index 729b3516..337d847d 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/EntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/EntryFieldModel.swift @@ -9,7 +9,7 @@ import Foundation -@objcMembers public class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol { +@objcMembers public class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, EnableableModelProtocol { //-------------------------------------------------- // MARK: - Properties @@ -22,29 +22,22 @@ import Foundation public var backgroundColor: Color? public var title: String? public var feedback: String? - public var errorMessage: String = "" - public var isEnabled: Bool = true - public var isLocked: Bool? - public var isSelected: Bool? + public var errorMessage: String? + public var enabled: Bool = true + public var locked: Bool? + public var selected: Bool? public var text: String? - public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName public var baseValue: AnyHashable? - + public var isValid: Bool? { - didSet { - updateUI?() - } + didSet { updateUI?() } } /// Temporary binding mechanism for the view to update on enable changes. - public var updateUI: (() -> Void)? + public var updateUI: (() -> ())? - public func setValidity(_ valid: Bool, rule: RulesProtocol) { - self.isValid = valid - } - //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -52,23 +45,29 @@ import Foundation private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor - case title = "label" - case isEnabled + case title + case enabled case feedback - case errorMessage = "errorMsg" - case isLocked - case isSelected - case isValid - case isRequired = "required" + case errorMessage + case locked + case selected case text case fieldKey case groupName } - + + //-------------------------------------------------- + // MARK: - Validation Methods + //-------------------------------------------------- + public func formFieldValue() -> AnyHashable? { return text } + public func setValidity(_ valid: Bool, rule: RulesProtocol) { + self.isValid = valid + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -78,18 +77,20 @@ import Foundation baseValue = text } + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) title = try typeContainer.decodeIfPresent(String.self, forKey: .title) feedback = try typeContainer.decodeIfPresent(String.self, forKey: .feedback) - errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage) ?? "" - isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? true - isLocked = try typeContainer.decodeIfPresent(Bool.self, forKey: .isLocked) - isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .isSelected) - isValid = try typeContainer.decodeIfPresent(Bool.self, forKey: .isValid) + errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage) + enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true + locked = try typeContainer.decodeIfPresent(Bool.self, forKey: .locked) + selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) text = try typeContainer.decodeIfPresent(String.self, forKey: .text) - baseValue = text fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { @@ -103,13 +104,12 @@ import Foundation try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(title, forKey: .title) try container.encodeIfPresent(feedback, forKey: .feedback) - try container.encode(errorMessage, forKey: .errorMessage) - try container.encode(isEnabled, forKey: .isEnabled) - try container.encode(isLocked, forKey: .isLocked) - try container.encode(isSelected, forKey: .isSelected) - try container.encodeIfPresent(fieldKey, forKey: .fieldKey) - try container.encodeIfPresent(isValid, forKey: .isValid) try container.encodeIfPresent(text, forKey: .text) + try container.encodeIfPresent(locked, forKey: .locked) + try container.encodeIfPresent(selected, forKey: .selected) + try container.encodeIfPresent(errorMessage, forKey: .errorMessage) + try container.encode(enabled, forKey: .enabled) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) } } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/ItemDropdownEntryField.swift index bd6d793c..cc8a4c71 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/ItemDropdownEntryField.swift @@ -8,6 +8,8 @@ import UIKit +public typealias TextFieldAndPickerDelegate = (UITextFieldDelegate & UIPickerViewDelegate & UIPickerViewDataSource) + open class ItemDropdownEntryField: BaseDropdownEntryField { //-------------------------------------------------- @@ -62,7 +64,7 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { @objc open override func setupFieldContainerContent(_ container: UIView) { super.setupFieldContainerContent(container) - pickerView = MVMCoreUICommonViewsUtility.addPicker(to: textField, delegate: self) + pickerView = UIPickerView.addPicker(to: textField, delegate: self, dismissAction: #selector(dismissFieldInput)) textField.hideBlinkingCaret = true textField.autocorrectionType = .no uiTextFieldDelegate = self diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/ItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/TextFields/ItemDropdownEntryFieldModel.swift index 56612b7b..2105120c 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/ItemDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/ItemDropdownEntryFieldModel.swift @@ -17,17 +17,21 @@ public var options: [String] = [] public var selectedIndex: Int = 0 + + public override func formFieldValue() -> AnyHashable? { + guard !options.isEmpty else { return nil } + return options[selectedIndex] + } //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- private enum CodingKeys: String, CodingKey { - case moleculeName case options case selectedIndex } - + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -35,14 +39,17 @@ required public init(from decoder: Decoder) throws { try super.init(from: decoder) let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + options = try typeContainer.decode([String].self, forKey: .options) - selectedIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) ?? 0 + + if let selectedIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) { + self.selectedIndex = selectedIndex + } } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) try container.encode(options, forKey: .options) try container.encode(options, forKey: .selectedIndex) } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/MdnEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/MdnEntryField.swift index 77b5a4a4..d80a008b 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/MdnEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/MdnEntryField.swift @@ -48,6 +48,18 @@ import MVMCore set { text = MVMCoreUIUtility.formatMdn(newValue) } } + /// Toggles selected or original (unselected) UI. + public override var isSelected: Bool { + get { return entryFieldContainer.isSelected } + set (selected) { + if selected && showError { + showError = false + } + + super.isSelected = selected + } + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -77,11 +89,14 @@ import MVMCore super.setupFieldContainerContent(container) textField.keyboardType = .numberPad + } + + open override func setupTextFieldToolbar() { - let toolbar = MVMCoreUICommonViewsUtility.makeEmptyToolbar() + let toolbar = UIToolbar.createEmptyToolbar() let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - let contacts = UIBarButtonItem(title: MVMCoreUIUtility.hardcodedString(withKey: "textfield_contacts_barbutton"), style: .plain, target: self, action: #selector(getContacts(_:))) - let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFieldInput(_:))) + let contacts = UIBarButtonItem(title: MVMCoreUIUtility.hardcodedString(withKey: "textfield_contacts_barbutton"), style: .plain, target: self, action: #selector(getContacts)) + let dismissButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFieldInput)) toolbar.items = [contacts, space, dismissButton] textField.inputAccessoryView = toolbar } @@ -107,18 +122,18 @@ import MVMCore isValid = true return true } - - let isValid = hasValidMDN() - - if isValid { + + isValid = hasValidMDN() + + if self.isValid { showError = false - + } else { - errorMessage = errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") + entryFieldModel?.errorMessage = entryFieldModel?.errorMessage ?? MVMCoreUIUtility.hardcodedString(withKey: "textfield_phone_format_error_message") showError = true UIAccessibility.post(notification: .layoutChanged, argument: textField) } - + return isValid } @@ -196,7 +211,7 @@ import MVMCore } @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - + return proprietorTextDelegate?.textFieldShouldBeginEditing?(textField) ?? true } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift index de0d1789..f95c918d 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift @@ -15,7 +15,7 @@ import UIKit /// Called when the entered text becomes invalid based on the validation block @objc optional func isInvalid(textfield: TextEntryField?) /// Dismisses the keyboard. - @objc optional func dismissFieldInput(sender: Any?) + @objc optional func dismissFieldInput(_ sender: Any?) } @@ -28,7 +28,7 @@ import UIKit let textField = TextField() textField.isAccessibilityElement = true textField.setContentCompressionResistancePriority(.required, for: .vertical) - textField.font = MFStyler.fontRegularBodyLarge() + textField.font = Styler.Font.RegularBodyLarge.getFont() textField.textColor = .mvmBlack textField.smartQuotesType = .no textField.smartDashesType = .no @@ -36,13 +36,19 @@ import UIKit return textField }() + public lazy var errorImage: UIImageView = { + let image = MVMCoreUIUtility.imageNamed("alert_standard") + let imageView = UIImageView(image: image) + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.heightAnchor.constraint(equalToConstant: 20).isActive = true + imageView.widthAnchor.constraint(equalToConstant: 20).isActive = true + return imageView + }() + //-------------------------------------------------- // MARK: - Stored Properties //-------------------------------------------------- - /// Set enabled and disabled colors to be utilized when setting this texfield's isEnabled property. - public var textColor: (enabled: UIColor?, disabled: UIColor?) = (.mvmBlack, .mvmCoolGray3) - private var observingForChange: Bool = false /// Validate on each entry in the textField. Default: true @@ -54,7 +60,7 @@ import UIKit public var textEntryFieldModel: TextEntryFieldModel? { return model as? TextEntryFieldModel } - + //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- @@ -68,7 +74,7 @@ import UIKit guard let self = self else { return } self.textField.isEnabled = enabled - self.textField.textColor = enabled ? self.textColor.enabled : self.textColor.disabled + self.textField.textColor = enabled ? self.textEntryFieldModel?.enabledTextColor.uiColor : self.textEntryFieldModel?.disabledTextColor.uiColor } } } @@ -78,11 +84,15 @@ import UIKit set (error) { if error { - textField.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField.text ?? "", errorMessage ?? "") + textField.accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message") ?? "", textField.text ?? "", entryFieldModel?.errorMessage ?? "") } else { textField.accessibilityValue = nil } + if textField.isSecureTextEntry { + showErrorView(error) + } + super.showError = error } } @@ -102,12 +112,6 @@ import UIKit set { textField.placeholder = newValue } } - //-------------------------------------------------- - // MARK: - Property Observers - //-------------------------------------------------- - - public var validationBlock: ((_ value: String?) -> Bool)? - //-------------------------------------------------- // MARK: - Delegate Properties //-------------------------------------------------- @@ -169,21 +173,21 @@ import UIKit @objc open override func setupFieldContainerContent(_ container: UIView) { - textField.font = MFStyler.fontRegularBodyLarge() + textField.font = Styler.Font.RegularBodyLarge.getFont() container.addSubview(textField) NSLayoutConstraint.activate([ - textField.heightAnchor.constraint(equalToConstant: 24), - textField.topAnchor.constraint(equalTo: container.topAnchor, constant: 12), - textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16), - container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: 12) + textField.heightAnchor.constraint(equalToConstant: Padding.Five), + textField.topAnchor.constraint(equalTo: container.topAnchor, constant: Padding.Three), + textField.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: Padding.Three), + container.bottomAnchor.constraint(equalTo: textField.bottomAnchor, constant: Padding.Three) ]) - textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16) + textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Three) textFieldTrailingConstraint?.isActive = true textField.addTarget(self, action: #selector(startEditing), for: .editingDidBegin) - textField.addTarget(self, action: #selector(dismissFieldInput(_:)), for: .editingDidEnd) + textField.addTarget(self, action: #selector(dismissFieldInput), for: .editingDidEnd) let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) entryFieldContainer.addGestureRecognizer(tap) @@ -194,37 +198,31 @@ import UIKit @objc open override func updateView(_ size: CGFloat) { super.updateView(size) - textField.font = MFStyler.fontRegularBodyLarge() + textField.font = Styler.Font.RegularBodyLarge.getFont() layoutIfNeeded() } open override func reset() { super.reset() - textField.font = MFStyler.fontRegularBodyLarge() + textField.font = Styler.Font.RegularBodyLarge.getFont() } - @objc deinit { - setBothTextDelegates(to: nil) - } - @objc public func setBothTextDelegates(to delegate: (UITextFieldDelegate & ObservingTextFieldDelegate)?) { observingTextFieldDelegate = delegate uiTextFieldDelegate = delegate } + open func setupTextFieldToolbar() { + let observingDelegate = observingTextFieldDelegate ?? self + textField.inputAccessoryView = UIToolbar.getToolbarWithDoneButton(delegate: observingDelegate, + action: #selector(observingDelegate.dismissFieldInput)) + } + //-------------------------------------------------- // MARK: - Observing for Change (TextFieldDelegate) //-------------------------------------------------- - public func defaultValidationBlock() { - - validationBlock = { enteredValue in - guard let enteredValue = enteredValue else { return false } - return enteredValue.count > 0 - } - } - @discardableResult @objc override open func resignFirstResponder() -> Bool { if validateWhenDoneEditing { @@ -240,11 +238,11 @@ import UIKit text = textField.text _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) } - + @objc public func updateValidation(_ isValid: Bool) { let previousValidity = self.isValid self.isValid = isValid - + if previousValidity && !isValid { showError = true observingTextFieldDelegate?.isInvalid?(textfield: self) @@ -276,10 +274,30 @@ import UIKit } } - @objc func dismissFieldInput(_ sender: Any?) { + @objc public func dismissFieldInput(_ sender: Any?) { resignFirstResponder() } + private func showErrorView(_ show: Bool) { + + if show { + entryFieldContainer.addSubview(errorImage) + + textFieldTrailingConstraint?.isActive = false + textFieldTrailingConstraint = errorImage.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Two) + textFieldTrailingConstraint?.isActive = true + + entryFieldContainer.trailingAnchor.constraint(equalTo: errorImage.trailingAnchor, constant: Padding.Three).isActive = true + errorImage.centerYAnchor.constraint(equalTo: entryFieldContainer.centerYAnchor).isActive = true + + } else { + errorImage.removeFromSuperview() + textFieldTrailingConstraint?.isActive = false + textFieldTrailingConstraint = entryFieldContainer.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: Padding.Two) + textFieldTrailingConstraint?.isActive = true + } + } + //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- @@ -288,44 +306,39 @@ import UIKit super.set(with: model, delegateObject, additionalData) guard let model = model as? TextEntryFieldModel else { return } - + model.updateUI = { [weak self] in MVMCoreDispatchUtility.performBlock(onMainThread: { - if self?.isSelected ?? false { - self?.updateValidation(model.isValid ?? true) + guard let self = self else { return } + + if self.isSelected { + self.updateValidation(model.isValid ?? true) } }) } + self.delegateObject = delegateObject - FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) - textColor.enabled = model.enabledTextColor?.uiColor - textColor.disabled = model.disabledTextColor?.uiColor + FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) text = model.text placeholder = model.placeholder switch model.type { - case .password: + case .password, .secure: textField.isSecureTextEntry = true + case .number: textField.keyboardType = .numberPad + case .email: textField.keyboardType = .emailAddress + default: break } - if let regex = model.regex, !regex.isEmpty { - validationBlock = { enteredValue in - guard let value = enteredValue else { return false } - return MVMCoreUIUtility.validate(value, withRegularExpression: regex) - } - } else { - defaultValidationBlock() - } - uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate - textField.inputAccessoryView = MVMCoreUICommonViewsUtility.getToolbarWithDoneButton(delegate: observingTextFieldDelegate ?? self) + setupTextFieldToolbar() } } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryFieldModel.swift index c35d812e..491e9891 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryFieldModel.swift @@ -8,9 +8,13 @@ @objcMembers public class TextEntryFieldModel: EntryFieldModel { + //-------------------------------------------------- + // MARK: - Types + //-------------------------------------------------- public enum EntryType: String, Codable { case password + case secure case number case email } @@ -24,10 +28,9 @@ } public var placeholder: String? - public var enabledTextColor: Color? - public var disabledTextColor: Color? + public var enabledTextColor: Color = Color(uiColor: .mvmBlack) + public var disabledTextColor: Color = Color(uiColor: .mvmCoolGray3) public var type: EntryType? - public var regex: String? //-------------------------------------------------- // MARK: - Keys @@ -35,38 +38,38 @@ private enum CodingKeys: String, CodingKey { case moleculeName - case text case placeholder case enabledTextColor case disabledTextColor case type - case regex } //-------------------------------------------------- - // MARK: - Initializers + // MARK: - Codec //-------------------------------------------------- required public init(from decoder: Decoder) throws { try super.init(from: decoder) let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - text = try typeContainer.decodeIfPresent(String.self, forKey: .text) placeholder = try typeContainer.decodeIfPresent(String.self, forKey: .placeholder) - enabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledTextColor) - disabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledTextColor) type = try typeContainer.decodeIfPresent(EntryType.self, forKey: .type) - regex = try typeContainer.decodeIfPresent(String.self, forKey: .regex) + + if let enabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledTextColor) { + self.enabledTextColor = enabledTextColor + } + + if let disabledTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledTextColor) { + self.disabledTextColor = disabledTextColor + } } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(text, forKey: .text) try container.encodeIfPresent(placeholder, forKey: .placeholder) - try container.encodeIfPresent(enabledTextColor, forKey: .enabledTextColor) - try container.encodeIfPresent(disabledTextColor, forKey: .disabledTextColor) + try container.encode(enabledTextColor, forKey: .enabledTextColor) + try container.encode(disabledTextColor, forKey: .disabledTextColor) try container.encodeIfPresent(type, forKey: .type) - try container.encodeIfPresent(regex, forKey: .regex) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/LeftRightLabelView.swift b/MVMCoreUI/Atomic/Atoms/Views/LeftRightLabelView.swift index a7c59cd3..a7d85f86 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LeftRightLabelView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LeftRightLabelView.swift @@ -14,8 +14,8 @@ import Foundation // MARK: - Outlets //------------------------------------------------------ - let leftTextLabel = Label.commonLabelB1(true) - let rightTextLabel = Label.commonLabelB1(true) + let leftTextLabel = Label.createLabelBoldBodySmall(true) + let rightTextLabel = Label.createLabelBoldBodySmall(true) //------------------------------------------------------ // MARK: - Constraints @@ -23,6 +23,29 @@ import Foundation var rightTextLabelLeading: NSLayoutConstraint? var leftTextLabelTrailing: NSLayoutConstraint? + var rightTextLabelTrailing: NSLayoutConstraint? + + private func constrainLeftLabelOnly() { + + deactivateMiddleConstraint() + leftTextLabelTrailing?.isActive = false + leftTextLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: leftTextLabel.trailingAnchor) + leftTextLabelTrailing?.isActive = true + } + + private func constrainRightLabelOnly() { + + deactivateMiddleConstraint() + rightTextLabelLeading?.isActive = false + rightTextLabelLeading = rightTextLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + rightTextLabelLeading?.isActive = true + } + + private func deactivateMiddleConstraint() { + + leftTextLabelTrailing?.isActive = false + rightTextLabelLeading?.isActive = false + } //------------------------------------------------------ // MARK: - Initialization @@ -43,8 +66,6 @@ import Foundation override open func setupView() { super.setupView() - guard subviews.isEmpty else { return } - addSubview(leftTextLabel) addSubview(rightTextLabel) @@ -67,7 +88,7 @@ import Foundation // Resolves text layout issues found between both dynamically sized labels, number is not exact but performs as required. if leftTextLabel.hasText && rightTextLabel.hasText { let padding = MFStyler.defaultHorizontalPadding(forSize: size) * 2 - let maximumTextWidth = (size - (padding + 16)) * 0.4 + let maximumTextWidth = (size - (padding + Padding.Four)) * 0.4 // Subtracting 10 resolves issues of SE and iPad rightTextLabel.preferredMaxLayoutWidth = round(maximumTextWidth) - 10 } else { @@ -84,57 +105,38 @@ import Foundation leftTextLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true leftTextLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true - let leftTextBottom = leftTextLabel.bottomAnchor.constraint(equalTo: bottomAnchor) + let leftTextBottom = bottomAnchor.constraint(equalTo: leftTextLabel.bottomAnchor) leftTextBottom.priority = UILayoutPriority(249) leftTextBottom.isActive = true bottomAnchor.constraint(greaterThanOrEqualTo: leftTextLabel.bottomAnchor).isActive = true - rightTextLabelLeading = rightTextLabel.leadingAnchor.constraint(equalTo: leftTextLabel.trailingAnchor, constant: 16) + rightTextLabelLeading = rightTextLabel.leadingAnchor.constraint(equalTo: leftTextLabel.trailingAnchor, constant: Padding.Four) rightTextLabelLeading?.isActive = true rightTextLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true - let rightLayout = layoutMarginsGuide.trailingAnchor.constraint(equalTo: rightTextLabel.trailingAnchor) - rightLayout.priority = UILayoutPriority(rawValue: 995) - rightLayout.isActive = true - let rightTextBottom = rightTextLabel.bottomAnchor.constraint(equalTo: bottomAnchor) + rightTextLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: rightTextLabel.trailingAnchor) + rightTextLabelTrailing?.isActive = true + + let rightTextBottom = bottomAnchor.constraint(equalTo: rightTextLabel.bottomAnchor) rightTextBottom.priority = UILayoutPriority(rawValue: 249) rightTextBottom.isActive = true bottomAnchor.constraint(greaterThanOrEqualTo: rightTextLabel.bottomAnchor).isActive = true - let leftTextWidth = leftTextLabel.widthAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.widthAnchor, multiplier: 0.6) + let leftTextWidth = leftTextLabel.widthAnchor.constraint(equalTo: layoutMarginsGuide.widthAnchor, multiplier: 0.6) leftTextWidth.priority = UILayoutPriority(rawValue: 995) leftTextWidth.isActive = true - let rightTextWidth = rightTextLabel.widthAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.widthAnchor, multiplier: 0.4) - rightTextWidth.priority = UILayoutPriority(rawValue: 906) + let rightTextWidth = rightTextLabel.widthAnchor.constraint(equalTo: layoutMarginsGuide.widthAnchor, multiplier: 0.4) + rightTextWidth.priority = UILayoutPriority(rawValue: 995) rightTextWidth.isActive = true - leftTextLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 901), for: .horizontal) - rightTextLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 902), for: .horizontal) - - leftTextLabel.setContentHuggingPriority(.required, for: .vertical) - rightTextLabel.setContentHuggingPriority(.required, for: .vertical) - + rightTextLabel.setContentHuggingPriority(.required, for: .horizontal) leftTextLabel.setContentCompressionResistancePriority(.required, for: .vertical) rightTextLabel.setContentCompressionResistancePriority(.required, for: .vertical) - rightTextLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 902), for: .horizontal) - } - - private func constrainLeftLabel() { - - deactivateMiddleConstraint() - leftTextLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: leftTextLabel.trailingAnchor) - leftTextLabelTrailing?.isActive = true - } - - private func constrainRightLabel() { - - deactivateMiddleConstraint() - rightTextLabelLeading = rightTextLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - rightTextLabelLeading?.isActive = true + rightTextLabel.setContentCompressionResistancePriority(.required, for: .horizontal) } override open func reset() { @@ -144,31 +146,31 @@ import Foundation constrainBothLabels() leftTextLabel.text = "" rightTextLabel.text = "" + leftTextLabel.textAlignment = .left + rightTextLabel.textAlignment = .right backgroundColor = nil } - private func deactivateMiddleConstraint() { - - leftTextLabelTrailing?.isActive = false - rightTextLabelLeading?.isActive = false - } - //------------------------------------------------------ // MARK: - Atomization //------------------------------------------------------ open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) - guard let leftRightLabelModel = model as? LeftRightLabelModel else { - return - } + + guard let leftRightLabelModel = model as? LeftRightLabelModel else { return } + leftTextLabel.set(with: leftRightLabelModel.leftText, delegateObject, additionalData) rightTextLabel.setOptional(with: leftRightLabelModel.rightText, delegateObject, additionalData) - + + // Label defaults to .left if no textAlignment value is provided in model. + rightTextLabel.textAlignment = leftRightLabelModel.rightText?.textAlignment ?? .right + if !leftTextLabel.hasText { - constrainRightLabel() + constrainRightLabelOnly() + } else if !rightTextLabel.hasText { - constrainLeftLabel() + constrainLeftLabelOnly() } } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/MultiProgress.swift b/MVMCoreUI/Atomic/Atoms/Views/MultiProgress.swift index 72f84f5e..ceecdd9b 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/MultiProgress.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/MultiProgress.swift @@ -9,39 +9,27 @@ import UIKit @objcMembers open class MultiProgress: View { + private let stack = Stack() var multiProgressModel: MultiProgressBarModel? { get { return model as? MultiProgressBarModel } } - - ///passing value to progressList creates corresponding progress bars - var progressList: Array? { - didSet { - for subview in subviews { - subview.removeFromSuperview() - } - guard (progressList?.count ?? 0) > 0 else { - return - } - var previous: UIView? - for progressObject in progressList! { - guard progressObject.progress > 0.0 else { - continue - } - let view = UIView(frame: .zero) - view.translatesAutoresizingMaskIntoConstraints = false - addSubview(view) - view.backgroundColor = progressObject.progressColor.uiColor - view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: progressObject.progress/100.0).isActive = true - view.leadingAnchor.constraint(equalTo: previous?.trailingAnchor ?? leadingAnchor).isActive = true - previous = view - NSLayoutConstraint.constraintPinSubview(view, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false) - } - } + + public required init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.init(frame: .zero) + set(with: model, delegateObject, additionalData) } - var roundedRect: Bool = false { + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + var roundedCorners: Bool = false { didSet { - if roundedRect { + if roundedCorners { layer.cornerRadius = (thicknessConstraint?.constant ?? defaultHeight)/2 } else { layer.cornerRadius = 0 @@ -57,28 +45,53 @@ import UIKit translatesAutoresizingMaskIntoConstraints = false backgroundColor = .mfLightSilver() clipsToBounds = true + + addSubview(stack) + NSLayoutConstraint.constraintPinSubview(toSuperview: stack) + stack.backgroundColor = backgroundColor + stack.contentView.backgroundColor = .white + stack.model = StackModel(molecules: [], axis: .horizontal, spacing: 2) + stack.stackModel?.horizontalAlignment = .leading + if thicknessConstraint == nil { thicknessConstraint = heightAnchor.constraint(equalToConstant: defaultHeight) thicknessConstraint?.isActive = true } } + /// Creates the bars + open func set(with progressList: Array, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + stack.removeAllItemViews() + guard let stackModel = stack.stackModel else { return } + var views: [StackItem] = [] + var models: [StackItemModel] = [] + for progressObject in progressList { + guard progressObject.percent > 0.0 else { continue } + let model = StackItemModel(percent: Int(progressObject.percent), horizontalAlignment: .fill, verticalAlignment: .fill) + model.backgroundColor = progressObject.color + models.append(model) + views.append(StackItem()) + } + stack.stackItems = views + stackModel.molecules = models + stack.set(with: stackModel, delegateObject, additionalData) + } + //MARK: - MoleculeViewProtocol public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) - guard let multiProgressModel = multiProgressModel else { - return - } - roundedRect = multiProgressModel.roundedRect ?? false + guard let multiProgressModel = multiProgressModel else { return } + roundedCorners = multiProgressModel.roundedCorners ?? false thicknessConstraint?.constant = multiProgressModel.thickness ?? defaultHeight - progressList = multiProgressModel.progressList - + stack.model?.backgroundColor = model.backgroundColor + set(with: multiProgressModel.progressList, delegateObject, additionalData) } open override func reset() { super.reset() backgroundColor = .mfLightSilver() - progressList = nil + stack.reset() + set(with: [], nil, nil) } public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { diff --git a/MVMCoreUI/Atomic/Atoms/Views/MultiProgressModel.swift b/MVMCoreUI/Atomic/Atoms/Views/MultiProgressModel.swift index 4033fd28..fb70ffd0 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/MultiProgressModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/MultiProgressModel.swift @@ -9,12 +9,12 @@ import Foundation @objcMembers public class SingleProgressBarModel: Codable { - @Percent var progress: CGFloat - var progressColor: Color + @Percent var percent: CGFloat + var color: Color - init(_ progress: CGFloat, color: Color) { - self.progress = progress - self.progressColor = color + init(_ percent: CGFloat, color: Color) { + self.percent = percent + self.color = color } } @@ -23,13 +23,13 @@ import Foundation public var progressList: [SingleProgressBarModel] public var backgroundColor: Color? public var thickness: CGFloat? - public var roundedRect: Bool? + public var roundedCorners: Bool? private enum CodingKeys: String, CodingKey { case moleculeName case progressList case thickness - case roundedRect + case roundedCorners case backgroundColor } @@ -42,7 +42,7 @@ import Foundation progressList = try typeContainer.decode([SingleProgressBarModel].self, forKey: .progressList) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) thickness = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .thickness) - roundedRect = try typeContainer.decodeIfPresent(Bool.self, forKey: .roundedRect) + roundedCorners = try typeContainer.decodeIfPresent(Bool.self, forKey: .roundedCorners) } public func encode(to encoder: Encoder) throws { @@ -50,7 +50,7 @@ import Foundation try container.encode(moleculeName, forKey: .moleculeName) try container.encode(progressList, forKey: .progressList) try container.encodeIfPresent(thickness, forKey: .thickness) - try container.encodeIfPresent(roundedRect, forKey: .roundedRect) + try container.encodeIfPresent(roundedCorners, forKey: .roundedCorners) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/ProgressBar.swift b/MVMCoreUI/Atomic/Atoms/Views/ProgressBar.swift index 1b9f1e74..edb8c1de 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/ProgressBar.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/ProgressBar.swift @@ -14,7 +14,7 @@ import Foundation var thickness: CGFloat = 8.0 { willSet(newValue) { heightAnchor.constraint(equalToConstant: newValue).isActive = true - if progressBarModel?.isRounded ?? false { + if progressBarModel?.roundedCorners ?? false { layer.cornerRadius = newValue/2.0 } else { progressViewStyle = .bar @@ -52,12 +52,11 @@ import Foundation //MARK: - MoleculeViewProtocol public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let progressBarModel = model as? ProgressBarModel else { - return - } + guard let progressBarModel = model as? ProgressBarModel else { return } + self.progressBarModel = progressBarModel thickness = progressBarModel.thickness ?? 8 progress = Float((progressBarModel.percent)/100.0) - progressTintColor = progressBarModel.progressColor.uiColor + progressTintColor = progressBarModel.color.uiColor if let backgroundColor = progressBarModel.backgroundColor { trackTintColor = backgroundColor.uiColor } diff --git a/MVMCoreUI/Atomic/Atoms/Views/ProgressBarModel.swift b/MVMCoreUI/Atomic/Atoms/Views/ProgressBarModel.swift index dd53afac..7cef8936 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/ProgressBarModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/ProgressBarModel.swift @@ -11,17 +11,17 @@ import Foundation @objcMembers public class ProgressBarModel: MoleculeModelProtocol { public static var identifier: String = "progressBar" @Percent public var percent: CGFloat - public var progressColor: Color = Color(uiColor: .mfCerulean()) + public var color: Color = Color(uiColor: .mfCerulean()) public var backgroundColor: Color? = Color(uiColor: .mfLightSilver()) - public var isRounded: Bool? + public var roundedCorners: Bool? public var thickness: CGFloat? private enum CodingKeys: String, CodingKey { case moleculeName - case isRounded = "roundRect" + case roundedCorners case thickness case percent - case progressColor + case color case backgroundColor } @@ -32,13 +32,13 @@ import Foundation required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) percent = try typeContainer.decode(CGFloat.self, forKey: .percent) - if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .progressColor) { - progressColor = color + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) { + self.color = color } if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { backgroundColor = color } - isRounded = try typeContainer.decodeIfPresent(Bool.self, forKey: .isRounded) + roundedCorners = try typeContainer.decodeIfPresent(Bool.self, forKey: .roundedCorners) thickness = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .thickness) } @@ -46,9 +46,9 @@ import Foundation var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encode(percent, forKey: .percent) - try container.encode(progressColor, forKey: .progressColor) + try container.encode(color, forKey: .color) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(isRounded, forKey: .isRounded) + try container.encodeIfPresent(roundedCorners, forKey: .roundedCorners) try container.encodeIfPresent(thickness, forKey: .thickness) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift b/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift index 956c2afc..14cfb7b7 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Toggle.swift @@ -25,13 +25,13 @@ public typealias ActionBlockConfirmation = () -> (Bool) //-------------------------------------------------- /// Holds the on and off colors for the container. - public var containerTintColor: (on: UIColor?, off: UIColor?)? = (on: .mvmGreen, off: .mvmBlack) + public var containerTintColor: (on: UIColor, off: UIColor) = (on: .mvmGreen, off: .mvmBlack) /// Holds the on and off colors for the knob. - public var knobTintColor: (on: UIColor?, off: UIColor?)? = (on: .mvmWhite, off: .mvmWhite) + public var knobTintColor: (on: UIColor, off: UIColor) = (on: .mvmWhite, off: .mvmWhite) /// Holds the on and off colors for the disabled state.. - public var disabledTintColor: (container: UIColor?, knob: UIColor?)? = (container: .mvmCoolGray3, knob: .mvmWhite) + public var disabledTintColor: (container: UIColor, knob: UIColor) = (container: .mvmCoolGray3, knob: .mvmWhite) /// Set this flag to false if you do not want to animate state changes. public var isAnimated = true @@ -62,8 +62,8 @@ public typealias ActionBlockConfirmation = () -> (Bool) didSet { isUserInteractionEnabled = isEnabled changeStateNoAnimation(isEnabled ? isOn : false) - backgroundColor = isEnabled ? containerTintColor?.off : disabledTintColor?.container - knobView.backgroundColor = isEnabled ? knobTintColor?.off : disabledTintColor?.knob + backgroundColor = isEnabled ? (isOn ? containerTintColor.on : containerTintColor.off) : disabledTintColor.container + knobView.backgroundColor = isEnabled ? (isOn ? knobTintColor.on : knobTintColor.off) : disabledTintColor.knob } } @@ -80,12 +80,12 @@ public typealias ActionBlockConfirmation = () -> (Bool) if isAnimated { UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: { if self.isOn { - self.knobView.backgroundColor = self.knobTintColor?.on - self.backgroundColor = self.containerTintColor?.on + self.knobView.backgroundColor = self.knobTintColor.on + self.backgroundColor = self.containerTintColor.on } else { - self.knobView.backgroundColor = self.knobTintColor?.off - self.backgroundColor = self.containerTintColor?.off + self.knobView.backgroundColor = self.knobTintColor.off + self.backgroundColor = self.containerTintColor.off } }, completion: nil) @@ -96,12 +96,12 @@ public typealias ActionBlockConfirmation = () -> (Bool) }, completion: nil) } else { - backgroundColor = isOn ? containerTintColor?.on : containerTintColor?.off - knobView.backgroundColor = isOn ? knobTintColor?.on : knobTintColor?.off + backgroundColor = isOn ? containerTintColor.on : containerTintColor.off + knobView.backgroundColor = isOn ? knobTintColor.on : knobTintColor.off self.constrainKnob() } - (model as? ToggleModel)?.state = isOn + toggleModel?.state = isOn _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) accessibilityValue = isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") setNeedsLayout() @@ -109,6 +109,10 @@ public typealias ActionBlockConfirmation = () -> (Bool) } } + public var toggleModel: ToggleModel? { + return model as? ToggleModel + } + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- @@ -127,8 +131,26 @@ public typealias ActionBlockConfirmation = () -> (Bool) private var widthConstraint: NSLayoutConstraint? private func constrainKnob() { - knobLeadingConstraint?.isActive = !isOn - knobTrailingConstraint?.isActive = isOn + + knobLeadingConstraint?.isActive = false + knobTrailingConstraint?.isActive = false + + _ = isOn ? constrainKnobOn() : constrainKnobOff() + + knobTrailingConstraint?.isActive = true + knobLeadingConstraint?.isActive = true + } + + private func constrainKnobOn() { + + knobTrailingConstraint = trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 1) + knobLeadingConstraint = knobView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor) + } + + private func constrainKnobOff() { + + knobTrailingConstraint = trailingAnchor.constraint(greaterThanOrEqualTo: knobView.trailingAnchor) + knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) } //-------------------------------------------------- @@ -185,11 +207,13 @@ public typealias ActionBlockConfirmation = () -> (Bool) layer.cornerRadius = Self.getContainerHeight() / 2.0 knobView.layer.cornerRadius = Self.getKnobHeight() / 2.0 + + changeStateNoAnimation(isOn) } public override func setupView() { super.setupView() - guard subviews.isEmpty else { return } + isAccessibilityElement = true accessibilityTraits = .button accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccToggleHint") @@ -202,7 +226,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) widthConstraint?.isActive = true layer.cornerRadius = Self.containerSize.height / 2.0 - backgroundColor = containerTintColor?.off + backgroundColor = containerTintColor.off addSubview(knobView) @@ -214,19 +238,16 @@ public typealias ActionBlockConfirmation = () -> (Bool) knobView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true - knobTrailingConstraint = trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 1) - knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) - knobLeadingConstraint?.isActive = true + constrainKnobOff() } public override func reset() { super.reset() - backgroundColor = containerTintColor?.off - knobView.backgroundColor = knobTintColor?.off - isAnimated = false - isOn = false - constrainKnob() + backgroundColor = containerTintColor.off + knobView.backgroundColor = knobTintColor.off + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") + isAnimated = true didToggleAction = nil shouldToggleAction = { return true } } @@ -344,26 +365,19 @@ public typealias ActionBlockConfirmation = () -> (Bool) FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) - if let color = model.onTintColor?.uiColor { - containerTintColor?.on = color - } - - if let color = model.offTintColor?.uiColor { - containerTintColor?.off = color - } - - if let color = model.onKnobTintColor?.uiColor { - knobTintColor?.on = color - } - - if let color = model.offKnobTintColor?.uiColor { - knobTintColor?.off = color - } - - changeStateNoAnimation(model.state) + containerTintColor.on = model.onTintColor.uiColor + containerTintColor.off = model.offTintColor.uiColor + knobTintColor.on = model.onKnobTintColor.uiColor + knobTintColor.off = model.offKnobTintColor.uiColor + isOn = model.state + changeStateNoAnimation(isOn) isAnimated = model.animated isEnabled = model.enabled - + + if let accessibileString = model.accessibilityText { + accessibilityLabel = accessibileString + } + if let actionMap = model.action?.toJSON() { didToggleAction = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/ToggleModel.swift b/MVMCoreUI/Atomic/Atoms/Views/ToggleModel.swift index 9eb2f4b5..b39003d2 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/ToggleModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/ToggleModel.swift @@ -8,8 +8,12 @@ import UIKit -public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableModelProtocol { +public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "toggle" public var backgroundColor: Color? public var state: Bool = false @@ -18,10 +22,10 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo public var action: ActionModelProtocol? public var alternateAction: ActionModelProtocol? public var accessibilityText: String? - public var onTintColor: Color? - public var offTintColor: Color? - public var onKnobTintColor: Color? - public var offKnobTintColor: Color? + public var onTintColor: Color = Color(uiColor: .mvmGreen) + public var offTintColor: Color = Color(uiColor: .mvmBlack) + public var onKnobTintColor: Color = Color(uiColor: .mvmWhite) + public var offKnobTintColor: Color = Color(uiColor: .mvmWhite) public var fieldKey: String? public var groupName: String = FormValidator.defaultGroupName @@ -75,19 +79,35 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { self.state = state } + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { self.enabled = enabled } + if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) { self.animated = animated } + action = try typeContainer.decodeModelIfPresent(codingKey: .action) alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - onTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onTintColor) - offTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offTintColor) - onKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onKnobTintColor) - offKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offKnobTintColor) + + if let onTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onTintColor) { + self.onTintColor = onTintColor + } + + if let offTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offTintColor) { + self.offTintColor = offTintColor + } + + if let onKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .onKnobTintColor) { + self.onKnobTintColor = onKnobTintColor + } + + if let offKnobTintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .offKnobTintColor) { + self.offKnobTintColor = offKnobTintColor + } + accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) baseValue = state @@ -106,10 +126,10 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableMo try container.encode(state, forKey: .state) try container.encode(animated, forKey: .animated) try container.encode(enabled, forKey: .enabled) - try container.encodeIfPresent(onTintColor, forKey: .onTintColor) - try container.encodeIfPresent(onKnobTintColor, forKey: .onKnobTintColor) - try container.encodeIfPresent(onKnobTintColor, forKey: .onKnobTintColor) - try container.encodeIfPresent(offKnobTintColor, forKey: .offKnobTintColor) + try container.encode(onTintColor, forKey: .onTintColor) + try container.encode(onKnobTintColor, forKey: .onKnobTintColor) + try container.encode(onKnobTintColor, forKey: .onKnobTintColor) + try container.encode(offKnobTintColor, forKey: .offKnobTintColor) try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(fieldKey, forKey: .fieldKey) try container.encodeIfPresent(groupName, forKey: .groupName) diff --git a/MVMCoreUI/Atomic/Atoms/Views/GraphView.swift b/MVMCoreUI/Atomic/Atoms/Views/Wheel.swift similarity index 87% rename from MVMCoreUI/Atomic/Atoms/Views/GraphView.swift rename to MVMCoreUI/Atomic/Atoms/Views/Wheel.swift index 3cf22d79..da71ea0f 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/GraphView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Wheel.swift @@ -1,5 +1,5 @@ // -// GraphView.swift +// Wheel.swift // MobileFirstFramework // // Created by Ryan on 10/24/19. @@ -8,12 +8,12 @@ import UIKit -@objcMembers open class GraphView: View, MVMCoreUIViewConstrainingProtocol { +@objcMembers open class Wheel: View, MVMCoreUIViewConstrainingProtocol { var heightConstraint: NSLayoutConstraint? var gradientLayer: CALayer? - var graphModel: CircleProgressModel? { - return model as? CircleProgressModel + var graphModel: WheelModel? { + return model as? WheelModel } // MARK: setup @@ -28,7 +28,7 @@ import UIKit override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) - guard let model = model as? CircleProgressModel else { return } + guard let model = model as? WheelModel else { return } createGraphCircle(model) rotationAnimation(model) } @@ -42,7 +42,7 @@ import UIKit } // MARK: circle - open func createGraphCircle(_ graphObject: CircleProgressModel) { + open func createGraphCircle(_ graphObject: WheelModel) { if let sublayers = layer.sublayers { for sublayer in sublayers { sublayer.removeAllAnimations() @@ -56,7 +56,7 @@ import UIKit //begin point will be at the bottom, clockwise direction let path = UIBezierPath(arcCenter: CGPoint(x: radius - , y: radius), radius: radius - graphObject.lineWidth/2.0, startAngle: CGFloat(GraphView.getPiValue(90.0)), endAngle: CGFloat(GraphView.getPiValue(90.0 + 360.0)), clockwise: true) + , y: radius), radius: radius - graphObject.lineWidth/2.0, startAngle: CGFloat(Wheel.getPiValue(90.0)), endAngle: CGFloat(Wheel.getPiValue(90.0 + 360.0)), clockwise: true) path.lineWidth = graphObject.lineWidth let circleLayer = CAShapeLayer() @@ -83,7 +83,7 @@ import UIKit | | | ------------- */ - func createGradientLayer(_ graphObject: CircleProgressModel) -> CALayer { + func createGradientLayer(_ graphObject: WheelModel) -> CALayer { let containLayer = CALayer() containLayer.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter) let radius = graphObject.diameter / 2.0 @@ -151,18 +151,18 @@ import UIKit } //MARK: Animation - func rotationAnimation(_ object: CircleProgressModel) { + func rotationAnimation(_ object: WheelModel) { MVMCoreDispatchUtility.performBlock(onMainThread:{ let rotation = CABasicAnimation(keyPath: "transform.rotation") - let animationHandler = GraphViewAnimationHandler.shared + let animationHandler = WheelAnimationHandler.shared let startAngle = animationHandler.getAnimationStartAngle(object.duration, CACurrentMediaTime()) if startAngle == 0.0 { animationHandler.storeAnimation(object.duration, CACurrentMediaTime()) } - var fromValue = GraphView.getPiValue(0.0 + startAngle), toValue = GraphView.getPiValue(360.0 + startAngle) + var fromValue = Wheel.getPiValue(0.0 + startAngle), toValue = Wheel.getPiValue(360.0 + startAngle) if !object.clockwise { - fromValue = GraphView.getPiValue(360.0 - startAngle) - toValue = GraphView.getPiValue(0.0 - startAngle) + fromValue = Wheel.getPiValue(360.0 - startAngle) + toValue = Wheel.getPiValue(0.0 - startAngle) } rotation.fromValue = fromValue rotation.toValue = toValue @@ -180,7 +180,7 @@ import UIKit } -extension GraphView: CAAnimationDelegate { +extension Wheel: CAAnimationDelegate { public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { if let object = graphModel { rotationAnimation(object) diff --git a/MVMCoreUI/Atomic/Atoms/Views/GraphViewAnimationHandler.swift b/MVMCoreUI/Atomic/Atoms/Views/WheelAnimationHandler.swift similarity index 82% rename from MVMCoreUI/Atomic/Atoms/Views/GraphViewAnimationHandler.swift rename to MVMCoreUI/Atomic/Atoms/Views/WheelAnimationHandler.swift index 2d7489dc..9a862cec 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/GraphViewAnimationHandler.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/WheelAnimationHandler.swift @@ -1,5 +1,5 @@ // -// GraphViewAnimationHandler.swift +// WheelAnimationHandler.swift // MobileFirstFramework // // Created by Ryan on 10/29/19. @@ -8,12 +8,12 @@ import UIKit -@objcMembers open class GraphViewAnimationHandler: NSObject { +@objcMembers open class WheelAnimationHandler: NSObject { /// duration : CACurrentMediaTime() private var animations = [Double: Double]() - static let shared = GraphViewAnimationHandler() + static let shared = WheelAnimationHandler() open func storeAnimation(_ duration: Double, _ currentTime: CFTimeInterval) { guard animations[duration] == nil else { diff --git a/MVMCoreUI/Atomic/Atoms/Views/CircleProgressModel.swift b/MVMCoreUI/Atomic/Atoms/Views/WheelModel.swift similarity index 96% rename from MVMCoreUI/Atomic/Atoms/Views/CircleProgressModel.swift rename to MVMCoreUI/Atomic/Atoms/Views/WheelModel.swift index 2fc67b44..85e4fffb 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CircleProgressModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/WheelModel.swift @@ -1,5 +1,5 @@ // -// CircleProgressModel.swift +// WheelModel.swift // MVMCoreUI // // Created by Scott Pfeil on 1/13/20. @@ -16,9 +16,9 @@ public enum GraphStyle: String, Codable { case unlimited, safetyMode } -public class CircleProgressModel: MoleculeModelProtocol { +public class WheelModel: MoleculeModelProtocol { - public static var identifier: String = "circleProgress" + public static var identifier: String = "wheel" public var style: GraphStyle = .unlimited { didSet { updateStyle() diff --git a/MVMCoreUI/Atomic/Extensions/UIDatePicker+Extension.swift b/MVMCoreUI/Atomic/Extensions/UIDatePicker+Extension.swift new file mode 100644 index 00000000..4c1acfcb --- /dev/null +++ b/MVMCoreUI/Atomic/Extensions/UIDatePicker+Extension.swift @@ -0,0 +1,37 @@ +// +// UIDatePicker+Extension.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 4/14/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +public extension UIDatePicker { + + class func addDatePicker(to textField: UITextField) -> UIDatePicker { + + let datePicker = UIDatePicker() + datePicker.backgroundColor = .mvmWhite + datePicker.datePickerMode = .date + + let locale = NSLocale.current as NSLocale + datePicker.locale = locale as Locale + datePicker.calendar = locale.object(forKey: .calendar) as? Calendar + textField.inputView = datePicker + + return datePicker + } + + class func addTimeAndDatePicker(to textField: UITextField) -> UIDatePicker { + + let datePicker = UIDatePicker() + datePicker.backgroundColor = .mvmWhite + datePicker.datePickerMode = .time + textField.inputView = datePicker + + return datePicker + } +} diff --git a/MVMCoreUI/Atomic/Extensions/UIPickerView+Extension.swift b/MVMCoreUI/Atomic/Extensions/UIPickerView+Extension.swift new file mode 100644 index 00000000..aa933e05 --- /dev/null +++ b/MVMCoreUI/Atomic/Extensions/UIPickerView+Extension.swift @@ -0,0 +1,38 @@ +// +// UIPickerView.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 4/14/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +public extension UIPickerView { + + class func createPickerView() -> UIPickerView { + + let picker = UIPickerView(frame: .zero) + picker.backgroundColor = .mvmWhite + picker.showsSelectionIndicator = true + return picker + } + + class func addPicker(to textField: UITextField, delegate: TextFieldAndPickerDelegate?, dismissAction: Selector?) -> UIPickerView { + + // Sets up the picker (same tag as the textfield) + let picker = createPickerView() + picker.delegate = delegate + picker.dataSource = delegate + picker.tag = textField.tag + textField.inputView = picker + + // Adds a dismiss toolbar, since all fields with pickers should have one. + if let dismissAction = dismissAction { + UIToolbar.addDismissToolbar(to: textField, delegate: delegate, action: dismissAction) + } + + return picker + } +} diff --git a/MVMCoreUI/Atomic/Extensions/UIToolbar+Extension.swift b/MVMCoreUI/Atomic/Extensions/UIToolbar+Extension.swift new file mode 100644 index 00000000..cad763a6 --- /dev/null +++ b/MVMCoreUI/Atomic/Extensions/UIToolbar+Extension.swift @@ -0,0 +1,56 @@ +// +// UIToolbar+Extension.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 4/10/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol TextFieldOrView { } + +extension UITextView: TextFieldOrView { } +extension UITextField: TextFieldOrView { } + + +public extension UIToolbar { + + class func createEmptyToolbar() -> UIToolbar { + + let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 44)) + toolbar.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleWidth] + toolbar.barStyle = .default + toolbar.barTintColor = .mvmCoolGray3 + toolbar.tintColor = .mvmBlack + toolbar.isTranslucent = true + + return toolbar + } + + class func getToolbarWithDoneButton(delegate: Any?, action: Selector) -> UIToolbar { + + let toolbar = createEmptyToolbar() + let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let button = UIBarButtonItem(barButtonSystemItem: .done, target: delegate, action: action) + toolbar.setItems([space, button], animated: false) + + return toolbar + } + + class func addDismissToolbar(to object: TextFieldOrView, delegate: Any?, action: Selector) { + + let toolbar = Self.getToolbarWithDoneButton(delegate: delegate, action: action) + + switch object { + case is UITextField: + (object as? UITextField)?.inputAccessoryView = toolbar + + case is UITextView: + (object as? UITextView)?.inputAccessoryView = toolbar + + default: + return + } + } +} diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index eaddc237..7dd0ccad 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -37,8 +37,7 @@ import Foundation /// Call to register all of the CoreUI molecules. public static func registerObjects() { // Stacks - MoleculeObjectMapping.shared()?.register(viewClass: MoleculeStackView.self, viewModelClass: MoleculeStackModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: Stack.self, viewModelClass: StackModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: MoleculeStackView.self, viewModelClass: StackModel.self) MoleculeObjectMapping.shared()?.register(viewClass: UnOrderedList.self, viewModelClass: UnOrderedListModel.self) MoleculeObjectMapping.shared()?.register(viewClass: NumberedList.self, viewModelClass: NumberedListModel.self) @@ -70,6 +69,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: RadioButton.self, viewModelClass: RadioButtonModel.self) MoleculeObjectMapping.shared()?.register(viewClass: RadioBoxes.self, viewModelClass: RadioBoxesModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Checkbox.self, viewModelClass: CheckboxModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: RadioSwatches.self, viewModelClass: RadioSwatchesModel.self) // Other Atoms MoleculeObjectMapping.shared()?.register(viewClass: ProgressBar.self, viewModelClass: ProgressBarModel.self) @@ -78,7 +78,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: DashLine.self, viewModelClass: DashLineModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MFLoadImageView.self, viewModelClass: ImageViewModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Line.self, viewModelClass: LineModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: GraphView.self, viewModelClass: CircleProgressModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: Wheel.self, viewModelClass: WheelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Toggle.self, viewModelClass: ToggleModel.self) MoleculeObjectMapping.shared()?.register(viewClass: CheckboxLabel.self, viewModelClass: CheckboxLabelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Arrow.self, viewModelClass: ArrowModel.self) @@ -88,6 +88,7 @@ import Foundation // Horizontal Combination Molecules MoleculeObjectMapping.shared()?.register(viewClass: StringAndMoleculeView.self, viewModelClass: StringAndMoleculeModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ImageHeadlineBody.self, viewModelClass: ImageHeadlineBodyModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: Tabs.self, viewModelClass: TabsModel.self) // Vertical Combination Molecules MoleculeObjectMapping.shared()?.register(viewClass: HeadlineBody.self, viewModelClass: HeadlineBodyModel.self) @@ -120,6 +121,7 @@ import Foundation // Other Container Molecules + MoleculeObjectMapping.shared()?.register(viewClass: MoleculeContainer.self, viewModelClass: MoleculeContainerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MoleculeHeaderView.self, viewModelClass: MoleculeHeaderModel.self) MoleculeObjectMapping.shared()?.register(viewClass: FooterView.self, viewModelClass: FooterModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Scroller.self, viewModelClass: ScrollerModel.self) @@ -136,19 +138,23 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableCheckboxAllTextAndLinks.self, viewModelClass: ListLeftVariableCheckboxAllTextAndLinksModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableRadioButtonAndPaymentMethod.self, viewModelClass: ListLeftVariableRadioButtonAndPaymentMethodModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableRadioButtonBodyText.self, viewModelClass: ListLeftVariableRadioButtonBodyTextModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableCheckboxBodyText.self, viewModelClass: ListLeftVariableCheckboxBodyTextModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRVWheel.self, viewModelClass: ListRVWheelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariablePayments.self, viewModelClass: ListRightVariablePaymentsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariableTotalData.self, viewModelClass: ListRightVariableTotalDataModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariableTextLinkAllTextAndLinks.self, viewModelClass: ListRightVariableTextLinkAllTextAndLinksModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariableButtonAllTextAndLinks.self, viewModelClass: ListRightVariableButtonAllTextAndLinksModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariablePriceChangeBodyText.self, viewModelClass: ListRightVariablePriceChangeBodyTextModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariablePriceChangeAllTextAndLinks.self, viewModelClass: ListRightVariablePriceChangeAllTextAndLinksModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListOneColumnFullWidthTextAllTextAndLinks.self, viewModelClass: ListOneColumnFullWidthTextAllTextAndLinksModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListOneColumnFullWidthTextBodyText.self, viewModelClass: ListOneColumnFullWidthTextBodyTextModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListTwoColumnCompareChanges.self, viewModelClass: ListTwoColumnCompareChangesModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListTwoColumnPriceDetails.self, viewModelClass: ListTwoColumnPriceDetailsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListTwoColumnPriceDescription.self, viewModelClass: ListTwoColumnPriceDescriptionModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnInternationalData.self, viewModelClass: ListThreeColumnInternationalDataModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnDataUsage.self, viewModelClass: ListThreeColumnDataUsageModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListFourColumnDataUsageListItem.self, viewModelClass: ListFourColumnDataUsageListItemModel.self) - + // Designed Section Dividers MoleculeObjectMapping.shared()?.register(viewClass: ListFourColumnDataUsageDivider.self, viewModelClass: ListFourColumnDataUsageDividerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnPlanDataDivider.self, viewModelClass: ListThreeColumnPlanDataDividerModel.self) @@ -164,6 +170,11 @@ import Foundation // Designed Headers MoleculeObjectMapping.shared()?.register(viewClass: HeadersH2NoButtonsBodyText.self, viewModelClass: HeadersH2NoButtonsBodyTextModel.self) MoleculeObjectMapping.shared()?.register(viewClass: HeadersH2Buttons.self, viewModelClass: HeadersH2ButtonsModel.self) + + // Device Items + MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexButtonMedium.self, viewModelClass: ListDeviceComplexButtonMediumModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexLinkSmall.self, viewModelClass: ListDeviceComplexLinkSmallModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexLinkMedium.self, viewModelClass: ListDeviceComplexLinkMediumModel.self) // TODO: Need View try? ModelRegistry.register(TabsModel.self) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/HeadersH2NoButtonsBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/HeadersH2NoButtonsBodyTextModel.swift index eb5502eb..816db00d 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/HeadersH2NoButtonsBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/HeadersH2NoButtonsBodyTextModel.swift @@ -25,8 +25,8 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol public override func setDefaults() { super.setDefaults() - topMarginPadding = PaddingDefaultVerticalSpacing3 - bottomMarginPadding = PaddingDefaultVerticalSpacing3 + topPadding = PaddingDefaultVerticalSpacing3 + bottomPadding = PaddingDefaultVerticalSpacing3 } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMedium.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMedium.swift new file mode 100644 index 00000000..f8ab918d --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMedium.swift @@ -0,0 +1,79 @@ +// +// ListDeviceComplexButtonMedium.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 21/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers open class ListDeviceComplexButtonMedium: TableViewCell { + + public var verticalStack: Stack + public let eyebrow = Label.createLabelRegularMicro(true) + public let headline = Label.createLabelBoldTitleMedium(true) + public let body = Label.createLabelRegularBodySmall(true) + public let body2 = Label.createLabelRegularBodySmall(true) + public let button = PillButton(frame: .zero) + public let rightImageView = MFLoadImageView() + public var stack: Stack + + // MARK: - Initializers + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + rightImageView.addSizeConstraintsForAspectRatio = true + rightImageView.heightAnchor.constraint(equalToConstant: 116.0).isActive = true + rightImageView.widthAnchor.constraint(equalToConstant: 116.0).isActive = true + verticalStack = Stack.createStack(with: [(view: eyebrow, model: StackItemModel(horizontalAlignment: .leading)), + (view: headline, model: StackItemModel(horizontalAlignment: .leading)), + (view: body, model: StackItemModel(horizontalAlignment: .leading)), + (view: body2, model: StackItemModel(horizontalAlignment: .leading)), + (view: button, model: StackItemModel(spacing:16, horizontalAlignment: .leading))], + axis: .vertical, spacing: 0) + stack = Stack.createStack(with: [(view: verticalStack, model: StackItemModel(horizontalAlignment: .leading, verticalAlignment: .leading)), + (view: rightImageView, model: StackItemModel(horizontalAlignment: .fill, verticalAlignment: .center))], + axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - MFViewProtocol + open override func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + verticalStack.restack() + } + + // MARK: - ModelMoleculeViewProtocol + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListDeviceComplexButtonMediumModel else { return } + verticalStack.updateContainedMolecules(with: [model.eyebrow, + model.headline, + model.body, + model.body2, + model.button], + delegateObject, additionalData) + rightImageView.set(with: model.image, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 120 + } + + public func setDefault() { + eyebrow.styleRegularMicro(true) + headline.styleBoldTitleMedium(true) + body.styleRegularBodySmall(true) + body2.styleRegularBodySmall(true) + eyebrow.textColor = .mvmCoolGray6 + } + + open override func reset() { + super.reset() + setDefault() + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMediumModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMediumModel.swift new file mode 100644 index 00000000..7f676795 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMediumModel.swift @@ -0,0 +1,68 @@ +// +// ListDeviceComplexButtonMediumModel.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 21/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public class ListDeviceComplexButtonMediumModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listDvcBtnM" + public var eyebrow: LabelModel? + public var headline: LabelModel? + public var body: LabelModel? + public var body2: LabelModel? + public var button: ButtonModel + public var image: ImageViewModel + + public init(eyebrow: LabelModel, headline:LabelModel, body: LabelModel, body2: LabelModel, button: ButtonModel, image: ImageViewModel) { + self.eyebrow = eyebrow + self.headline = headline + self.body = body + self.body2 = body2 + self.button = button + self.image = image + super.init() + } + + /// Defaults to set + override public func setDefaults() { + super.setDefaults() + button.size = .tiny + button.style = .secondary + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case eyebrow + case headline + case body + case body2 + case button + case image + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) + headline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .headline) + body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body) + body2 = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body2) + button = try typeContainer.decode(ButtonModel.self, forKey: .button) + image = try typeContainer.decode(ImageViewModel.self, forKey: .image) + 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.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(eyebrow, forKey: .eyebrow) + try container.encodeIfPresent(headline, forKey: .headline) + try container.encodeIfPresent(body, forKey: .body) + try container.encodeIfPresent(body2, forKey: .body2) + try container.encode(button, forKey: .button) + try container.encode(image, forKey: .image) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift new file mode 100644 index 00000000..f6e18184 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift @@ -0,0 +1,73 @@ +// +// ListDeviceComplexLinkMedium.swift +// MVMCoreUI +// +// Created by Lekshmi S on 21/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers open class ListDeviceComplexLinkMedium: TableViewCell { + + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + public let eyebrow = Label.createLabelRegularMicro(true) + public let headline = Label.createLabelBoldTitleMedium(true) + public let body = Label.createLabelRegularBodySmall(true) + public let body2 = Label.createLabelRegularBodySmall(true) + public let link = Link() + public let rightImage = MFLoadImageView() + let verticalStack: Stack + public let stack: Stack + + //------------------------------------------------------ + // MARK: - Initializers + //------------------------------------------------------ + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + rightImage.addSizeConstraintsForAspectRatio = true + rightImage.heightAnchor.constraint(equalToConstant: 116.0).isActive = true + rightImage.widthAnchor.constraint(equalToConstant: 116.0).isActive = true + verticalStack = Stack.createStack(with: [(view: eyebrow, model: StackItemModel(horizontalAlignment: .leading)), (view: headline, model: StackItemModel(horizontalAlignment: .leading)), (view: body, model: StackItemModel(horizontalAlignment: .leading)), (view: body2, model: StackItemModel(horizontalAlignment: .leading)), (view: link, model: StackItemModel(spacing: 16, horizontalAlignment: .leading))], axis: .vertical, spacing: 0) + + stack = Stack.createStack(with: [(view: verticalStack, model: StackItemModel(horizontalAlignment: .leading)), (view: rightImage, model: StackItemModel(verticalAlignment: .center))], axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //----------------------------------------------------- + // MARK: - View Lifecycle + //----------------------------------------------------- + open override func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + verticalStack.restack() + } + + //------------------------------------------------------ + // MARK: - Molecule + //------------------------------------------------------ + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListDeviceComplexLinkMediumModel else { return } + verticalStack.updateContainedMolecules(with: [model.eyebrow, model.headline, model.body, model.body2, model.link], delegateObject, additionalData) + rightImage.set(with: model.image, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 120 + } + + open override func reset() { + super.reset() + eyebrow.styleRegularMicro(true) + headline.styleBoldTitleMedium(true) + body.styleRegularBodySmall(true) + body2.styleRegularBodySmall(true) + eyebrow.textColor = .mvmCoolGray6 + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMediumModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMediumModel.swift new file mode 100644 index 00000000..c07d7788 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMediumModel.swift @@ -0,0 +1,61 @@ +// +// ListDeviceComplexLinkMediumModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 21/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public class ListDeviceComplexLinkMediumModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listDvcLnkM" + public var eyebrow: LabelModel? + public var headline: LabelModel? + public var body: LabelModel? + public var body2: LabelModel? + public var link: LinkModel + public var image: ImageViewModel + + public init(eyebrow: LabelModel, headline: LabelModel, body: LabelModel, body2: LabelModel, link: LinkModel, image: ImageViewModel) { + self.eyebrow = eyebrow + self.headline = headline + self.body = body + self.body2 = body2 + self.link = link + self.image = image + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case eyebrow + case headline + case body + case body2 + case link + case image + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) + headline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .headline) + body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body) + body2 = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body2) + link = try typeContainer.decode(LinkModel.self, forKey: .link) + image = try typeContainer.decode(ImageViewModel.self, forKey: .image) + 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.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(eyebrow, forKey: .eyebrow) + try container.encodeIfPresent(headline, forKey: .headline) + try container.encodeIfPresent(body, forKey: .body) + try container.encodeIfPresent(body2, forKey: .body2) + try container.encode(link, forKey: .link) + try container.encode(image, forKey: .image) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift new file mode 100644 index 00000000..c57b3b18 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift @@ -0,0 +1,73 @@ +// +// ListDeviceComplexLinkSmall.swift +// MVMCoreUI +// +// Created by Lekshmi S on 24/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers open class ListDeviceComplexLinkSmall: TableViewCell { + + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + public let eyebrow = Label.createLabelRegularMicro(true) + public let headline = Label.createLabelBoldTitleMedium(true) + public let body = Label.createLabelRegularBodySmall(true) + public let body2 = Label.createLabelRegularBodySmall(true) + public let link = Link() + public let rightImage = MFLoadImageView() + let verticalStack: Stack + public let stack: Stack + + //------------------------------------------------------ + // MARK: - Initializers + //------------------------------------------------------ + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + rightImage.addSizeConstraintsForAspectRatio = true + rightImage.heightAnchor.constraint(equalToConstant: 71.0).isActive = true + rightImage.widthAnchor.constraint(equalToConstant: 71.0).isActive = true + verticalStack = Stack.createStack(with: [(view: eyebrow, model: StackItemModel(horizontalAlignment: .leading)), (view: headline, model: StackItemModel(horizontalAlignment: .leading)), (view: body, model: StackItemModel(horizontalAlignment: .leading)), (view: body2, model: StackItemModel(horizontalAlignment: .leading)), (view: link, model: StackItemModel(spacing: 16, horizontalAlignment: .leading))], axis: .vertical, spacing: 0) + + stack = Stack.createStack(with: [(view: verticalStack, model: StackItemModel(horizontalAlignment: .leading)), (view: rightImage, model: StackItemModel(verticalAlignment: .center))], axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //----------------------------------------------------- + // MARK: - View Lifecycle + //----------------------------------------------------- + open override func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + verticalStack.restack() + } + + //------------------------------------------------------ + // MARK: - Molecule + //------------------------------------------------------ + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListDeviceComplexLinkSmallModel else { return } + verticalStack.updateContainedMolecules(with: [model.eyebrow, model.headline, model.body, model.body2, model.link], delegateObject, additionalData) + rightImage.set(with: model.image, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 120 + } + + open override func reset() { + super.reset() + eyebrow.styleRegularMicro(true) + headline.styleBoldTitleMedium(true) + body.styleRegularBodySmall(true) + body2.styleRegularBodySmall(true) + eyebrow.textColor = .mvmCoolGray6 + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmallModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmallModel.swift new file mode 100644 index 00000000..a0b3047b --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmallModel.swift @@ -0,0 +1,61 @@ +// +// ListDeviceComplexLinkSmallModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 24/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public class ListDeviceComplexLinkSmallModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listDvcLnkS" + public var eyebrow: LabelModel? + public var headline: LabelModel? + public var body: LabelModel? + public var body2: LabelModel? + public var link: LinkModel + public var image: ImageViewModel + + public init(eyebrow: LabelModel, headline: LabelModel, body: LabelModel, body2: LabelModel, link: LinkModel, image: ImageViewModel) { + self.eyebrow = eyebrow + self.headline = headline + self.body = body + self.body2 = body2 + self.link = link + self.image = image + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case eyebrow + case headline + case body + case body2 + case link + case image + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) + headline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .headline) + body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body) + body2 = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body2) + link = try typeContainer.decode(LinkModel.self, forKey: .link) + image = try typeContainer.decode(ImageViewModel.self, forKey: .image) + 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.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(eyebrow, forKey: .eyebrow) + try container.encodeIfPresent(headline, forKey: .headline) + try container.encodeIfPresent(body, forKey: .body) + try container.encodeIfPresent(body2, forKey: .body2) + try container.encode(link, forKey: .link) + try container.encode(image, forKey: .image) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift new file mode 100644 index 00000000..e1f823ce --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift @@ -0,0 +1,55 @@ +// +// ListLeftVariableCheckboxBodyText.swift +// MVMCoreUI +// +// Created by Lekshmi S on 14/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class ListLeftVariableCheckboxBodyText: TableViewCell { + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + public let checkbox = Checkbox(frame: .zero) + public var headlineBody = HeadlineBody() + public var stack: Stack + + //----------------------------------------------------- + // MARK: - Initializers + //----------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + stack = Stack.createStack(with: [(view: checkbox, model: StackItemModel(horizontalAlignment: .fill)), + (view: headlineBody, model: StackItemModel(horizontalAlignment: .leading))], + axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //----------------------------------------------------- + // MARK: - View Lifecycle + //----------------------------------------------------- + override open func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + } + + //---------------------------------------------------- + // MARK: - Molecule + //---------------------------------------------------- + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableCheckboxBodyTextModel else { return } + checkbox.set(with: model.checkbox, delegateObject, additionalData) + headlineBody.set(with: model.headlineBody, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 90 + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift new file mode 100644 index 00000000..f6fb19fc --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift @@ -0,0 +1,62 @@ +// +// ListLeftVariableCheckboxBodyTextModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 14/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "listLVCBBdy" + public var checkbox: CheckboxModel + public var headlineBody: HeadlineBodyModel + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public init(checkbox: CheckboxModel, headlineBody: HeadlineBodyModel) { + self.checkbox = checkbox + self.headlineBody = headlineBody + super.init() + } + + open override func setDefaults() { + super.setDefaults() + headlineBody.style = .item + if let headline = headlineBody.headline { + headline.hero = 0 + } + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { + case moleculeName + case headlineBody + case checkbox + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + headlineBody = try typeContainer.decode(HeadlineBodyModel.self, forKey: .headlineBody) + checkbox = try typeContainer.decode(CheckboxModel.self, forKey: .checkbox) + try super.init(from: decoder) + } + + open override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(headlineBody, forKey: .headlineBody) + try container.encode(checkbox, forKey: .checkbox) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift index 4597a0b9..7bf1ff6a 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift @@ -71,9 +71,6 @@ import UIKit } public override func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - - if radioButton.isEnabled { - radioButton.tapAction() - } + radioButton.tapAction() } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextAllTextAndLinks.swift index 1ea1a4a9..ad74039a 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/OneColumn/ListOneColumnFullWidthTextAllTextAndLinks.swift @@ -40,24 +40,18 @@ import Foundation override open func setupView() { super.setupView() addMolecule(stack) + stack.restack() } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?){ super.set(with: model, delegateObject, additionalData) guard let model = model as? ListOneColumnFullWidthTextAllTextAndLinksModel else { return } - eyebrow.setOptional(with: model.eyebrow, delegateObject, additionalData) - headline.setOptional(with: model.headline, delegateObject, additionalData) - subHeadline.setOptional(with: model.subHeadline, delegateObject, additionalData) - body.setOptional(with: model.body, delegateObject, additionalData) - link.setOptional(with: model.link, delegateObject, additionalData) - - // Hide labels if neeeded. - stack.stackModel?.molecules[0].gone = !eyebrow.hasText - stack.stackModel?.molecules[1].gone = !headline.hasText - stack.stackModel?.molecules[2].gone = !subHeadline.hasText - stack.stackModel?.molecules[3].gone = !body.hasText - stack.stackModel?.molecules[4].gone = (link.titleLabel?.text?.count ?? 0) == 0 - stack.restack() + stack.updateContainedMolecules(with: [model.eyebrow, + model.headline, + model.subHeadline, + model.body, + model.link], + delegateObject, additionalData) } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRVWheel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRVWheel.swift index 8ca28609..f7c65dbe 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRVWheel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRVWheel.swift @@ -8,7 +8,7 @@ import Foundation @objcMembers open class ListRVWheel: TableViewCell { - let wheel = GraphView(frame: .zero) + let wheel = Wheel(frame: .zero) let leftLabel = Label.commonLabelB1(true) let rightLabel = Label.commonLabelB2(true) var stack: Stack diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRVWheelModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRVWheelModel.swift index 89b70b0f..a5df2cfe 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRVWheelModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRVWheelModel.swift @@ -11,9 +11,9 @@ public class ListRVWheelModel: ListItemModel, MoleculeModelProtocol { public static var identifier: String = "listRVWheel" public var leftLabel: LabelModel public var rightLabel: LabelModel - public var wheel : CircleProgressModel + public var wheel: WheelModel - public init(leftLabel: LabelModel, rightLabel: LabelModel, wheel: CircleProgressModel) { + public init(leftLabel: LabelModel, rightLabel: LabelModel, wheel: WheelModel) { self.leftLabel = leftLabel self.rightLabel = rightLabel self.wheel = wheel @@ -37,7 +37,7 @@ public class ListRVWheelModel: ListItemModel, MoleculeModelProtocol { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) leftLabel = try typeContainer.decode(LabelModel.self, forKey: .leftLabel) rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) - wheel = try typeContainer.decodeIfPresent(CircleProgressModel.self, forKey: .wheel) ?? CircleProgressModel() + wheel = try typeContainer.decodeIfPresent(WheelModel.self, forKey: .wheel) ?? WheelModel() try super.init(from: decoder) } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinks.swift new file mode 100644 index 00000000..7a74de6e --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinks.swift @@ -0,0 +1,82 @@ +// +// ListRightVariablePriceChangeAllTextAndLinks.swift +// MVMCoreUI +// +// Created by Kruthika KP on 13/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class ListRightVariablePriceChangeAllTextAndLinks: TableViewCell { + + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(frame: .zero) + public let arrow = Arrow(frame: .zero) + public let rightLabel = Label.commonLabelB2(true) + private let stack: Stack + private let arrowStackItem: StackItem + private let rightLabelStackItem: StackItem + + //----------------------------------------------------- + // MARK: - Initializers + //----------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + let stackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .leading), + StackItemModel(horizontalAlignment: .fill), + StackItemModel(spacing: 6, horizontalAlignment: .fill)], + axis: .horizontal) + arrowStackItem = StackItem(andContain: arrow) + rightLabelStackItem = StackItem(andContain: rightLabel) + let stackItems = [StackItem(andContain: eyebrowHeadlineBodyLink), arrowStackItem, rightLabelStackItem] + stack = Stack(with: stackModel, stackItems: stackItems) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //----------------------------------------------------- + // MARK: - View Lifecycle + //----------------------------------------------------- + + open override func alignAccessoryToHero() -> CGPoint? { + let heroCenter = super.alignAccessoryToHero() + + // Aligns the center of the right side items to the headline. + if let heroCenter = heroCenter { + let convertedPoint = stack.convert(heroCenter, from: self) + arrowStackItem.containerHelper.alignCenterVerticalConstraint?.constant = convertedPoint.y - stack.bounds.midY + rightLabelStackItem.containerHelper.alignCenterVerticalConstraint?.constant = convertedPoint.y - stack.bounds.midY + } + return heroCenter + } + + open override func setupView() { + super.setupView() + rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) + rightLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .horizontal) + rightLabel.numberOfLines = 1 + + arrow.pinHeightAndWidth() + + addMolecule(stack) + stack.restack() + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListRightVariablePriceChangeAllTextAndLinksModel else { return } + eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + arrow.set(with: model.arrow, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 121 + } +} + diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinksModel.swift new file mode 100644 index 00000000..ae4be269 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeAllTextAndLinksModel.swift @@ -0,0 +1,47 @@ +// +// ListRightVariablePriceChangeAllTextAndLinksModel.swift +// MVMCoreUI +// +// Created by Kruthika KP on 13/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class ListRightVariablePriceChangeAllTextAndLinksModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listRVArwAll" + public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel + public var rightLabel: LabelModel + public var arrow: ArrowModel + + public init(eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel, rightLabel: LabelModel, arrow: ArrowModel) { + self.eyebrowHeadlineBodyLink = eyebrowHeadlineBodyLink + self.rightLabel = rightLabel + self.arrow = arrow + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case eyebrowHeadlineBodyLink + case rightLabel + case arrow + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + eyebrowHeadlineBodyLink = try typeContainer.decode(EyebrowHeadlineBodyLinkModel.self, forKey: .eyebrowHeadlineBodyLink) + rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) + arrow = try typeContainer.decode(ArrowModel.self, forKey: .arrow) + 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.encode(moleculeName, forKey: .moleculeName) + try container.encode(eyebrowHeadlineBodyLink, forKey: .eyebrowHeadlineBodyLink) + try container.encode(rightLabel, forKey: .rightLabel) + try container.encode(arrow, forKey: .arrow) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeBodyText.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeBodyText.swift new file mode 100644 index 00000000..2b87294a --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeBodyText.swift @@ -0,0 +1,70 @@ +// +// ListRightVariablePriceChangeBodyText.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 13/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers open class ListRightVariablePriceChangeBodyText: TableViewCell { + + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + private let stack: Stack + public let headlineBody = HeadlineBody(frame: .zero) + public let arrow = Arrow(frame: .zero) + public let rightLabel = Label.commonLabelB2(true) + let arrowAndRightLabelStack: Stack + + + // MARK: - Initializers + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + arrowAndRightLabelStack = Stack.createStack(with: [(view: arrow, model: StackItemModel(horizontalAlignment: .fill)), + (view: rightLabel, model: StackItemModel(horizontalAlignment: .fill))], + axis: .horizontal, spacing: 6) + stack = Stack.createStack(with: [(view: headlineBody, model: StackItemModel(horizontalAlignment: .leading)), + (view: arrowAndRightLabelStack, model: StackItemModel(horizontalAlignment: .fill, verticalAlignment: .leading))], + axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //----------------------------------------------------- + // MARK: - View Lifecycle + //----------------------------------------------------- + open override func setupView() { + super.setupView() + headlineBody.styleListItem() + + rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) + rightLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .horizontal) + rightLabel.numberOfLines = 1 + + arrow.pinHeightAndWidth() + addMolecule(stack) + stack.restack() + arrowAndRightLabelStack.restack() + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListRightVariablePriceChangeBodyTextModel else { return } + headlineBody.set(with: model.headlineBody, delegateObject, additionalData) + rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + arrow.set(with: model.arrow, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 80 + } + + open override func reset() { + super.reset() + headlineBody.styleListItem() + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeBodyTextModel.swift new file mode 100644 index 00000000..7bf118df --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/RightVariable/ListRightVariablePriceChangeBodyTextModel.swift @@ -0,0 +1,54 @@ +// +// ListRightVariablePriceChangeBodyTextModel.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 13/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public class ListRightVariablePriceChangeBodyTextModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listRVArwBdy" + public var headlineBody: HeadlineBodyModel + public var rightLabel: LabelModel + public var arrow: ArrowModel + + public init(headlineBody: HeadlineBodyModel,rightLabel: LabelModel,arrow: ArrowModel) { + self.headlineBody = headlineBody + self.rightLabel = rightLabel + self.arrow = arrow + super.init() + } + + /// Defaults to set + public override func setDefaults() { + super.setDefaults() + if let headline = headlineBody.headline { + headline.hero = 0 + } + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case headlineBody + case rightLabel + case arrow + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + headlineBody = try typeContainer.decode(HeadlineBodyModel.self, forKey: .headlineBody) + rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) + arrow = try typeContainer.decode(ArrowModel.self, forKey: .arrow) + 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.encode(moleculeName, forKey: .moleculeName) + try container.encode(headlineBody, forKey: .headlineBody) + try container.encode(rightLabel, forKey: .rightLabel) + try container.encode(arrow, forKey: .arrow) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ThreeColumn/ListThreeColumnDataUsage.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ThreeColumn/ListThreeColumnDataUsage.swift new file mode 100644 index 00000000..5e7d39c6 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ThreeColumn/ListThreeColumnDataUsage.swift @@ -0,0 +1,61 @@ +// +// ListThreeColumnDataUsage.swift +// MVMCoreUI +// +// Created by Kruthika KP on 20/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class ListThreeColumnDataUsage: TableViewCell { + + //----------------------------------------------------- + // MARK: - Outlets + //------------------------------------------------------- + public let leftLabel = Label.createLabelRegularBodySmall(true) + public let centerLabel = Label.createLabelRegularBodySmall(true) + public let rightLabel = Label.createLabelRegularBodySmall(true) + var stack: Stack + + //------------------------------------------------------ + // MARK: - Initializers + //------------------------------------------------------ + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + stack = Stack.createStack(with: [(view: leftLabel, model: StackItemModel(percent: 40, horizontalAlignment: .leading)), + (view: centerLabel, model: StackItemModel(percent: 37, horizontalAlignment: .leading)), + (view: rightLabel, model: StackItemModel(percent: 23, horizontalAlignment: .leading))], + axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + } + + // MARK: - ModelMoleculeViewProtocol + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListThreeColumnDataUsageModel else { return } + leftLabel.set(with: model.leftLabel, delegateObject, additionalData) + centerLabel.set(with: model.centerLabel, delegateObject, additionalData) + rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 121 + } + + open override func reset() { + super.reset() + leftLabel.styleRegularBodySmall(true) + centerLabel.styleRegularBodySmall(true) + rightLabel.styleRegularBodySmall(true) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ThreeColumn/ListThreeColumnDataUsageModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ThreeColumn/ListThreeColumnDataUsageModel.swift new file mode 100644 index 00000000..56f1e402 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ThreeColumn/ListThreeColumnDataUsageModel.swift @@ -0,0 +1,48 @@ +// +// ListThreeColumnDataUsageModel.swift +// MVMCoreUI +// +// Created by Kruthika KP on 20/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class ListThreeColumnDataUsageModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "list3CDataUsg" + public var leftLabel: LabelModel + public var centerLabel: LabelModel + public var rightLabel: LabelModel + + public init(leftLabel:LabelModel, centerLabel:LabelModel, rightLabel:LabelModel) { + self.leftLabel = leftLabel + self.centerLabel = centerLabel + self.rightLabel = rightLabel + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case leftLabel + case centerLabel + case rightLabel + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + leftLabel = try typeContainer.decode(LabelModel.self, forKey: .leftLabel) + centerLabel = try typeContainer.decode(LabelModel.self, forKey: .centerLabel) + rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(leftLabel, forKey: .leftLabel) + try container.encode(centerLabel, forKey: .centerLabel) + try container.encode(rightLabel, forKey: .rightLabel) + } +} + diff --git a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartView.swift b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartView.swift index 1c0ea963..4a122543 100644 --- a/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartView.swift +++ b/MVMCoreUI/Atomic/Molecules/Doughnut/DoughnutChartView.swift @@ -86,7 +86,7 @@ import Foundation for item in model.sections { stackItems.append(MoleculeStackItemModel(with: item)) } - let stack = MoleculeStackModel(molecules: stackItems) + let stack = StackModel(molecules: stackItems) stack.verticalAlignment = .fill colorLablesStack.set(with: stack, delegateObject, additionalData) } diff --git a/MVMCoreUI/Atomic/Molecules/FooterModel.swift b/MVMCoreUI/Atomic/Molecules/FooterModel.swift index d61d1194..12e0eca8 100644 --- a/MVMCoreUI/Atomic/Molecules/FooterModel.swift +++ b/MVMCoreUI/Atomic/Molecules/FooterModel.swift @@ -9,47 +9,24 @@ import Foundation -@objcMembers public class FooterModel: MoleculeContainerModel, MoleculeModelProtocol { - public static var identifier: String = "footer" - public var backgroundColor: Color? - - private enum CodingKeys: String, CodingKey { - case moleculeName - case backgroundColor +@objcMembers public class FooterModel: MoleculeContainerModel { + public override class var identifier: String { + return "footer" } /// Defaults to set - func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefaultVerticalSpacing + if topPadding == nil { + topPadding = PaddingDefaultVerticalSpacing } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefaultVerticalSpacing + if bottomPadding == nil { + bottomPadding = PaddingDefaultVerticalSpacing } } - - public override init(with moleculeModel: MoleculeModelProtocol) { - super.init(with: moleculeModel) - setDefaults() - } - - required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - try super.init(from: decoder) - setDefaults() - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - } } diff --git a/MVMCoreUI/Atomic/Molecules/HeaderModel.swift b/MVMCoreUI/Atomic/Molecules/HeaderModel.swift index 6314947c..09fe077f 100644 --- a/MVMCoreUI/Atomic/Molecules/HeaderModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HeaderModel.swift @@ -18,18 +18,18 @@ import Foundation } /// Defaults to set - public func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefaultVerticalSpacing + if topPadding == nil { + topPadding = PaddingDefaultVerticalSpacing } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefaultVerticalSpacing + if bottomPadding == nil { + bottomPadding = PaddingDefaultVerticalSpacing } if line == nil { line = LineModel(type: .heavy) @@ -38,7 +38,6 @@ import Foundation public override init() { super.init() - setDefaults() } required public init(from decoder: Decoder) throws { @@ -46,7 +45,6 @@ import Foundation let typeContainer = try decoder.container(keyedBy: CodingKeys.self) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - setDefaults() } public override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift new file mode 100644 index 00000000..dc8b9cae --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift @@ -0,0 +1,320 @@ +// +// Tabs.swift +// MVMCoreUI +// +// Created by Ryan on 2/7/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objc public protocol TabsDelegate { + func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool + func didSelectItem(_ indexPath: IndexPath, tabs: Tabs) +} + +@objcMembers open class Tabs: View, MVMCoreUIViewConstrainingProtocol { + + public var tabsModel: TabsModel? { + get { return model as? TabsModel } + } + + var delegateObject: MVMCoreUIDelegateObject? + var additionalData: [AnyHashable: Any]? + + let layout = UICollectionViewFlowLayout() + public var collectionView: UICollectionView? + + let bottomScrollView = UIScrollView(frame: .zero) + let bottomContentView = View() + let bottomLine = View() + var bottomLineLeftConstraint: NSLayoutConstraint? + var bottomLineWidthConstraint: NSLayoutConstraint? + + private var widthLabel = Label() + + //delegate + weak public var delegate: TabsDelegate? + + //control var + public var heightConstraint: NSLayoutConstraint? + public var selectedIndex: Int = 0 + public var paddingBeforeFirstTab: Bool = true + + //constant + let TabCellId = "TabCell" + public let sectionPadding: CGFloat = 20.0 + public let cellSpacing: CGFloat = 34.0 + public let cellHeight: CGFloat = 34.0 + public let bottomLineHeight: CGFloat = 4.0 + public let bottomLineWidth: CGFloat = 32.0 + public let tabsHeight: CGFloat = 38.0 + public let bottomLineMovingTime: TimeInterval = 0.2 + + //------------------------------------------------- + // MARK:- Layout Views + //------------------------------------------------- + + open override func reset() { + super.reset() + heightConstraint?.constant = tabsHeight + selectedIndex = 0 + paddingBeforeFirstTab = true + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + } + + open override func setupView() { + super.setupView() + backgroundColor = .white + setupCollectionView() + setupBottomLine() + setupConstraints() + } + + func setupCollectionView () { + layout.scrollDirection = .horizontal + layout.minimumLineSpacing = cellSpacing + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.register(TabItemCell.self, forCellWithReuseIdentifier: TabCellId) + collectionView.backgroundColor = .clear + collectionView.showsVerticalScrollIndicator = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.dataSource = self + collectionView.delegate = self + addSubview(collectionView) + self.collectionView = collectionView + } + + func setupBottomLine() { + bottomScrollView.translatesAutoresizingMaskIntoConstraints = false + bottomScrollView.delegate = self + addSubview(bottomScrollView) + bottomScrollView.addSubview(bottomContentView) + bottomLine.backgroundColor = .mvmRed + bottomContentView.addSubview(bottomLine) + bringSubviewToFront(bottomScrollView) + } + + func setupConstraints() { + //collection view + NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) + + //bottom lines + NSLayoutConstraint.constraintPinSubview(bottomScrollView, pinTop: false, pinBottom: true, pinLeft: true, pinRight: true) + bottomScrollView.heightAnchor.constraint(equalToConstant: bottomLineHeight).isActive = true + NSLayoutConstraint.constraintPinSubview(bottomLine, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false) + bottomLine.heightAnchor.constraint(equalToConstant: bottomLineHeight).isActive = true + bottomLineLeftConstraint = bottomLine.leftAnchor.constraint(equalTo: bottomContentView.leftAnchor) + bottomLineLeftConstraint?.isActive = true + bottomLineWidthConstraint = bottomLine.widthAnchor.constraint(equalToConstant: bottomLineWidth) + bottomLineWidthConstraint?.isActive = true + NSLayoutConstraint.constraintPinSubview(toSuperview: bottomContentView) + + //height + heightConstraint = heightAnchor.constraint(equalToConstant: tabsHeight) + heightConstraint?.isActive = true + } + + //------------------------------------------------- + // MARK:- Control Methods + //------------------------------------------------- + + public func pinHeight(_ height: CGFloat) { + heightConstraint?.constant = height + setNeedsLayout() + layoutIfNeeded() + } + + public func selectIndex(_ index: Int, animated: Bool) { + guard let _ = collectionView, tabsModel?.tabs.count ?? 0 > 0 else { + selectedIndex = index + return + } + MVMCoreDispatchUtility.performBlock(onMainThread: { + let currentIndex = self.selectedIndex + self.selectedIndex = index + self.deselect(indexPath: IndexPath(row: currentIndex, section: 0)) + self.selectItem(atIndexPath: IndexPath(row: index, section: 0), animated: animated) + }) + } + + public func reloadData() { + collectionView?.reloadData() + } + + //------------------------------------------------- + // MARK:- Molecule Setup + //------------------------------------------------- + + override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject + self.additionalData = additionalData + self.selectedIndex = tabsModel?.selectedIndex ?? 0 + self.bottomLine.backgroundColor = tabsModel?.selectedColor.uiColor + reloadData() + } +} + +//------------------------------------------------- +// MARK:- Collection View Methods +//------------------------------------------------- + +extension Tabs: UICollectionViewDataSource { + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return tabsModel?.tabs.count ?? 0 + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let labelModel = tabsModel?.tabs[indexPath.row].label, let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TabCellId, for: indexPath) as? TabItemCell else { + return UICollectionViewCell() + } + cell.updateCell(labelModel: labelModel, indexPath: indexPath, delegateObject: delegateObject, additionalData: additionalData, selected: indexPath.row == selectedIndex, tabsModel: tabsModel) + return cell + } +} + +extension Tabs: UICollectionViewDelegateFlowLayout { + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + guard let labelModel = tabsModel?.tabs[indexPath.row].label else { + return .zero + } + return CGSize(width: getLabelWidth(labelModel).width, height: cellHeight) + } + + //pre calculate the width of the collection cell + //when user select tabs, it will reload related collectionview, if we use autosize, it would relayout the width, need to keep the cell width constant. + func getLabelWidth(_ labelModel: LabelModel?) -> CGSize { + guard let labelModel = labelModel else { return .zero} + widthLabel.set(with: labelModel, nil, nil) + let cgSize = widthLabel.intrinsicContentSize + widthLabel.reset() + return cgSize + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + if !paddingBeforeFirstTab && section == 0 { + return .zero + } else { + return UIEdgeInsets(top: 0, left: sectionPadding, bottom: 0, right: 0) + } + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return sectionPadding + } + + public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + return delegate?.shouldSelectItem(indexPath, tabs: self) ?? true + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + selectIndex(indexPath.row, animated: true) + } + + public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let tabCell = cell as? TabItemCell else { return } + if indexPath.row == selectedIndex { + moveBottomLine(toIndex: indexPath, animated: false, cell: tabCell) + } + } + + func deselect(indexPath:IndexPath) { + collectionView?.deselectItem(at: indexPath, animated: false) + collectionView?.reloadItems(at: [indexPath]) + } + + func selectItem(atIndexPath indexPath: IndexPath, animated: Bool) { + + guard let collect = collectionView, tabsModel?.tabs.count ?? 0 > 0 else { return } + + collect.selectItem(at: indexPath, animated: animated, scrollPosition: .centeredHorizontally) + guard let tabCell = collect.cellForItem(at: indexPath) as? TabItemCell, let tabsModel = self.tabsModel else { return } + self.moveBottomLine(toIndex: indexPath, animated: animated, cell: tabCell) + tabCell.label.textColor = tabsModel.selectedColor.uiColor + tabCell.updateAccessibility(indexPath: indexPath, selected: true, tabsModel: tabsModel) + tabCell.setNeedsDisplay() + tabCell.setNeedsLayout() + tabCell.layoutIfNeeded() + self.delegate?.didSelectItem(indexPath, tabs: self) + } +} + + +extension Tabs: UIScrollViewDelegate { + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + /*bottomScrollview is subview of self, it's not belongs to collectionview. + When collectionview is scrolling, bottomScrollView will stay without moving + Adding collectionview's offset to bottomScrollView, will make the bottomScrollview looks like scrolling with the selected tab item. + */ + guard let offsetX = collectionView?.contentOffset.x else { return } + bottomScrollView.setContentOffset(CGPoint(x: offsetX, y: bottomScrollView.contentOffset.y), animated: false) + } +} + + +//------------------------------------------------- +// MARK:- Bottom Line Methods +//------------------------------------------------- +extension Tabs { + func moveBottomLine(toIndex indexPath: IndexPath, animated: Bool, cell: TabItemCell) { + guard let collect = self.collectionView else {return} + + let size = collectionView(collect, layout: layout, sizeForItemAt: indexPath) + let barWidth = max(size.width, bottomLineWidth) + let animationBlock = { + [weak self] in + let x = cell.frame.origin.x + self?.bottomLineWidthConstraint?.constant = barWidth + self?.bottomLineLeftConstraint?.constant = x + (size.width - barWidth) / 2.0 + self?.bottomContentView.layoutIfNeeded() + } + if animated { + UIView.animate(withDuration: bottomLineMovingTime, animations: animationBlock) + } else { + animationBlock() + } + } +} + +@objcMembers public class TabItemCell: CollectionViewCell { + public let label = Label() + public var labelModel: LabelModel? + + public override func setupView() { + super.setupView() + contentView.addSubview(label) + NSLayoutConstraint.constraintPinSubview(label, pinTop: false, pinBottom: false, pinLeft: true, pinRight: true) + label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true + label.baselineAdjustment = .alignCenters + } + + public func updateCell(labelModel: LabelModel, indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, selected: Bool, tabsModel: TabsModel?) { + label.reset() + label.set(with: labelModel, delegateObject, additionalData) + self.labelModel = labelModel + if selected, let selectedColor = tabsModel?.selectedColor { + label.textColor = selectedColor.uiColor + } + updateAccessibility(indexPath: indexPath, selected: selected, tabsModel: tabsModel) + } + + public func updateAccessibility(indexPath: IndexPath, selected: Bool, tabsModel: TabsModel?) { + //Accessibility + isAccessibilityElement = false + contentView.isAccessibilityElement = true + let accKey = selected ? "toptabbar_tab_selected" : "AccTab" + let accLabel = "\(label.text ?? "") \(MVMCoreUIUtility.hardcodedString(withKey: accKey) ?? "")" + let accOrder = String(format: MVMCoreUIUtility.hardcodedString(withKey: "AccTabIndex") ?? "", indexPath.row + 1, tabsModel?.tabs.count ?? 0) + contentView.accessibilityLabel = "\(accLabel) \(accOrder)" + contentView.accessibilityHint = selected ? nil : MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint") + } +} + + diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift index 8b69c590..d663a703 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift @@ -11,7 +11,7 @@ import UIKit public class TabsModel: MoleculeModelProtocol { public static var identifier: String = "tabs" public var backgroundColor: Color? - public var tabs: [LabelModel] + public var tabs: [TabItemModel] public var selectedColor = Color(uiColor: .mfTomatoRed()) // Must be capped to 0...(tabs.count - 1) @@ -25,13 +25,13 @@ public class TabsModel: MoleculeModelProtocol { case moleculeName } - public init(with tabs: [LabelModel]) { + public init(with tabs: [TabItemModel]) { self.tabs = tabs } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - tabs = try typeContainer.decode([LabelModel].self, forKey: .tabs) + tabs = try typeContainer.decode([TabItemModel].self, forKey: .tabs) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedColor) { selectedColor = color @@ -50,3 +50,33 @@ public class TabsModel: MoleculeModelProtocol { try container.encode(selectedIndex, forKey: .selectedIndex) } } + + + +public class TabItemModel: Codable { + var label: LabelModel + var action: ActionModelProtocol? + + init(label: LabelModel) { + self.label = label + } + + private enum CodingKeys: String, CodingKey { + case label + case action + } + + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + label = try typeContainer.decode(LabelModel.self, forKey: .label) + action = try typeContainer.decodeModelIfPresent(codingKey: .action) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeModel(label, forKey: .label) + try container.encodeModelIfPresent(action, forKey: .action) + } + +} diff --git a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift index 9a00a94d..8fdb4d77 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift @@ -38,7 +38,7 @@ import Foundation //-------------------------------------------------- /// Defaults to set - open func setDefaults() { + open override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } @@ -54,9 +54,12 @@ import Foundation // MARK: - Initializer //-------------------------------------------------- + public override init(horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil, useHorizontalMargins: Bool? = nil, leftPadding: CGFloat? = nil, rightPadding: CGFloat? = nil, useVerticalMargins: Bool? = nil, topPadding: CGFloat? = nil, bottomPadding: CGFloat? = nil) { + super.init(horizontalAlignment: horizontalAlignment, verticalAlignment: verticalAlignment, useHorizontalMargins: useHorizontalMargins, leftPadding: leftPadding, rightPadding: rightPadding, useVerticalMargins: useVerticalMargins, topPadding: topPadding, bottomPadding: bottomPadding) + } + public override init() { super.init() - setDefaults() } //-------------------------------------------------- @@ -71,7 +74,6 @@ import Foundation line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) style = try typeContainer.decodeIfPresent(String.self, forKey: .style) try super.init(from: decoder) - setDefaults() } open override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift index ed9467fd..4f203813 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -9,49 +9,36 @@ import Foundation /// A model for a collection item that is a container for any molecule. -@objcMembers public class MoleculeCollectionItemModel: MoleculeContainerModel, CollectionItemModelProtocol, MoleculeModelProtocol { - open class var identifier: String { +@objcMembers public class MoleculeCollectionItemModel: MoleculeContainerModel, CollectionItemModelProtocol { + open override class var identifier: String { return "collectionItem" } - public var backgroundColor: Color? /// Defaults to set - public func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefault + if topPadding == nil { + topPadding = PaddingDefault } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefault + if bottomPadding == nil { + bottomPadding = PaddingDefault } } - private enum CodingKeys: String, CodingKey { - case moleculeName - case backgroundColor - } - public override init(with moleculeModel: MoleculeModelProtocol) { super.init(with: moleculeModel) - setDefaults() } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) try super.init(from: decoder) - setDefaults() } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift index 42af1a53..10b51bff 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift @@ -54,6 +54,6 @@ open class MoleculeCollectionViewCell: CollectionViewCell { let height = classType.estimatedHeight(with: model.molecule, delegateObject) else { return 100 } - return height + (model.topMarginPadding ?? 0) + (model.bottomMarginPadding ?? 0) + return height + (model.topPadding ?? 0) + (model.bottomPadding ?? 0) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift index dc01fd11..1f6e4d88 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift @@ -8,15 +8,15 @@ import Foundation -@objcMembers public class MoleculeStackItemModel: MoleculeContainerModel, MoleculeModelProtocol, StackItemModelProtocol { - public static var identifier: String = "stackItem" - public var backgroundColor: Color? +@objcMembers public class MoleculeStackItemModel: MoleculeContainerModel, StackItemModelProtocol { + public override class var identifier: String { + return "stackItem" + } public var spacing: CGFloat? public var percent: Int? public var gone: Bool = false private enum CodingKeys: String, CodingKey { - case moleculeName case spacing case percent case gone @@ -39,7 +39,6 @@ import Foundation public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(spacing, forKey: .spacing) try container.encodeIfPresent(percent, forKey: .percent) try container.encode(gone, forKey: .gone) diff --git a/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift index 436ed9cd..69fe570d 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift @@ -24,16 +24,17 @@ import Foundation // MARK: - Initializer //-------------------------------------------------- - public convenience init(spacing: CGFloat? = nil, percent: Int? = nil, horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil, gone: Bool? = nil) { - self.init() - - self.horizontalAlignment = horizontalAlignment - self.verticalAlignment = verticalAlignment + public init(spacing: CGFloat? = nil, percent: Int? = nil, horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil, gone: Bool? = nil) { self.spacing = spacing self.percent = percent if let gone = gone { self.gone = gone } + super.init(horizontalAlignment: horizontalAlignment, verticalAlignment: verticalAlignment) + } + + required public init(from decoder: Decoder) throws { + fatalError("init(from:) has not been implemented") } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/StackItemModelProtocol.swift b/MVMCoreUI/Atomic/Molecules/Items/StackItemModelProtocol.swift index 170fce69..2223ed39 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/StackItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/StackItemModelProtocol.swift @@ -8,7 +8,7 @@ import Foundation -public protocol StackItemModelProtocol: MoleculeModelProtocol { +public protocol StackItemModelProtocol { var spacing: CGFloat? { get set } var percent: Int? { get set } var gone: Bool { get set } diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift index e49b4adb..c83b8933 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift @@ -24,8 +24,8 @@ public class TabsListItemModel: ListItemModel, MoleculeModelProtocol { hideArrow = true action = nil style = nil - topMarginPadding = 8 - bottomMarginPadding = 0 + topPadding = 8 + bottomPadding = 0 } public init(with tabs: TabsModel, molecules: [[ListItemModelProtocol & MoleculeModelProtocol]]) { diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift index 16794eb1..f9ed4243 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift @@ -12,7 +12,7 @@ import UIKit var tabsListItemModel: TabsListItemModel? { return listItemModel as? TabsListItemModel } - let tabs = TopTabbar(frame: .zero) + let tabs = Tabs(frame: .zero) var delegateObject: MVMCoreUIDelegateObject? var previousTabIndex = 0 @@ -22,7 +22,6 @@ import UIKit tabs.paddingBeforeFirstTab = false tabs.translatesAutoresizingMaskIntoConstraints = false tabs.delegate = self - tabs.datasource = self contentView.addSubview(tabs) NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: tabs, useMargins: true).values)) @@ -39,7 +38,9 @@ import UIKit public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) self.delegateObject = delegateObject - tabs.reloadData() + if let tabsModel = tabsListItemModel?.tabs { + tabs.set(with: tabsModel, delegateObject, additionalData) + } } public override func reset() { @@ -53,33 +54,22 @@ import UIKit } } -extension TabsTableViewCell: TopTabbarDelegate { - public func shouldSelectItem(at index: Int, topTabbar: TopTabbar) -> Bool { +extension TabsTableViewCell: TabsDelegate { + public func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool { if let model = tabsListItemModel { - let molecules = model.molecules[topTabbar.selectedIndex] - delegateObject?.moleculeDelegate?.removeMolecules(molecules, animation: index < tabs.selectedIndex ? .right : .left) + let molecules = model.molecules[tabs.selectedIndex] + delegateObject?.moleculeDelegate?.removeMolecules(molecules, animation: indexPath.row < tabs.selectedIndex ? .right : .left) } previousTabIndex = tabs.selectedIndex return true } - public func topTabbar(_ topTabbar: TopTabbar, didSelectItemAt index: Int) { - guard let model = tabsListItemModel, - let indexPath = delegateObject?.moleculeDelegate?.getIndexPath(for: model) else { return } - let molecules = model.molecules[index] - delegateObject?.moleculeDelegate?.addMolecules(molecules, indexPath: indexPath, animation: index < previousTabIndex ? .left : .right) - } -} - -extension TabsTableViewCell: TopTabbarDataSource { - public func number(ofTopTabbarItems topTabbar: TopTabbar) -> Int { - return tabsListItemModel?.tabs.tabs.count ?? 0 - } - - public func topTabbar(_ topTabbar: TopTabbar, titleForItemAt index: Int) -> String? { - guard let title = tabsListItemModel?.tabs.tabs[index].text else { - return "Select" + public func didSelectItem(_ indexPath: IndexPath, tabs: Tabs) { + let index = indexPath.row + if let model = tabsListItemModel, index < model.molecules.count { + let molecules = model.molecules[index] + delegateObject?.moleculeDelegate?.addMolecules(molecules, indexPath: indexPath, animation: index < previousTabIndex ? .left : .right) } - return title } } + diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabels.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabels.swift index c0018e9b..7eb3282b 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabels.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/CornerLabels.swift @@ -164,10 +164,12 @@ import UIKit public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? CornerLabelsModel else { return } - if middleView != nil { - (middleView as? MoleculeViewProtocol)?.set(with: model, delegateObject, additionalData) - } else if let moleculeModel = model.molecule, let molecule = MoleculeObjectMapping.shared()?.createMolecule(moleculeModel, delegateObject: delegateObject, additionalData: additionalData) { - addMiddleView(molecule) + if let moleculeModel = model.molecule { + if middleView != nil { + (middleView as? MoleculeViewProtocol)?.set(with: moleculeModel, delegateObject, additionalData) + } else if let molecule = MoleculeObjectMapping.shared()?.createMolecule(moleculeModel, delegateObject: delegateObject, additionalData: additionalData) { + addMiddleView(molecule) + } } topLeftLabel.setOptional(with: model.topLeftLabel, delegateObject, additionalData) diff --git a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggle.swift b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggle.swift index 9ce24d3b..3ff3a41f 100644 --- a/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggle.swift +++ b/MVMCoreUI/Atomic/Molecules/LeftRightViews/ToggleMolecules/LabelToggle.swift @@ -8,11 +8,19 @@ import UIKit + @objcMembers open class LabelToggle: View { - public let label = Label.commonLabelB1(true) + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public let label = Label.createLabelBoldBodySmall(true) public let toggle = Toggle() + //-------------------------------------------------- // MARK: - MVMCoreViewProtocol + //-------------------------------------------------- + open override func updateView(_ size: CGFloat) { super.updateView(size) label.updateView(size) @@ -21,28 +29,26 @@ import UIKit open override func setupView() { super.setupView() - guard toggle.superview == nil else { - return - } addSubview(label) addSubview(toggle) - label.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + label.setContentHuggingPriority(.required, for: .vertical) NSLayoutConstraint.pinViews(leftView: label, rightView: toggle, alignTop: false) } - // MARK:- MoleculeViewProtocol open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { guard let model = model as? LabelToggleModel, let toggleHeight = Toggle.estimatedHeight(with: model.toggle, delegateObject), - let labelHeight = Label.estimatedHeight(with: model.label, delegateObject) else { return nil } + let labelHeight = Label.estimatedHeight(with: model.label, delegateObject) + else { return nil } + return max(toggleHeight, labelHeight) } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let labelToggleModel = model as? LabelToggleModel else { - return - } + + guard let labelToggleModel = model as? LabelToggleModel else { return } + label.set(with: labelToggleModel.label, delegateObject, additionalData) toggle.set(with: labelToggleModel.toggle, delegateObject, additionalData) } @@ -52,6 +58,6 @@ import UIKit super.reset() label.reset() toggle.reset() - label.styleB1(true) + label.styleBoldBodySmall(true) } } diff --git a/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift index d7df7cc9..f15a847a 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift @@ -10,17 +10,16 @@ import Foundation public protocol NavigationItemModelProtocol { var title: String? { get set } - var titleView: MoleculeModelProtocol? { get set } var hidden: Bool { get set } var backgroundColor: Color? { get set } - var transparent: Bool { get set } + var translucent: Bool { get set } var tintColor: Color { get set } var line: LineModel? { get set } - var systemBackButton: Bool { get set } - var showLeftPanelButton: Bool? { get set } - var showRightPanelButton: Bool? { get set } - var additionalLeftItems: [NavigationItemButtonModel]? { get set } - var additionalRightItems: [NavigationItemButtonModel]? { get set } + var showLeftPanelButton: Bool { get set } + var showRightPanelButton: Bool { get set } + var backButton: NavigationItemButtonModel? { get set } + var additionalLeftButtons: [NavigationItemButtonModel]? { get set } + var additionalRightButtons: [NavigationItemButtonModel]? { get set } } public class NavigationItemButtonModel: Codable { @@ -52,74 +51,73 @@ public class NavigationItemButtonModel: Codable { public class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtocol { public class var identifier: String { - return "navigationItem" + return "navigationBar" } public var title: String? - public var titleView: MoleculeModelProtocol? public var hidden: Bool public var backgroundColor: Color? - public var transparent: Bool + public var translucent: Bool public var tintColor: Color public var line: LineModel? - public var systemBackButton = false - public var showLeftPanelButton: Bool? - public var showRightPanelButton: Bool? - public var additionalLeftItems: [NavigationItemButtonModel]? - public var additionalRightItems: [NavigationItemButtonModel]? + public var showLeftPanelButton: Bool + public var showRightPanelButton: Bool + public var backButton: NavigationItemButtonModel? + public var additionalLeftButtons: [NavigationItemButtonModel]? + public var additionalRightButtons: [NavigationItemButtonModel]? - init() { + public init() { hidden = false - transparent = false + translucent = false backgroundColor = Color(uiColor: .white) tintColor = Color(uiColor: .black) line = LineModel(type: .standard) + showLeftPanelButton = true + showRightPanelButton = true + backButton = NavigationItemButtonModel(with: "back", action: ActionBackModel()) } private enum CodingKeys: String, CodingKey { case title - case titleView case hidden case backgroundColor - case transparent + case translucent case tintColor case line - case systemBackButton + case backButton case showLeftPanelButton case showRightPanelButton - case additionalLeftItems - case additionalRightItems + case additionalLeftButtons + case additionalRightButtons } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) title = try typeContainer.decodeIfPresent(String.self, forKey: .title) - titleView = try typeContainer.decodeModelIfPresent(codingKey: .titleView) hidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidden) ?? false backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) ?? Color(uiColor: .white) - transparent = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparent) ?? false + translucent = try typeContainer.decodeIfPresent(Bool.self, forKey: .translucent) ?? false tintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .tintColor) ?? Color(uiColor: .black) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) - systemBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .systemBackButton) ?? false - showLeftPanelButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .showLeftPanelButton) - showRightPanelButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .showRightPanelButton) - additionalLeftItems = try typeContainer.decodeIfPresent([NavigationItemButtonModel].self, forKey: .additionalLeftItems) - additionalRightItems = try typeContainer.decodeIfPresent([NavigationItemButtonModel].self, forKey: .additionalRightItems) + backButton = try typeContainer.decodeIfPresent(NavigationItemButtonModel.self, forKey: .backButton) ?? NavigationItemButtonModel(with: "back", action: ActionBackModel()) + showLeftPanelButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .showLeftPanelButton) ?? true + showRightPanelButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .showRightPanelButton) ?? true + additionalLeftButtons = try typeContainer.decodeIfPresent([NavigationItemButtonModel].self, forKey: .additionalLeftButtons) + additionalRightButtons = try typeContainer.decodeIfPresent([NavigationItemButtonModel].self, forKey: .additionalRightButtons) } open func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(title, forKey: .title) - try container.encodeModelIfPresent(titleView, forKey: .titleView) try container.encode(hidden, forKey: .hidden) try container.encode(backgroundColor, forKey: .backgroundColor) - try container.encode(transparent, forKey: .transparent) + try container.encode(translucent, forKey: .translucent) try container.encode(tintColor, forKey: .tintColor) try container.encodeIfPresent(line, forKey: .line) - try container.encode(systemBackButton, forKey: .systemBackButton) + try container.encodeIfPresent(backButton, forKey: .backButton) try container.encode(showLeftPanelButton, forKey: .showLeftPanelButton) try container.encode(showRightPanelButton, forKey: .showRightPanelButton) - try container.encodeIfPresent(additionalLeftItems, forKey: .additionalLeftItems) - try container.encodeIfPresent(additionalRightItems, forKey: .additionalRightItems) + try container.encodeIfPresent(additionalLeftButtons, forKey: .additionalLeftButtons) + try container.encodeIfPresent(additionalRightButtons, forKey: .additionalRightButtons) } } diff --git a/MVMCoreUI/Atomic/Molecules/ScrollerModel.swift b/MVMCoreUI/Atomic/Molecules/ScrollerModel.swift index 74256e99..697556b5 100644 --- a/MVMCoreUI/Atomic/Molecules/ScrollerModel.swift +++ b/MVMCoreUI/Atomic/Molecules/ScrollerModel.swift @@ -8,8 +8,9 @@ import UIKit -public class ScrollerModel: MoleculeContainerModel, MoleculeModelProtocol { - public static var identifier: String = "scroller" +public class ScrollerModel: MoleculeContainerModel { + public override class var identifier: String { + return "scroller" + } public var moleculeName: String = ScrollerModel.identifier - public var backgroundColor: Color? } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift index 650e4875..f6fd4fcf 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift @@ -20,22 +20,21 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc self.headlineBody = headlineBody self.image = image super.init() - setDefaults() } /// Defaults to set - func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefault + if topPadding == nil { + topPadding = PaddingDefault } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefault + if bottomPadding == nil { + bottomPadding = PaddingDefault } if image.height == nil { image.height = BGImageHeadlineBodyButton.heightConstant @@ -59,7 +58,6 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc image = try typeContainer.decode(ImageViewModel.self, forKey: .image) button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button) try super.init(from: decoder) - setDefaults() } public override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift index 82f83530..dd04140b 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -33,6 +33,7 @@ import UIKit stack.stackModel?.spacing = 0 addSubview(stack) NSLayoutConstraint.constraintPinSubview(toSuperview: stack) + stack.restack() } open override func updateView(_ size: CGFloat) { @@ -58,18 +59,11 @@ import UIKit open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) - - eyebrow.setOptional(with: castModel?.eyebrow, delegateObject, additionalData) - headline.setOptional(with: castModel?.headline, delegateObject, additionalData) - body.setOptional(with: castModel?.body, delegateObject, additionalData) - link.setOptional(with: castModel?.link, delegateObject, additionalData) - - // Hide labels if neeeded. - stack.stackModel?.molecules[0].gone = !eyebrow.hasText - stack.stackModel?.molecules[1].gone = !headline.hasText - stack.stackModel?.molecules[2].gone = !body.hasText - stack.stackModel?.molecules[3].gone = (link.titleLabel?.text?.count ?? 0) == 0 - stack.restack() + stack.updateContainedMolecules(with: [castModel?.eyebrow, + castModel?.headline, + castModel?.body, + castModel?.link], + delegateObject, additionalData) } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift index f30b89ff..9fdd8612 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift @@ -19,22 +19,21 @@ public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProto self.headlineBody = headlineBody self.image = image super.init() - setDefaults() } /// Defaults to set - func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefault + if topPadding == nil { + topPadding = PaddingDefault } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefault + if bottomPadding == nil { + bottomPadding = PaddingDefault } if image.height == nil { image.height = HeadLineBodyCaretLinkImage.heightConstant @@ -56,7 +55,6 @@ public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProto image = try typeContainer.decode(ImageViewModel.self, forKey: .image) caretLink = try typeContainer.decodeIfPresent(CaretLinkModel.self, forKey: .caretLink) try super.init(from: decoder) - setDefaults() } public override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift index 8a7b6125..eeacb98d 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers public class NumberedListModel: MoleculeStackModel { +@objcMembers public class NumberedListModel: StackModel { public override class var identifier: String { return "numberedList" } @@ -28,8 +28,7 @@ import Foundation for (index, molecule) in list.enumerated() { models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: "\(index+1).", molecule: molecule))) } - super.init(molecules: models) - spacing = 0 + super.init(molecules: models, spacing: 0) } public override func encode(to encoder: Encoder) throws { @@ -39,7 +38,7 @@ import Foundation var models: [MoleculeModelProtocol] = [] for molecule in molecules { - models.append(molecule.molecule) + models.append(((molecule as! MoleculeStackItemModel).molecule as! StringAndMoleculeModel).molecule) } try container.encodeModels(models, forKey: .list) } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift index 4ff045ce..c887e838 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift @@ -11,8 +11,9 @@ 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 open func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { - guard let model = stackModel else { return } - for stackItemModel in model.molecules { + guard let model = model as? StackModelProtocol, + let molcules = model.molecules as? [MoleculeStackItemModel] else { return } + for stackItemModel in molcules { guard let stringAndMoleculeModel = stackItemModel.molecule as? StringAndMoleculeModel, let molecule = MoleculeObjectMapping.shared()?.createMolecule(stringAndMoleculeModel.molecule, delegateObject: delegateObject , additionalData: additionalData) else { diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift index 6db91703..a7c9e20d 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift @@ -10,7 +10,6 @@ import Foundation open class StringAndMoleculeView: View { var label = Label.commonLabelB2(true) - var string: String var molecule: MoleculeViewProtocol var leftWidthConstraint: NSLayoutConstraint? @@ -28,7 +27,7 @@ open class StringAndMoleculeView: View { // MARK: - Inits public init(string: String, molecule: MoleculeViewProtocol) { - self.string = string + self.label.text = string self.molecule = molecule super.init(frame: .zero) } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift index 84d23a11..3866e918 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers public class UnOrderedListModel: MoleculeStackModel { +@objcMembers public class UnOrderedListModel: StackModel { public override class var identifier: String { return "unOrderedList" } @@ -33,8 +33,7 @@ import Foundation for molecule in list { models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: bulletChar, molecule: molecule))) } - super.init(molecules: models) - spacing = 0 + super.init(molecules: models, spacing: 0) } public override func encode(to encoder: Encoder) throws { @@ -45,7 +44,7 @@ import Foundation var models: [MoleculeModelProtocol] = [] for molecule in molecules { - models.append(molecule.molecule) + models.append(((molecule as! MoleculeStackItemModel).molecule as! StringAndMoleculeModel).molecule) } try container.encodeModels(models, forKey: .list) } diff --git a/MVMCoreUI/Atomic/Organisms/MoleculeStackModel.swift b/MVMCoreUI/Atomic/Organisms/MoleculeStackModel.swift deleted file mode 100644 index 134cc701..00000000 --- a/MVMCoreUI/Atomic/Organisms/MoleculeStackModel.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// MoleculeStack.swift -// MVMCoreUI -// -// 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, MoleculeModelProtocol, StackModelProtocol { - public class var identifier: String { - return "stack" - } - public var backgroundColor: Color? - public var molecules: [MoleculeStackItemModel] - public var axis: NSLayoutConstraint.Axis = .vertical - public var spacing: CGFloat = 16.0 - public var useStackSpacingBeforeFirstItem = false - - public init(molecules: [MoleculeStackItemModel]) { - self.molecules = molecules - super.init() - } - - private enum CodingKeys: String, CodingKey { - case moleculeName - 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([MoleculeStackItemModel].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 - } - 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.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(molecules, forKey: .molecules) - try container.encodeIfPresent(axis.rawValueString, forKey: .axis) - try container.encodeIfPresent(spacing, forKey: .spacing) - } -} diff --git a/MVMCoreUI/Atomic/Organisms/MoleculeStackView.swift b/MVMCoreUI/Atomic/Organisms/MoleculeStackView.swift index 0f13cde5..e0517d45 100644 --- a/MVMCoreUI/Atomic/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Atomic/Organisms/MoleculeStackView.swift @@ -5,13 +5,12 @@ // Created by Scott Pfeil on 2/11/19. // Copyright © 2019 Verizon Wireless. All rights reserved. // +// This is a dynamic stack view that creates molecules if needed on set. import UIKit -open class MoleculeStackView: Stack { - open override var stackModel: MoleculeStackModel? { - get { return model as? MoleculeStackModel } - } +open class MoleculeStackView: Stack { + var previousModel: MoleculeModelProtocol? /// Convenience function, adds a molecule to a MoleculeStackItem to the MoleculeStack func setup(with views: [View], lastItem: Bool) { @@ -26,14 +25,16 @@ open class MoleculeStackView: Stack { if let stackModel = stackModel { stackModel.molecules = models } else { - model = MoleculeStackModel(molecules: models) + model = StackModel(molecules: models) } restack() } + + // MARK: - Adding to stack - /// Creates all of the stackItems for the stackItemModels - override open func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + /// Can be subclassed to create views when we get stack item models and have no views yet + open func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { guard let stackItemModels = stackModel?.molecules else { return } for model in stackItemModels { if let stackItem = MoleculeObjectMapping.shared()?.createMolecule(model, delegateObject: delegateObject, additionalData: additionalData) as? MoleculeStackItem { @@ -41,4 +42,20 @@ open class MoleculeStackView: Stack { } } } + + open override func setStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + // If the items in the stack are different, clear them, create new ones. + if (previousModel == nil) || Self.nameForReuse(with: previousModel!, delegateObject) != Self.nameForReuse(with: model, delegateObject) { + removeAllItemViews() + stackItems = [] + createStackItemsFromModel(model, delegateObject, additionalData) + } else { + super.setStackItemsFromModel(model, delegateObject, additionalData) + } + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + previousModel = self.model + super.set(with: model, delegateObject, additionalData) + } } diff --git a/MVMCoreUI/Atomic/Organisms/Stack.swift b/MVMCoreUI/Atomic/Organisms/Stack.swift index e957a2a6..ad961586 100644 --- a/MVMCoreUI/Atomic/Organisms/Stack.swift +++ b/MVMCoreUI/Atomic/Organisms/Stack.swift @@ -62,6 +62,32 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } } + /// A convenience function for when the stackItems are containers and we want to update them based on the contained molecules models. If model is nil, stackItem is set to gone. Restacks if necessary. + open func updateContainedMolecules(with models: [MoleculeModelProtocol?], _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard var stackModel = stackModel else { return } + var needsRestack = false + for (index, item) in stackItems.enumerated() { + guard let container = item as? UIView & ContainerProtocol, + let contained = container.view as? MoleculeViewProtocol else { + continue + } + if let model = models[index] { + contained.set(with: model, delegateObject, additionalData) + if stackModel.molecules[index].gone { + stackModel.molecules[index].gone = false + needsRestack = true + } + } else if !stackModel.molecules[index].gone { + stackModel.molecules[index].gone = true + needsRestack = true + } + } + + if needsRestack { + restack() + } + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -147,18 +173,8 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - let previousModel = self.model super.set(with: model, delegateObject, additionalData) - removeAllItemViews() - - // If the items in the stack are different, clear them, create new ones. - if (previousModel == nil) || Self.nameForReuse(with: previousModel!, delegateObject) != Self.nameForReuse(with: model, delegateObject) { - stackItems = [] - createStackItemsFromModel(model, delegateObject, additionalData) - } else { - setStackItemsFromModel(model, delegateObject, additionalData) - } - + setStackItemsFromModel(model, delegateObject, additionalData) restack() } @@ -216,9 +232,6 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto // MARK: - Subclassables //-------------------------------------------------- - /// Can be subclassed to create views when we get stack item models and have no views yet - open func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { } - /// Can be subclassed to set stack items with model when we already have views open func setStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let models = stackModel?.molecules else { return } @@ -243,10 +256,10 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } /// Sets the stack with StackItems containing the passed in views and sets the StackModel with models. - open func set(with viewModels:[(view: UIView, model: T.AnyStackItemModel)]) { + open func set(with viewModels:[(view: UIView, model: StackItemModel)]) { guard var stackModel = self.stackModel else { return } var stackItems: [StackItem] = [] - var models: [T.AnyStackItemModel] = [] + var models: [StackItemModel] = [] for item in viewModels { stackItems.append(StackItem(andContain: item.view)) models.append(item.model) @@ -307,7 +320,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto if let percent = model.percent { let multiplier = CGFloat(percent) / 100.0 let constant = multiplier * totalSpacing - view.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: multiplier, constant: -constant).isActive = true + view.heightAnchor.constraint(equalTo: heightAnchor, multiplier: multiplier, constant: -constant).isActive = true } if lastItem { pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) @@ -326,7 +339,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto if let percent = model.percent { let multiplier = CGFloat(percent) / 100.0 let constant = multiplier * totalSpacing - view.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: multiplier, constant: -constant).isActive = true + view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: multiplier, constant: -constant).isActive = true } if lastItem { pinView(contentView, toView: view, attribute: .right, relation: .equal, priority: .required, constant: 0) diff --git a/MVMCoreUI/Atomic/Organisms/StackModel.swift b/MVMCoreUI/Atomic/Organisms/StackModel.swift index 46f68c3b..3f13a580 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModel.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModel.swift @@ -8,17 +8,19 @@ import Foundation -@objcMembers public class StackModel: StackModelProtocol, MoleculeModelProtocol { +@objcMembers public class StackModel: ContainerModel, StackModelProtocol, MoleculeModelProtocol { static let defaultSpacing: CGFloat = 16.0 - public static var identifier: String = "simpleStack" + public class var identifier: String { + return "stack" + } public var backgroundColor: Color? - public var molecules: [StackItemModel] + public var molecules: [StackItemModelProtocol & MoleculeModelProtocol] public var axis: NSLayoutConstraint.Axis = .vertical public var spacing: CGFloat = StackModel.defaultSpacing public var useStackSpacingBeforeFirstItem = false - public init(molecules: [StackItemModel], axis: NSLayoutConstraint.Axis? = nil, spacing: CGFloat? = nil) { + public init(molecules: [StackItemModelProtocol & MoleculeModelProtocol], axis: NSLayoutConstraint.Axis? = nil, spacing: CGFloat? = nil) { self.molecules = molecules if let axis = axis { self.axis = axis @@ -26,6 +28,7 @@ import Foundation if let spacing = spacing { self.spacing = spacing } + super.init() } private enum CodingKeys: String, CodingKey { @@ -38,21 +41,24 @@ import Foundation required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - - molecules = try typeContainer.decode([StackItemModel].self, forKey: .molecules) + molecules = try typeContainer.decodeModels(codingKey: .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 } + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + try super.init(from: decoder) } - public func encode(to encoder: Encoder) throws { + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(molecules, forKey: .molecules) + try container.encodeModels(molecules, forKey: .molecules) try container.encodeIfPresent(axis.rawValueString, forKey: .axis) try container.encodeIfPresent(spacing, forKey: .spacing) try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } diff --git a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift index b7385466..54f6c9d0 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModelProtocol.swift @@ -9,9 +9,7 @@ import Foundation public protocol StackModelProtocol { - associatedtype AnyStackItemModel: StackItemModelProtocol - - var molecules: [AnyStackItemModel] { get set } + var molecules: [StackItemModelProtocol & MoleculeModelProtocol] { get set } var axis: NSLayoutConstraint.Axis { get set } var spacing: CGFloat { get set } var useStackSpacingBeforeFirstItem: Bool { get set } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ContainerModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ContainerModelProtocol.swift index bc230731..29e25903 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ContainerModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ContainerModelProtocol.swift @@ -11,10 +11,12 @@ import Foundation public protocol ContainerModelProtocol { var horizontalAlignment: UIStackView.Alignment? { get set } - var verticalAlignment: UIStackView.Alignment? { get set } var useHorizontalMargins: Bool? { get set } - + var leftPadding: CGFloat? { get set } + var rightPadding: CGFloat? { get set } + + var verticalAlignment: UIStackView.Alignment? { get set } var useVerticalMargins: Bool? { get set } - var topMarginPadding: CGFloat? { get set } - var bottomMarginPadding: CGFloat? { get set } + var topPadding: CGFloat? { get set } + var bottomPadding: CGFloat? { get set } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/PageModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/PageModelProtocol.swift index be4e4e65..ec2bead8 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/PageModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/PageModelProtocol.swift @@ -12,5 +12,6 @@ public protocol PageModelProtocol { var pageType: String { get set } /// Temporary: for legacy response var screenHeading: String? { get set } + var backgroundColor: Color? { get set } var navigationItem: (NavigationItemModelProtocol & MoleculeModelProtocol)? { get set } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ThreeLayerTemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ThreeLayerTemplateModelProtocol.swift new file mode 100644 index 00000000..3f05a1e5 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ThreeLayerTemplateModelProtocol.swift @@ -0,0 +1,17 @@ +// +// ThreeLayerTemplateModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ThreeLayerTemplateModelProtocol: TemplateModelProtocol { + var anchorHeader: Bool { get set } + var header: MoleculeModelProtocol? { get set } + + var anchorFooter: Bool { get set } + var footer: MoleculeModelProtocol? { get set } +} diff --git a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift index 56b1a86c..fc9873bd 100644 --- a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift @@ -22,5 +22,8 @@ public extension TemplateProtocol where Self: ViewController { let templateModel = try decoder.decode(TemplateModel.self, from: data) self.templateModel = templateModel self.pageModel = templateModel as? MVMControllerModelProtocol + if let backgroundColor = templateModel.backgroundColor { + view.backgroundColor = backgroundColor.uiColor + } } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index 1d2f07a6..be4273df 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -79,6 +79,8 @@ import Foundation open override func handleNewData() { + topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false + bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false setup() registerCells() super.handleNewData() diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift index 56c56456..f9d8c0a7 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers public class CollectionTemplateModel: TemplateModel { +@objcMembers public class CollectionTemplateModel: ThreeLayerModelBase { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -16,9 +16,7 @@ import Foundation public override class var identifier: String { return "collection" } - public var header: MoleculeModelProtocol? public var molecules: [CollectionItemModelProtocol & MoleculeModelProtocol]? - public var footer: MoleculeModelProtocol? public var columns: Int? //-------------------------------------------------- @@ -37,8 +35,6 @@ import Foundation private enum CodingKeys: String, CodingKey { case molecules - case header - case footer case columns } @@ -49,8 +45,6 @@ import Foundation required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeModelsIfPresent(codingKey: .molecules) - header = try typeContainer.decodeModelIfPresent(codingKey: .header) - footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) columns = try typeContainer.decodeIfPresent(Int.self, forKey: .columns) try super.init(from: decoder) } @@ -59,8 +53,6 @@ import Foundation try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeModelsIfPresent(molecules, forKey: .molecules) - try container.encodeModelIfPresent(header, forKey: .header) - try container.encodeModelIfPresent(footer, forKey: .footer) try container.encodeIfPresent(columns, forKey: .columns) } } diff --git a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift index d2418cbc..b239bd81 100644 --- a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers public class ListPageTemplateModel: TemplateModel { +@objcMembers public class ListPageTemplateModel: ThreeLayerModelBase { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -16,9 +16,7 @@ import Foundation public override class var identifier: String { return "list" } - public var header: MoleculeModelProtocol? public var molecules: [ListItemModelProtocol & MoleculeModelProtocol]? - public var footer: MoleculeModelProtocol? public var line: LineModel? //-------------------------------------------------- @@ -37,8 +35,6 @@ import Foundation private enum CodingKeys: String, CodingKey { case molecules - case header - case footer case line } @@ -49,8 +45,6 @@ import Foundation required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeModelsIfPresent(codingKey: .molecules) - header = try typeContainer.decodeModelIfPresent(codingKey: .header) - footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) try super.init(from: decoder) } @@ -59,8 +53,6 @@ import Foundation try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeModelsIfPresent(molecules, forKey: .molecules) - try container.encodeModelIfPresent(header, forKey: .header) - try container.encodeModelIfPresent(footer, forKey: .footer) try container.encode(line, forKey: .line) } } diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index b236e332..e40fbc33 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -81,6 +81,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } open override func handleNewData() { + topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false + bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false setup() registerWithTable() super.handleNewData() diff --git a/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift index b26bbc8a..90096145 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift @@ -11,6 +11,12 @@ import UIKit open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { var observer: NSKeyValueObservation? public var templateModel: StackPageTemplateModel? + + open override func handleNewData() { + topViewOutsideOfScroll = templateModel?.anchorHeader ?? false + bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false + super.handleNewData() + } open override func parsePageJSON() throws { try parseTemplate(json: loadObject?.pageJSON) diff --git a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift index 2b683d98..f21a0421 100644 --- a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift @@ -9,31 +9,24 @@ import Foundation -@objcMembers public class StackPageTemplateModel: TemplateModel { +@objcMembers public class StackPageTemplateModel: ThreeLayerModelBase { public override class var identifier: String { return "stack" } - - public var header: MoleculeModelProtocol? - public var moleculeStack: MoleculeStackModel - public var footer: MoleculeModelProtocol? + public var moleculeStack: StackModel - public init(pageType: String, moleculeStack: MoleculeStackModel) { + public init(pageType: String, moleculeStack: StackModel) { self.moleculeStack = moleculeStack super.init(pageType: pageType) } private enum CodingKeys: String, CodingKey { - case header - case footer case stack } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - moleculeStack = try typeContainer.decode(MoleculeStackModel.self, forKey: .stack) - header = try typeContainer.decodeModelIfPresent(codingKey: .header) - footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) + moleculeStack = try typeContainer.decode(StackModel.self, forKey: .stack) try super.init(from: decoder) } @@ -41,7 +34,5 @@ import Foundation try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeStack, forKey: .stack) - try container.encodeModelIfPresent(header, forKey: .header) - try container.encodeModelIfPresent(footer, forKey: .footer) } } diff --git a/MVMCoreUI/Atomic/Templates/TemplateModel.swift b/MVMCoreUI/Atomic/Templates/TemplateModel.swift index dbbbc494..86da1981 100644 --- a/MVMCoreUI/Atomic/Templates/TemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/TemplateModel.swift @@ -17,6 +17,7 @@ import Foundation // Although this is done in the extension, it is needed for the encoding. return Self.identifier } + public var backgroundColor: Color? public var screenHeading: String? public var navigationItem: (NavigationItemModelProtocol & MoleculeModelProtocol)? public var formRules: [FormGroupRule]? @@ -29,6 +30,7 @@ import Foundation case pageType case template case screenHeading + case backgroundColor case formRules case navigationItem } @@ -37,6 +39,7 @@ import Foundation let typeContainer = try decoder.container(keyedBy: CodingKeys.self) pageType = try typeContainer.decode(String.self, forKey: .pageType) screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) formRules = try typeContainer.decodeIfPresent([FormGroupRule].self, forKey: .formRules) navigationItem = try typeContainer.decodeModelIfPresent(codingKey: .navigationItem) } @@ -45,6 +48,7 @@ import Foundation var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(pageType, forKey: .pageType) try container.encode(template, forKey: .template) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(screenHeading, forKey: .screenHeading) try container.encodeIfPresent(formRules, forKey: .formRules) try container.encodeModelIfPresent(navigationItem, forKey: .navigationItem) diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplate.swift new file mode 100644 index 00000000..3d0119a9 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplate.swift @@ -0,0 +1,15 @@ +// +// ThreeLayerCenterTemplate.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class ThreeLayerCenterTemplate: ThreeLayerTemplate { + open override func spaceBetweenTopAndMiddle() -> CGFloat? { + return nil + } +} diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplateModel.swift new file mode 100644 index 00000000..4bea76f2 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplateModel.swift @@ -0,0 +1,15 @@ +// +// ThreeLayerCenterTemplateModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class ThreeLayerCenterPageTemplateModel: ThreeLayerPageTemplateModel { + public override class var identifier: String { + return "threeLayerCenter" + } +} diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift new file mode 100644 index 00000000..f0276979 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift @@ -0,0 +1,49 @@ +// +// ThreeLayerModelBase.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class ThreeLayerModelBase: TemplateModel, ThreeLayerTemplateModelProtocol { + public var anchorHeader: Bool = false + public var header: MoleculeModelProtocol? + public var anchorFooter: Bool = false + public var footer: MoleculeModelProtocol? + + public override init(pageType: String) { + super.init(pageType: pageType) + } + + private enum CodingKeys: String, CodingKey { + case anchorHeader + case header + case anchorFooter + case footer + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let anchor = try typeContainer.decodeIfPresent(Bool.self, forKey: .anchorHeader) { + anchorHeader = anchor + } + header = try typeContainer.decodeModelIfPresent(codingKey: .header) + if let anchor = try typeContainer.decodeIfPresent(Bool.self, forKey: .anchorFooter) { + anchorFooter = anchor + } + footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) + 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(anchorHeader, forKey: .anchorHeader) + try container.encodeModelIfPresent(header, forKey: .header) + try container.encodeIfPresent(anchorFooter, forKey: .anchorFooter) + try container.encodeModelIfPresent(footer, forKey: .footer) + } +} diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift index 61476707..c4e93f7a 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift @@ -8,13 +8,11 @@ import Foundation -@objcMembers public class ThreeLayerPageTemplateModel: TemplateModel { +@objcMembers public class ThreeLayerPageTemplateModel: ThreeLayerModelBase { public override class var identifier: String { return "threeLayer" } - public var header: MoleculeModelProtocol? public var middle: MoleculeModelProtocol? - public var footer: MoleculeModelProtocol? public init(pageType: String, header: MoleculeModelProtocol?, middle: MoleculeModelProtocol?, footer: MoleculeModelProtocol?) { super.init(pageType: pageType) @@ -24,24 +22,18 @@ import Foundation } private enum CodingKeys: String, CodingKey { - case header - case footer case middle } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - header = try typeContainer.decodeModelIfPresent(codingKey: .header) middle = try typeContainer.decodeModelIfPresent(codingKey: .middle) - footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) 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.encodeModelIfPresent(header, forKey: .header) try container.encodeModelIfPresent(header, forKey: .middle) - try container.encodeModelIfPresent(footer, forKey: .footer) } } diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift index 96e85fad..4884d5b1 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift @@ -8,23 +8,18 @@ import UIKit -@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController, TemplateProtocol { - public var templateModel: ThreeLayerPageTemplateModel? +@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController, TemplateProtocol { + public var templateModel: TemplateModel? open override func parsePageJSON() throws { try parseTemplate(json: loadObject?.pageJSON) try super.parsePageJSON() } - override open func viewDidLoad() { - super.viewDidLoad() - bottomViewOutsideOfScroll = true - // Do any additional setup after loading the view. - } - open override func handleNewData() { + topViewOutsideOfScroll = templateModel?.anchorHeader ?? false + bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false super.handleNewData() - heightConstraint?.isActive = true } open override func viewForTop() -> UIView? { diff --git a/MVMCoreUI/BaseClasses/CollectionView.swift b/MVMCoreUI/BaseClasses/CollectionView.swift index a1a57376..4b034462 100644 --- a/MVMCoreUI/BaseClasses/CollectionView.swift +++ b/MVMCoreUI/BaseClasses/CollectionView.swift @@ -10,6 +10,11 @@ import Foundation open class CollectionView: UICollectionView, MVMCoreViewProtocol { + /// A block that gets called on tableview frame changes + public var frameChangeAction: (() -> Void)? + + private var previousFrame = CGRect.zero + private var initialSetupPerformed = false private func initialSetup() { @@ -29,6 +34,14 @@ open class CollectionView: UICollectionView, MVMCoreViewProtocol { initialSetup() } + open override func layoutSubviews() { + super.layoutSubviews() + if frame != previousFrame { + frameChangeAction?() + } + previousFrame = frame + } + public func updateView(_ size: CGFloat) { for cell in visibleCells { (cell as? MVMCoreViewProtocol)?.updateView(size) diff --git a/MVMCoreUI/BaseClasses/TableView.swift b/MVMCoreUI/BaseClasses/TableView.swift new file mode 100644 index 00000000..9fb07ac7 --- /dev/null +++ b/MVMCoreUI/BaseClasses/TableView.swift @@ -0,0 +1,25 @@ +// +// TableView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class TableView: UITableView { + + /// A block that gets called on tableview frame changes + public var frameChangeAction: (() -> Void)? + + private var previousFrame = CGRect.zero + + open override func layoutSubviews() { + super.layoutSubviews() + if frame != previousFrame { + frameChangeAction?() + } + previousFrame = frame + } +} diff --git a/MVMCoreUI/BaseClasses/TableViewCell.swift b/MVMCoreUI/BaseClasses/TableViewCell.swift index dc4d270a..30f9df22 100644 --- a/MVMCoreUI/BaseClasses/TableViewCell.swift +++ b/MVMCoreUI/BaseClasses/TableViewCell.swift @@ -26,7 +26,7 @@ import UIKit /// For subclasses that want to use a custom accessory view. open var customAccessoryView = false - private var heroAccessoryCenter: CGPoint? + public var heroAccessoryCenter: CGPoint? private var initialSetupPerformed = false @@ -51,36 +51,36 @@ import UIKit } open func styleStandard() { - listItemModel?.topMarginPadding = 24 - listItemModel?.bottomMarginPadding = 24 + listItemModel?.topPadding = 24 + listItemModel?.bottomPadding = 24 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.standard) } open func styleTallDivider() { - listItemModel?.topMarginPadding = 48 - listItemModel?.bottomMarginPadding = 16 + listItemModel?.topPadding = 48 + listItemModel?.bottomPadding = 16 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.thin) } open func styleShortDivider() { - listItemModel?.topMarginPadding = 32 - listItemModel?.bottomMarginPadding = 16 + listItemModel?.topPadding = 32 + listItemModel?.bottomPadding = 16 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.thin) } open func styleFooter() { - listItemModel?.topMarginPadding = 24 - listItemModel?.bottomMarginPadding = 0 + listItemModel?.topPadding = 24 + listItemModel?.bottomPadding = 0 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.none) } open func styleNone() { - listItemModel?.topMarginPadding = 0 - listItemModel?.bottomMarginPadding = 0 + listItemModel?.topPadding = 0 + listItemModel?.bottomPadding = 0 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.none) } @@ -97,7 +97,7 @@ import UIKit // Ensures accessory view aligns to the center y derived from the if let _ = heroAccessoryCenter { - alignAccessoryToHero() + _ = alignAccessoryToHero() } } @@ -215,14 +215,16 @@ import UIKit } /// NOTE: Should only be called when displayed or about to be displayed. - public func alignAccessoryToHero() { + public func alignAccessoryToHero() -> CGPoint? { // Layout call required to force draw in memory to get dimensions of subviews. layoutIfNeeded() - guard let heroLabel = findHeroLabel(views: contentView.subviews), let hero = heroLabel.hero else { return } + guard let heroLabel = findHeroLabel(views: contentView.subviews), let hero = heroLabel.hero else { return nil } let rect = Label.boundingRect(forCharacterRange: NSRange(location: hero, length: 1), in: heroLabel) - accessoryView?.center.y = convert(UIView(frame: rect).center, from: heroLabel).y + let y = convert(UIView(frame: rect).center, from: heroLabel).y + accessoryView?.center.y = y heroAccessoryCenter = accessoryView?.center + return heroAccessoryCenter ?? CGPoint(x: 0, y: y) } /// Traverses the view hierarchy for a 🦸‍♂️ heroic Label. @@ -269,7 +271,7 @@ import UIKit } public func willDisplay() { - alignAccessoryToHero() + _ = alignAccessoryToHero() } // MARK: - Separator diff --git a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift index d7fb0ed4..c59fd097 100644 --- a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift +++ b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift @@ -20,11 +20,8 @@ public class ContainerCollectionReusableView: UICollectionReusableView { view.setContentCompressionResistancePriority(.required, for: .vertical) addSubview(view) self.view = view - topConstraint = view.topAnchor.constraint(equalTo: topAnchor) - topConstraint?.isActive = true - bottomConstraint = bottomAnchor.constraint(equalTo: view.bottomAnchor) - bottomConstraint?.isActive = true - rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true - view.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + let constraints = NSLayoutConstraint.constraintPinSubview(toSuperview: view) + topConstraint = constraints?[ConstraintTop] as? NSLayoutConstraint + bottomConstraint = constraints?[ConstraintBot] as? NSLayoutConstraint } } diff --git a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift index 5e7b87b0..85e0f3a7 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift @@ -11,7 +11,9 @@ import Foundation /// A base view controller with a collection view. @objc open class ProgrammaticCollectionViewController: ScrollingViewController { - public var collectionView: UICollectionView? + public var collectionView: CollectionView? + public var topConstraint: NSLayoutConstraint? + public var bottomConstraint: NSLayoutConstraint? open override func loadView() { let view = UIView() @@ -19,7 +21,9 @@ import Foundation let collection = createCollectionView() view.addSubview(collection) - NSLayoutConstraint.constraintPinSubview(toSuperview: collection) + let constraints = NSLayoutConstraint.constraintPinSubview(toSuperview: collection) + topConstraint = constraints?[ConstraintTop] as? NSLayoutConstraint + bottomConstraint = constraints?[ConstraintBot] as? NSLayoutConstraint collectionView = collection scrollView = collectionView @@ -45,7 +49,7 @@ import Foundation } /// Creates the collection view. - open func createCollectionView() -> UICollectionView { + open func createCollectionView() -> CollectionView { let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) collection.dataSource = self collection.delegate = self diff --git a/MVMCoreUI/BaseControllers/ProgrammaticScrollViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticScrollViewController.swift index b6ed6353..19c6f2b7 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticScrollViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticScrollViewController.swift @@ -35,19 +35,20 @@ open class ProgrammaticScrollViewController: ScrollingViewController { view.addSubview(scrollView) // Sets the constraints for the scroll view - let constraints = NSLayoutConstraint.constraintPinSubview(toSuperview: scrollView) - topConstraint = constraints?[ConstraintTop] as? NSLayoutConstraint - bottomConstraint = constraints?[ConstraintBot] as? NSLayoutConstraint + scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true + topConstraint = scrollView.topAnchor.constraint(equalTo: view.topAnchor) + topConstraint?.isActive = true + bottomConstraint = view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor) + bottomConstraint?.isActive = true + // Sets the constraints for the content view let contentView = MVMCoreUICommonViewsUtility.commonView() scrollView.addSubview(contentView) - - // Sets the constraints for the content view - NSLayoutConstraint.constraintPinSubview(toSuperview: contentView) - - // Super will set later. - contentWidthConstraint = contentView.widthAnchor.constraint(equalToConstant: 320.0) - contentWidthConstraint?.isActive = true + contentView.leftAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.leftAnchor).isActive = true + scrollView.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true + contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true + scrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true self.contentView = contentView self.scrollView = scrollView diff --git a/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift index b27845b3..21f1de1e 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift @@ -9,9 +9,9 @@ import Foundation open class ProgrammaticTableViewController: ProgrammaticScrollViewController, UITableViewDelegate, UITableViewDataSource { - @IBOutlet public var tableView: UITableView! + @IBOutlet public var tableView: TableView! - public init(with tableView: UITableView) { + public init(with tableView: TableView) { self.tableView = tableView super.init(with: tableView) } @@ -48,8 +48,8 @@ open class ProgrammaticTableViewController: ProgrammaticScrollViewController, UI } /// This class should create the table view that will be used here. Subclass this for different table styles. - open func createTableView() -> UITableView { - let tableView = UITableView(frame: .zero, style: .grouped) + open func createTableView() -> TableView { + let tableView = TableView(frame: .zero, style: .grouped) tableView.backgroundColor = .clear tableView.separatorStyle = UITableViewCell.SeparatorStyle.none tableView.delegate = self @@ -67,6 +67,11 @@ open class ProgrammaticTableViewController: ProgrammaticScrollViewController, UI tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude } + open func refreshTable() { + tableView.beginUpdates() + tableView.endUpdates() + } + /// For subclassing, returns the number of sections for table. This function calls numberOfSectionsForTableview aftre ensuring the table is setup properly. open func getNumberOfSections() -> Int { return 1 diff --git a/MVMCoreUI/BaseControllers/ScrollingViewController.swift b/MVMCoreUI/BaseControllers/ScrollingViewController.swift index 2e4eceef..0eea84cc 100644 --- a/MVMCoreUI/BaseControllers/ScrollingViewController.swift +++ b/MVMCoreUI/BaseControllers/ScrollingViewController.swift @@ -12,7 +12,6 @@ open class ScrollingViewController: ViewController { public var dismissKeyboardTapGesture: UITapGestureRecognizer? @IBOutlet public var scrollView: UIScrollView! public var contentView: UIView? - public var contentWidthConstraint: NSLayoutConstraint? private var keyboardNotificationsAdded = false private var keyboardIsShowing = false @@ -36,19 +35,13 @@ open class ScrollingViewController: ViewController { super.viewDidLoad() // Adds the tap gesture to dismiss the keyboard. - dismissKeyboardTapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissFieldInput(sender:))) + dismissKeyboardTapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissFieldInput)) view.addGestureRecognizer(dismissKeyboardTapGesture!) dismissKeyboardTapGesture?.isEnabled = false scrollView.alwaysBounceVertical = false scrollView.delegate = self } - - open override func updateViewConstraints() { - super.updateViewConstraints() - // Sets the width of the content to the width of the screen. - contentWidthConstraint?.constant = view.bounds.width - scrollView.contentInset.left - scrollView.contentInset.right - } - + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view.setNeedsUpdateConstraints() diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index aa34f2f2..88d8157a 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -17,10 +17,18 @@ import Foundation private var footerView: ContainerCollectionReusableView? private let headerID = "header" private let footerID = "footer" + public var bottomViewOutsideOfScrollArea: Bool = false + public var topViewOutsideOfScrollArea: Bool = false + + open override func updateViewConstraints() { + // Update the spacing on constraint update + updateFlexibleSpace() + super.updateViewConstraints() + } /// Updates the padding for flexible space (header or footer) private func updateFlexibleSpace() { - guard let tableView = collectionView else { return } + guard let collectionView = collectionView else { return } let minimumSpace: CGFloat = minimumFillSpace() var currentSpace: CGFloat = 0 @@ -41,33 +49,43 @@ import Foundation } guard fillTop || fillBottom else { return } - - let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) - - // If the bottom view is outside of the scroll, then only the top view constraint is being used, so we have to double it to account for the bottom constraint not being there when we compare to the new value. - var currentSpaceForCompare: CGFloat = currentSpace - if fillTop { - currentSpaceForCompare = currentSpace * 2; - } - - if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 1) { + let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: collectionView, minimumHeight: totalMinimumSpace) + if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpace, 1) { if fillTop && fillBottom { // space both let half = newSpace / 2 headerView?.bottomConstraint?.constant = half footerView?.topConstraint?.constant = half - collectionView?.collectionViewLayout.invalidateLayout() + collectionView.collectionViewLayout.invalidateLayout() } else if fillTop { - // Only top is spaced (half the size if the bottom view is out of the scroll because it needs to be sized as if there are two spacers but there is only one. + // Only top is spaced. headerView?.bottomConstraint?.constant = newSpace - collectionView?.collectionViewLayout.invalidateLayout() + collectionView.collectionViewLayout.invalidateLayout() } else if fillBottom { // Only bottom is spaced. footerView?.topConstraint?.constant = newSpace - collectionView?.collectionViewLayout.invalidateLayout() + collectionView.collectionViewLayout.invalidateLayout() } } } + + func addTopViewOutside() { + guard let collectionView = collectionView, let topView = topView else { return } + view.addSubview(topView) + topView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true + topView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true + view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true + collectionView.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true + } + + func addBottomViewOutside() { + guard let collectionView = collectionView, let bottomView = bottomView else { return } + view.addSubview(bottomView) + bottomView.topAnchor.constraint(equalTo: collectionView.bottomAnchor).isActive = true + bottomView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true + view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true + view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true + } //MARK: - ViewController open override func updateViews() { @@ -89,17 +107,35 @@ import Foundation self.invalidateCollectionLayout() } } - + open override func handleNewData() { super.handleNewData() - createViewForHeader() - createViewForFooter() + topView?.removeFromSuperview() + bottomView?.removeFromSuperview() + topView = viewForTop() + bottomView = viewForBottom() + if topViewOutsideOfScrollArea { + topConstraint?.isActive = false + addTopViewOutside() + } else { + topConstraint?.isActive = true + } + if bottomViewOutsideOfScrollArea { + bottomConstraint?.isActive = false + addBottomViewOutside() + } else { + bottomConstraint?.isActive = true + } reloadCollectionData() } override open func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. + + collectionView?.frameChangeAction = { [weak self] in + self?.invalidateCollectionLayout() + } } //MARK: - Spacing @@ -118,27 +154,6 @@ import Foundation open func minimumFillSpace() -> CGFloat { return 0 } - - //MARK: - Header Footer - /// Creates the top view. - open func createViewForHeader() { - guard let topView = viewForTop() else { - self.topView = nil - self.headerView = nil - return - } - self.topView = topView - } - - /// Creates the footer - open func createViewForFooter() { - guard let bottomView = viewForBottom() else { - self.bottomView = nil - self.footerView = nil - return - } - self.bottomView = bottomView - } //MARK: - Functions to subclass /// Subclass for a top view. @@ -187,18 +202,14 @@ import Foundation } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { - guard let _ = topView, - section == 0 else { return .zero } - + guard section == 0 else { return .zero } // Calculate the height of the header since apple doesn't support autolayout. Width is fixed, height is tall as content. let header = headerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: section)) return header.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { - guard let _ = bottomView, - section == numberOfSections(in: collectionView) - 1 else { return .zero } - + guard section == numberOfSections(in: collectionView) - 1 else { return .zero } // Calculate the height of the footr since apple doesn't support autolayout. Width is fixed, height is tall as content. let footer = footerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionFooter, at: IndexPath(row: 0, section: section)) return footer.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) @@ -207,13 +218,15 @@ import Foundation open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if kind == UICollectionView.elementKindSectionFooter, let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footerID, for: indexPath) as? ContainerCollectionReusableView { - footerView.addAndContain(view: bottomView!) + let bottomView = (bottomViewOutsideOfScrollArea ? nil : self.bottomView) ?? MVMCoreUICommonViewsUtility.getView(with: 0.5) + footerView.addAndContain(view: bottomView) footerView.topConstraint?.constant = spaceAboveBottomView() ?? 0 self.footerView = footerView return footerView } else if kind == UICollectionView.elementKindSectionHeader, let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? ContainerCollectionReusableView { - headerView.addAndContain(view: topView!) + let topView = (topViewOutsideOfScrollArea ? nil : self.topView) ?? MVMCoreUICommonViewsUtility.getView(with: 0.5) + headerView.addAndContain(view: topView) headerView.bottomConstraint?.constant = spaceBelowTopView() ?? 0 self.headerView = headerView return headerView diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 000f2e7f..8e01cf6c 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -15,9 +15,8 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { private var bottomView: UIView? private var headerView: UIView? private var footerView: UIView? - private var safeAreaView: UIView? - var useMargins: Bool = true public var bottomViewOutsideOfScrollArea: Bool = false + public var topViewOutsideOfScrollArea: Bool = false private var topViewBottomConstraint: NSLayoutConstraint? private var bottomViewTopConstraint: NSLayoutConstraint? @@ -33,7 +32,7 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { bottomView.updateView(width) showFooter(width) } - tableView?.reloadData() + tableView.reloadData() } open override func handleNewData() { @@ -47,6 +46,11 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { super.viewDidLoad() // Do any additional setup after loading the view. setNoSectionHeadersFooters() + + // Ensures the footer and headers are the right size + tableView.frameChangeAction = { [weak self] in + self?.view.setNeedsUpdateConstraints() + } } //MARK: - Spacing @@ -67,40 +71,37 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { } open override func updateViewConstraints() { - super.updateViewConstraints() - - guard let tableView = tableView else { return } + guard let tableView = tableView else { + super.updateViewConstraints() + return + } let minimumSpace: CGFloat = minimumFillSpace() var currentSpace: CGFloat = 0 var totalMinimumSpace: CGFloat = 0 var fillTop = false - if spaceBelowTopView() == nil, self.tableView?.tableHeaderView != nil { + if spaceBelowTopView() == nil, tableView.tableHeaderView != nil { fillTop = true currentSpace += topViewBottomConstraint?.constant ?? 0 totalMinimumSpace += minimumSpace } var fillBottom = false - if spaceAboveBottomView() == nil, !bottomViewOutsideOfScrollArea, self.tableView?.tableFooterView != nil { + if spaceAboveBottomView() == nil, tableView.tableFooterView != nil { fillBottom = true currentSpace += bottomViewTopConstraint?.constant ?? 0 totalMinimumSpace += minimumSpace } - guard fillTop || fillBottom else { return } - - let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) - - // If the bottom view is outside of the scroll, then only the top view constraint is being used, so we have to double it to account for the bottom constraint not being there when we compare to the new value. - var currentSpaceForCompare: CGFloat = currentSpace - if fillTop && bottomViewOutsideOfScrollArea { - currentSpaceForCompare = currentSpace * 2; + guard fillTop || fillBottom else { + super.updateViewConstraints() + return } + let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) let width = view.bounds.width - if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 0.1) { + if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpace, 0.1) { if fillTop && fillBottom { // space both let half = newSpace / 2 @@ -109,27 +110,30 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { showHeader(width) showFooter(width) } else if fillTop { - // Only top is spaced (half the size if the bottom view is out of the scroll because it needs to be sized as if there are two spacers but there is only one. - if bottomViewOutsideOfScrollArea { - topViewBottomConstraint?.constant = newSpace / 2 - } else { - topViewBottomConstraint?.constant = newSpace - } + topViewBottomConstraint?.constant = newSpace showHeader(width) } else if fillBottom { // Only bottom is spaced. bottomViewTopConstraint?.constant = newSpace showFooter(width) } + DispatchQueue.main.async { + self.refreshTable() + } } + super.updateViewConstraints() } //MARK: - Header Footer /// Gets the top view and adds it to a spacing view, headerView, and then calls showHeader. open func createViewForTableHeader() { - let topView = viewForTop() + var topView = viewForTop() self.topView = topView + // If top view is outside of scroll area, create a dummy view for the header. Small height is needed to stop apple from adding padding for grouped tables when no header. + if topViewOutsideOfScrollArea { + topView = MVMCoreUICommonViewsUtility.getView(with: 0.5) + } let headerView = MVMCoreUICommonViewsUtility.commonView() headerView.addSubview(topView) topView.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true @@ -143,9 +147,13 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { /// Gets the bottom view and adds it to a spacing view, footerView, and then calls showFooter. open func createViewForTableFooter() { - let bottomView = viewForBottom() + var bottomView = viewForBottom() self.bottomView = bottomView + // If bottom view is outside of scroll area, create a dummy view for the header. Small height is needed to stop apple from adding padding for grouped tables when no header. + if bottomViewOutsideOfScrollArea { + bottomView = MVMCoreUICommonViewsUtility.getView(with: 0.5) + } let footerView = MVMCoreUICommonViewsUtility.commonView() footerView.addSubview(bottomView) bottomViewTopConstraint = bottomView.topAnchor.constraint(equalTo: footerView.topAnchor, constant: spaceAboveBottomView() ?? 0) @@ -161,10 +169,20 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { func showHeader(_ sizingWidth: CGFloat?) { headerView?.removeFromSuperview() tableView?.tableHeaderView = nil - guard let headerView = headerView else { - return - } + guard let headerView = headerView else { return } + if topViewOutsideOfScrollArea, + let topView = topView { + // put top view outside of scrolling area. + topConstraint?.isActive = false + view.addSubview(topView) + topView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true + topView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true + tableView.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true + } else { + topConstraint?.isActive = true + } // This extra view is needed because of the wonkiness of apple's table header. Things breaks if using autolayout. headerView.setNeedsLayout() headerView.layoutIfNeeded() @@ -178,56 +196,55 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { /// Takes the current footerView and adds it to the tableFooterView func showFooter(_ sizingWidth: CGFloat?) { footerView?.removeFromSuperview() - safeAreaView?.removeFromSuperview() - guard let footerView = footerView, let tableView = tableView else { - return + guard let footerView = footerView, + let tableView = tableView else { + self.tableView?.tableFooterView = nil + return } - if bottomViewOutsideOfScrollArea { + if bottomViewOutsideOfScrollArea, + let bottomView = bottomView { // put bottom view outside of scrolling area. bottomConstraint?.isActive = false - view.addSubview(footerView) - footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true - footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - view.rightAnchor.constraint(equalTo: footerView.rightAnchor).isActive = true - view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true - safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) - safeAreaView?.backgroundColor = bottomView?.backgroundColor + view.addSubview(bottomView) + bottomView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true + bottomView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true + view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true } else { bottomConstraint?.isActive = true - var y: CGFloat? - if let tableFooterView = tableView.tableFooterView { - // if footer already exists, use the same y location to avoid strange moving animation - y = tableFooterView.frame.minY - } - - // This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout. - MVMCoreUIUtility.sizeView(toFit: footerView) - let tableFooterView = UIView(frame: CGRect(x: 0, y: y ?? 0, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height)) - tableFooterView.addSubview(footerView) - NSLayoutConstraint.constraintPinSubview(toSuperview: footerView) - tableView.tableFooterView = tableFooterView } + + // if footer already exists, use the same y location to avoid strange moving animation + let y = tableView.tableFooterView?.frame.minY ?? 0.0 + + // This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout. + MVMCoreUIUtility.sizeView(toFit: footerView) + let tableFooterView = UIView(frame: CGRect(x: 0, y: y, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height)) + tableFooterView.addSubview(footerView) + NSLayoutConstraint.constraintPinSubview(toSuperview: footerView) + tableView.tableFooterView = tableFooterView } //MARK: - Functions to subclass /// Subclass for a top view. open func viewForTop() -> UIView { - let view = MVMCoreUICommonViewsUtility.commonView() // Small height is needed to stop apple from adding padding for grouped tables when no header. - view.heightAnchor.constraint(equalToConstant: 1).isActive = true - return view + return MVMCoreUICommonViewsUtility.getView(with: 0.5) } /// Subclass for a bottom view. open func viewForBottom() -> UIView { // Default spacing is standard when no buttons. - let view = MVMCoreUICommonViewsUtility.commonView() - view.heightAnchor.constraint(equalToConstant: PaddingDefaultVerticalSpacing).isActive = true - return view + return MVMCoreUICommonViewsUtility.getView(with: PaddingDefaultVerticalSpacing) } deinit { tableView?.delegate = nil } + + // Ensures the footer and headers are the right size + func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { + view.setNeedsUpdateConstraints() + } } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index 9eacd25c..210148cf 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -18,10 +18,12 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { var bottomView: UIView? var useMargins: Bool = false + // The top view can be put outside of the scrolling area. + var topViewOutsideOfScroll = false + // The bottom view can be put outside of the scrolling area. var bottomViewOutsideOfScroll = false - private var safeAreaView: UIView? var heightConstraint: NSLayoutConstraint? open override func updateViews() { @@ -39,24 +41,11 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { } } - open override func updateViewConstraints() { - super.updateViewConstraints() - guard let scrollView = scrollView else { - return - } - - if scrollView.contentInsetAdjustmentBehavior == UIScrollView.ContentInsetAdjustmentBehavior.automatic { - heightConstraint?.constant = -scrollView.adjustedContentInset.top - scrollView.adjustedContentInset.bottom - } else { - heightConstraint?.constant = -scrollView.contentInset.top - scrollView.contentInset.bottom - } - } - open override func loadView() { super.loadView() // The height is used to keep the bottom view at the bottom. if let contentView = contentView, let scrollView = scrollView { - heightConstraint = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.0) + heightConstraint = contentView.heightAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.heightAnchor, multiplier: 1.0) heightConstraint?.priority = UILayoutPriority.defaultLow } } @@ -68,7 +57,6 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { topView?.removeFromSuperview() middleView?.removeFromSuperview() bottomView?.removeFromSuperview() - safeAreaView?.removeFromSuperview() if let subViews = contentView?.subviews { for view in subViews { view.removeFromSuperview() @@ -76,6 +64,7 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { } // Reset constraints + topConstraint?.isActive = true bottomConstraint?.isActive = true heightConstraint?.isActive = false @@ -111,33 +100,30 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { //MARK:-Setup extension ThreeLayerViewController { - func setupViewAsTop() -> UIView? { - if let topView = viewForTop() { - self.topView = topView + func setupViewAsTop() -> UIView { + var topView = viewForTop() ?? MVMCoreUICommonViewsUtility.getView(with: 0) + self.topView = topView + + // Adds the top view outside the scroll if directed. + if topViewOutsideOfScroll { + topConstraint?.isActive = false + addViewOutsideOfScrollViewTop(topView) + + // Adds and returns an empty view to use for the internal logic. + topView = MVMCoreUICommonViewsUtility.getView(with: 0) + addViewInsideOfScrollViewTop(topView) } else { - topView = MVMCoreUICommonViewsUtility.commonView() - topView?.heightAnchor.constraint(equalToConstant: 0).isActive = true + topConstraint?.isActive = true + addViewInsideOfScrollViewTop(topView) } - guard let topView = topView, let contentView = contentView else { - return nil - } - contentView.addSubview(topView) - NSLayoutConstraint.pinViewTop(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true - NSLayoutConstraint.pinViewLeft(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true - NSLayoutConstraint.pinViewRight(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true return topView } - func setupViewAsMiddle() -> UIView? { - if let middleView = viewForMiddle() { - self.middleView = middleView - } else { - middleView = MVMCoreUICommonViewsUtility.commonView() - middleView?.heightAnchor.constraint(equalToConstant: 0).isActive = true - } - guard let middleView = middleView, let contentView = contentView else { - return nil - } + func setupViewAsMiddle() -> UIView { + let middleView = viewForMiddle() ?? MVMCoreUICommonViewsUtility.getView(with: 0) + self.middleView = middleView + + guard let contentView = contentView else { return middleView } contentView.addSubview(middleView) NSLayoutConstraint.pinViewLeft(toSuperview: middleView, useMargins: useMargins, constant: 0).isActive = true NSLayoutConstraint.pinViewRight(toSuperview: middleView, useMargins: useMargins, constant: 0).isActive = true @@ -145,33 +131,30 @@ extension ThreeLayerViewController { return middleView } - func setupViewAsBottom() -> UIView? { - if let bottomView = viewForBottom() { - self.bottomView = bottomView - } else { - bottomView = MVMCoreUICommonViewsUtility.commonView() - bottomView?.heightAnchor.constraint(equalToConstant: 0).isActive = true - } - guard let bottomView = bottomView else { - return nil - } + func setupViewAsBottom() -> UIView { + var bottomView = viewForBottom() ?? MVMCoreUICommonViewsUtility.getView(with: 0) + self.bottomView = bottomView // Adds the bottom view outside the scroll if directed. if bottomViewOutsideOfScroll { - bottomConstraint?.isActive = false; - addViewInsideOfScrollViewBottom(ViewConstrainingView.empty()) + bottomConstraint?.isActive = false addViewOutsideOfScrollViewBottom(bottomView) + + // Adds and returns an empty view to use for the internal logic. + bottomView = MVMCoreUICommonViewsUtility.getView(with: 0) + addViewInsideOfScrollViewBottom(bottomView) } else { - bottomConstraint?.isActive = true; + bottomConstraint?.isActive = true addViewInsideOfScrollViewBottom(bottomView) } return bottomView } func setupLayers() { - guard let contentView = contentView, let topView = setupViewAsTop(), let middleView = setupViewAsMiddle(), let bottomView = setupViewAsBottom() else { - return - } + guard let contentView = contentView else { return } + let topView = setupViewAsTop() + let middleView = setupViewAsMiddle() + let bottomView = setupViewAsBottom() let spaceAbove = spaceBetweenTopAndMiddle() let spaceBelow = spaceBetweenMiddleAndBottom() if let spaceAbove = spaceAbove, let spaceBelow = spaceBelow { @@ -197,7 +180,7 @@ extension ThreeLayerViewController { contentView.addSubview(topSpacer) topSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true contentView.rightAnchor.constraint(equalTo: topSpacer.rightAnchor).isActive = true - topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingTwo).isActive = true + topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true topSpacer.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true middleView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor).isActive = true bottomView.topAnchor.constraint(equalTo: middleView.bottomAnchor, constant: spaceBelow).isActive = true @@ -212,7 +195,7 @@ extension ThreeLayerViewController { bottomSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true contentView.rightAnchor.constraint(equalTo: bottomSpacer.rightAnchor).isActive = true topSpacer.heightAnchor.constraint(equalTo: bottomSpacer.heightAnchor).isActive = true - topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingTwo).isActive = true + topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true topSpacer.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true middleView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor).isActive = true bottomSpacer.topAnchor.constraint(equalTo: middleView.bottomAnchor).isActive = true @@ -221,10 +204,25 @@ extension ThreeLayerViewController { } } + func addViewInsideOfScrollViewTop(_ view: UIView) { + guard let contentView = contentView else { return } + contentView.addSubview(view) + NSLayoutConstraint.pinViewTop(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + } + + func addViewOutsideOfScrollViewTop(_ view: UIView) { + guard let scrollView = scrollView, let parentView = self.view else { return } + parentView.addSubview(view) + scrollView.topAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + view.topAnchor.constraint(equalTo: parentView.safeAreaLayoutGuide.topAnchor).isActive = true + } + func addViewInsideOfScrollViewBottom(_ view: UIView) { - guard let contentView = contentView else { - return - } + guard let contentView = contentView else { return } contentView.addSubview(view); NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true @@ -232,16 +230,11 @@ extension ThreeLayerViewController { } func addViewOutsideOfScrollViewBottom(_ view: UIView) { - self.view?.addSubview(view) - if let scrollView = scrollView, let parentView = self.view { - view.topAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true - NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true - NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true - parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true - if let safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: parentView) { - safeAreaView.backgroundColor = bottomView?.backgroundColor - self.safeAreaView = safeAreaView - } - } + guard let scrollView = scrollView, let parentView = self.view else { return } + parentView.addSubview(view) + view.topAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true + NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true } } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index a49dfad3..b12440a6 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -236,10 +236,13 @@ import UIKit } // MARK: - View lifecycle + + /// Called only once in viewDidLoad open func initialLoad() { observeForResponseJSONUpdates() } + /// Called on screen size update. open func updateViews() { _ = formValidator?.validate() } @@ -265,8 +268,7 @@ import UIKit initialLoad() } - // Handle data for first load. Dispatched to allow subclasses to finish their view did load implementations. - self.handleNewDataAndUpdateUI() + handleNewDataAndUpdateUI() } open override func viewDidLayoutSubviews() { @@ -403,7 +405,7 @@ import UIKit } } - @objc open func dismissFieldInput(sender: Any?) { + @objc open func dismissFieldInput(_ sender: Any?) { selectedField?.resignFirstResponder() } diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index eb869e89..f278236c 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -43,10 +43,10 @@ import UIKit public static func set(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { viewController.navigationItem.title = navigationItemModel.title viewController.navigationItem.accessibilityLabel = navigationItemModel.title - viewController.navigationItem.hidesBackButton = !navigationItemModel.systemBackButton + viewController.navigationItem.hidesBackButton = (navigationItemModel.backButton != nil) navigationController.setNavigationBarHidden(navigationItemModel.hidden, animated: true) - UIColor.setBackgroundColor(navigationItemModel.backgroundColor?.uiColor ?? .white, for: navigationController.navigationBar, isTransparent: navigationItemModel.transparent) + UIColor.setBackgroundColor(navigationItemModel.backgroundColor?.uiColor ?? .white, for: navigationController.navigationBar, isTransparent: navigationItemModel.translucent) let tint = navigationItemModel.tintColor.uiColor navigationController.navigationBar.tintColor = tint @@ -64,8 +64,8 @@ import UIKit if navigationController == MVMCoreUISplitViewController.main()?.navigationController, navigationController.topViewController == viewController { // Update Panels - MVMCoreUISplitViewController.main()?.setLeftPanelIsAccessible(navigationItemModel.showLeftPanelButton ?? false, for: viewController) - MVMCoreUISplitViewController.main()?.setRightPanelIsAccessible(navigationItemModel.showRightPanelButton ?? false, for: viewController) + MVMCoreUISplitViewController.main()?.setLeftPanelIsAccessible(navigationItemModel.showLeftPanelButton , for: viewController) + MVMCoreUISplitViewController.main()?.setRightPanelIsAccessible(navigationItemModel.showRightPanelButton , for: viewController) MVMCoreUISession.sharedGlobal()?.splitViewController?.setNavigationIconColor(tint) } } diff --git a/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift b/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift index 223589fe..4ce97f5c 100644 --- a/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift +++ b/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift @@ -169,8 +169,15 @@ open class ContainerHelper: NSObject { } } + /// Updates the view margins according to model and size. If useHorizontalMargins is true, we will try to use the left and right padding, else we will default to the normal gutters. If useVerticalMargins is true, we will try to use the top and bottom padding. All else, we use zero. open func updateViewMargins(_ view: UIView, model: ContainerModelProtocol?, size: CGFloat) { - MFStyler.setMarginsFor(view, size: size, defaultHorizontal: model?.useHorizontalMargins ?? false, top: (model?.useVerticalMargins ?? false) ? (model?.topMarginPadding ?? 0) : 0, bottom: (model?.useVerticalMargins ?? false) ? (model?.bottomMarginPadding ?? 0) : 0) + let left = (model?.useHorizontalMargins ?? false) ? (model?.leftPadding ?? Padding.Component.horizontalPaddingForSize(size)) : 0.0 + let right = (model?.useHorizontalMargins ?? false) ? (model?.rightPadding ?? Padding.Component.horizontalPaddingForSize(size)) : 0.0 + let top = (model?.useVerticalMargins ?? false) ? (model?.topPadding ?? 0.0) : 0.0 + let bottom = (model?.useVerticalMargins ?? false) ? (model?.bottomPadding ?? 0.0) : 0.0 + MVMCoreDispatchUtility.performBlock(onMainThread: { + MVMCoreUIUtility.setMarginsFor(view, leading: left, top: top, trailing: right, bottom: bottom) + }) } open func set(with model: ContainerModelProtocol, for contained: MVMCoreUIViewConstrainingProtocol?) { diff --git a/MVMCoreUI/Containers/Views/Container/ContainerModel.swift b/MVMCoreUI/Containers/Views/Container/ContainerModel.swift index ec6f9769..ea7d8012 100644 --- a/MVMCoreUI/Containers/Views/Container/ContainerModel.swift +++ b/MVMCoreUI/Containers/Views/Container/ContainerModel.swift @@ -9,41 +9,61 @@ import Foundation open class ContainerModel: ContainerModelProtocol, Codable { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- public var horizontalAlignment: UIStackView.Alignment? - public var verticalAlignment: UIStackView.Alignment? public var useHorizontalMargins: Bool? + public var leftPadding: CGFloat? + public var rightPadding: CGFloat? + public var verticalAlignment: UIStackView.Alignment? public var useVerticalMargins: Bool? - public var topMarginPadding: CGFloat? - public var bottomMarginPadding: CGFloat? + public var topPadding: CGFloat? + public var bottomPadding: CGFloat? //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- private enum CodingKeys: String, CodingKey { - case verticalAlignment case horizontalAlignment case useHorizontalMargins + case leftPadding + case rightPadding + case verticalAlignment case useVerticalMargins - case topMarginPadding - case bottomMarginPadding + case topPadding + case bottomPadding } + //-------------------------------------------------- + // MARK: - Subclassable + //-------------------------------------------------- + + /// Sets the default values. Should be called on init. + public func setDefaults() {} + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- - public init() {} + public init() { + setDefaults() + } - public convenience init(horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil) { - self.init() + public init(horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil, useHorizontalMargins: Bool? = nil, leftPadding: CGFloat? = nil, rightPadding: CGFloat? = nil, useVerticalMargins: Bool? = nil, topPadding: CGFloat? = nil, bottomPadding: CGFloat? = nil) { self.horizontalAlignment = horizontalAlignment self.verticalAlignment = verticalAlignment + self.useHorizontalMargins = useHorizontalMargins + self.leftPadding = leftPadding + self.rightPadding = rightPadding + self.useVerticalMargins = useVerticalMargins + self.topPadding = topPadding + self.bottomPadding = bottomPadding + setDefaults() } //-------------------------------------------------- @@ -59,9 +79,12 @@ open class ContainerModel: ContainerModelProtocol, Codable { horizontalAlignment = ContainerHelper.getAlignment(for: horizontalAlignmentString) } useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins) + leftPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .leftPadding) + rightPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .rightPadding) useVerticalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useVerticalMargins) - topMarginPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .topMarginPadding) - bottomMarginPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .bottomMarginPadding) + topPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .topPadding) + bottomPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .bottomPadding) + setDefaults() } open func encode(to encoder: Encoder) throws { @@ -69,9 +92,10 @@ open class ContainerModel: ContainerModelProtocol, Codable { try container.encodeIfPresent(ContainerHelper.getAlignmentString(for: verticalAlignment), forKey: .verticalAlignment) try container.encodeIfPresent(ContainerHelper.getAlignmentString(for: horizontalAlignment), forKey: .horizontalAlignment) try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins) + try container.encodeIfPresent(leftPadding, forKey: .leftPadding) + try container.encodeIfPresent(rightPadding, forKey: .rightPadding) try container.encodeIfPresent(useVerticalMargins, forKey: .useVerticalMargins) - // TODO: can add this back once we have type erasures. - //try container.encodeIfPresent(topMarginPadding, forKey: .topMarginPadding) - //try container.encodeIfPresent(bottomMarginPadding, forKey: .bottomMarginPadding) + try container.encodeIfPresent(topPadding, forKey: .topPadding) + try container.encodeIfPresent(bottomPadding, forKey: .bottomPadding) } } diff --git a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift index 3a8fac64..9ccd1d25 100644 --- a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift @@ -25,9 +25,7 @@ import UIKit /// Total control over the drawn top, bottom, left and right borders. public var disableAllBorders = false { - didSet { - bottomBar?.isHidden = disableAllBorders - } + didSet { bottomBar?.isHidden = disableAllBorders } } private(set) var fieldState: FieldState = .original { diff --git a/MVMCoreUI/Containers/Views/MoleculeContainer.swift b/MVMCoreUI/Containers/Views/MoleculeContainer.swift index 98ecfa28..4e2c6279 100644 --- a/MVMCoreUI/Containers/Views/MoleculeContainer.swift +++ b/MVMCoreUI/Containers/Views/MoleculeContainer.swift @@ -41,9 +41,9 @@ open class MoleculeContainer: Container { guard let containerModel = model as? MoleculeContainerModelProtocol else { return 0 } guard let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(containerModel.molecule), let moleculeHeight = moleculeClass.estimatedHeight(with: containerModel.molecule, delegateObject) else { - return (containerModel.topMarginPadding ?? 0) + (containerModel.bottomMarginPadding ?? 0) + return (containerModel.topPadding ?? 0) + (containerModel.bottomPadding ?? 0) } - return moleculeHeight + (containerModel.topMarginPadding ?? 0) + (containerModel.bottomMarginPadding ?? 0) + return moleculeHeight + (containerModel.topPadding ?? 0) + (containerModel.bottomPadding ?? 0) } public override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { diff --git a/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift b/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift index 01116a48..86693823 100644 --- a/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift +++ b/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift @@ -8,11 +8,17 @@ import Foundation -public class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtocol { +public class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtocol, MoleculeModelProtocol { + public class var identifier: String { + return "container" + } + public var backgroundColor: Color? public var molecule: MoleculeModelProtocol private enum CodingKeys: String, CodingKey { + case moleculeName case molecule + case backgroundColor } public init(with moleculeModel: MoleculeModelProtocol) { @@ -23,12 +29,15 @@ public class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProto required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecule = try typeContainer.decodeModel(codingKey: .molecule) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) 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.encode(moleculeName, forKey: .moleculeName) try container.encodeModel(molecule, forKey: .molecule) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject+Extension.swift b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject+Extension.swift new file mode 100644 index 00000000..5cb2abdb --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject+Extension.swift @@ -0,0 +1,29 @@ +// +// MVMCoreUIViewControllerMappingObject+Extension.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public extension MVMCoreUIViewControllerMappingObject { + func register(template: T.Type) { + add(toTemplateViewControllerMapping: [template.TemplateModel.identifier: MVMCoreViewControllerProgrammaticMappingObject(with: template)!]) + } + + @objc func registerTemplates() { + register(template: MoleculeStackTemplate.self) + add(toTemplateViewControllerMapping: [StackCenteredPageTemplateModel.identifier: MVMCoreViewControllerProgrammaticMappingObject(with: MoleculeStackCenteredTemplate.self)!]) + add(toTemplateViewControllerMapping: ["modalStack": MVMCoreViewControllerProgrammaticMappingObject(with: ModalMoleculeStackTemplate.self)!]) + + register(template: MoleculeListTemplate.self) + add(toTemplateViewControllerMapping: ["modalList": MVMCoreViewControllerProgrammaticMappingObject(with: ModalMoleculeListTemplate.self)!]) + + register(template: ThreeLayerTemplate.self) + register(template: ThreeLayerCenterTemplate.self) + + register(template: CollectionTemplate.self) + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m index 20fb924e..0ad51d4c 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m @@ -12,23 +12,14 @@ @implementation MVMCoreUIViewControllerMappingObject -- (NSMutableDictionary *)viewControllerMapping { - - // Keeps a mapping of the given page type - static dispatch_once_t onceToken; - static NSMutableDictionary *viewControllerMapping; - dispatch_once(&onceToken, ^{ - viewControllerMapping = [@{ - @"stack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackTemplate class]], - @"centerMoleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackCenteredTemplate class]], - @"list" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]], - @"threeLayer" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ThreeLayerTemplate class]], - @"modalStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeStackTemplate class]], - @"modalList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeListTemplate class]], - @"collection" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[CollectionTemplate class]] - } mutableCopy]; - }); - return viewControllerMapping; +- (instancetype)init { + if (self = [super init]) { + if (self.viewControllerMapping == nil) { + self.viewControllerMapping = [NSMutableDictionary dictionary]; + } + [self registerTemplates]; + } + return self; } @end diff --git a/MVMCoreUI/Styles/Styler.swift b/MVMCoreUI/Styles/Styler.swift index b5d6627c..509ad107 100644 --- a/MVMCoreUI/Styles/Styler.swift +++ b/MVMCoreUI/Styles/Styler.swift @@ -10,10 +10,8 @@ import Foundation open class Styler { - //-------------------------------------------------- - // MARK: - Enums - //-------------------------------------------------- + // MARK:- Font Enum public enum Font: String, Codable { case Title2XLarge case TitleXLarge diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/Contents.json b/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/Contents.json new file mode 100644 index 00000000..af2434ac --- /dev/null +++ b/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "alert_standard @1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "alert_standard @2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "alert_standard @3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @1x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @1x.png new file mode 100644 index 00000000..f5fbe367 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @1x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @2x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @2x.png new file mode 100644 index 00000000..d40e7149 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @2x.png differ diff --git a/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @3x.png b/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @3x.png new file mode 100644 index 00000000..00589541 Binary files /dev/null and b/MVMCoreUI/SupportingFiles/Media.xcassets/alert_standard.imageset/alert_standard @3x.png differ diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m index 6fc7121f..bc45656e 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertExpandableView.m @@ -301,11 +301,12 @@ [weakSelf.viewToLayout layoutIfNeeded]; }; + //accessibility - added to make only top alert label and close button accessible. Posted notification when top alert is displayed + weakSelf.accessibilityElements = @[weakSelf.buttonView]; + weakSelf.shortView.isAccessibilityElement = NO; + weakSelf.buttonView.label.accessibilityLabel = [NSString stringWithFormat:@"%@ - %@", [MVMCoreUIUtility hardcodedStringWithKey:@"top_alert_notification"],weakSelf.buttonView.label.accessibilityLabel]; + void(^completion)(void) = ^(void) { - //accessibility - added to make only top alert label and close button accessible. Posted notification when top alert is displayed - weakSelf.accessibilityElements = @[weakSelf.buttonView]; - weakSelf.shortView.isAccessibilityElement = NO; - weakSelf.buttonView.label.accessibilityLabel = [NSString stringWithFormat:@"%@ - %@", [MVMCoreUIUtility hardcodedStringWithKey:@"top_alert_notification"],weakSelf.buttonView.label.accessibilityLabel]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.buttonView.label); [operation markAsFinished]; }; diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m index 4ebfd013..d5ad9a83 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m @@ -198,7 +198,7 @@ [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=space-[button]->=space-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:@{@"space":@(PaddingFive)} views:NSDictionaryOfVariableBindings(button)]]; [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0].active = YES; [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:PaddingThree].active = YES; - [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : PaddingThree)].active = YES; + [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : PaddingFive)].active = YES; self.button = button; } } else { @@ -210,7 +210,7 @@ } if (!self.labelRightConstraint) { - self.labelRightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : PaddingThree)]; + self.labelRightConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.centerView attribute:NSLayoutAttributeRight multiplier:1 constant:(self.closeButton ? PaddingTen : PaddingFive)]; } self.labelRightConstraint.active = YES; } diff --git a/MVMCoreUI/Utility/MFFonts.m b/MVMCoreUI/Utility/MFFonts.m index 533b3a87..99ad05c4 100644 --- a/MVMCoreUI/Utility/MFFonts.m +++ b/MVMCoreUI/Utility/MFFonts.m @@ -84,15 +84,19 @@ NSString * const DS55Rg = @"NHaasGroteskDSStd-55Rg"; + (nonnull UIFont *)mfFont75Bd:(CGFloat)size { - [self loadMVMFonts]; - UIFont *font = [UIFont fontWithName:DS75Bd size:size]; - return font ?: [UIFont boldSystemFontOfSize:size]; + if (size >= 15) { + return [MFFonts mfFontDSBold:size]; + } else { + return [MFFonts mfFontTXBold:size]; + } } + (nonnull UIFont *)mfFont55Rg:(CGFloat)size { - [self loadMVMFonts]; - UIFont *font = [UIFont fontWithName:DS55Rg size:size]; - return font ?: [UIFont systemFontOfSize:size]; + if (size >= 15) { + return [MFFonts mfFontDSRegular:size]; + } else { + return [MFFonts mfFontTXRegular:size]; + } } + (nullable UIFont *)mfFontOcratxt:(CGFloat)size { diff --git a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility+Extension.swift b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility+Extension.swift index 3ee452b0..84a6534c 100644 --- a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility+Extension.swift +++ b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility+Extension.swift @@ -14,10 +14,16 @@ public extension MVMCoreUICommonViewsUtility { let toolbar = self.makeEmptyToolbar() let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - let button = UIBarButtonItem(barButtonSystemItem: .done, target: delegate, action: #selector(ObservingTextFieldDelegate.dismissFieldInput(sender:))) + let button = UIBarButtonItem(barButtonSystemItem: .done, target: delegate, action: #selector(ObservingTextFieldDelegate.dismissFieldInput)) button.tintColor = .black toolbar.setItems([space, button], animated: false) return toolbar } + + static func getView(with height: CGFloat) -> UIView { + let view = self.commonView() + view.heightAnchor.constraint(equalToConstant: height).isActive = true + return view + } }