Merge branch 'develop' into feature/revised_accessibility

This commit is contained in:
Kevin G Christiano 2020-04-27 17:14:04 -04:00
commit 9b570c1a9a
92 changed files with 1695 additions and 656 deletions

View File

@ -94,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 */; };
@ -102,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 */; };
@ -142,6 +145,8 @@
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 */; };
@ -151,7 +156,7 @@
9432A79F23DB47BA00719041 /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9432A79E23DB47BA00719041 /* EntryFieldContainer.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 */; };
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 */; };
9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */; };
@ -189,8 +194,12 @@
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 */; };
@ -199,6 +208,8 @@
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 */; };
@ -232,6 +243,12 @@
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 */; };
@ -529,6 +546,7 @@
0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxLabel.swift; sourceTree = "<group>"; };
0A7ECC5C243CE85300C828E8 /* DoughnutChartItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoughnutChartItemModel.swift; sourceTree = "<group>"; };
0A7ECC5E243CEB1200C828E8 /* ColorViewWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorViewWithLabel.swift; sourceTree = "<group>"; };
0A7ECC6F2441001C00C828E8 /* UIToolbar+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIToolbar+Extension.swift"; sourceTree = "<group>"; };
0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldModel.swift; sourceTree = "<group>"; };
0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryFieldModel.swift; sourceTree = "<group>"; };
0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryFieldModel.swift; sourceTree = "<group>"; };
@ -539,6 +557,8 @@
0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = "<group>"; };
0AA33B33239813C50067DD0F /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = "<group>"; };
0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = "<group>"; };
0AB764D024460F6300E7FE72 /* UIDatePicker+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDatePicker+Extension.swift"; sourceTree = "<group>"; };
0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPickerView+Extension.swift"; sourceTree = "<group>"; };
0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = "<group>"; };
0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = "<group>"; };
0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
@ -579,6 +599,8 @@
8D8067D22444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePriceChangeAllTextAndLinks.swift; sourceTree = "<group>"; };
8DD1E36D243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnInternationalDataModel.swift; sourceTree = "<group>"; };
8DD1E36F243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnInternationalData.swift; sourceTree = "<group>"; };
8DDD6C1C244D90B8006A2232 /* ListThreeColumnDataUsage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsage.swift; sourceTree = "<group>"; };
8DDD6C1E244D90E1006A2232 /* ListThreeColumnDataUsageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageModel.swift; sourceTree = "<group>"; };
8DEFA95B243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageDividerModel.swift; sourceTree = "<group>"; };
8DEFA95D243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageDivider.swift; sourceTree = "<group>"; };
9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftRightLabelModel.swift; sourceTree = "<group>"; };
@ -625,8 +647,12 @@
AA11A42023F15D7000D7962F /* ListRightVariablePaymentsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePaymentsModel.swift; sourceTree = "<group>"; };
AA1EC59624373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnSpeedTestDividerModel.swift; sourceTree = "<group>"; };
AA1EC59824373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnSpeedTestDivider.swift; sourceTree = "<group>"; };
AA2AD115244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkMedium.swift; sourceTree = "<group>"; };
AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkMediumModel.swift; sourceTree = "<group>"; };
AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDividerModel.swift; sourceTree = "<group>"; };
AA56A210243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDivider.swift; sourceTree = "<group>"; };
AA617AAF2453010A00910B8F /* ListDeviceComplexLinkSmall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkSmall.swift; sourceTree = "<group>"; };
AA617AB12453012400910B8F /* ListDeviceComplexLinkSmallModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkSmallModel.swift; sourceTree = "<group>"; };
AA69AAF52445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxBodyText.swift; sourceTree = "<group>"; };
AA69AAF72445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxBodyTextModel.swift; sourceTree = "<group>"; };
AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchCollectionViewCell.swift; sourceTree = "<group>"; };
@ -635,6 +661,8 @@
AAB9C10724346F4B00151545 /* RadioSwatches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatches.swift; sourceTree = "<group>"; };
AAB9C109243496DD00151545 /* RadioSwatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatch.swift; sourceTree = "<group>"; };
AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchesModel.swift; sourceTree = "<group>"; };
BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMediumModel.swift; sourceTree = "<group>"; };
BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexButtonMedium.swift; sourceTree = "<group>"; };
BB2C968D24330EA7006FF80C /* ListRightVariableTextLinkAllTextAndLinksModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListRightVariableTextLinkAllTextAndLinksModel.swift; sourceTree = "<group>"; };
BB2C969124330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListRightVariableTextLinkAllTextAndLinks.swift; sourceTree = "<group>"; };
BB47A585241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextDividerSubsectionModel.swift; sourceTree = "<group>"; };
@ -668,6 +696,12 @@
D202AFE3242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextAlignment+Extension.swift"; sourceTree = "<group>"; };
D202AFE5242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewScrollPosition+Extension.swift"; sourceTree = "<group>"; };
D2092348244A51D40044AD09 /* RadioSwatchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchModel.swift; sourceTree = "<group>"; };
D209234E244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCenterTemplate.swift; sourceTree = "<group>"; };
D2092350244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCenterTemplateModel.swift; sourceTree = "<group>"; };
D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUIViewControllerMappingObject+Extension.swift"; sourceTree = "<group>"; };
D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTemplateModelProtocol.swift; sourceTree = "<group>"; };
D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerModelBase.swift; sourceTree = "<group>"; };
D20923582450ECE00044AD09 /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = "<group>"; };
D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = "<group>"; };
D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = "<group>"; };
D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = "<group>"; };
@ -926,6 +960,7 @@
D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */,
011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */,
D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */,
D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */,
);
path = ModelProtocols;
sourceTree = "<group>";
@ -1065,6 +1100,8 @@
children = (
8DD1E36D243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift */,
8DD1E36F243B3D0500D8F2DF /* ListThreeColumnInternationalData.swift */,
8DDD6C1E244D90E1006A2232 /* ListThreeColumnDataUsageModel.swift */,
8DDD6C1C244D90B8006A2232 /* ListThreeColumnDataUsage.swift */,
);
path = ThreeColumn;
sourceTree = "<group>";
@ -1140,6 +1177,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 = "<group>";
@ -1153,6 +1193,19 @@
path = FourColumn;
sourceTree = "<group>";
};
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 = "<group>";
};
D213347423842FE3008E41B3 /* Controllers */ = {
isa = PBXGroup;
children = (
@ -1351,6 +1404,7 @@
D22B38EA23F4E08B00490EF6 /* List */ = {
isa = PBXGroup;
children = (
D20FFFB42451E32100A31DA2 /* Device */,
52267A0523FFE0A900906CBA /* OneColumn */,
D22D8396241FDE4700D3DF69 /* TwoColumn */,
8DD1E36C243B3CD900D8F2DF /* ThreeColumn */,
@ -1508,6 +1562,7 @@
isa = PBXGroup;
children = (
D22D8392241C27B100D3DF69 /* TemplateModel.swift */,
D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */,
014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */,
D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */,
942C378D2412F5B60066E45E /* ModalMoleculeStackTemplate.swift */,
@ -1519,6 +1574,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 */,
@ -1771,6 +1828,7 @@
D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */,
D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */,
D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */,
D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */,
D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */,
);
path = OtherHandlers;
@ -1876,6 +1934,7 @@
D2B18B7E2360913400A9AEDC /* Control.swift */,
D2B18B802360945C00A9AEDC /* View.swift */,
0AE14F63238315D2005417F8 /* TextField.swift */,
D20923582450ECE00044AD09 /* TableView.swift */,
D2755D7A23689C7500485468 /* TableViewCell.swift */,
D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */,
D264FAA92440F97600D98315 /* CollectionView.swift */,
@ -2096,6 +2155,7 @@
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 */,
@ -2103,6 +2163,7 @@
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 */,
@ -2133,11 +2194,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 */,
@ -2149,6 +2212,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 */,
@ -2184,6 +2248,7 @@
014AA72623C501E2006F3E93 /* ContainerModelProtocol.swift in Sources */,
AA11A42123F15D7000D7962F /* ListRightVariablePaymentsModel.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 */,
@ -2192,12 +2257,13 @@
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 /* WheelModel.swift in Sources */,
EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */,
EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */,
D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */,
DBEFFA04225A829700230692 /* Label.swift in Sources */,
D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */,
@ -2216,14 +2282,18 @@
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 */,
@ -2314,6 +2384,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 */,
@ -2326,9 +2397,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 */,
@ -2392,6 +2466,7 @@
D21B7F73243BAC6800051ABF /* CollectionItemModelProtocol.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 */,
@ -2408,6 +2483,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 */,

View File

@ -9,8 +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 init(_ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
}

View File

@ -9,7 +9,7 @@
import Foundation
public class ActionOpenPanelModel: ActionModelProtocol {
public enum Panel: String, Codable {
case left
case right
@ -23,7 +23,9 @@ public class ActionOpenPanelModel: ActionModelProtocol {
public var extraParameters: JSONValueDictionary?
public var analyticsData: JSONValueDictionary?
public init(panel: Panel) {
public init(panel: Panel, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
self.panel = panel
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
}

View File

@ -9,13 +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?
public init(pageType: String) {
public init(pageType: String, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
self.pageType = pageType
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
}

View File

@ -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 {
@ -118,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
//--------------------------------------------------
@ -136,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)

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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,12 +31,12 @@ 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
//--------------------------------------------------
@ -48,14 +48,6 @@ import UIKit
//--------------------------------------------------
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,7 +252,6 @@ import UIKit
title = model.title
feedback = model.feedback
errorMessage = model.errorMessage
isEnabled = model.enabled
if let isLocked = model.locked {

View File

@ -22,29 +22,22 @@ import Foundation
public var backgroundColor: Color?
public var title: String?
public var feedback: String?
public var errorMessage: String = ""
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
//--------------------------------------------------
@ -62,11 +55,19 @@ import Foundation
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
//--------------------------------------------------
@ -76,17 +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) ?? ""
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) {
@ -100,11 +104,11 @@ 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(enabled, forKey: .enabled)
try container.encode(locked, forKey: .locked)
try container.encode(selected, forKey: .selected)
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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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,37 +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
}
defaultValidationBlock()
uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate
observingTextFieldDelegate = delegateObject?.observingTextFieldDelegate
textField.inputAccessoryView = MVMCoreUICommonViewsUtility.getToolbarWithDoneButton(delegate: observingTextFieldDelegate ?? self)
setupTextFieldToolbar()
}
}

View File

@ -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,8 +28,8 @@
}
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?
//--------------------------------------------------
@ -34,7 +38,6 @@
private enum CodingKeys: String, CodingKey {
case moleculeName
case text
case placeholder
case enabledTextColor
case disabledTextColor
@ -42,27 +45,31 @@
}
//--------------------------------------------------
// 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)
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)
}
}

View File

@ -131,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)
}
//--------------------------------------------------
@ -189,6 +207,8 @@ public typealias ActionBlockConfirmation = () -> (Bool)
layer.cornerRadius = Self.getContainerHeight() / 2.0
knobView.layer.cornerRadius = Self.getKnobHeight() / 2.0
changeStateNoAnimation(isOn)
}
public override func setupView() {
@ -218,9 +238,7 @@ 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() {
@ -228,9 +246,8 @@ public typealias ActionBlockConfirmation = () -> (Bool)
backgroundColor = containerTintColor.off
knobView.backgroundColor = knobTintColor.off
accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel")
isAnimated = true
isOn = false
constrainKnob()
didToggleAction = nil
shouldToggleAction = { return true }
}
@ -352,10 +369,15 @@ public typealias ActionBlockConfirmation = () -> (Bool)
containerTintColor.off = model.offTintColor.uiColor
knobTintColor.on = model.onKnobTintColor.uiColor
knobTintColor.off = model.offKnobTintColor.uiColor
changeStateNoAnimation(model.state)
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) }
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -120,6 +120,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)
@ -150,6 +151,7 @@ import Foundation
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
@ -166,6 +168,11 @@ import Foundation
// Designed Headers
MoleculeObjectMapping.shared()?.register(viewClass: HeadersH2NoButtonsBodyText.self, viewModelClass: HeadersH2NoButtonsBodyTextModel.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)

View File

@ -25,8 +25,8 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol
public override func setDefaults() {
super.setDefaults()
topMarginPadding = PaddingDefaultVerticalSpacing3
bottomMarginPadding = PaddingDefaultVerticalSpacing3
topPadding = PaddingDefaultVerticalSpacing3
bottomPadding = PaddingDefaultVerticalSpacing3
}
//--------------------------------------------------

View File

@ -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<StackModel>
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<StackModel>
// 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<StackModel>.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<StackModel>.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()
}
}

View File

@ -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)
}
}

View File

@ -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<StackModel>
public let stack: Stack<StackModel>
//------------------------------------------------------
// 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<StackModel>.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<StackModel>.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
}
}

View File

@ -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)
}
}

View File

@ -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<StackModel>
public let stack: Stack<StackModel>
//------------------------------------------------------
// 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<StackModel>.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<StackModel>.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
}
}

View File

@ -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)
}
}

View File

@ -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? {

View File

@ -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<StackModel>
//------------------------------------------------------
// MARK: - Initializers
//------------------------------------------------------
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
stack = Stack<StackModel>.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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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]]) {

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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?
}

View File

@ -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 {

View File

@ -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? {

View File

@ -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 {

View File

@ -62,6 +62,32 @@ open class Stack<T>: 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
//--------------------------------------------------

View File

@ -48,6 +48,7 @@ import Foundation
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)
}
@ -58,5 +59,6 @@ import Foundation
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)
}
}

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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
}
}
}

View File

@ -79,6 +79,8 @@ import Foundation
open override func handleNewData() {
topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false
setup()
registerCells()
super.handleNewData()

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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)

View File

@ -9,14 +9,11 @@
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: StackModel
public var footer: MoleculeModelProtocol?
public init(pageType: String, moleculeStack: StackModel) {
self.moleculeStack = moleculeStack
@ -24,16 +21,12 @@ import Foundation
}
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(StackModel.self, forKey: .stack)
header = try typeContainer.decodeModelIfPresent(codingKey: .header)
footer = try typeContainer.decodeModelIfPresent(codingKey: .footer)
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)
}
}

View File

@ -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)

View File

@ -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<ThreeLayerCenterPageTemplateModel> {
open override func spaceBetweenTopAndMiddle() -> CGFloat? {
return nil
}
}

View File

@ -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"
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -8,23 +8,18 @@
import UIKit
@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController, TemplateProtocol {
public var templateModel: ThreeLayerPageTemplateModel?
@objcMembers open class ThreeLayerTemplate<TemplateModel: ThreeLayerPageTemplateModel>: 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? {

View File

@ -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)

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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() {
@ -48,6 +47,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
@ -68,40 +72,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
@ -110,27 +111,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
@ -144,9 +148,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)
@ -162,10 +170,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()
@ -179,56 +197,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()
}
}

View File

@ -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
}
}

View File

@ -405,7 +405,7 @@ import UIKit
}
}
@objc open func dismissFieldInput(sender: Any?) {
@objc open func dismissFieldInput(_ sender: Any?) {
selectedField?.resignFirstResponder()
}

View File

@ -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)
}
}

View File

@ -185,8 +185,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?) {

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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<MVMCoreErrorObject?>?) -> [String]? {

View File

@ -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)
}
}

View File

@ -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<T: TemplateProtocol>(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)
}
}

View File

@ -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

View File

@ -10,10 +10,8 @@ import Foundation
open class Styler {
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
// MARK:- Font Enum
public enum Font: String, Codable {
case Title2XLarge
case TitleXLarge

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

View File

@ -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;
}

View File

@ -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 {

View File

@ -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
}
}