diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 0973ba4b..2323285f 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -8,19 +8,57 @@ /* Begin PBXBuildFile section */ 01004F3022721C3800991ECC /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01004F2F22721C3800991ECC /* RadioButton.swift */; }; + 0103B84E23D7E33A009C315C /* HeadlineBodyToggleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0103B84D23D7E33A009C315C /* HeadlineBodyToggleModel.swift */; }; 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105618A224BBE7700E1557D /* FormValidator.swift */; }; 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105618B224BBE7700E1557D /* FormValidator+TextFields.swift */; }; 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0105618C224BBE7700E1557D /* FormValidator+FormParams.swift */; }; 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */; }; + 011B58F023A2AA980085F53C /* ListItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */; }; + 011B58F223A2AE2C0085F53C /* DropDownListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011B58F123A2AE2C0085F53C /* DropDownListItemModel.swift */; }; + 012A889C23889E8400FE3DA1 /* TemplateModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */; }; + 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88AC238C418100FE3DA1 /* TemplateProtocol.swift */; }; + 012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */; }; + 012A88C2238D7BCA00FE3DA1 /* CarouselItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */; }; + 012A88C4238D86E600FE3DA1 /* CarouselItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */; }; + 012A88C6238DA34000FE3DA1 /* ModuleMoleculeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88C5238DA34000FE3DA1 /* ModuleMoleculeModel.swift */; }; + 012A88C8238DB02000FE3DA1 /* ModelMoleculeDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88C7238DB02000FE3DA1 /* ModelMoleculeDelegateProtocol.swift */; }; + 012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88AE238C626E00FE3DA1 /* CarouselModel.swift */; }; + 012A88EC238F084D00FE3DA1 /* FooterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88EB238F084D00FE3DA1 /* FooterModel.swift */; }; + 012A88F123985E0100FE3DA1 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012A88F023985E0100FE3DA1 /* Color.swift */; }; + 012CA99A2384A687003F810F /* MFTextField+ModelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012CA9992384A687003F810F /* MFTextField+ModelExtension.swift */; }; + 012CA99E2385A2D3003F810F /* MFView+ModelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */; }; + 014AA72423C501E2006F3E93 /* MoleculeContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014AA72123C501E2006F3E93 /* MoleculeContainerModel.swift */; }; + 014AA72523C501E2006F3E93 /* ContainerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014AA72223C501E2006F3E93 /* ContainerModel.swift */; }; + 014AA72623C501E2006F3E93 /* ContainerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */; }; + 014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */; }; + 014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014AA72923C5059B006F3E93 /* StackCenteredPageTemplateModel.swift */; }; + 014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */; }; + 014AA73123C5059B006F3E93 /* ListPageTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014AA72C23C5059B006F3E93 /* ListPageTemplateModel.swift */; }; 01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */; }; 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D902327ECE600EF99AA /* CornerLabels.swift */; }; 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D922327ECFB00EF99AA /* ProgressBar.swift */; }; 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */; }; 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */; }; + 017BEB3C2361EA1D0024EF95 /* MFViewController+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB3B2361EA1D0024EF95 /* MFViewController+Model.swift */; }; + 017BEB4023620A230024EF95 /* TextFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB3F23620A230024EF95 /* TextFieldModel.swift */; }; + 017BEB4223620AD20024EF95 /* FormModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB4123620AD20024EF95 /* FormModelProtocol.swift */; }; + 017BEB442362192F0024EF95 /* MVMCoreUIMoleculeMappingObject+ModelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB432362192F0024EF95 /* MVMCoreUIMoleculeMappingObject+ModelExtension.swift */; }; + 017BEB48236230DB0024EF95 /* MoleculeViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */; }; + 017BEB4A236235BA0024EF95 /* ModelMoleculeViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB49236235BA0024EF95 /* ModelMoleculeViewProtocol.swift */; }; + 017BEB7B236763000024EF95 /* LineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB7A236763000024EF95 /* LineModel.swift */; }; + 017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017BEB7E23676E870024EF95 /* MoleculeObjectMapping.swift */; }; 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0198F79E225679870066C936 /* FormValidationProtocol.swift */; }; 0198F7A62256A80B0066C936 /* MFRadioButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 0198F7A02256A80A0066C936 /* MFRadioButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0198F7A22256A80A0066C936 /* MFRadioButton.m */; }; + 01C851D323CF9E740021F976 /* LabelToggleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C851D223CF9E740021F976 /* LabelToggleModel.swift */; }; 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; + 01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */; }; + 01EB368F23609801006832FA /* LabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368823609801006832FA /* LabelModel.swift */; }; + 01EB369023609801006832FA /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368923609801006832FA /* ListItemModel.swift */; }; + 01EB369223609801006832FA /* MoleculeStackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368B23609801006832FA /* MoleculeStackModel.swift */; }; + 01EB369323609801006832FA /* HeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368C23609801006832FA /* HeaderModel.swift */; }; + 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368D23609801006832FA /* HeadlineBodyModel.swift */; }; + 01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB7E235DECC500C160A2 /* EntryField.swift */; }; @@ -40,23 +78,59 @@ 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */; }; 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */; }; - 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; }; 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */; }; + 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 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 */; }; + 0A7EF86123D8AC2500B2AAD1 /* DigitEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86023D8AC2500B2AAD1 /* DigitEntryFieldModel.swift */; }; + 0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */; }; + 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 */; }; - 0ABD136B237B193A0081388D /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* EntryFieldContainer.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 */; }; + 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */; }; + 31BE15CC23D8924D00452370 /* CheckboxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15CA23D8924C00452370 /* CheckboxModel.swift */; }; + 9432A79F23DB47BA00719041 /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */; }; 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; + 9445890C2385BCE300DE9FD4 /* ProgressBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */; }; + 9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */; }; + 9445891F2385D2E900DE9FD4 /* CaretViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9445891E2385D2E900DE9FD4 /* CaretViewModel.swift */; }; + 944589212385D6E900DE9FD4 /* DashLineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944589202385D6E900DE9FD4 /* DashLineModel.swift */; }; + 944589232385DA9600DE9FD4 /* ImageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 944589222385DA9500DE9FD4 /* ImageViewModel.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; + 946EE1BA237B66D80036751F /* MoleculeModelHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1B9237B66D80036751F /* MoleculeModelHelper.swift */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; + 94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9832386F3F80006CF46 /* LabelAttributeModel.swift */; }; + 94C2D9A123872BCC0006CF46 /* LabelAttributeUnderlineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9A023872BCC0006CF46 /* LabelAttributeUnderlineModel.swift */; }; + 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9A223872C110006CF46 /* LabelAttributeStrikeThroughModel.swift */; }; + 94C2D9A523872C350006CF46 /* LabelAttributeFontModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9A423872C350006CF46 /* LabelAttributeFontModel.swift */; }; + 94C2D9A723872DA90006CF46 /* LabelAttributeColorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9A623872DA90006CF46 /* LabelAttributeColorModel.swift */; }; + 94C2D9A923872E5E0006CF46 /* LabelAttributeImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */; }; + 94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */; }; + 94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */; }; + 94C661DA23CCF4FB00D9FE5B /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B33239813C50067DD0F /* UIColor+Extension.swift */; }; + 94FB966223D797DA003D482B /* MFTextButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 94FB966023D797DA003D482B /* MFTextButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 94FB966323D797DA003D482B /* MFTextButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 94FB966123D797DA003D482B /* MFTextButton.m */; }; C003506123AA94CD00B6AC29 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C003506023AA94CD00B6AC29 /* Button.swift */; }; + C07065C42395677300FBF997 /* Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07065C32395677300FBF997 /* Link.swift */; }; + C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */; }; + C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A68023C9830D00BFB94E /* NumberedListModel.swift */; }; + C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A69323C9909000BFB94E /* DoughnutChartModel.swift */; }; + C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A69523C990BC00BFB94E /* DoughnutChart.swift */; }; + C695A69823C990C200BFB94E /* DoughnutChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C695A69723C990C200BFB94E /* DoughnutChartView.swift */; }; + C6FA7D5223C77A4A00A3614A /* UnOrderedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FA7D4F23C77A4700A3614A /* UnOrderedList.swift */; }; + C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FA7D5023C77A4800A3614A /* StringAndMoleculeStack.swift */; }; + C6FA7D5423C77A4A00A3614A /* NumberedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FA7D5123C77A4900A3614A /* NumberedList.swift */; }; C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; + D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */; }; D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479892314445E003FCCF9 /* LabelSwitch.swift */; }; D224798C231450C8003FCCF9 /* HeadlineBodySwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224798B231450C8003FCCF9 /* HeadlineBodySwitch.swift */; }; D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479932316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift */; }; @@ -71,20 +145,41 @@ D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */; }; D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; }; + D260105323CEA61600764D80 /* ToggleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105223CEA61600764D80 /* ToggleModel.swift */; }; + D260105523CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */; }; + D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105823D0A92900764D80 /* ContainerProtocol.swift */; }; + D260105B23D0BB7100764D80 /* StackModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105A23D0BB7100764D80 /* StackModelProtocol.swift */; }; + D260105D23D0BCD400764D80 /* Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105C23D0BCD400764D80 /* Stack.swift */; }; + D260105F23D0BFFC00764D80 /* StackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260105E23D0BFFC00764D80 /* StackItem.swift */; }; + D260106123D0C02A00764D80 /* StackItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106023D0C02A00764D80 /* StackItemModelProtocol.swift */; }; + D260106323D0C05000764D80 /* StackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106223D0C05000764D80 /* StackItemModel.swift */; }; + D260106523D0CEA700764D80 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.swift */; }; D260D7B122D65BDD007E7233 /* MVMCoreUIPageControl.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; }; D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; - D268C712238D6699007F2C1C /* DropDown.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C711238D6699007F2C1C /* DropDown.swift */; }; - D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; }; + D274CA332236A78900B01B62 /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* FooterView.swift */; }; D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2755D7A23689C7500485468 /* TableViewCell.swift */; }; D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */; }; D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */; }; D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */; }; D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */; }; D282AACB2243C61700C46919 /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AACA2243C61700C46919 /* ButtonView.swift */; }; - D296E13C229598BF0051EBE7 /* MoleculeListCellProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E13B2295969C0051EBE7 /* MoleculeListCellProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D296E1412295EBBA0051EBE7 /* MoleculeDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E1402295EBBA0051EBE7 /* MoleculeDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D28A837723C79FC600DFE4FC /* MFCustomButton+ActionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837623C79FC600DFE4FC /* MFCustomButton+ActionModel.swift */; }; + D28A837923C7D5BC00DFE4FC /* PageModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */; }; + D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837A23C928DA00DFE4FC /* MoleculeListCellProtocol.swift */; }; + D28A837D23CCA86A00DFE4FC /* TabsListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837C23CCA86A00DFE4FC /* TabsListItemModel.swift */; }; + D28A837F23CCA96400DFE4FC /* TabsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A837E23CCA96400DFE4FC /* TabsModel.swift */; }; + D28A838123CCB0D800DFE4FC /* AccordionListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838023CCB0D800DFE4FC /* AccordionListItemModel.swift */; }; + D28A838323CCBD3F00DFE4FC /* CircleProgressModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */; }; + D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838423CCCA8900DFE4FC /* ScrollerModel.swift */; }; + D28A838923CCCFCB00DFE4FC /* LinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838823CCCFCB00DFE4FC /* LinkModel.swift */; }; + D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */; }; + D28A838D23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */; }; + D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */; }; + D28A839123CD4FD400DFE4FC /* CornerLabelsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839023CD4FD400DFE4FC /* CornerLabelsModel.swift */; }; + D28A839323CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */; }; D296E14722A5984C0051EBE7 /* MVMCoreUIViewConstrainingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */; }; D29770C921F7C4AE00B2F0D0 /* TopLabelsView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -119,10 +214,8 @@ D29DF17421E69E1F003B2FB9 /* MFCustomButton.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF16A21E69E1F003B2FB9 /* MFCustomButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF17521E69E1F003B2FB9 /* ButtonDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF17621E69E1F003B2FB9 /* PrimaryButton.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF17721E69E1F003B2FB9 /* MFTextButton.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF16D21E69E1F003B2FB9 /* MFTextButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF17A21E69E1F003B2FB9 /* MFCustomButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */; }; D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */; }; - D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF17221E69E1F003B2FB9 /* MFTextButton.m */; }; D29DF18021E69E49003B2FB9 /* MFView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF17E21E69E2E003B2FB9 /* MFView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF18121E69E50003B2FB9 /* MFView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF17F21E69E2E003B2FB9 /* MFView.m */; }; D29DF18221E69E54003B2FB9 /* SeparatorView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF15921E697DA003B2FB9 /* SeparatorView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -187,13 +280,16 @@ D29DF32521ED0DA2003B2FB9 /* TextButtonView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF32321ED0DA2003B2FB9 /* TextButtonView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF32C21EE8736003B2FB9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D29DF32821EE8736003B2FB9 /* Localizable.strings */; }; D29DF32E21EE8C3D003B2FB9 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D29DF32D21EE8C3D003B2FB9 /* Media.xcassets */; }; + D29E28D823D21AB800ACEA85 /* StringAndMoleculeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29E28D723D21AB800ACEA85 /* StringAndMoleculeView.swift */; }; + D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29E28D923D21AFA00ACEA85 /* StringAndMoleculeModel.swift */; }; + D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29E28DC23D7404C00ACEA85 /* ContainerHelper.swift */; }; D2A514582211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */; }; D2A5145D2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */; }; D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */; }; D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */; }; - D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514662213885800345BFB /* StandardHeaderView.swift */; }; + D2A514672213885800345BFB /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514662213885800345BFB /* HeaderView.swift */; }; D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; }; D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A638FC22CA98280052ED1F /* HeadlineBody.swift */; }; D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A6390022CBB1820052ED1F /* Carousel.swift */; }; @@ -210,8 +306,15 @@ D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; }; D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */; }; D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */; }; + D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A98223D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift */; }; + D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99323D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift */; }; + D2E2A99623D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99523D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift */; }; + D2E2A99823D8D63C000B42E6 /* ActionDetailWithImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99723D8D63C000B42E6 /* ActionDetailWithImageModel.swift */; }; + D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99923D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift */; }; + D2E2A99C23D8D975000B42E6 /* ImageHeadlineBodyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99B23D8D975000B42E6 /* ImageHeadlineBodyModel.swift */; }; + D2E2A99D23DA3217000B42E6 /* UIStackViewAlignment+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */; }; D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */; }; - D2FB151D23A40F1500C20E10 /* StackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151C23A40F1500C20E10 /* StackItem.swift */; }; + D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */; }; DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; DBC4391822442197001AB423 /* CaretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391622442196001AB423 /* CaretView.swift */; }; DBC4391922442197001AB423 /* DashLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391722442197001AB423 /* DashLine.swift */; }; @@ -222,43 +325,119 @@ /* Begin PBXFileReference section */ 01004F2F22721C3800991ECC /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; + 0103B84D23D7E33A009C315C /* HeadlineBodyToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyToggleModel.swift; sourceTree = ""; }; 0105618A224BBE7700E1557D /* FormValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormValidator.swift; sourceTree = ""; }; 0105618B224BBE7700E1557D /* FormValidator+TextFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FormValidator+TextFields.swift"; sourceTree = ""; }; 0105618C224BBE7700E1557D /* FormValidator+FormParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FormValidator+FormParams.swift"; sourceTree = ""; }; 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonModel.swift; sourceTree = ""; }; + 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModelProtocol.swift; sourceTree = ""; }; + 011B58F123A2AE2C0085F53C /* DropDownListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDownListItemModel.swift; sourceTree = ""; }; + 012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModelProtocol.swift; sourceTree = ""; }; + 012A88AC238C418100FE3DA1 /* TemplateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateProtocol.swift; sourceTree = ""; }; + 012A88AE238C626E00FE3DA1 /* CarouselModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselModel.swift; sourceTree = ""; }; + 012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselPagingModelProtocol.swift; sourceTree = ""; }; + 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselItemModel.swift; sourceTree = ""; }; + 012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselItemModelProtocol.swift; sourceTree = ""; }; + 012A88C5238DA34000FE3DA1 /* ModuleMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleMoleculeModel.swift; sourceTree = ""; }; + 012A88C7238DB02000FE3DA1 /* ModelMoleculeDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelMoleculeDelegateProtocol.swift; sourceTree = ""; }; + 012A88EB238F084D00FE3DA1 /* FooterModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterModel.swift; sourceTree = ""; }; + 012A88F023985E0100FE3DA1 /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; + 012CA9992384A687003F810F /* MFTextField+ModelExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFTextField+ModelExtension.swift"; sourceTree = ""; }; + 012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFView+ModelExtension.swift"; sourceTree = ""; }; + 014AA72123C501E2006F3E93 /* MoleculeContainerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeContainerModel.swift; sourceTree = ""; }; + 014AA72223C501E2006F3E93 /* ContainerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerModel.swift; sourceTree = ""; }; + 014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerModelProtocol.swift; sourceTree = ""; }; + 014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackPageTemplateModel.swift; sourceTree = ""; }; + 014AA72923C5059B006F3E93 /* StackCenteredPageTemplateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackCenteredPageTemplateModel.swift; sourceTree = ""; }; + 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeLayerPageTemplateModel.swift; sourceTree = ""; }; + 014AA72C23C5059B006F3E93 /* ListPageTemplateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListPageTemplateModel.swift; sourceTree = ""; }; 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeTableViewCell.swift; sourceTree = ""; }; 01509D902327ECE600EF99AA /* CornerLabels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; 01509D922327ECFB00EF99AA /* ProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyTextButtonSwitch.swift; sourceTree = ""; }; 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButtonLabel.swift; sourceTree = ""; }; + 017BEB3B2361EA1D0024EF95 /* MFViewController+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFViewController+Model.swift"; sourceTree = ""; }; + 017BEB3F23620A230024EF95 /* TextFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldModel.swift; sourceTree = ""; }; + 017BEB4123620AD20024EF95 /* FormModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormModelProtocol.swift; sourceTree = ""; }; + 017BEB432362192F0024EF95 /* MVMCoreUIMoleculeMappingObject+ModelExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUIMoleculeMappingObject+ModelExtension.swift"; sourceTree = ""; }; + 017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeViewProtocol.swift; sourceTree = ""; }; + 017BEB49236235BA0024EF95 /* ModelMoleculeViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelMoleculeViewProtocol.swift; sourceTree = ""; }; + 017BEB7A236763000024EF95 /* LineModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineModel.swift; sourceTree = ""; }; + 017BEB7E23676E870024EF95 /* MoleculeObjectMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeObjectMapping.swift; sourceTree = ""; }; 0198F79E225679870066C936 /* FormValidationProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormValidationProtocol.swift; sourceTree = ""; }; 0198F7A02256A80A0066C936 /* MFRadioButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFRadioButton.h; sourceTree = ""; }; 0198F7A22256A80A0066C936 /* MFRadioButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFRadioButton.m; sourceTree = ""; }; + 01C851D223CF9E740021F976 /* LabelToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelToggleModel.swift; sourceTree = ""; }; + 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeModelProtocol.swift; sourceTree = ""; }; + 01EB368823609801006832FA /* LabelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelModel.swift; sourceTree = ""; }; + 01EB368923609801006832FA /* ListItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; + 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeStackItemModel.swift; sourceTree = ""; }; + 01EB368B23609801006832FA /* MoleculeStackModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeStackModel.swift; sourceTree = ""; }; + 01EB368C23609801006832FA /* HeaderModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderModel.swift; sourceTree = ""; }; + 01EB368D23609801006832FA /* HeadlineBodyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyModel.swift; sourceTree = ""; }; + 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretLinkModel.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; + 0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackViewAlignment+Extension.swift"; sourceTree = ""; }; 0A21DB7E235DECC500C160A2 /* EntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryField.swift; sourceTree = ""; }; 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryField.swift; sourceTree = ""; }; 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryField.swift; sourceTree = ""; }; 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryField.swift; sourceTree = ""; }; - 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = ""; }; 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleGuidelinesProtocol.swift; sourceTree = ""; }; + 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryField.swift; sourceTree = ""; }; 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; - 0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; + 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldModel.swift; sourceTree = ""; }; + 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEntryFieldModel.swift; sourceTree = ""; }; + 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdnEntryFieldModel.swift; sourceTree = ""; }; + 0A7EF86023D8AC2500B2AAD1 /* DigitEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitEntryFieldModel.swift; sourceTree = ""; }; + 0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDropdownEntryFieldModel.swift; sourceTree = ""; }; + 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryFieldModel.swift; sourceTree = ""; }; + 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryFieldModel.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; - 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; + 0AA33B33239813C50067DD0F /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; + 0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryField.swift; sourceTree = ""; }; 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryField.swift; sourceTree = ""; }; 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; + 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxLabelModel.swift; sourceTree = ""; }; + 31BE15CA23D8924C00452370 /* CheckboxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxModel.swift; sourceTree = ""; }; + 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftRightLabelModel.swift; sourceTree = ""; }; + 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = ""; }; + 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBarModel.swift; sourceTree = ""; }; + 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgressModel.swift; sourceTree = ""; }; + 9445891E2385D2E900DE9FD4 /* CaretViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaretViewModel.swift; sourceTree = ""; }; + 944589202385D6E900DE9FD4 /* DashLineModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashLineModel.swift; sourceTree = ""; }; + 944589222385DA9500DE9FD4 /* ImageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewModel.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; + 946EE1B9237B66D80036751F /* MoleculeModelHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeModelHelper.swift; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; + 94C2D9832386F3F80006CF46 /* LabelAttributeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeModel.swift; sourceTree = ""; }; + 94C2D9A023872BCC0006CF46 /* LabelAttributeUnderlineModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeUnderlineModel.swift; sourceTree = ""; }; + 94C2D9A223872C110006CF46 /* LabelAttributeStrikeThroughModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeStrikeThroughModel.swift; sourceTree = ""; }; + 94C2D9A423872C350006CF46 /* LabelAttributeFontModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeFontModel.swift; sourceTree = ""; }; + 94C2D9A623872DA90006CF46 /* LabelAttributeColorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeColorModel.swift; sourceTree = ""; }; + 94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeImageModel.swift; sourceTree = ""; }; + 94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeActionModel.swift; sourceTree = ""; }; + 94FB966023D797DA003D482B /* MFTextButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTextButton.h; sourceTree = ""; }; + 94FB966123D797DA003D482B /* MFTextButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTextButton.m; sourceTree = ""; }; C003506023AA94CD00B6AC29 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; + C07065C32395677300FBF997 /* Link.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Link.swift; sourceTree = ""; }; + C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnOrderedListModel.swift; sourceTree = ""; }; + C695A68023C9830D00BFB94E /* NumberedListModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberedListModel.swift; sourceTree = ""; }; + C695A69323C9909000BFB94E /* DoughnutChartModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoughnutChartModel.swift; sourceTree = ""; }; + C695A69523C990BC00BFB94E /* DoughnutChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoughnutChart.swift; sourceTree = ""; }; + C695A69723C990C200BFB94E /* DoughnutChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoughnutChartView.swift; sourceTree = ""; }; + C6FA7D4F23C77A4700A3614A /* UnOrderedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnOrderedList.swift; sourceTree = ""; }; + C6FA7D5023C77A4800A3614A /* StringAndMoleculeStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringAndMoleculeStack.swift; sourceTree = ""; }; + C6FA7D5123C77A4900A3614A /* NumberedList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberedList.swift; sourceTree = ""; }; C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadLineBodyCaretLinkImage.swift; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; + D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLayoutConstraintAxis+Extension.swift"; sourceTree = ""; }; D22479892314445E003FCCF9 /* LabelSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelSwitch.swift; sourceTree = ""; }; D224798B231450C8003FCCF9 /* HeadlineBodySwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodySwitch.swift; sourceTree = ""; }; D22479932316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLayoutConstraintExtension.swift; sourceTree = ""; }; @@ -273,20 +452,40 @@ D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIStackableViewController.h; sourceTree = ""; }; D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIStackableViewController.m; sourceTree = ""; }; D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + D260105223CEA61600764D80 /* ToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleModel.swift; sourceTree = ""; }; + D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUISwitch+Model.swift"; sourceTree = ""; }; + D260105823D0A92900764D80 /* ContainerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerProtocol.swift; sourceTree = ""; }; + D260105A23D0BB7100764D80 /* StackModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackModelProtocol.swift; sourceTree = ""; }; + D260105C23D0BCD400764D80 /* Stack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stack.swift; sourceTree = ""; }; + D260105E23D0BFFC00764D80 /* StackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItem.swift; sourceTree = ""; }; + D260106023D0C02A00764D80 /* StackItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItemModelProtocol.swift; sourceTree = ""; }; + D260106223D0C05000764D80 /* StackItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItemModel.swift; sourceTree = ""; }; + D260106423D0CEA700764D80 /* StackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackModel.swift; sourceTree = ""; }; D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPageControl.h; sourceTree = ""; }; D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = ""; }; D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; - D268C711238D6699007F2C1C /* DropDown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDown.swift; sourceTree = ""; }; - D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = ""; }; + D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; D2755D7A23689C7500485468 /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; }; D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewCell.swift; sourceTree = ""; }; D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EyebrowHeadlineBodyLink.swift; sourceTree = ""; }; D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFLoadImageView.swift; sourceTree = ""; }; D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFTransparentGIFView.swift; sourceTree = ""; }; D282AACA2243C61700C46919 /* ButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = ""; }; - D296E13B2295969C0051EBE7 /* MoleculeListCellProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MoleculeListCellProtocol.h; sourceTree = ""; }; - D296E1402295EBBA0051EBE7 /* MoleculeDelegateProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MoleculeDelegateProtocol.h; sourceTree = ""; }; + D28A837623C79FC600DFE4FC /* MFCustomButton+ActionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MFCustomButton+ActionModel.swift"; sourceTree = ""; }; + D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageModelProtocol.swift; sourceTree = ""; }; + D28A837A23C928DA00DFE4FC /* MoleculeListCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListCellProtocol.swift; sourceTree = ""; }; + D28A837C23CCA86A00DFE4FC /* TabsListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsListItemModel.swift; sourceTree = ""; }; + D28A837E23CCA96400DFE4FC /* TabsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsModel.swift; sourceTree = ""; }; + D28A838023CCB0D800DFE4FC /* AccordionListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccordionListItemModel.swift; sourceTree = ""; }; + D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleProgressModel.swift; sourceTree = ""; }; + D28A838423CCCA8900DFE4FC /* ScrollerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollerModel.swift; sourceTree = ""; }; + D28A838823CCCFCB00DFE4FC /* LinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkModel.swift; sourceTree = ""; }; + D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonModel.swift; sourceTree = ""; }; + D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PrimaryButton+MoleculeProtocolExtension.swift"; sourceTree = ""; }; + D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonViewModel.swift; sourceTree = ""; }; + D28A839023CD4FD400DFE4FC /* CornerLabelsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabelsModel.swift; sourceTree = ""; }; + D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyCaretLinkImageModel.swift; sourceTree = ""; }; D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewConstrainingProtocol.h; sourceTree = ""; }; D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopLabelsView.m; sourceTree = ""; }; D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TopLabelsView.h; sourceTree = ""; }; @@ -333,10 +532,8 @@ D29DF16A21E69E1F003B2FB9 /* MFCustomButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFCustomButton.h; sourceTree = ""; }; D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ButtonDelegateProtocol.h; sourceTree = ""; }; D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PrimaryButton.h; sourceTree = ""; }; - D29DF16D21E69E1F003B2FB9 /* MFTextButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFTextButton.h; sourceTree = ""; }; D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFCustomButton.m; sourceTree = ""; }; D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PrimaryButton.m; sourceTree = ""; }; - D29DF17221E69E1F003B2FB9 /* MFTextButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTextButton.m; sourceTree = ""; }; D29DF17E21E69E2E003B2FB9 /* MFView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MFView.h; sourceTree = ""; }; D29DF17F21E69E2E003B2FB9 /* MFView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MFView.m; sourceTree = ""; }; D29DF24221E6A176003B2FB9 /* MFTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTextField.m; sourceTree = ""; }; @@ -404,13 +601,16 @@ D29DF32A21EE8736003B2FB9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; D29DF32B21EE8736003B2FB9 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; D29DF32D21EE8C3D003B2FB9 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; + D29E28D723D21AB800ACEA85 /* StringAndMoleculeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAndMoleculeView.swift; sourceTree = ""; }; + D29E28D923D21AFA00ACEA85 /* StringAndMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringAndMoleculeModel.swift; sourceTree = ""; }; + D29E28DC23D7404C00ACEA85 /* ContainerHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerHelper.swift; sourceTree = ""; }; D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIMoleculeMappingObject.h; sourceTree = ""; }; D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIMoleculeMappingObject.m; sourceTree = ""; }; D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIMoleculeViewProtocol.h; sourceTree = ""; }; D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackView.swift; sourceTree = ""; }; D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackTemplate.swift; sourceTree = ""; }; D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackCenteredTemplate.swift; sourceTree = ""; }; - D2A514662213885800345BFB /* StandardHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardHeaderView.swift; sourceTree = ""; }; + D2A514662213885800345BFB /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerViewController.swift; sourceTree = ""; }; D2A638FC22CA98280052ED1F /* HeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBody.swift; sourceTree = ""; }; D2A6390022CBB1820052ED1F /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = ""; }; @@ -427,9 +627,15 @@ D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIDelegateObject.swift; sourceTree = ""; }; D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTableViewController.swift; sourceTree = ""; }; D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListTemplate.swift; sourceTree = ""; }; + D2E2A98223D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EyebrowHeadlineBodyLinkModel.swift; sourceTree = ""; }; + D2E2A99323D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyLinkModel.swift; sourceTree = ""; }; + D2E2A99523D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyLinkToggleModel.swift; sourceTree = ""; }; + D2E2A99723D8D63C000B42E6 /* ActionDetailWithImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImageModel.swift; sourceTree = ""; }; + D2E2A99923D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButtonModel.swift; sourceTree = ""; }; + D2E2A99B23D8D975000B42E6 /* ImageHeadlineBodyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeadlineBodyModel.swift; sourceTree = ""; }; D2F4DDE52371A4CB00CD28BB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeContainer.swift; sourceTree = ""; }; - D2FB151C23A40F1500C20E10 /* StackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItem.swift; sourceTree = ""; }; + D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackItem.swift; sourceTree = ""; }; DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftRightLabelView.swift; sourceTree = ""; }; DB891E822253FA8500022516 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; DBC4391622442196001AB423 /* CaretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretView.swift; sourceTree = ""; }; @@ -451,6 +657,38 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 011B58EE23A2AA850085F53C /* ModelProtocols */ = { + isa = PBXGroup; + children = ( + 014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */, + 017BEB4123620AD20024EF95 /* FormModelProtocol.swift */, + 012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */, + 012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */, + 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */, + 012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */, + D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */, + 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */, + ); + path = ModelProtocols; + sourceTree = ""; + }; + 012A88EF23985E0100FE3DA1 /* CustomPrimitives */ = { + isa = PBXGroup; + children = ( + 012A88F023985E0100FE3DA1 /* Color.swift */, + ); + path = CustomPrimitives; + sourceTree = ""; + }; + 01509D96232803B200EF99AA /* Models */ = { + isa = PBXGroup; + children = ( + 011B58EE23A2AA850085F53C /* ModelProtocols */, + 946EE1B5237B663A0036751F /* Extensions */, + ); + path = Models; + sourceTree = ""; + }; 01C74D87224298E2009C25A3 /* FormUIHelpers */ = { isa = PBXGroup; children = ( @@ -462,14 +700,6 @@ path = FormUIHelpers; sourceTree = ""; }; - 0ABD1369237B18EE0081388D /* views */ = { - isa = PBXGroup; - children = ( - 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */, - ); - path = views; - sourceTree = ""; - }; 0A5D59C323AD488600EFD9E9 /* Protocols */ = { isa = PBXGroup; children = ( @@ -478,6 +708,48 @@ path = Protocols; sourceTree = ""; }; + 0ABD1369237B18EE0081388D /* Views */ = { + isa = PBXGroup; + children = ( + 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */, + D29E28DE23D740FC00ACEA85 /* Container */, + 014AA72123C501E2006F3E93 /* MoleculeContainerModel.swift */, + D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */, + ); + path = Views; + sourceTree = ""; + }; + 946EE1B5237B663A0036751F /* Extensions */ = { + isa = PBXGroup; + children = ( + 946EE1B9237B66D80036751F /* MoleculeModelHelper.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 94C2D9822386F3E30006CF46 /* Label */ = { + isa = PBXGroup; + children = ( + 01EB368823609801006832FA /* LabelModel.swift */, + 94C2D9832386F3F80006CF46 /* LabelAttributeModel.swift */, + 94C2D9A023872BCC0006CF46 /* LabelAttributeUnderlineModel.swift */, + 94C2D9A223872C110006CF46 /* LabelAttributeStrikeThroughModel.swift */, + 94C2D9A423872C350006CF46 /* LabelAttributeFontModel.swift */, + 94C2D9A623872DA90006CF46 /* LabelAttributeColorModel.swift */, + 94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */, + 94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */, + DB891E822253FA8500022516 /* Label.swift */, + ); + path = Label; + sourceTree = ""; + }; + 94FB5B83238D892800EB2193 /* Recovered References */ = { + isa = PBXGroup; + children = ( + ); + name = "Recovered References"; + sourceTree = ""; + }; D213347423842FE3008E41B3 /* Controllers */ = { isa = PBXGroup; children = ( @@ -494,6 +766,8 @@ D213347523842FF5008E41B3 /* Views */ = { isa = PBXGroup; children = ( + 94FB966023D797DA003D482B /* MFTextButton.h */, + 94FB966123D797DA003D482B /* MFTextButton.m */, D29DF17E21E69E2E003B2FB9 /* MFView.h */, D29DF17F21E69E2E003B2FB9 /* MFView.m */, D29DF31E21ED0CBA003B2FB9 /* LabelView.h */, @@ -506,6 +780,42 @@ D22D1F19220341F50077CEC0 /* MVMCoreUICheckBox.m */, 0198F7A02256A80A0066C936 /* MFRadioButton.h */, 0198F7A22256A80A0066C936 /* MFRadioButton.m */, + D29DF28721E7AC2B003B2FB9 /* ViewConstrainingView.h */, + D29DF28821E7AC2B003B2FB9 /* ViewConstrainingView.m */, + D29DF2AD21E7B3A4003B2FB9 /* MFTextView.h */, + D29DF2AB21E7B3A4003B2FB9 /* MFTextView.m */, + D29DF2AC21E7B3A4003B2FB9 /* MFTextView.xib */, + D29DF2B121E7B76C003B2FB9 /* MFLoadingSpinner.h */, + D29DF2B221E7B76D003B2FB9 /* MFLoadingSpinner.m */, + D29DF32321ED0DA2003B2FB9 /* TextButtonView.h */, + D29DF32221ED0DA2003B2FB9 /* TextButtonView.m */, + D29770FB21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h */, + D29770FA21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m */, + D22D1F44220496A30077CEC0 /* MVMCoreUISwitch.h */, + D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */, + D29DF16A21E69E1F003B2FB9 /* MFCustomButton.h */, + D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */, + D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */, + D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */, + D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */, + D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */, + D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, + D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, + D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, + D29DF24C21E6A177003B2FB9 /* MFTextField.h */, + D29DF24221E6A176003B2FB9 /* MFTextField.m */, + D29DF24421E6A176003B2FB9 /* MFTextField.xib */, + D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */, + D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */, + D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */, + D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */, + D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */, + D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */, + D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, + D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, + D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, + D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */, + D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, ); path = Views; sourceTree = ""; @@ -513,8 +823,11 @@ D224798823142BF2003FCCF9 /* SwitchMolecules */ = { isa = PBXGroup; children = ( + D2E2A99523D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift */, 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */, + 01C851D223CF9E740021F976 /* LabelToggleModel.swift */, D22479892314445E003FCCF9 /* LabelSwitch.swift */, + 0103B84D23D7E33A009C315C /* HeadlineBodyToggleModel.swift */, D224798B231450C8003FCCF9 /* HeadlineBodySwitch.swift */, ); path = SwitchMolecules; @@ -523,10 +836,17 @@ D224798D2316A988003FCCF9 /* VerticalCombinationViews */ = { isa = PBXGroup; children = ( + D29E28D423D1FFFA00ACEA85 /* Lists */, + 01EB368D23609801006832FA /* HeadlineBodyModel.swift */, D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, + D2E2A99323D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift */, D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */, + D2E2A98223D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift */, D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */, + D28A839223CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift */, C7192E7C23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift */, + D2E2A99923D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift */, + 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */, ); path = VerticalCombinationViews; sourceTree = ""; @@ -534,8 +854,11 @@ D224798E2316A995003FCCF9 /* HorizontalCombinationViews */ = { isa = PBXGroup; children = ( + D2E2A99B23D8D975000B42E6 /* ImageHeadlineBodyModel.swift */, D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */, + D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */, D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */, + D28A837E23CCA96400DFE4FC /* TabsModel.swift */, ); path = HorizontalCombinationViews; sourceTree = ""; @@ -543,6 +866,9 @@ D224798F2316A99F003FCCF9 /* LeftRightViews */ = { isa = PBXGroup; children = ( + D2E2A99723D8D63C000B42E6 /* ActionDetailWithImageModel.swift */, + 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */, + D28A839023CD4FD400DFE4FC /* CornerLabelsModel.swift */, 01509D902327ECE600EF99AA /* CornerLabels.swift */, D224798823142BF2003FCCF9 /* SwitchMolecules */, ); @@ -552,7 +878,12 @@ D22479902316A9CB003FCCF9 /* Organisms */ = { isa = PBXGroup; children = ( + D260105A23D0BB7100764D80 /* StackModelProtocol.swift */, + D260106423D0CEA700764D80 /* StackModel.swift */, + D260105C23D0BCD400764D80 /* Stack.swift */, + 01EB368B23609801006832FA /* MoleculeStackModel.swift */, D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */, + 012A88AE238C626E00FE3DA1 /* CarouselModel.swift */, D2A6390022CBB1820052ED1F /* Carousel.swift */, ); path = Organisms; @@ -562,12 +893,21 @@ isa = PBXGroup; children = ( D2755D7A23689C7500485468 /* TableViewCell.swift */, + 01EB368923609801006832FA /* ListItemModel.swift */, 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */, + 012A88C1238D7BCA00FE3DA1 /* CarouselItemModel.swift */, D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, + D28A838023CCB0D800DFE4FC /* AccordionListItemModel.swift */, D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, + D28A837C23CCA86A00DFE4FC /* TabsListItemModel.swift */, D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */, + 011B58F123A2AE2C0085F53C /* DropDownListItemModel.swift */, D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */, - D2FB151C23A40F1500C20E10 /* StackItem.swift */, + D260106023D0C02A00764D80 /* StackItemModelProtocol.swift */, + D260106223D0C05000764D80 /* StackItemModel.swift */, + D260105E23D0BFFC00764D80 /* StackItem.swift */, + 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */, + D2FB151C23A40F1500C20E10 /* MoleculeStackItem.swift */, ); path = Items; sourceTree = ""; @@ -581,12 +921,23 @@ path = Legacy; sourceTree = ""; }; + D260105723CF9CC500764D80 /* Doughnut */ = { + isa = PBXGroup; + children = ( + C695A69323C9909000BFB94E /* DoughnutChartModel.swift */, + C695A69523C990BC00BFB94E /* DoughnutChart.swift */, + C695A69723C990C200BFB94E /* DoughnutChartView.swift */, + ); + path = Doughnut; + sourceTree = ""; + }; D29DF0C221E404D4003B2FB9 = { isa = PBXGroup; children = ( D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */, D29DF0CD21E404D4003B2FB9 /* Products */, D29DF0E421E4F3C7003B2FB9 /* Frameworks */, + 94FB5B83238D892800EB2193 /* Recovered References */, ); sourceTree = ""; }; @@ -601,6 +952,8 @@ D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */ = { isa = PBXGroup; children = ( + 01509D96232803B200EF99AA /* Models */, + 012A88EF23985E0100FE3DA1 /* CustomPrimitives */, D2B18B7D236090D500A9AEDC /* BaseClasses */, 01C74D87224298E2009C25A3 /* FormUIHelpers */, D29DF31421ECECA7003B2FB9 /* SupportingFiles */, @@ -625,10 +978,15 @@ D29DF0DF21E418B2003B2FB9 /* Templates */ = { isa = PBXGroup; children = ( + 012A88AC238C418100FE3DA1 /* TemplateProtocol.swift */, + 014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */, D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */, + 014AA72923C5059B006F3E93 /* StackCenteredPageTemplateModel.swift */, D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */, - D296E13B2295969C0051EBE7 /* MoleculeListCellProtocol.h */, + D28A837A23C928DA00DFE4FC /* MoleculeListCellProtocol.swift */, + 014AA72C23C5059B006F3E93 /* ListPageTemplateModel.swift */, D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */, + 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */, D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */, ); path = Templates; @@ -660,18 +1018,19 @@ D224798F2316A99F003FCCF9 /* LeftRightViews */, D224798E2316A995003FCCF9 /* HorizontalCombinationViews */, D224798D2316A988003FCCF9 /* VerticalCombinationViews */, - D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, - 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */, - D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */, - D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, - D2A514662213885800345BFB /* StandardHeaderView.swift */, - D274CA322236A78900B01B62 /* StandardFooterView.swift */, + 01EB368C23609801006832FA /* HeaderModel.swift */, + D2A514662213885800345BFB /* HeaderView.swift */, + 012A88EB238F084D00FE3DA1 /* FooterModel.swift */, + D274CA322236A78900B01B62 /* FooterView.swift */, 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */, + 012A88C5238DA34000FE3DA1 /* ModuleMoleculeModel.swift */, D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */, + D28A838423CCCA8900DFE4FC /* ScrollerModel.swift */, D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */, - 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */, 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */, - D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */, + 017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */, + 017BEB49236235BA0024EF95 /* ModelMoleculeViewProtocol.swift */, + D260105723CF9CC500764D80 /* Doughnut */, ); path = Molecules; sourceTree = ""; @@ -681,6 +1040,7 @@ children = ( D29DF16021E69996003B2FB9 /* MFViewController.h */, D29DF15F21E69996003B2FB9 /* MFViewController.m */, + 017BEB3B2361EA1D0024EF95 /* MFViewController+Model.swift */, D29DF28F21E7ADB8003B2FB9 /* MFScrollingViewController.h */, D29DF29021E7ADB8003B2FB9 /* MFScrollingViewController.m */, D29DF29121E7ADB8003B2FB9 /* ProgrammaticScrollViewController.h */, @@ -706,6 +1066,7 @@ D29DF11221E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h */, D29DF11421E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m */, D22479932316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift */, + 0AA33B33239813C50067DD0F /* UIColor+Extension.swift */, ); path = Categories; sourceTree = ""; @@ -713,11 +1074,10 @@ D29DF11921E68467003B2FB9 /* Containers */ = { isa = PBXGroup; children = ( - 0ABD1369237B18EE0081388D /* views */, + 0ABD1369237B18EE0081388D /* Views */, D29DF2B721E7BE79003B2FB9 /* TabBarController */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, D2B18B93236214AD00A9AEDC /* NavigationController.swift */, - D243859823A16B1800332775 /* Container.swift */, ); path = Containers; sourceTree = ""; @@ -761,6 +1121,8 @@ D29DF2A721E7B2F9003B2FB9 /* MVMCoreUIConstants.h */, D29DF2A821E7B2F9003B2FB9 /* MVMCoreUIConstants.m */, 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */, + 0A209CD223A7E2810068F8B0 /* UIStackViewAlignment+Extension.swift */, + D21EE53B23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift */, ); path = Utility; sourceTree = ""; @@ -779,16 +1141,14 @@ D29DF16821E69E1F003B2FB9 /* Buttons */ = { isa = PBXGroup; children = ( + 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */, DBC4391A224421A0001AB423 /* CaretButton.swift */, - D29DF25821E6A22D003B2FB9 /* MFButtonProtocol.h */, - D29DF16B21E69E1F003B2FB9 /* ButtonDelegateProtocol.h */, - D29DF16A21E69E1F003B2FB9 /* MFCustomButton.h */, - D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */, - D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */, - D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */, + D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */, D282AACA2243C61700C46919 /* ButtonView.swift */, - D29DF16D21E69E1F003B2FB9 /* MFTextButton.h */, - D29DF17221E69E1F003B2FB9 /* MFTextButton.m */, + D28A838823CCCFCB00DFE4FC /* LinkModel.swift */, + D28A838C23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift */, + D28A837623C79FC600DFE4FC /* MFCustomButton+ActionModel.swift */, + C07065C32395677300FBF997 /* Link.swift */, ); path = Buttons; sourceTree = ""; @@ -796,39 +1156,35 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + 9445890B2385BCE300DE9FD4 /* ProgressBarModel.swift */, 01509D922327ECFB00EF99AA /* ProgressBar.swift */, - D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, - D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, - D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, + 9445890D2385C3F800DE9FD4 /* MultiProgressModel.swift */, 948DB67D2326DCD90011F916 /* MultiProgress.swift */, + 9445891E2385D2E900DE9FD4 /* CaretViewModel.swift */, DBC4391622442196001AB423 /* CaretView.swift */, + 944589202385D6E900DE9FD4 /* DashLineModel.swift */, DBC4391722442197001AB423 /* DashLine.swift */, + 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */, DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */, - D29DF28721E7AC2B003B2FB9 /* ViewConstrainingView.h */, - D29DF28821E7AC2B003B2FB9 /* ViewConstrainingView.m */, D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */, + 944589222385DA9500DE9FD4 /* ImageViewModel.swift */, D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */, + 017BEB7A236763000024EF95 /* LineModel.swift */, D213347623843825008E41B3 /* Line.swift */, - D29DF2AD21E7B3A4003B2FB9 /* MFTextView.h */, - D29DF2AB21E7B3A4003B2FB9 /* MFTextView.m */, - D29DF2AC21E7B3A4003B2FB9 /* MFTextView.xib */, - D268C711238D6699007F2C1C /* DropDown.swift */, - D29DF2B121E7B76C003B2FB9 /* MFLoadingSpinner.h */, - D29DF2B221E7B76D003B2FB9 /* MFLoadingSpinner.m */, - D29DF32321ED0DA2003B2FB9 /* TextButtonView.h */, - D29DF32221ED0DA2003B2FB9 /* TextButtonView.m */, - D29770FB21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h */, - D29770FA21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m */, - D22D1F44220496A30077CEC0 /* MVMCoreUISwitch.h */, - D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */, DBC4391C2245232D001AB423 /* LabelWithInternalButton.swift */, - DB891E822253FA8500022516 /* Label.swift */, + 94C2D9822386F3E30006CF46 /* Label */, + 31BE15CA23D8924C00452370 /* CheckboxModel.swift */, 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, + 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */, 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */, 01004F2F22721C3800991ECC /* RadioButton.swift */, + D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */, 943784F3236B77BB006A1E82 /* GraphView.swift */, 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */, + D260105223CEA61600764D80 /* ToggleModel.swift */, 0AA33B392398524F0067DD0F /* Toggle.swift */, + D260105423CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift */, + 012CA99D2385A2D3003F810F /* MFView+ModelExtension.swift */, ); path = Views; sourceTree = ""; @@ -836,25 +1192,23 @@ D29DF22B21E6A0FA003B2FB9 /* TextFields */ = { isa = PBXGroup; children = ( - D29DF24C21E6A177003B2FB9 /* MFTextField.h */, - D29DF24221E6A176003B2FB9 /* MFTextField.m */, - D29DF24421E6A176003B2FB9 /* MFTextField.xib */, - D29DF24B21E6A177003B2FB9 /* MFTextFieldSubclassExtension.h */, - D29DF24721E6A176003B2FB9 /* MFMdnTextField.h */, - D29DF24921E6A177003B2FB9 /* MFMdnTextField.m */, - D29DF24521E6A176003B2FB9 /* MFDigitTextBox.h */, - D29DF24621E6A176003B2FB9 /* MFDigitTextBox.m */, - D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */, - D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */, - D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */, + 0A7EF85C23D8A95600B2AAD1 /* TextEntryFieldModel.swift */, 0A41BA7E23453A6400D4C0BC /* TextEntryField.swift */, - 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */, + 0A7EF85A23D8A52800B2AAD1 /* EntryFieldModel.swift */, 0A21DB7E235DECC500C160A2 /* EntryField.swift */, + 0A7EF85E23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift */, 0A21DB82235DFBC500C160A2 /* MdnEntryField.swift */, + 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */, + 0A7EF86023D8AC2500B2AAD1 /* DigitEntryFieldModel.swift */, 0A21DB93235E24ED00C160A2 /* DigitEntryField.swift */, + 0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */, 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */, + 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */, 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */, + 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */, 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */, + 017BEB3F23620A230024EF95 /* TextFieldModel.swift */, + 012CA9992384A687003F810F /* MFTextField+ModelExtension.swift */, ); path = TextFields; sourceTree = ""; @@ -890,9 +1244,11 @@ D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */, D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */, D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */, - D296E1402295EBBA0051EBE7 /* MoleculeDelegateProtocol.h */, + 012A88C7238DB02000FE3DA1 /* ModelMoleculeDelegateProtocol.swift */, D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */, D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */, + 017BEB432362192F0024EF95 /* MVMCoreUIMoleculeMappingObject+ModelExtension.swift */, + 017BEB7E23676E870024EF95 /* MoleculeObjectMapping.swift */, ); path = OtherHandlers; sourceTree = ""; @@ -954,6 +1310,39 @@ path = Strings; sourceTree = ""; }; + D29E28D423D1FFFA00ACEA85 /* Lists */ = { + isa = PBXGroup; + children = ( + D29E28DB23D21B0A00ACEA85 /* StringAndMoleculeStack */, + C695A68023C9830D00BFB94E /* NumberedListModel.swift */, + C6FA7D5123C77A4900A3614A /* NumberedList.swift */, + C695A67E23C9830600BFB94E /* UnOrderedListModel.swift */, + C6FA7D4F23C77A4700A3614A /* UnOrderedList.swift */, + ); + path = Lists; + sourceTree = ""; + }; + D29E28DB23D21B0A00ACEA85 /* StringAndMoleculeStack */ = { + isa = PBXGroup; + children = ( + D29E28D923D21AFA00ACEA85 /* StringAndMoleculeModel.swift */, + D29E28D723D21AB800ACEA85 /* StringAndMoleculeView.swift */, + C6FA7D5023C77A4800A3614A /* StringAndMoleculeStack.swift */, + ); + path = StringAndMoleculeStack; + sourceTree = ""; + }; + D29E28DE23D740FC00ACEA85 /* Container */ = { + isa = PBXGroup; + children = ( + 014AA72223C501E2006F3E93 /* ContainerModel.swift */, + D260105823D0A92900764D80 /* ContainerProtocol.swift */, + D243859823A16B1800332775 /* Container.swift */, + D29E28DC23D7404C00ACEA85 /* ContainerHelper.swift */, + ); + path = Container; + sourceTree = ""; + }; D2B18B7D236090D500A9AEDC /* BaseClasses */ = { isa = PBXGroup; children = ( @@ -1000,6 +1389,7 @@ D29DF2CE21E7C104003B2FB9 /* MFLoadingViewController.h in Headers */, 0A21DB84235E06EF00C160A2 /* MFTextField.h in Headers */, D29DF12A21E6851E003B2FB9 /* MVMCoreUITopAlertView.h in Headers */, + 94FB966223D797DA003D482B /* MFTextButton.h in Headers */, D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */, D29DF28B21E7AC2B003B2FB9 /* ViewConstrainingView.h in Headers */, D29DF2B321E7B76D003B2FB9 /* MFLoadingSpinner.h in Headers */, @@ -1012,7 +1402,6 @@ D29DF18221E69E54003B2FB9 /* SeparatorView.h in Headers */, D29DF26E21E6AA0B003B2FB9 /* FLAnimatedImage.h in Headers */, D29DF11621E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h in Headers */, - D29DF17721E69E1F003B2FB9 /* MFTextButton.h in Headers */, 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */, D29DF16221E69996003B2FB9 /* MFViewController.h in Headers */, 0A21DB88235E06EF00C160A2 /* MFMdnTextField.h in Headers */, @@ -1025,7 +1414,6 @@ D29DF2EE21ECEADF003B2FB9 /* MFFonts.h in Headers */, D29DF12D21E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h in Headers */, D29770F321F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.h in Headers */, - D296E1412295EBBA0051EBE7 /* MoleculeDelegateProtocol.h in Headers */, D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */, D29770FD21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h in Headers */, D29DF17421E69E1F003B2FB9 /* MFCustomButton.h in Headers */, @@ -1035,7 +1423,6 @@ D29DF2A121E7AF4E003B2FB9 /* MVMCoreUIUtility.h in Headers */, D29DF17621E69E1F003B2FB9 /* PrimaryButton.h in Headers */, D29DF2C821E7BFC1003B2FB9 /* MFSizeObject.h in Headers */, - D296E13C229598BF0051EBE7 /* MoleculeListCellProtocol.h in Headers */, D29DF32021ED0CBA003B2FB9 /* LabelView.h in Headers */, D29770C921F7C4AE00B2F0D0 /* TopLabelsView.h in Headers */, D29DF2E121E9240B003B2FB9 /* MVMCoreUIPanelProtocol.h in Headers */, @@ -1126,76 +1513,151 @@ files = ( 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */, 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */, + 31BE15CC23D8924D00452370 /* CheckboxModel.swift in Sources */, + 94C661DA23CCF4FB00D9FE5B /* UIColor+Extension.swift in Sources */, D29DF32121ED0CBA003B2FB9 /* LabelView.m in Sources */, + D28A838123CCB0D800DFE4FC /* AccordionListItemModel.swift in Sources */, DBC4391822442197001AB423 /* CaretView.swift in Sources */, + C07065C42395677300FBF997 /* Link.swift in Sources */, D29770F221F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.m in Sources */, D29B771022C281F400D6ACE0 /* ModuleMolecule.swift in Sources */, + D28A838923CCCFCB00DFE4FC /* LinkModel.swift in Sources */, + 94C2D9A923872E5E0006CF46 /* LabelAttributeImageModel.swift in Sources */, DBC4391922442197001AB423 /* DashLine.swift in Sources */, 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */, - D2FB151D23A40F1500C20E10 /* StackItem.swift in Sources */, + D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */, + 017BEB48236230DB0024EF95 /* MoleculeViewProtocol.swift in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */, D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, + D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */, 01004F3022721C3800991ECC /* RadioButton.swift in Sources */, D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */, + 017BEB3C2361EA1D0024EF95 /* MFViewController+Model.swift in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, + 012A88C8238DB02000FE3DA1 /* ModelMoleculeDelegateProtocol.swift in Sources */, + 0A7EF86123D8AC2500B2AAD1 /* DigitEntryFieldModel.swift in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, D224798C231450C8003FCCF9 /* HeadlineBodySwitch.swift in Sources */, - D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, + 017BEB442362192F0024EF95 /* MVMCoreUIMoleculeMappingObject+ModelExtension.swift in Sources */, + 9445890C2385BCE300DE9FD4 /* ProgressBarModel.swift in Sources */, + 9445891F2385D2E900DE9FD4 /* CaretViewModel.swift in Sources */, + 01C851D323CF9E740021F976 /* LabelToggleModel.swift in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, + D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */, + 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, + D260106323D0C05000764D80 /* StackItemModel.swift in Sources */, + D2E2A99823D8D63C000B42E6 /* ActionDetailWithImageModel.swift in Sources */, + D2E2A99D23DA3217000B42E6 /* UIStackViewAlignment+Extension.swift in Sources */, + 01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */, 0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */, D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */, D29DF12E21E6851E003B2FB9 /* MVMCoreUITopAlertView.m in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, + D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */, + 014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */, 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, + D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */, + D28A838D23CCDCC200DFE4FC /* PrimaryButton+MoleculeProtocolExtension.swift in Sources */, D2A5145F2211DDC100345BFB /* MoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, + C695A69623C990BC00BFB94E /* DoughnutChart.swift in Sources */, + 014AA72D23C5059B006F3E93 /* StackPageTemplateModel.swift in Sources */, + D260106123D0C02A00764D80 /* StackItemModelProtocol.swift in Sources */, + 012A88C4238D86E600FE3DA1 /* CarouselItemModelProtocol.swift in Sources */, + 94C2D9AB23872EB50006CF46 /* LabelAttributeActionModel.swift in Sources */, + D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */, + 014AA73123C5059B006F3E93 /* ListPageTemplateModel.swift in Sources */, + 017BEB4023620A230024EF95 /* TextFieldModel.swift in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, + 94C2D9A723872DA90006CF46 /* LabelAttributeColorModel.swift in Sources */, + 0103B84E23D7E33A009C315C /* HeadlineBodyToggleModel.swift in Sources */, D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */, 0A21DB85235E06EF00C160A2 /* MFTextField.m in Sources */, + 014AA72623C501E2006F3E93 /* ContainerModelProtocol.swift in Sources */, + 01EB369223609801006832FA /* MoleculeStackModel.swift in Sources */, + 012CA99E2385A2D3003F810F /* MFView+ModelExtension.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, + 944589232385DA9600DE9FD4 /* ImageViewModel.swift in Sources */, D213347723843825008E41B3 /* Line.swift in Sources */, - D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, + D28A837723C79FC600DFE4FC /* MFCustomButton+ActionModel.swift in Sources */, + D2E2A99C23D8D975000B42E6 /* ImageHeadlineBodyModel.swift in Sources */, + D28A837F23CCA96400DFE4FC /* TabsModel.swift in Sources */, + 012A88EC238F084D00FE3DA1 /* FooterModel.swift in Sources */, + D2A514672213885800345BFB /* HeaderView.swift in Sources */, + D29E28D823D21AB800ACEA85 /* StringAndMoleculeView.swift in Sources */, + 01EB369023609801006832FA /* ListItemModel.swift in Sources */, + D28A838323CCBD3F00DFE4FC /* CircleProgressModel.swift in Sources */, + D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, + 0A7EF85F23D8ABC500B2AAD1 /* MdnEntryFieldModel.swift in Sources */, 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */, + 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */, + D260105523CEA7DC00764D80 /* MVMCoreUISwitch+Model.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */, + 0A7EF85B23D8A52800B2AAD1 /* EntryFieldModel.swift in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D2B18B812360945C00A9AEDC /* View.swift in Sources */, + C6FA7D5423C77A4A00A3614A /* NumberedList.swift in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, + 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */, + 94FB966323D797DA003D482B /* MFTextButton.m in Sources */, + D260105323CEA61600764D80 /* ToggleModel.swift in Sources */, + 014AA72523C501E2006F3E93 /* ContainerModel.swift in Sources */, + 0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */, D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */, + D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, + D260105D23D0BCD400764D80 /* Stack.swift in Sources */, + 0A7EF85D23D8A95600B2AAD1 /* TextEntryFieldModel.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, + 01EB368F23609801006832FA /* LabelModel.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, + 01F2A03223A4498200D954D8 /* CaretLinkModel.swift in Sources */, 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */, + 011B58F023A2AA980085F53C /* ListItemModelProtocol.swift in Sources */, D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */, 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, + D28A837923C7D5BC00DFE4FC /* PageModelProtocol.swift in Sources */, D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */, D29DF17A21E69E1F003B2FB9 /* MFCustomButton.m in Sources */, - D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */, + 017BEB7B236763000024EF95 /* LineModel.swift in Sources */, + 94C2D9A523872C350006CF46 /* LabelAttributeFontModel.swift in Sources */, + 017BEB7F23676E870024EF95 /* MoleculeObjectMapping.swift in Sources */, + D274CA332236A78900B01B62 /* FooterView.swift in Sources */, D29DF2BF21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.m in Sources */, + 014AA72423C501E2006F3E93 /* MoleculeContainerModel.swift in Sources */, D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */, + 011B58F223A2AE2C0085F53C /* DropDownListItemModel.swift in Sources */, + 94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */, + 944589212385D6E900DE9FD4 /* DashLineModel.swift in Sources */, + D2E2A99623D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift in Sources */, + C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, + D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */, + 012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */, D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */, + 9445890E2385C3F800DE9FD4 /* MultiProgressModel.swift in Sources */, D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */, D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */, D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */, @@ -1209,17 +1671,32 @@ 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */, D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */, D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, + C695A67F23C9830600BFB94E /* UnOrderedListModel.swift in Sources */, + 017BEB4223620AD20024EF95 /* FormModelProtocol.swift in Sources */, + 012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, + 017BEB4A236235BA0024EF95 /* ModelMoleculeViewProtocol.swift in Sources */, + C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */, + 012CA99A2384A687003F810F /* MFTextField+ModelExtension.swift in Sources */, + 01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, D243859923A16B1800332775 /* Container.swift in Sources */, + D260105B23D0BB7100764D80 /* StackModelProtocol.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, + D28A839323CE828900DFE4FC /* HeadlineBodyCaretLinkImageModel.swift in Sources */, D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, + D260105F23D0BFFC00764D80 /* StackItem.swift in Sources */, + 9432A79F23DB47BA00719041 /* EntryFieldContainer.swift in Sources */, + 01EB369323609801006832FA /* HeaderModel.swift in Sources */, D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */, 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */, + D28A837D23CCA86A00DFE4FC /* TabsListItemModel.swift in Sources */, + 012A88C6238DA34000FE3DA1 /* ModuleMoleculeModel.swift in Sources */, + 94C2D9A123872BCC0006CF46 /* LabelAttributeUnderlineModel.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, @@ -1229,33 +1706,47 @@ 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, + 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */, + D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, + D28A839123CD4FD400DFE4FC /* CornerLabelsModel.swift in Sources */, + 012A88F123985E0100FE3DA1 /* Color.swift in Sources */, + 012A889C23889E8400FE3DA1 /* TemplateModelProtocol.swift in Sources */, D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, C003506123AA94CD00B6AC29 /* Button.swift in Sources */, - D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, - D268C712238D6699007F2C1C /* DropDown.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, C7192E7D23C301750050C2A0 /* HeadLineBodyCaretLinkImage.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, + 0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */, 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */, D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, - 0ABD136B237B193A0081388D /* EntryFieldContainer.swift in Sources */, + 014AA72E23C5059B006F3E93 /* StackCenteredPageTemplateModel.swift in Sources */, D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, + D260105923D0A92900764D80 /* ContainerProtocol.swift in Sources */, + C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, + D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */, + 012A88C2238D7BCA00FE3DA1 /* CarouselItemModel.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, + 94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */, + D21EE53C23AD3AD4003D1A30 /* NSLayoutConstraintAxis+Extension.swift in Sources */, + C6FA7D5223C77A4A00A3614A /* UnOrderedList.swift in Sources */, 01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */, 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */, 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */, D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */, + C695A69823C990C200BFB94E /* DoughnutChartView.swift in Sources */, D29DF2CB21E7BFCC003B2FB9 /* MFSizeThreshold.m in Sources */, + 946EE1BA237B66D80036751F /* MoleculeModelHelper.swift in Sources */, 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */, + D260106523D0CEA700764D80 /* StackModel.swift in Sources */, D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/MVMCoreUI/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift new file mode 100644 index 00000000..61e3ca3e --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift @@ -0,0 +1,75 @@ +// +// ButtonModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public enum ButtonStyle: String, Codable { + case primary + case secondary +} + +public enum ButtonSize: String, Codable { + case standard + case tiny +} + +public class ButtonModel: MoleculeModelProtocol { + public static var identifier: String = "button" + public var moleculeName: String? + public var backgroundColor: Color? + public var title: String + public var action: ActionModelProtocol + public var style: ButtonStyle? + public var size: ButtonSize? = .standard + public var required: Bool? + public var requiredGroups: [String]? + + init(with title: String, action: ActionModelProtocol) { + self.title = title + self.action = action + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case title + case action + case style + case size + case required + case requiredGroups + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName) + + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + title = try typeContainer.decode(String.self, forKey: .title) + action = try typeContainer.decodeModel(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) + required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) + requiredGroups = try typeContainer.decodeIfPresent([String].self, forKey: .requiredGroups) + if let style = try typeContainer.decodeIfPresent(ButtonStyle.self, forKey: .style) { + self.style = style + } + if let size = try typeContainer.decodeIfPresent(ButtonSize.self, forKey: .size) { + self.size = size + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(title, forKey: .title) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeModel(action, forKey: .action) + try container.encodeIfPresent(style, forKey: .style) + try container.encodeIfPresent(size, forKey: .size) + try container.encodeIfPresent(required, forKey: .required) + } +} diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretButton.swift index 887c4c5f..3fcf453a 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretButton.swift @@ -8,7 +8,8 @@ // -open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { +open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, ModelMoleculeViewProtocol { + //------------------------------------------------------ // MARK: - Constants //------------------------------------------------------ @@ -88,6 +89,8 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI NSLayoutConstraint(item: caretView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0).isActive = true NSLayoutConstraint(item: self, attribute: .right, relatedBy: .greaterThanOrEqual, toItem: caretView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true + caretView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: caretView.bottomAnchor).isActive = true contentHorizontalAlignment = .left //set correct color after layout @@ -107,6 +110,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI @objc open func setAsMolecule() { backgroundColor = .clear + translatesAutoresizingMaskIntoConstraints = false setTitleColor(enabledColor, for: .normal) setTitleColor(disabledColor, for: .disabled) } @@ -137,6 +141,20 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI } } + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let caretLinkModel = model as? CaretLinkModel else { return } + if let color = caretLinkModel.backgroundColor { + backgroundColor = color.uiColor + } + enabledColor = caretLinkModel.enabledColor.uiColor + if let color = caretLinkModel.disabledColor { + disabledColor = color.uiColor + } + isEnabled = caretLinkModel.enabled + set(with: caretLinkModel.action, delegateObject: delegateObject, additionalData: additionalData) + setTitle(caretLinkModel.title, for: .normal) + } + public func needsToBeConstrained() -> Bool { return true } diff --git a/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift b/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift new file mode 100644 index 00000000..04b5aa07 --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/CaretLinkModel.swift @@ -0,0 +1,60 @@ +// +// CaretLinkModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 12/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore + +public class CaretLinkModel: MoleculeModelProtocol { + public static var identifier: String = "caretLink" + public var backgroundColor: Color? + public var title: String + public var action: ActionModelProtocol + public var enabledColor: Color = Color(uiColor: .black) + public var disabledColor: Color? = Color(uiColor: .mfSilver()) + public var enabled: Bool = true + + public init(title: String, action: ActionModelProtocol) { + self.title = title + self.action = action + } + + private enum CodingKeys: String, CodingKey { + case backgroundColor + case title + case action + case enabledColor + case disabledColor + case enabled + } + + 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.decode(String.self, forKey: .title) + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .enabledColor) { + enabledColor = color + } + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor) { + disabledColor = color + } + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } + action = try typeContainer.decodeModel(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(title, forKey: .title) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeModel(action, forKey: .action) + try container.encode(enabled, forKey: .enabledColor) + try container.encodeIfPresent(disabledColor, forKey: .disabledColor) + try container.encode(enabled, forKey: .enabled) + } +} diff --git a/MVMCoreUI/Atoms/Buttons/Link.swift b/MVMCoreUI/Atoms/Buttons/Link.swift new file mode 100644 index 00000000..8cb9bb49 --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/Link.swift @@ -0,0 +1,88 @@ +// +// Link.swift +// MVMCoreUI +// +// Created by Robinson, Blake on 11/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + + +@objcMembers open class Link: Button { + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + open override func draw(_ rect: CGRect) { + + guard let textRect = titleLabel?.frame else { return } + + let context = UIGraphicsGetCurrentContext() + + // Set line to the same color as the text + if let color = titleLabel?.textColor?.cgColor { + context?.setStrokeColor(color) + } + + // x should be according to the text, not the button + let x = textRect.origin.x + + // Line is 1 point below the text + let y = textRect.origin.y + textRect.size.height + 1 + + context?.move(to: CGPoint(x: x, y: y)) + context?.addLine(to: CGPoint(x: x + textRect.size.width, y: y)) + context?.strokePath() + } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? LinkModel else { return } + setTitle(model.title, for: .normal) + setTitleColor(model.textColor.uiColor, for: .normal) + setTitleColor(model.disabledColor.uiColor, for: .disabled) + isEnabled = model.enabled + set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) + } + + public static func estimatedHeight(forRow molecule: ModuleMoleculeModel?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 31.0 + } +} + +// MARK: - MVMCoreViewProtocol +extension Link { + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + var width = size + if MVMCoreGetterUtility.fequal(a: Float(CGFloat.leastNormalMagnitude), b: Float(size)) { + width = MVMCoreUIUtility.getWidth() + } + self.titleLabel?.font = MFStyler.fontB2(forWidth: width) + } + } + + public override func setupView() { + super.setupView() + backgroundColor = .clear + contentMode = .redraw + setTitleColor(.mvmBlack, for: .normal) + setTitleColor(.mvmCoolGray6, for: .disabled) + titleLabel?.numberOfLines = 1 + titleLabel?.lineBreakMode = .byTruncatingTail + titleLabel?.textAlignment = .left + contentHorizontalAlignment = .left + } +} + +// MARK: - MVMCoreUIViewConstrainingProtocol +extension Link: MVMCoreUIViewConstrainingProtocol { + + public func horizontalAlignment() -> UIStackView.Alignment { + return .leading + } +} diff --git a/MVMCoreUI/Atoms/Buttons/LinkModel.swift b/MVMCoreUI/Atoms/Buttons/LinkModel.swift new file mode 100644 index 00000000..28e00d60 --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/LinkModel.swift @@ -0,0 +1,60 @@ +// +// LinkModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class LinkModel: MoleculeModelProtocol { + public static var identifier: String = "link" + public var backgroundColor: Color? + public var title: String + public var action: ActionModelProtocol + public var enabled = true + public var textColor = Color(uiColor: .mvmBlack) + public var disabledColor = Color(uiColor: .mvmCoolGray6) + + public init(title: String, action: ActionModelProtocol) { + self.title = title + self.action = action + } + + private enum CodingKeys: String, CodingKey { + case backgroundColor + case title + case action + case enabled + case textColor + case disabledColor + } + + 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.decode(String.self, forKey: .title) + action = try typeContainer.decodeModel(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) + if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + self.enabled = enabled + } + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor) { + textColor = color + } + + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledColor) { + disabledColor = color + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(title, forKey: .title) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeModel(action, forKey: .action) + try container.encode(enabled, forKey: .enabled) + try container.encode(textColor, forKey: .textColor) + try container.encode(disabledColor, forKey: .disabledColor) + } +} diff --git a/MVMCoreUI/Atoms/Buttons/MFCustomButton+ActionModel.swift b/MVMCoreUI/Atoms/Buttons/MFCustomButton+ActionModel.swift new file mode 100644 index 00000000..d008a7fb --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/MFCustomButton+ActionModel.swift @@ -0,0 +1,23 @@ +// +// MFCustomButton+ActionModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/9/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public extension MFCustomButton { + func set(with action: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + buttonDelegate = delegateObject?.buttonDelegate + add({ [weak self] sender in + guard let self = self else { return } + if let data = try? action.encode(using: JSONEncoder()), + let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any], + delegateObject?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + }, for: .touchUpInside) + } +} diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift b/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift new file mode 100644 index 00000000..39b015ae --- /dev/null +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton+MoleculeProtocolExtension.swift @@ -0,0 +1,45 @@ +// +// PrimaryButton+MoleculeProtocolExtension.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +// temporary until link is finished +extension PrimaryButton: ModelMoleculeViewProtocol { + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let model = model as? ButtonModel else { return } + setTitle(model.title, for: .normal) + backgroundColor = model.backgroundColor?.uiColor + + self.validationRequired = model.required ?? false + self.requiredGroupsList = model.requiredGroups + + if self.validationRequired, + let selfForm = self as? FormValidationEnableDisableProtocol { + FormValidator.setupValidation(molecule: selfForm, delegate: delegateObject?.formValidationProtocol) + } + + + if let style = model.style { + switch style { + case .primary: + setAsStandardCustom() + case .secondary: + setAsSecondaryCustom() + } + } + if let size = model.size { + switch size { + case .standard: + setAsTiny(false) + case .tiny: + setAsTiny(true) + } + } + set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) + } +} diff --git a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift index 05e64f70..3290c744 100644 --- a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift @@ -68,6 +68,14 @@ import UIKit container.trailingAnchor.constraint(equalTo: dropDownCaretView.trailingAnchor, constant: 16).isActive = true dropDownCaretView.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + + guard let model = model as? BaseDropdownEntryFieldModel else { return } + + dropDownCaretView.setWithModel(model.caretView, delegateObject, additionalData) + } } // MARK: - MVMCoreUIMoleculeViewProtocol diff --git a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryFieldModel.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryFieldModel.swift new file mode 100644 index 00000000..e6e7cb24 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryFieldModel.swift @@ -0,0 +1,45 @@ +// +// BaseDropdownEntryFieldModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +@objcMembers public class BaseDropdownEntryFieldModel: TextEntryFieldModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var caretView: CaretViewModel? + + public override class var identifier: String { + return "" + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case caretView + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + caretView = try typeContainer.decodeIfPresent(CaretViewModel.self, forKey: .caretView) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(caretView, forKey: .caretView) + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift index feddfd6b..acfe0a42 100644 --- a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryField.swift @@ -22,7 +22,20 @@ import UIKit return calendar }() - public var dateFormat: String? + public var dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeZone = NSTimeZone.system + formatter.locale = .current + formatter.formatterBehavior = .default + return formatter + }() + + public var dateFormat: String = "MMM d, y" { + didSet { + dateFormatter.dateFormat = dateFormat + } + } //-------------------------------------------------- // MARK: - Initializers @@ -83,7 +96,7 @@ import UIKit if calendar.isDate(date, inSameDayAs: Date()) { text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string") } else { - text = dateFormatter().string(from: date) + text = dateFormatter.string(from: date) } } @@ -98,19 +111,12 @@ import UIKit setTextWith(date: datePicker?.date) } - public func dateFormatter() -> DateFormatter { + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeZone = NSTimeZone.system - formatter.locale = .current - formatter.formatterBehavior = .default + guard let model = model as? DateDropdownEntryFieldModel else { return } - if let dateFormat = dateFormat { - formatter.dateFormat = dateFormat - } - - return formatter + dateFormat = model.dateFormat } } diff --git a/MVMCoreUI/Atoms/TextFields/DateDropdownEntryFieldModel.swift b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryFieldModel.swift new file mode 100644 index 00000000..cdafc9e9 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DateDropdownEntryFieldModel.swift @@ -0,0 +1,45 @@ +// +// DateDropdownEntryFieldModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +@objcMembers public class DateDropdownEntryFieldModel: BaseDropdownEntryFieldModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public override class var identifier: String { + return "dateDropdownEntryField" + } + + public var dateFormat: String = "MMM d, y" + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case dateFormat + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + 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" + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(dateFormat, forKey: .dateFormat) + } +} diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift index 4cd9b67a..9febf0c1 100644 --- a/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryField.swift @@ -327,6 +327,22 @@ import UIKit } } } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + + + guard let model = model as? DigitEntryFieldModel else { return } + + numberOfDigits = model.digits + + setAsSecureTextEntry(model.secureEntry) + + for digitBox in digitBoxes { + MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate) + } + + super.setWithModel(model, delegateObject, additionalData) + } } // MARK: - TextField Delegate diff --git a/MVMCoreUI/Atoms/TextFields/DigitEntryFieldModel.swift b/MVMCoreUI/Atoms/TextFields/DigitEntryFieldModel.swift new file mode 100644 index 00000000..0291066c --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/DigitEntryFieldModel.swift @@ -0,0 +1,50 @@ +// +// DigitEntryFieldModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + + +@objcMembers public class DigitEntryFieldModel: TextEntryFieldModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public override class var identifier: String { + return "digitTextField" + } + + 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 + //-------------------------------------------------- + + 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 + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(digits, forKey: .digits) + try container.encode(secureEntry, forKey: .secureEntry) + } +} diff --git a/MVMCoreUI/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atoms/TextFields/EntryField.swift index 7e40feba..82cc69f6 100644 --- a/MVMCoreUI/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/EntryField.swift @@ -119,6 +119,7 @@ import UIKit feedbackLabel.text = newFeedback feedbackLabel.accessibilityElementsHidden = feedbackLabel.text?.isEmpty ?? true entryFieldContainer.refreshUI() + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) } } @@ -238,6 +239,31 @@ import UIKit feedbackLabel.textColor = .black entryFieldContainer.reset() } + + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + self.delegateObject = delegateObject + + guard let model = model as? EntryFieldModel else { return } + + entryFieldContainer.setWithModel(model, delegateObject, additionalData) + + title = model.title + feedback = model.feedback + errorMessage = model.errorMessage + isEnabled = model.isEnabled + + if let isLocked = model.isLocked { + self.isLocked = isLocked + + } else if let isSelected = model.isSelected{ + self.isSelected = isSelected + } + + if let fieldKey = model.fieldKey { + self.fieldKey = fieldKey + } + } } // MARK: - MVMCoreUIMoleculeViewProtocol diff --git a/MVMCoreUI/Atoms/TextFields/EntryFieldModel.swift b/MVMCoreUI/Atoms/TextFields/EntryFieldModel.swift new file mode 100644 index 00000000..5f60081e --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/EntryFieldModel.swift @@ -0,0 +1,82 @@ +// +// EntryFieldModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +@objcMembers public class EntryFieldModel: MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public class var identifier: String { + return "" + } + + public var backgroundColor: Color? + public var moleculeName: String? + public var title: String? + public var feedback: String? + public var errorMessage: String = "" + public var isEnabled: Bool = true + public var isLocked: Bool? + public var isSelected: Bool? + public var fieldKey: String? + public var isValid: Bool? + public var isRequired: Bool? + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case title + case isEnabled + case feedback + case errorMessage = "errorMsg" + case isLocked + case isSelected + case fieldKey + case isValid + case isRequired = "required" + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + title = try typeContainer.decodeIfPresent(String.self, forKey: .title) + feedback = try typeContainer.decodeIfPresent(String.self, forKey: .feedback) + errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage) ?? "" + isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? true + isLocked = try typeContainer.decodeIfPresent(Bool.self, forKey: .isLocked) + isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .isSelected) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + isValid = try typeContainer.decodeIfPresent(Bool.self, forKey: .isValid) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(title, forKey: .title) + try container.encodeIfPresent(feedback, forKey: .feedback) + try container.encode(errorMessage, forKey: .errorMessage) + try container.encode(isEnabled, forKey: .isEnabled) + try container.encode(isLocked, forKey: .isLocked) + try container.encode(isSelected, forKey: .isSelected) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(isValid, forKey: .isValid) + } +} diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index 7eaf6c5c..d1728b46 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -21,7 +21,7 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { public var setInitialValueInTextField = true /// Closure passed here will run as picker changes items. - public var observeDropdownChange: ((String)->())? + public var observeDropdownChange: ((String, String)->())? /// Closure passed here will run upon dismissing the selection picker. public var observeDropdownSelection: ((String)->())? @@ -90,6 +90,19 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { observeDropdownSelection?(pickerData[pickerIndex]) } } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + + guard let model = model as? ItemDropdownEntryFieldModel else { return } + + pickerData = model.options + setPickerDelegates(delegate: self) + + if let pickerView = pickerView { + self.pickerView(pickerView, didSelectRow: 0, inComponent: 0) + } + } } // MARK:- Base Picker Delegate @@ -104,12 +117,16 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { } @objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + guard !pickerData.isEmpty else { return nil } + return pickerData[row] } @objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + guard !pickerData.isEmpty else { return } + + observeDropdownChange?(text ?? "", pickerData[row]) text = pickerData[row] - observeDropdownChange?(pickerData[row]) } } diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryFieldModel.swift new file mode 100644 index 00000000..03c1cf40 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryFieldModel.swift @@ -0,0 +1,45 @@ +// +// ItemDropdownEntryFieldModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +@objcMembers public class ItemDropdownEntryFieldModel: BaseDropdownEntryFieldModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public override class var identifier: String { + return "dropDown" + } + + public var options: [String] = [] + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case options + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + 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) + } + + 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) + } +} diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField+ModelExtension.swift b/MVMCoreUI/Atoms/TextFields/MFTextField+ModelExtension.swift new file mode 100644 index 00000000..a077c285 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/MFTextField+ModelExtension.swift @@ -0,0 +1,79 @@ +// +// MFTextField+ModelExtension.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/19/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +enum TextType: String { + case dropDown = "dropDown" + case password = "password" + case number = "number" + case email = "email" +} +extension MFTextField: ModelMoleculeViewProtocol { + // + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + //TODO: Need to create setWithModel in ViewConstraining View + #warning("This below call should be repaced with super.setWithModel once we get rid of ViewConstrainingView.") + //TODO: This below call should be repaced with super.setWithModel once we get rid of ViewConstrainingView. + setUpDefaultWithModel(model, delegateObject, additionalData) + + guard let textFieldModel = model as? TextFieldModel, + let delegateObject = delegateObject else { + return + } + + if let delegate = delegateObject.formValidationProtocol { + let formValidator = FormValidator.getFormValidatorFor(delegate: delegate) + mfTextFieldDelegate = formValidator + uiTextFieldDelegate = delegateObject.uiTextFieldDelegate + if let textField = textField { + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) + } + } + + formText = textFieldModel.label as NSString? + text = textFieldModel.value as NSString? + if let disabled = textFieldModel.disabled { + enable(disabled) + } + errMessage = textFieldModel.errorMsg + fieldKey = textFieldModel.fieldKey + groupName = textFieldModel.groupName + + + switch textFieldModel.type { + case TextType.dropDown.rawValue: + dropDownCarrotLabel?.isHidden = true + hasDropDown = true + break + case TextType.password.rawValue: + textField?.isSecureTextEntry = true + break + case TextType.number.rawValue: + textField?.keyboardType = .numberPad + break + case TextType.email.rawValue: + textField?.keyboardType = .emailAddress + break + default: + print("default") + } + + if let regex = textFieldModel.regex { + validationBlock = {(enteredValue: String?) -> Bool in + if let enteredValue = enteredValue { + return MVMCoreUIUtility.validate(enteredValue, withRegularExpression: regex) + } + return true + } + } else { + setDefaultValidationBlock() + } + } +} + diff --git a/MVMCoreUI/Atoms/TextFields/MdnEntryFieldModel.swift b/MVMCoreUI/Atoms/TextFields/MdnEntryFieldModel.swift new file mode 100644 index 00000000..ea367447 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/MdnEntryFieldModel.swift @@ -0,0 +1,17 @@ +// +// MdnEntryFieldModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +@objcMembers public class MdnEntryFieldModel: TextEntryFieldModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public override class var identifier: String { + return "mdnEntryField" + } +} diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 5ff12679..66f3ecd4 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -270,6 +270,49 @@ import UIKit resignFirstResponder() } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + + guard let model = model as? TextEntryFieldModel else { return } + + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + + textColor.enabled = model.enabledTextColor?.uiColor + textColor.disabled = model.disabledTextColor?.uiColor + text = model.text + placeholder = model.placeholder + + switch model.type { + case "password": + textField.isSecureTextEntry = true + + case "number": + textField.keyboardType = .numberPad + + case "email": + textField.keyboardType = .emailAddress + + default: + break + } + + if let regex = model.regex, !regex.isEmpty { + validationBlock = { enteredValue in + guard let value = enteredValue else { return false } + return MVMCoreUIUtility.validate(value, withRegularExpression: regex) + } + } else { + defaultValidationBlock() + } + + if let formValidationProtocol = delegateObject?.formValidationProtocol { + observingTextFieldDelegate = FormValidator.getFormValidatorFor(delegate: formValidationProtocol) + } + + uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate + MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: uiTextFieldDelegate) + } } // MARK: - MVMCoreUIMoleculeViewProtocol diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryFieldModel.swift b/MVMCoreUI/Atoms/TextFields/TextEntryFieldModel.swift new file mode 100644 index 00000000..27b0e933 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/TextEntryFieldModel.swift @@ -0,0 +1,66 @@ +// +// TextEntryFieldModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + + +@objcMembers public class TextEntryFieldModel: EntryFieldModel { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public override class var identifier: String { + return "textField" + } + + public var text: String? + public var placeholder: String? + public var enabledTextColor: Color? + public var disabledTextColor: Color? + public var type: String? + public var regex: String? + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case text + case placeholder + case enabledTextColor + case disabledTextColor + case type + case regex + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + 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(String.self, forKey: .type) + regex = try typeContainer.decodeIfPresent(String.self, forKey: .regex) + } + + 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.encodeIfPresent(type, forKey: .type) + try container.encodeIfPresent(regex, forKey: .regex) + } +} diff --git a/MVMCoreUI/Atoms/TextFields/TextFieldModel.swift b/MVMCoreUI/Atoms/TextFields/TextFieldModel.swift new file mode 100644 index 00000000..6caa70b0 --- /dev/null +++ b/MVMCoreUI/Atoms/TextFields/TextFieldModel.swift @@ -0,0 +1,27 @@ +// +// TextFieldModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class TextFieldModel: MoleculeModelProtocol, FormModelProtocol { + + public static var identifier: String = "textField" + public var backgroundColor: Color? + public var moleculeName: String + public var editable: Bool? + public var disabled: Bool? + public var errorMsg: String? + public var label: String? + public var type: String? + public var value: String? + public var regex: String? + + public var required: Bool? + public var fieldKey: String? + public var groupName: String? +} diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index 2534c346..26573af5 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -178,29 +178,27 @@ open class CaretView: View { //------------------------------------------------------ // Default values for view. - @objc open func setAsMolecule() { + @objc open override func setAsMolecule() { defaultState() } - @objc open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - - guard let dictionary = json else { return } - - if let strokeColorHex = dictionary["strokeColor"] as? String { - strokeColor = UIColor.mfGet(forHex: strokeColorHex) + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: CaretViewModel.self) else { return } + setWithModel(model, delegateObject, additionalData) + } + + //MARK: - MVMCoreMoleculeViewProtocol + override public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let caretModel = model as? CaretViewModel else { + return } - - if let isHidden = dictionary[KeyIsHidden] as? Bool { - self.isHidden = isHidden - } - - if let isOpaque = dictionary[KeyIsOpaque] as? Bool { - self.isOpaque = isOpaque - } - - if let lineWidth = dictionary["lineWidth"] as? CGFloat { - self.lineWidth = lineWidth + strokeColor = caretModel.strokeColor.uiColor + isHidden = caretModel.isHidden ?? false + isOpaque = caretModel.isOpaque ?? false + + if let lineWidthValue = caretModel.lineWidth { + lineWidth = lineWidthValue } } } diff --git a/MVMCoreUI/Atoms/Views/CaretViewModel.swift b/MVMCoreUI/Atoms/Views/CaretViewModel.swift new file mode 100644 index 00000000..58291984 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CaretViewModel.swift @@ -0,0 +1,47 @@ +// +// CaretViewModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class CaretViewModel: MoleculeModelProtocol { + + public static var identifier: String = "caretView" + public var backgroundColor: Color? + public var strokeColor: Color = Color(uiColor: .black) + public var isHidden: Bool? + public var isOpaque: Bool? + public var lineWidth: CGFloat? + + private enum CodingKeys: String, CodingKey { + case backgroundColor + case strokeColor + case isHidden + case isOpaque + case lineWidth + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let strokeColor = try typeContainer.decodeIfPresent(Color.self, forKey: .strokeColor) { + self.strokeColor = strokeColor + } + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + isHidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .isHidden) + isOpaque = try typeContainer.decodeIfPresent(Bool.self, forKey: .isOpaque) + lineWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .lineWidth) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(strokeColor, forKey: .strokeColor) + try container.encodeIfPresent(isHidden, forKey: .isHidden) + try container.encodeIfPresent(isOpaque, forKey: .isOpaque) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(lineWidth, forKey: .lineWidth) + } +} diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index a206953a..27bed31e 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -76,6 +76,27 @@ import MVMCore } } + open override var isEnabled: Bool { + didSet { + + isUserInteractionEnabled = isEnabled + + if isEnabled { + layer.borderColor = borderColor.cgColor + backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor + setShapeLayerStrokeColor(checkColor) + } else { + layer.borderColor = disabledBorderColor.cgColor + backgroundColor = disabledBackgroundColor + setShapeLayerStrokeColor(disabledCheckColor) + } + } + } + + public var disabledBackgroundColor: UIColor = .clear + public var disabledBorderColor: UIColor = .mvmCoolGray3 + public var disabledCheckColor: UIColor = .mvmCoolGray3 + /// Color of the check mark. public var checkColor: UIColor = .black { didSet { @@ -107,7 +128,6 @@ import MVMCore if !updateSelectionOnly { layoutIfNeeded() shapeLayer?.removeAllAnimations() - updateCheckboxUI(isSelected: isSelected, isAnimated: isAnimated) FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) updateAccessibilityLabel() @@ -154,16 +174,12 @@ import MVMCore public convenience init(isChecked: Bool) { self.init(frame: .zero) - updateSelectionOnly = true - isSelected = isChecked - updateSelectionOnly = false + checkAndBypassAnimations(selected: isChecked) } public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) { self.init(frame: .zero) - updateSelectionOnly = true - isSelected = isChecked - updateSelectionOnly = false + checkAndBypassAnimations(selected: isChecked) self.checkedBackgroundColor = checkedBackgroundColor self.unCheckedBackgroundColor = unCheckedBackgroundColor } @@ -177,8 +193,6 @@ import MVMCore drawShapeLayer() layer.cornerRadius = isRound ? cornerRadiusValue : 0 - layer.borderWidth = borderWidth - layer.borderColor = borderColor.cgColor } open override func setupView() { @@ -228,7 +242,7 @@ import MVMCore self.shapeLayer = shapeLayer shapeLayer.frame = bounds layer.addSublayer(shapeLayer) - shapeLayer.strokeColor = checkColor.cgColor + shapeLayer.strokeColor = isEnabled ? checkColor.cgColor : disabledCheckColor.cgColor shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.path = checkMarkPath() shapeLayer.lineJoin = .miter @@ -268,9 +282,7 @@ import MVMCore DispatchQueue.main.async { - self.updateSelectionOnly = true - self.isSelected = selected - self.updateSelectionOnly = false + self.checkAndBypassAnimations(selected: selected) self.drawShapeLayer() self.shapeLayer?.removeAllAnimations() self.updateCheckboxUI(isSelected: selected, isAnimated: animated) @@ -313,23 +325,6 @@ import MVMCore } } - func isEnabled(_ enabled: Bool) { - - isUserInteractionEnabled = enabled - - if enabled { - layer.borderColor = borderColor.cgColor - backgroundColor = isSelected ? checkedBackgroundColor : unCheckedBackgroundColor - alpha = 1.0 - setShapeLayerStrokeColor(checkColor) - } else { - layer.borderColor = UIColor.mfSilver().cgColor - backgroundColor = .clear - alpha = DisableOppacity - setShapeLayerStrokeColor(UIColor.mfSilver()) - } - } - private func setShapeLayerStrokeColor(_ color: UIColor) { if let shapeLayer = shapeLayer { @@ -345,13 +340,19 @@ import MVMCore widthConstraint?.isActive = isActive } + private func checkAndBypassAnimations(selected: Bool) { + updateSelectionOnly = true + isSelected = selected + updateSelectionOnly = false + } + //-------------------------------------------------- // MARK: - UITouch //-------------------------------------------------- open override func touchesEnded(_ touches: Set, with event: UIEvent?) { - sendActions(for: .touchUpInside) + sendActions(for: .touchUpInside) } override open func accessibilityActivate() -> Bool { @@ -370,7 +371,7 @@ import MVMCore open override func reset() { super.reset() - isEnabled(true) + isEnabled = true shapeLayer?.removeAllAnimations() shapeLayer?.removeFromSuperlayer() shapeLayer = nil @@ -379,9 +380,7 @@ import MVMCore borderWidth = 1.0 checkColor = .black checkWidth = 2.0 - updateSelectionOnly = true - isSelected = false - updateSelectionOnly = false + checkAndBypassAnimations(selected: false) } open func setAsMolecule() { @@ -424,16 +423,14 @@ import MVMCore layer.borderWidth = borderWidth } - if let isChecked = dictionary["isChecked"] as? Bool, isChecked { - updateSelectionOnly = true - isSelected = isChecked - updateSelectionOnly = false - } - if let checkColorHex = dictionary["checkColor"] as? String { checkColor = UIColor.mfGet(forHex: checkColorHex) } + if let isChecked = dictionary["isChecked"] as? Bool, isChecked { + checkAndBypassAnimations(selected: isChecked) + } + if let unCheckedBackgroundColorHex = dictionary["unCheckedBackgroundColor"] as? String { unCheckedBackgroundColor = UIColor.mfGet(forHex: unCheckedBackgroundColorHex) } @@ -451,13 +448,57 @@ import MVMCore } if let enabled = dictionary["isEnabled"] as? Bool { - isEnabled(enabled) + isEnabled = enabled } - if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { + if let actionMap = dictionary.optionalDictionaryForKey("action") { actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + + guard let model = model as? CheckboxModel else { return } + + self.delegateObject = delegateObject + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + + groupName = model.groupName + fieldValue = model.value + isRequired = model.required + + if let fieldKey = model.fieldKey { + self.fieldKey = fieldKey + } + + borderColor = model.borderColor.uiColor + borderWidth = model.borderWidth + + checkColor = model.checkColor.uiColor + unCheckedBackgroundColor = model.unCheckedBackgroundColor.uiColor + checkedBackgroundColor = model.checkedBackgroundColor.uiColor + disabledCheckColor = model.disabledCheckColor.uiColor + disabledBorderColor = model.disabledBorderColor.uiColor + disabledBackgroundColor = model.disabledBackgroundColor.uiColor + + isAnimated = model.isAnimated + isRound = model.isRound + + if model.isChecked { + checkAndBypassAnimations(selected: model.isChecked) + } + + isEnabled = model.isEnabled + + if let action = model.action { + actionBlock = { + if let actionMap = action.toJSON() { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + } } // MARK:- FormValidationProtocol diff --git a/MVMCoreUI/Atoms/Views/CheckboxLabelModel.swift b/MVMCoreUI/Atoms/Views/CheckboxLabelModel.swift new file mode 100644 index 00000000..5a9d09f0 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CheckboxLabelModel.swift @@ -0,0 +1,24 @@ +// +// CheckboxWithLabelViewModel.swift +// MVMCoreUI +// +// Created by Chintakrinda, Arun Kumar (Arun) on 21/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public enum CheckboxPosition: String, Codable { + case center + case top + case bottom +} + +@objcMembers public class CheckboxLabelModel: MoleculeModelProtocol { + public static var identifier: String = "checkboxLabel" + public var backgroundColor: Color? + + public var checkboxAlignment: CheckboxPosition? + public var checkbox: CheckboxModel + public var label: LabelModel +} diff --git a/MVMCoreUI/Atoms/Views/CheckboxModel.swift b/MVMCoreUI/Atoms/Views/CheckboxModel.swift new file mode 100644 index 00000000..aa5dedb4 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CheckboxModel.swift @@ -0,0 +1,106 @@ +// +// CheckboxModel.swift +// MVMCoreUI +// +// Created by Chintakrinda, Arun Kumar (Arun) on 21/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class CheckboxModel: MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "checkbox" + public var backgroundColor: Color? + + public var groupName: String? + public var value: String? + public var fieldKey: String? + public var required: Bool = false + public var borderColor: Color = Color(uiColor: .black) + public var borderWidth: CGFloat = 1 + public var isChecked: Bool = false + public var checkColor: Color = Color(uiColor: .black) + public var unCheckedBackgroundColor: Color = Color(uiColor: .clear) + public var checkedBackgroundColor: Color = Color(uiColor: .clear) + public var isAnimated: Bool = true + public var isRound: Bool = false + public var isEnabled: Bool = true + public var action: ActionModelProtocol? + public var disabledBackgroundColor: Color = Color(uiColor: .clear) + public var disabledBorderColor: Color = Color(uiColor: .mvmCoolGray3) + public var disabledCheckColor: Color = Color(uiColor: .mvmCoolGray3) + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case groupName + case value + case fieldKey + case required + case borderColor + case borderWidth + case isChecked + case checkColor + case unCheckedBackgroundColor + case checkedBackgroundColor + case disabledBackgroundColor + case disabledCheckColor + case disabledBorderColor + case isAnimated + case isRound + case isEnabled + case action + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) + value = try typeContainer.decodeIfPresent(String.self, forKey: .value) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) ?? false + borderWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .borderWidth) ?? 1 + borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) ?? Color(uiColor: .black) + checkColor = try typeContainer.decodeIfPresent(Color.self, forKey: .checkColor) ?? Color(uiColor: .black) + unCheckedBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .unCheckedBackgroundColor) ?? Color(uiColor: .clear) + checkedBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .checkedBackgroundColor) ?? Color(uiColor: .clear) + disabledBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBackgroundColor) ?? Color(uiColor: .clear) + disabledBorderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledBorderColor) ?? Color(uiColor: .mvmCoolGray3) + disabledCheckColor = try typeContainer.decodeIfPresent(Color.self, forKey: .disabledCheckColor) ?? Color(uiColor: .mvmCoolGray3) + isChecked = try typeContainer.decodeIfPresent(Bool.self, forKey: .isChecked) ?? false + isAnimated = try typeContainer.decodeIfPresent(Bool.self, forKey: .isAnimated) ?? true + isRound = try typeContainer.decodeIfPresent(Bool.self, forKey: .isRound) ?? false + isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? true + action = try typeContainer.decodeModelIfPresent(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(groupName, forKey: .groupName) + try container.encodeIfPresent(value, forKey: .value) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encodeIfPresent(required, forKey: .required) + try container.encodeIfPresent(borderColor, forKey: .borderColor) + try container.encode(borderWidth, forKey: .borderWidth) + try container.encode(isChecked, forKey: .isChecked) + try container.encodeIfPresent(checkColor, forKey: .checkColor) + try container.encodeIfPresent(unCheckedBackgroundColor, forKey: .unCheckedBackgroundColor) + try container.encodeIfPresent(checkedBackgroundColor, forKey: .checkedBackgroundColor) + try container.encodeIfPresent(disabledBorderColor, forKey: .disabledBorderColor) + try container.encodeIfPresent(disabledBackgroundColor, forKey: .disabledBackgroundColor) + try container.encodeIfPresent(disabledCheckColor, forKey: .disabledCheckColor) + try container.encodeIfPresent(isAnimated, forKey: .isAnimated) + try container.encodeIfPresent(isRound, forKey: .isRound) + try container.encodeIfPresent(isEnabled, forKey: .isEnabled) + try container.encodeModelIfPresent(action, forKey: .action) + } +} diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index 229efe2a..bc23db73 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -21,12 +21,6 @@ public var checkboxPosition: CheckboxPosition = .center - public enum CheckboxPosition: String { - case center - case top - case bottom - } - //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- @@ -120,6 +114,17 @@ checkboxCenterYConstraint?.isActive = false } } + + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let checkBoxWithLabelModel = model as? CheckboxLabelModel else { return } + + if let checkboxAlignment = checkBoxWithLabelModel.checkboxAlignment { + alignCheckbox(checkboxAlignment) + } + + checkbox.setWithModel(checkBoxWithLabelModel.checkbox, delegateObject, additionalData) + label.setWithModel(checkBoxWithLabelModel.label, delegateObject, additionalData) + } } // MARK: - Molecular diff --git a/MVMCoreUI/Atoms/Views/CircleProgressModel.swift b/MVMCoreUI/Atoms/Views/CircleProgressModel.swift new file mode 100644 index 00000000..feb69083 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CircleProgressModel.swift @@ -0,0 +1,128 @@ +// +// CircleProgressModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public enum GraphSize: String, Codable { + case small, medium, large +} + +public enum GraphStyle: String, Codable { + case unlimited, safetyMode +} + +public class CircleProgressModel: MoleculeModelProtocol { + public static var identifier: String = "circleProgress" + public var style: GraphStyle = .unlimited { + didSet { + updateStyle() + } + } + + public var size: GraphSize = .small { + didSet { + updateSize() + } + } + public var diameter: CGFloat = 24 + public var lineWidth: CGFloat = 5 + public var clockwise: Bool = true + public var duration : Double = 1.0 + public var colors = [Color]() + public var backgroundColor: Color? + + public init() {} + + private enum CodingKeys: String, CodingKey { + case style + case size + case diameter + case lineWidth + case clockwise + case duration + case colors + case backgroundColor + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let style = try typeContainer.decodeIfPresent(GraphStyle.self, forKey: .style) { + self.style = style + } + if let size = try typeContainer.decodeIfPresent(GraphSize.self, forKey: .size) { + self.size = size + } + if let diameter = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .diameter) { + self.diameter = diameter + } + if let lineWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .lineWidth) { + self.lineWidth = lineWidth + } + if let clockwise = try typeContainer.decodeIfPresent(Bool.self, forKey: .clockwise) { + self.clockwise = clockwise + } + if let duration = try typeContainer.decodeIfPresent(Double.self, forKey: .duration) { + self.duration = duration + } + if let colors = try typeContainer.decodeIfPresent([Color].self, forKey: .colors) { + self.colors = colors + } + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(style, forKey: .style) + try container.encode(size, forKey: .size) + try container.encode(diameter, forKey: .diameter) + try container.encode(lineWidth, forKey: .lineWidth) + try container.encode(clockwise, forKey: .clockwise) + try container.encode(duration, forKey: .duration) + try container.encode(colors, forKey: .colors) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } + + func getCGColorsFromArray(_ colorArray: [String]) -> [Color] { + return colorArray.map { (colorString) -> Color in + return Color(uiColor: UIColor.mfGet(forHex: colorString)) + } + } + + func updateStyle() { + switch style { + case .unlimited: + duration = 1.0 + clockwise = true + //current style, only the end part shows darker look + colors = getCGColorsFromArray(["#007AB8","#007AB8","#033554"]) + break + case .safetyMode: + duration = 1.5 + clockwise = true + colors = getCGColorsFromArray(["#CC4D0F","#CC4D0F","AB0309"]) + break + } + } + + func updateSize() { + switch size { + case .small: + diameter = MFSizeObject(standardSize: 20)?.getValueBasedOnApplicationWidth() ?? 20 + lineWidth = MFSizeObject(standardSize: 4)?.getValueBasedOnApplicationWidth() ?? 4 + break + case .medium: + diameter = MFSizeObject(standardSize: 100)?.getValueBasedOnApplicationWidth() ?? 100 + lineWidth = MFSizeObject(standardSize: 8)?.getValueBasedOnApplicationWidth() ?? 8 + break + case .large: + diameter = MFSizeObject(standardSize: 180)?.getValueBasedOnApplicationWidth() ?? 180 + lineWidth = MFSizeObject(standardSize: 12)?.getValueBasedOnApplicationWidth() ?? 12 + break + } + } +} diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index c66d68b7..fc8f8bc3 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -15,7 +15,14 @@ open class DashLine: View { // MARK: - Properties //------------------------------------------------------ - @objc public var dashColor: UIColor? + var dashModel: DashLineModel? { + get { return model as? DashLineModel } + } + + //TODO: Need this for BAU. Can remove once we fix BAU + public var dashColor: UIColor? + + @objc private var dashLayer: CAShapeLayer? //------------------------------------------------------ // MARK: - Initializer @@ -64,9 +71,10 @@ open class DashLine: View { dashLayer.lineCap = .round dashLayer.lineDashPattern = [NSNumber(value: 2), NSNumber(value: 2)] dashLayer.path = path.cgPath - dashLayer.strokeColor = dashColor?.cgColor ?? UIColor.mfLighterGray().cgColor + dashLayer.strokeColor = dashModel?.dashColor.cgColor ?? dashColor?.cgColor dashLayer.fillColor = UIColor.clear.cgColor dashLayer.backgroundColor = backgroundColor?.cgColor ?? UIColor.white.cgColor + self.dashLayer = dashLayer } //------------------------------------------------------ @@ -74,23 +82,27 @@ open class DashLine: View { //------------------------------------------------------ // Default values for view. - @objc open func setAsMolecule() { + @objc open override func reset() { backgroundColor = .clear isHidden = false } open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - - // Configure class properties with JSON values - guard let jsonDictionary = json else { return } - - if let isHiddenValue = jsonDictionary[KeyIsHidden] as? Bool { + guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: DashLineModel.self) else { return } + setWithModel(model, delegateObject, additionalData) + } + + //MARK: - MVMCoreMoleculeViewProtocol + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let dashLineModel = dashModel else { + return + } + if let isHiddenValue = dashLineModel.isHidden { isHidden = isHiddenValue } - - if let dashColorHex = jsonDictionary["dashColor"] as? String { - dashColor = UIColor.mfGet(forHex: dashColorHex) + if let backgroundColor = dashLineModel.backgroundColor { + dashLayer?.backgroundColor = backgroundColor.uiColor.cgColor } } } diff --git a/MVMCoreUI/Atoms/Views/DashLineModel.swift b/MVMCoreUI/Atoms/Views/DashLineModel.swift new file mode 100644 index 00000000..1346b5c2 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/DashLineModel.swift @@ -0,0 +1,43 @@ +// +// DashLineModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class DashLineModel: MoleculeModelProtocol { + public static var identifier: String = "dashLine" + public var backgroundColor: Color? + + public var dashColor: Color = Color(uiColor: .mfLighterGray()) + public var isHidden: Bool? + + public init(dashColor: Color) { + self.dashColor = dashColor + } + + private enum CodingKeys: String, CodingKey { + case backgroundColor + case dashColor + case isHidden + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let dashColor = try typeContainer.decodeIfPresent(Color.self, forKey: .dashColor) { + self.dashColor = dashColor + } + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + isHidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .isHidden) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(dashColor, forKey: .dashColor) + try container.encodeIfPresent(isHidden, forKey: .isHidden) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } +} diff --git a/MVMCoreUI/Atoms/Views/DropDown.swift b/MVMCoreUI/Atoms/Views/DropDown.swift deleted file mode 100644 index 929bd67f..00000000 --- a/MVMCoreUI/Atoms/Views/DropDown.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// DropDown.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 11/26/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -@objcMembers public class DropDown: MFTextField { - let picker = MVMCoreUICommonViewsUtility.commonPickerView()! - - public override func getNib() -> UINib? { - return UINib(nibName: String(describing: MFTextField.self), bundle: MVMCoreUIUtility.bundleForMVMCoreUI()) - } - - public override func setupView() { - super.setupView() - dropDownCarrotWidth?.isActive = false - errorHeightConstraint?.constant = 0 - } - - override public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - guard let textField = textField, textField.inputView == nil else { return } - picker.delegate = self - picker.dataSource = self - picker.tag = textField.tag - textField.inputView = picker - picker.reloadAllComponents() - MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegateObject?.uiTextFieldDelegate) - textField.text = json?.stringWithChainOfKeysOrIndexes(["options",picker.selectedRow(inComponent: 0)]) - } -} - -extension DropDown: UIPickerViewDelegate { - public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - return json?.stringWithChainOfKeysOrIndexes(["options",row]) - } - - public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - textField?.text = json?.stringWithChainOfKeysOrIndexes(["options",row]) - } -} - -extension DropDown: UIPickerViewDataSource { - public func numberOfComponents(in pickerView: UIPickerView) -> Int { - return 1 - } - - public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return json?.optionalArrayForKey("options")?.count ?? 0 - } -} diff --git a/MVMCoreUI/Atoms/Views/GraphView.swift b/MVMCoreUI/Atoms/Views/GraphView.swift index 6ef3132c..5818078c 100644 --- a/MVMCoreUI/Atoms/Views/GraphView.swift +++ b/MVMCoreUI/Atoms/Views/GraphView.swift @@ -8,113 +8,13 @@ import UIKit - -enum GraphSize: String { - case small, medium, large -} - -enum GraphStyle: String { - case unlimited, safetyMode -} - -///Graph Object contains properties -public struct GraphObject { - - var style: GraphStyle { - didSet { - updateStyle() - } - } - var size: GraphSize { - didSet { - updateSize() - } - } - var diameter: CGFloat = 24 - var lineWidth: CGFloat = 5 - var clockwise: Bool = true - var duration : Double = 1.0 - var colors = [CGColor]() - - public init(_ json: [AnyHashable : Any]?) { - style = .unlimited - size = .small - guard let json = json else { - return - } - if let styleString = json.optionalStringForKey("style") { - style = GraphStyle(rawValue: styleString) ?? .unlimited - } - if let sizeString = json.optionalStringForKey("size") { - size = GraphSize(rawValue: sizeString) ?? .small - } - updateStyle() - updateSize() - if let diameter = json.optionalCGFloatForKey("diameter") { - self.diameter = diameter - } - if let lineWidth = json.optionalCGFloatForKey("lineWidth") { - self.lineWidth = lineWidth - } - if let clockwise = json.optionalBoolForKey("clockwise") { - self.clockwise = clockwise - } - if let duration = json["duration"] as? Double { - self.duration = duration - } - if let colorArray = json.optionalArrayForKey("colors") as? [String] { - colors = getCGColorsFromArray(colorArray) - } - } - - func getCGColorsFromArray(_ colorArray: [String]) -> [CGColor] { - return colorArray.map { (colorString) -> CGColor in - return UIColor.mfGet(forHex: colorString).cgColor - } - } - - mutating func updateStyle() { - switch style { - case .unlimited: - duration = 1.0 - clockwise = true - //current style, only the end part shows darker look - colors = getCGColorsFromArray(["#007AB8","#007AB8","#033554"]) - break - case .safetyMode: - duration = 1.5 - clockwise = true - colors = getCGColorsFromArray(["#CC4D0F","#CC4D0F","AB0309"]) - break - } - } - - //those are - mutating func updateSize() { - switch size { - case .small: - diameter = MFSizeObject(standardSize: 20)?.getValueBasedOnApplicationWidth() ?? 20 - lineWidth = MFSizeObject(standardSize: 4)?.getValueBasedOnApplicationWidth() ?? 4 - break - case .medium: - diameter = MFSizeObject(standardSize: 100)?.getValueBasedOnApplicationWidth() ?? 100 - lineWidth = MFSizeObject(standardSize: 8)?.getValueBasedOnApplicationWidth() ?? 8 - break - case .large: - diameter = MFSizeObject(standardSize: 180)?.getValueBasedOnApplicationWidth() ?? 180 - lineWidth = MFSizeObject(standardSize: 12)?.getValueBasedOnApplicationWidth() ?? 12 - break - } - } -} - - @objcMembers open class GraphView: View, MVMCoreUIViewConstrainingProtocol { var heightConstraint: NSLayoutConstraint? var gradientLayer: CALayer? - var graphObject: GraphObject? - + var graphModel: CircleProgressModel? { + return model as? CircleProgressModel + } // MARK: setup open override func setupView() { @@ -125,13 +25,17 @@ public struct GraphObject { heightConstraint?.isActive = true widthAnchor.constraint(equalTo: heightAnchor).isActive = true } - - override open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - let object = GraphObject(json) - graphObject = object - createGraphCircle(object) - rotationAnimation(object) + + override open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? CircleProgressModel else { return } + createGraphCircle(model) + rotationAnimation(model) + } + + override open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: CircleProgressModel.self) else { return } + setWithModel(model, delegateObject, additionalData) } class func getAngle(_ piValue: Double) -> Double { @@ -143,7 +47,7 @@ public struct GraphObject { } // MARK: circle - open func createGraphCircle(_ graphObject: GraphObject) { + open func createGraphCircle(_ graphObject: CircleProgressModel) { if let sublayers = layer.sublayers { for sublayer in sublayers { sublayer.removeAllAnimations() @@ -184,14 +88,14 @@ public struct GraphObject { | | | ------------- */ - func createGradientLayer(_ graphObject: GraphObject) -> CALayer { + func createGradientLayer(_ graphObject: CircleProgressModel) -> CALayer { let containLayer = CALayer() containLayer.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter) let radius = graphObject.diameter / 2.0 //create graident layers guard graphObject.colors.count > 1 else { - containLayer.backgroundColor = graphObject.colors.first + containLayer.backgroundColor = graphObject.colors.first?.uiColor.cgColor return containLayer } var topGradientHeight : CGFloat = 0.0 @@ -208,9 +112,11 @@ public struct GraphObject { //if number of colors is even, need to display gradient layer, otherwise make top layer as solid color layer if graphObject.colors.count % 2 == 0 { leftColors.removeLast() - topLayer.colors = [leftColors.last!, rightColors.first!] + let firstColor = leftColors.last!.uiColor.cgColor + let secondColor = rightColors.first!.uiColor.cgColor + topLayer.colors = [firstColor, secondColor] } else { - topLayer.backgroundColor = leftColors.last + topLayer.backgroundColor = leftColors.last?.uiColor.cgColor } containLayer.addSublayer(topLayer) @@ -221,9 +127,11 @@ public struct GraphObject { //count of graidentLayer.colors must be bigger than 1, otherwise set backgroundColor if leftColors.count > 1 { - leftLayer.colors = Array(leftColors) + leftLayer.colors = leftColors.map({ (color) -> CGColor in + return color.uiColor.cgColor + }) } else { - leftLayer.backgroundColor = leftColors.first + leftLayer.backgroundColor = leftColors.first?.uiColor.cgColor } containLayer.addSublayer(leftLayer) @@ -232,9 +140,11 @@ public struct GraphObject { rightLayer.startPoint = CGPoint(x: 0, y: 0) rightLayer.endPoint = CGPoint(x: 0, y: 1) if rightColors.count > 1 { - rightLayer.colors = Array(rightColors) + rightLayer.colors = rightColors.map({ (color) -> CGColor in + return color.uiColor.cgColor + }) } else { - rightLayer.backgroundColor = rightColors.first + rightLayer.backgroundColor = rightColors.first?.uiColor.cgColor } containLayer.addSublayer(rightLayer) @@ -246,7 +156,7 @@ public struct GraphObject { } //MARK: Animation - func rotationAnimation(_ object: GraphObject) { + func rotationAnimation(_ object: CircleProgressModel) { MVMCoreDispatchUtility.performBlock(onMainThread:{ let rotation = CABasicAnimation(keyPath: "transform.rotation") let animationHandler = GraphViewAnimationHandler.shared @@ -277,7 +187,7 @@ public struct GraphObject { extension GraphView: CAAnimationDelegate { public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { - if let object = graphObject { + if let object = graphModel { rotationAnimation(object) } } diff --git a/MVMCoreUI/Atoms/Views/ImageViewModel.swift b/MVMCoreUI/Atoms/Views/ImageViewModel.swift new file mode 100644 index 00000000..ce46a930 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/ImageViewModel.swift @@ -0,0 +1,21 @@ +// +// ImageViewModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class ImageViewModel: MoleculeModelProtocol { + public static var identifier: String = "image" + public var backgroundColor: Color? + + public var image: String + public var accessibilityText: String? + public var fallbackImage: String? + public var imageFormat: String? + public var width: CGFloat? + public var height: CGFloat? +} diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label/Label.swift similarity index 83% rename from MVMCoreUI/Atoms/Views/Label.swift rename to MVMCoreUI/Atoms/Views/Label/Label.swift index 30101710..208676df 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label/Label.swift @@ -12,7 +12,8 @@ import MVMCore public typealias ActionBlock = () -> () -@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol { +@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol, ModelMoleculeViewProtocol { + //------------------------------------------------------ // MARK: - Properties //------------------------------------------------------ @@ -212,6 +213,123 @@ public typealias ActionBlock = () -> () } } + enum LabelAlignment: String { + case center + case right + case left + } + + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + clauses = [] + guard let labelModel = model as? LabelModel else { return } + attributedText = nil + text = labelModel.text + Label.setLabel(self, withHTML: labelModel.html) + let alignment = LabelAlignment(rawValue: labelModel.textAlignment ?? "") + switch alignment { + case .center: + textAlignment = .center + case .right: + textAlignment = .right + default: + textAlignment = .left + } + + makeWholeViewClickable = labelModel.makeWholeViewClickable ?? false + if let backgroundColor = labelModel.backgroundColor { + self.backgroundColor = backgroundColor.uiColor + } + if let accessibilityText = labelModel.accessibilityText { + accessibilityLabel = accessibilityText + } + if let fontStyle = labelModel.fontStyle { + MFStyler.styleLabel(self, withStyle: fontStyle) + MFStyler.styleLabel(self, withStyle: fontStyle, genericScaling: false) + standardFontSize = font.pointSize + } else { + let fontSize = labelModel.fontSize + if let fontSize = fontSize { + standardFontSize = fontSize + } + if let fontName = labelModel.fontName { + font = MFFonts.mfFont(withName: fontName, size: fontSize ?? standardFontSize) + } else if let fontSize = fontSize { + font = font.withSize(fontSize) + } + } + + if let textColorHex = labelModel.textColor, !textColorHex.isEmpty { + textColor = UIColor.mfGet(forHex: textColorHex) + } + + if let attributes = labelModel.attributes, let labelText = text { + let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font.withSize(standardFontSize), NSAttributedString.Key.foregroundColor: textColor as UIColor]) + for attribute in attributes { + let range = NSRange(location: attribute.location, length: attribute.length) + switch attribute { + case _ as LabelAttributeUnderlineModel: + attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range) + case _ as LabelAttributeStrikeThroughModel: + attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) + attributedString.addAttribute(.baselineOffset, value: 0, range: range) + case let colorAtt as LabelAttributeColorModel: + if let colorHex = colorAtt.textColor, !colorHex.isEmpty { + attributedString.removeAttribute(.foregroundColor, range: range) + attributedString.addAttribute(.foregroundColor, value: UIColor.mfGet(forHex: colorHex), range: range) + } + case let imageAtt as LabelAttributeImageModel: + var fontSize = font.pointSize + if let attributeSize = imageAtt.size { + fontSize = attributeSize + } + let imageName = imageAtt.name ?? "externalLink" + let imageAttachment: NSTextAttachment + + if let url = imageAtt.URL { + imageAttachment = Label.getTextAttachmentFrom(url: url, dimension: fontSize, label: self) + } else { + imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize) + } + let mutableString = NSMutableAttributedString() + mutableString.append(NSAttributedString(attachment: imageAttachment)) + attributedString.insert(mutableString, at: imageAtt.location) + case let fontAtt as LabelAttributeFontModel: + if let fontStyle = fontAtt.style { + let styles = MFStyler.styleGetAttributedString("0", withStyle: fontStyle) + attributedString.removeAttribute(.font, range: range) + attributedString.removeAttribute(.foregroundColor, range: range) + attributedString.addAttributes(styles.attributes(at: 0, effectiveRange: nil), range: range) + } else { + let fontSize = fontAtt.size + var font: UIFont? + + if let fontName = fontAtt.name { + font = MFFonts.mfFont(withName: fontName, size: fontSize ?? self.font.pointSize) + } else if let fontSize = fontSize { + font = self.font.withSize(fontSize) + } + if let font = font { + attributedString.removeAttribute(.font, range: range) + attributedString.addAttribute(.font, value: font, range: range) + } + } + case let actionAtt as LabelAttributeActionModel: + addTappableLinkAttribute(range: NSRange(location: range.location, length: range.length)) { + if let data = try? actionAtt.action.encode(using: JSONEncoder()), let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any] { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + } + addActionAttributes(range: range, string: attributedString) + default: + continue + } + } + attributedText = attributedString + originalAttributedString = attributedText + hero = labelModel.hero + } + } + @objc public static func setUILabel(_ label: UILabel?, withJSON json: [AnyHashable: Any]?, delegate: DelegateObject?, additionalData: [AnyHashable: Any]?) { guard let label = label else { return } diff --git a/MVMCoreUI/Atoms/Views/Label/LabelAttributeActionModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeActionModel.swift new file mode 100644 index 00000000..2ebcb4b4 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Label/LabelAttributeActionModel.swift @@ -0,0 +1,32 @@ +// +// LabelAttributeActionModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +class LabelAttributeActionModel: LabelAttributeModel { + override public class var identifier: String { + return "action" + } + var action: ActionModelProtocol + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + action = try typeContainer.decodeModel(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeModel(action, forKey: .action) + } + + private enum CodingKeys: String, CodingKey { + case action + } +} diff --git a/MVMCoreUI/Atoms/Views/Label/LabelAttributeColorModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeColorModel.swift new file mode 100644 index 00000000..26f1fa54 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Label/LabelAttributeColorModel.swift @@ -0,0 +1,35 @@ +// +// LabelAttributeColorModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class LabelAttributeColorModel: LabelAttributeModel { + + override public class var identifier: String { + return "color" + } + + var textColor: String? + + private enum CodingKeys: String, CodingKey { + case textColor + } + + required public init(from decoder: Decoder) throws { + + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.textColor = try typeContainer.decodeIfPresent(String.self, forKey: .textColor) + 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(textColor, forKey: .textColor) + } +} diff --git a/MVMCoreUI/Atoms/Views/Label/LabelAttributeFontModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeFontModel.swift new file mode 100644 index 00000000..88ae728d --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Label/LabelAttributeFontModel.swift @@ -0,0 +1,41 @@ +// +// LabelAttributeFontModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class LabelAttributeFontModel: LabelAttributeModel { + override public class var identifier: String { + return "font" + } + + var style: String? + var name: String? + var size: CGFloat? + + private enum CodingKeys: String, CodingKey { + case style + case name + case size + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.style = try typeContainer.decodeIfPresent(String.self, forKey: .style) + self.name = try typeContainer.decodeIfPresent(String.self, forKey: .name) + self.size = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .size) + 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(style, forKey: .style) + try container.encodeIfPresent(name, forKey: .name) + try container.encodeIfPresent(size, forKey: .size) + } +} diff --git a/MVMCoreUI/Atoms/Views/Label/LabelAttributeImageModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeImageModel.swift new file mode 100644 index 00000000..0095a589 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Label/LabelAttributeImageModel.swift @@ -0,0 +1,42 @@ +// +// LabelAttributeImageModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +class LabelAttributeImageModel: LabelAttributeModel { + + override public class var identifier: String { + return "image" + } + + var size: CGFloat? + var name: String? + var URL: String? + + private enum CodingKeys: String, CodingKey { + case size + case name + case URL + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.size = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .size) + self.name = try typeContainer.decodeIfPresent(String.self, forKey: .name) + self.URL = try typeContainer.decodeIfPresent(String.self, forKey: .URL) + 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(size, forKey: .size) + try container.encodeIfPresent(name, forKey: .name) + try container.encodeIfPresent(URL, forKey: .URL) + } +} diff --git a/MVMCoreUI/Atoms/Views/Label/LabelAttributeModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeModel.swift new file mode 100644 index 00000000..ae2768ab --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Label/LabelAttributeModel.swift @@ -0,0 +1,41 @@ +// +// LabelAttributeModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class LabelAttributeModel: Model { + + public class var identifier: String { + return "" + } + + var type: String + var location: Int + var length: Int + + private enum CodingKeys: String, CodingKey { + case type + case location + case length + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.type = try typeContainer.decode(String.self, forKey: .type) + self.location = try typeContainer.decode(Int.self, forKey: .location) + self.length = try typeContainer.decode(Int.self, forKey: .length) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(type, forKey: .type) + try container.encode(location, forKey: .location) + try container.encode(length, forKey: .length) + } + +} diff --git a/MVMCoreUI/Atoms/Views/Label/LabelAttributeStrikeThroughModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeStrikeThroughModel.swift new file mode 100644 index 00000000..f69e966d --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Label/LabelAttributeStrikeThroughModel.swift @@ -0,0 +1,22 @@ +// +// LabelAttributeStrikeThroughModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class LabelAttributeStrikeThroughModel: LabelAttributeModel { + override public class var identifier: String { + return "strikethrough" + } + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + } +} diff --git a/MVMCoreUI/Atoms/Views/Label/LabelAttributeUnderlineModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelAttributeUnderlineModel.swift new file mode 100644 index 00000000..d052686d --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Label/LabelAttributeUnderlineModel.swift @@ -0,0 +1,23 @@ +// +// LabelAttributeUnderlineModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/21/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class LabelAttributeUnderlineModel: LabelAttributeModel { + override public class var identifier: String { + return "underline" + } + + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + } +} diff --git a/MVMCoreUI/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelModel.swift new file mode 100644 index 00000000..b0ad442f --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Label/LabelModel.swift @@ -0,0 +1,85 @@ +// +// Label.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + + +import Foundation + +@objcMembers public class LabelModel: MoleculeModelProtocol { + public static var identifier: String = "label" + public var moleculeName: String? + public var backgroundColor: Color? + + public var text: String + public var accessibilityText: String? + public var textColor: String? + public var fontStyle: String? + public var fontName: String? + public var fontSize: CGFloat? + public var textAlignment: String? + public var attributes: [LabelAttributeModel]? + public var html: String? + public var hero: Int? + public var makeWholeViewClickable: Bool? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case text + case accessibilityText + case textColor + case backgroundColor + case fontStyle + case fontName + case fontSize + case textAlignment + case attributes + case html + case hero + case makeWholeViewClickable + } + + enum AttributeTypeKey: String, CodingKey { + case type + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName) + self.text = try typeContainer.decode(String.self, forKey: .text) + self.accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) + self.textColor = try typeContainer.decodeIfPresent(String.self, forKey: .textColor) + self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + self.fontStyle = try typeContainer.decodeIfPresent(String.self, forKey: .fontStyle) + self.fontName = try typeContainer.decodeIfPresent(String.self, forKey: .fontName) + self.fontSize = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .fontSize) + self.textAlignment = try typeContainer.decodeIfPresent(String.self, forKey: .textAlignment) + self.attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes, typeCodingKey: AttributeTypeKey.type) as? [LabelAttributeModel] + self.html = try typeContainer.decodeIfPresent(String.self, forKey: .html) + self.hero = try typeContainer.decodeIfPresent(Int.self, forKey: .hero) + self.makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(moleculeName, forKey: .moleculeName) + try container.encode(text, forKey: .text) + try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) + try container.encodeIfPresent(textColor, forKey: .textColor) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(fontStyle, forKey: .fontStyle) + try container.encodeIfPresent(fontName, forKey: .fontName) + try container.encodeIfPresent(fontSize, forKey: .fontSize) + try container.encodeIfPresent(textAlignment, forKey: .textAlignment) + var attributeContainer = container.nestedUnkeyedContainer(forKey: .attributes) + try attributes?.forEach { attributeModel in + try attributeContainer.encode(attributeModel) + } + try container.encodeIfPresent(html, forKey: .html) + try container.encodeIfPresent(hero, forKey: .hero) + try container.encodeIfPresent(makeWholeViewClickable, forKey: .makeWholeViewClickable) + } +} diff --git a/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift b/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift new file mode 100644 index 00000000..325611ec --- /dev/null +++ b/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift @@ -0,0 +1,16 @@ +// +// LeftRightLabelModel.swift +// MVMCoreUI +// +// Created by Ryan on 12/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class LeftRightLabelModel: MoleculeModelProtocol { + public static var identifier: String = "leftRightLabelView" + public var backgroundColor: Color? + public var leftText: LabelModel + public var rightText: LabelModel? +} diff --git a/MVMCoreUI/Atoms/Views/LeftRightLabelView.swift b/MVMCoreUI/Atoms/Views/LeftRightLabelView.swift index a059aef5..1096df37 100644 --- a/MVMCoreUI/Atoms/Views/LeftRightLabelView.swift +++ b/MVMCoreUI/Atoms/Views/LeftRightLabelView.swift @@ -9,7 +9,7 @@ import Foundation -@objcMembers open class LeftRightLabelView: ViewConstrainingView { +@objcMembers open class LeftRightLabelView: View { //------------------------------------------------------ // MARK: - Outlets //------------------------------------------------------ @@ -28,8 +28,8 @@ import Foundation // MARK: - Initialization //------------------------------------------------------ - public init() { - super.init(frame: .zero) + public convenience init() { + self.init(frame: .zero) } public override init(frame: CGRect) { @@ -176,4 +176,20 @@ import Foundation constrainLeftLabel() } } + + //MARK: - MVMCoreMoleculeViewProtocol + + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let leftRightLabelModel = model as? LeftRightLabelModel else { + return + } + leftTextLabel.setWithModel(leftRightLabelModel.leftText, delegateObject, additionalData) + rightTextLabel.setWithModel(leftRightLabelModel.rightText, delegateObject, additionalData) + if !leftTextLabel.hasText { + constrainRightLabel() + } else if !rightTextLabel.hasText { + constrainLeftLabel() + } + } } diff --git a/MVMCoreUI/Atoms/Views/Line.swift b/MVMCoreUI/Atoms/Views/Line.swift index d5f4f4b6..d6ec5d8a 100644 --- a/MVMCoreUI/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atoms/Views/Line.swift @@ -9,42 +9,35 @@ import UIKit @objcMembers open class Line: View { + var lineModel: LineModel? { + get { return model as? LineModel } + } public var heightConstraint: NSLayoutConstraint? - public enum Style: String, Codable { - case standard - case thin - case medium - case heavy - case none - } - - public var style = Style.standard { - didSet { - switch style { - case .standard: - heightConstraint?.constant = 1 - backgroundColor = .mfSilver() - case .thin: - heightConstraint?.constant = 1 - backgroundColor = .black - case .medium: - heightConstraint?.constant = 2 - backgroundColor = .black - case .heavy: - heightConstraint?.constant = 4 - backgroundColor = .black - case .none: - heightConstraint?.constant = 0 - } + open func setStyle(_ style: LineModel.Style) { + switch style { + case .standard: + heightConstraint?.constant = 1 + backgroundColor = .mfSilver() + case .thin: + heightConstraint?.constant = 1 + backgroundColor = .black + case .medium: + heightConstraint?.constant = 2 + backgroundColor = .black + case .heavy: + heightConstraint?.constant = 4 + backgroundColor = .black + case .none: + heightConstraint?.constant = 0 } } // MARK: - Helpers open func shouldBeVisible() -> Bool { - guard let type = json?.optionalStringForKey(KeyType) else { return false } - return type != "none" + guard let type = lineModel?.type else { return false } + return type != .none } public convenience init(pinTo view: UIView, edge: UIRectEdge, useMargin: Bool) { @@ -56,26 +49,33 @@ import UIKit // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() - backgroundColor = .black heightConstraint = heightAnchor.constraint(equalToConstant: 1) heightConstraint?.isActive = true + setStyle(.standard) } // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { // If no type, default to standard. - if let typeString = json?.optionalStringForKey(KeyType), let type = Style.init(rawValue: typeString) { - style = type + if let typeString = json?.optionalStringForKey(KeyType), let type = LineModel.Style.init(rawValue: typeString) { + setStyle(type) } else { - style = .standard + setStyle(.standard) } super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + if let lineModel = model as? LineModel { + setStyle(lineModel.type ?? .standard) + } + super.setWithModel(model, delegateObject, additionalData) + } + open override func reset() { - style = .standard + setStyle(.standard) } open func copyBackgroundColor() -> Bool { @@ -83,7 +83,7 @@ import UIKit } public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - guard let type = json?.optionalStringForKey(KeyType), let style = Style(rawValue: type) else { return 1 } + guard let type = json?.optionalStringForKey(KeyType), let style = LineModel.Style(rawValue: type) else { return 1 } switch style { case .none: return 0 diff --git a/MVMCoreUI/Atoms/Views/LineModel.swift b/MVMCoreUI/Atoms/Views/LineModel.swift new file mode 100644 index 00000000..53bc1f4c --- /dev/null +++ b/MVMCoreUI/Atoms/Views/LineModel.swift @@ -0,0 +1,81 @@ +// +// LineModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/28/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class LineModel: MoleculeModelProtocol { + + /* + The frequency of the line in a moleculeList. + all (between all cells, above top, below bottom) + allExceptTop (between all cells, below bottom) + allExceptBottom (between all cells, above top) + between (between all cells) + */ + public enum Frequency: String, Codable { + case all + case allExceptTop + case allExceptBottom + case between + } + + /* + The style of the line. + standard (1 height, silver) + thin (1 height, black) + medium (2 height, black) + heavy (4 height, black) + none (hidden) + */ + public enum Style: String, Codable { + case standard + case thin + case medium + case heavy + case none + } + + public static var identifier: String = "line" + public var type: Style = .standard + public var frequency: Frequency? = .allExceptTop + + //TODO: use color insted of backgroundColor. Needs server changes +// public var color: Color? + public var backgroundColor: Color? + + public init(type: Style) { + self.type = type + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case type + case backgroundColor + case color + case frequency + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let type = try typeContainer.decodeIfPresent(Style.self, forKey: .type) { + self.type = type + } + if let frequency = try typeContainer.decodeIfPresent(Frequency.self, forKey: .frequency) { + self.frequency = frequency + } + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(type, forKey: .type) + try container.encodeIfPresent(frequency, forKey: .frequency) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } +} diff --git a/MVMCoreUI/Atoms/Views/MFLoadImageView.swift b/MVMCoreUI/Atoms/Views/MFLoadImageView.swift index 361c0aa2..242e299a 100644 --- a/MVMCoreUI/Atoms/Views/MFLoadImageView.swift +++ b/MVMCoreUI/Atoms/Views/MFLoadImageView.swift @@ -8,7 +8,7 @@ import UIKit -@objcMembers open class MFLoadImageView: ViewConstrainingView { +@objcMembers open class MFLoadImageView: ViewConstrainingView, ModelMoleculeViewProtocol { public let loadingSpinner = MFLoadingSpinner(frame: .zero) public let imageView = MFTransparentGIFView(frame: .zero) public var addSizeConstraintsForAspectRatio = false @@ -209,6 +209,31 @@ import UIKit } } + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let imageModel = model as? ImageViewModel else { + return + } + if let accessibilityString = imageModel.accessibilityText { + imageView.accessibilityLabel = accessibilityString + imageView.accessibilityTraits = .staticText + imageView.isAccessibilityElement = true + } + let width = imageModel.width ?? imageWidth + let height = imageModel.height ?? imageHeight + // For smoother transitions, set constraints that we know immediately. + if let width = width, addSizeConstraintsForAspectRatio { + setWidth(width) + } + if let height = height, addSizeConstraintsForAspectRatio { + setHeight(height) + } + if shouldLoadImage(withName: imageModel.image, width: width, height: height) { + imageView.image = nil + imageView.animatedImage = nil + loadImage(withName: imageModel.image, format: imageModel.imageFormat, width: width as NSNumber?, height: height as NSNumber?, customFallbackImage: imageModel.fallbackImage) + } + } + // MARK: - MVMCoreUIMoleculeViewProtocol functions open override func setAsMolecule() { addSizeConstraintsForAspectRatio = true @@ -264,7 +289,7 @@ import UIKit self?.addConstraints(width: width, height: height, size: image?.size) self?.loadingSpinnerHeightConstraint?.constant = 0 if layoutWillChange { - self?.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self!) + self?.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self!) } completionHandler(image,data,isFallbackImage) })} diff --git a/MVMCoreUI/Atoms/Views/MFView+ModelExtension.swift b/MVMCoreUI/Atoms/Views/MFView+ModelExtension.swift new file mode 100644 index 00000000..d532d7c4 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/MFView+ModelExtension.swift @@ -0,0 +1,24 @@ +// +// MFView+ModelExtension.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +extension MFView { + public func setUpDefaultWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + self.model = model + if let backgroundColor = model?.backgroundColor { + self.backgroundColor = backgroundColor.uiColor + } + } +} + +extension ModelMoleculeViewProtocol where Self: MFView { + func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + setUpDefaultWithModel(model, delegateObject, additionalData) + } +} diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch+Model.swift b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch+Model.swift new file mode 100644 index 00000000..e2ef7b31 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch+Model.swift @@ -0,0 +1,31 @@ +// +// MVMCoreUISwitch+Model.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/14/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +// temporary until link is finished +extension MVMCoreUISwitch: ModelMoleculeViewProtocol { + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let model = model as? ToggleModel else { return } + + if let castSelf = self as? FormValidationProtocol { + FormValidator.setupValidation(molecule: castSelf, delegate: delegateObject?.formValidationProtocol) + } + + setState(model.state, animated: false) + + guard let action = model.action else { return } + actionBlock = { + if let data = try? action.encode(using: JSONEncoder()), + let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any] { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + + } + } + } +} diff --git a/MVMCoreUI/Atoms/Views/MultiProgress.swift b/MVMCoreUI/Atoms/Views/MultiProgress.swift index fdfb5316..cee425cc 100644 --- a/MVMCoreUI/Atoms/Views/MultiProgress.swift +++ b/MVMCoreUI/Atoms/Views/MultiProgress.swift @@ -8,35 +8,13 @@ import UIKit -@objcMembers open class ProgressBarObject { - ///from 0.0 to 1.0. input progress should be [0 100] - var progress: CGFloat = 0.0 - ///default color is cerulean - var color: UIColor = UIColor.mfCerulean() - - init(_ module: [AnyHashable: Any]?) { - progress = (module?.optionalCGFloatForKey("progress") ?? 0.0)/100 - if let colorString = module?.optionalStringForKey("progressColor") { - color = UIColor.mfGet(forHex: colorString) - } - } - - static func getProgressBarObjectList(_ list: [[AnyHashable: Any]]?) -> [ProgressBarObject]? { - guard list?.count ?? 0 > 0 else { - return nil - } - var progressList = [ProgressBarObject]() - for module in list! { - let progressObject = ProgressBarObject(module) - progressList.append(progressObject) - } - return progressList - } -} - @objcMembers open class MultiProgress: View { + var multiProgressModel: MultiProgressBarModel? { + get { return model as? MultiProgressBarModel } + } + ///passing value to progressList creates corresponding progress bars - var progressList: Array? { + var progressList: Array? { didSet { for subview in subviews { subview.removeFromSuperview() @@ -52,7 +30,7 @@ import UIKit let view = UIView(frame: .zero) view.translatesAutoresizingMaskIntoConstraints = false addSubview(view) - view.backgroundColor = progressObject.color + view.backgroundColor = progressObject.progressColor.uiColor view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: progressObject.progress).isActive = true view.leadingAnchor.constraint(equalTo: previous?.trailingAnchor ?? leadingAnchor).isActive = true previous = view @@ -70,6 +48,7 @@ import UIKit } } } + var thicknessConstraint: NSLayoutConstraint? let defaultHeight: CGFloat = 8 @@ -84,16 +63,26 @@ import UIKit } } + //MARK: - MVMCoreMoleculeViewProtocol + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let multiProgressModel = multiProgressModel else { + return + } + roundedRect = multiProgressModel.roundedRect ?? false + thicknessConstraint?.constant = multiProgressModel.thickness ?? defaultHeight + progressList = multiProgressModel.progressList + + } + open override func reset() { super.reset() backgroundColor = .mfLightSilver() progressList = nil } - override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - thicknessConstraint?.constant = json?.optionalCGFloatForKey("thickness") ?? defaultHeight - roundedRect = json?.optionalBoolForKey("roundedRect") ?? false - progressList = ProgressBarObject.getProgressBarObjectList(json?.optionalArrayForKey("progressList") as? [[AnyHashable: Any]]) + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: MultiProgressBarModel.self) else { return } + setWithModel(model, delegateObject, additionalData) } } diff --git a/MVMCoreUI/Atoms/Views/MultiProgressModel.swift b/MVMCoreUI/Atoms/Views/MultiProgressModel.swift new file mode 100644 index 00000000..6dd05b0b --- /dev/null +++ b/MVMCoreUI/Atoms/Views/MultiProgressModel.swift @@ -0,0 +1,54 @@ +// +// MultiProgressModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class SingleProgressBarModel: Codable { + @Percent var progress: CGFloat + var progressColor: Color + + init(_ progress: CGFloat, color: Color) { + self.progress = progress + self.progressColor = color + } +} + +@objcMembers public class MultiProgressBarModel: MoleculeModelProtocol { + public static var identifier: String = "multiProgressBar" + public var progressList: [SingleProgressBarModel] + public var backgroundColor: Color? + public var thickness: CGFloat? + public var roundedRect: Bool? + + private enum CodingKeys: String, CodingKey { + case progressList + case thickness + case roundedRect + case backgroundColor + } + + public init(_ progressList: [SingleProgressBarModel]) { + self.progressList = progressList + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + progressList = try typeContainer.decode([SingleProgressBarModel].self, forKey: .progressList) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + thickness = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .thickness) + roundedRect = try typeContainer.decodeIfPresent(Bool.self, forKey: .roundedRect) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(progressList, forKey: .progressList) + try container.encodeIfPresent(thickness, forKey: .thickness) + try container.encodeIfPresent(roundedRect, forKey: .roundedRect) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } +} diff --git a/MVMCoreUI/Atoms/Views/ProgressBar.swift b/MVMCoreUI/Atoms/Views/ProgressBar.swift index c73c77b4..17d35c29 100644 --- a/MVMCoreUI/Atoms/Views/ProgressBar.swift +++ b/MVMCoreUI/Atoms/Views/ProgressBar.swift @@ -8,12 +8,13 @@ import Foundation -@objcMembers open class ProgressBar: UIProgressView, MVMCoreUIMoleculeViewProtocol, MVMCoreViewProtocol { - var isRounded = false +@objcMembers open class ProgressBar: UIProgressView, MVMCoreViewProtocol, ModelMoleculeViewProtocol, MVMCoreUIMoleculeViewProtocol { + var progressBarModel: ProgressBarModel? + var thickness: CGFloat = 8.0 { willSet(newValue) { heightAnchor.constraint(equalToConstant: newValue).isActive = true - if isRounded { + if progressBarModel?.isRounded ?? false { layer.cornerRadius = newValue/2.0 } else { progressViewStyle = .bar @@ -40,34 +41,35 @@ import Foundation public func setupView() { clipsToBounds = true translatesAutoresizingMaskIntoConstraints = false - reset() + thickness = 8 + progress = 0 + progressTintColor = UIColor.mfCerulean() + trackTintColor = UIColor.mfLightSilver() } public func updateView(_ size: CGFloat) { } - // MARK: - MVMCoreUIMoleculeViewProtocol - public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - if let isRounded = json?.optionalBoolForKey("roundedRect") { - self.isRounded = isRounded + //MARK: - MVMCoreMoleculeViewProtocol + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let progressBarModel = model as? ProgressBarModel else { + return } - if let thickness = json?.optionalCGFloatForKey("thickness") { - self.thickness = thickness - } - // as? Float returns nil, apple defect. - if let percentage = json?["percent"] as? CGFloat { - progress = Float(percentage/100.0) - } - if let progressColor = json?.optionalStringForKey("progressColor") { - progressTintColor = UIColor.mfGet(forHex: progressColor) - } - if let backgroundColor = json?.optionalStringForKey("backgroundColor") { - trackTintColor = UIColor.mfGet(forHex: backgroundColor) + thickness = progressBarModel.thickness ?? 8 + progress = Float((progressBarModel.percent)/100.0) + progressTintColor = progressBarModel.progressColor.uiColor + if let backgroundColor = progressBarModel.backgroundColor { + trackTintColor = backgroundColor.uiColor } } + // MARK: - MVMCoreUIMoleculeViewProtocol + public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: ProgressBarModel.self) else { return } + setWithModel(model, delegateObject, additionalData) + } + public func reset() { - isRounded = false thickness = 8 progress = 0 progressTintColor = UIColor.mfCerulean() diff --git a/MVMCoreUI/Atoms/Views/ProgressBarModel.swift b/MVMCoreUI/Atoms/Views/ProgressBarModel.swift new file mode 100644 index 00000000..dd53afac --- /dev/null +++ b/MVMCoreUI/Atoms/Views/ProgressBarModel.swift @@ -0,0 +1,54 @@ +// +// ProgressBarModel.swift +// MVMCoreUI +// +// Created by Ryan on 11/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class ProgressBarModel: MoleculeModelProtocol { + public static var identifier: String = "progressBar" + @Percent public var percent: CGFloat + public var progressColor: Color = Color(uiColor: .mfCerulean()) + public var backgroundColor: Color? = Color(uiColor: .mfLightSilver()) + public var isRounded: Bool? + public var thickness: CGFloat? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case isRounded = "roundRect" + case thickness + case percent + case progressColor + case backgroundColor + } + + public init(_ percent: CGFloat) { + self.percent = percent + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + percent = try typeContainer.decode(CGFloat.self, forKey: .percent) + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .progressColor) { + progressColor = color + } + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { + backgroundColor = color + } + isRounded = try typeContainer.decodeIfPresent(Bool.self, forKey: .isRounded) + thickness = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .thickness) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(percent, forKey: .percent) + try container.encode(progressColor, forKey: .progressColor) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(isRounded, forKey: .isRounded) + try container.encodeIfPresent(thickness, forKey: .thickness) + } +} diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index 17d286ac..80425054 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -332,6 +332,20 @@ public typealias ActionBlockConfirmation = () -> (Bool) layoutIfNeeded() } } + + // MARK:- ModelMoleculeViewProtocol + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let toggleModel = model as? ToggleModel else { + return + } + + let toggleModelJSON = toggleModel.toJSON() + setWithJSON(toggleModelJSON, delegateObject: delegateObject, additionalData: additionalData) + } + + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return Self.getContainerHeight() + } } // MARK: - Accessibility @@ -389,7 +403,7 @@ extension Toggle { changeStateNoAnimation(state) } - if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { + if let actionMap = dictionary.optionalDictionaryForKey("action") { didToggleAction = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } @@ -410,7 +424,7 @@ extension Toggle { return true } - public func alignment() -> UIStackView.Alignment { + public func horizontalAlignment() -> UIStackView.Alignment { return .trailing } } diff --git a/MVMCoreUI/Atoms/Views/ToggleModel.swift b/MVMCoreUI/Atoms/Views/ToggleModel.swift new file mode 100644 index 00000000..9cd96f45 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/ToggleModel.swift @@ -0,0 +1,57 @@ +// +// ToggleModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/14/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class ToggleModel: MoleculeModelProtocol { + public static var identifier: String = "toggle" + public var moleculeName: String? + public var backgroundColor: Color? + public var state: Bool = true + public var action: ActionModelProtocol? + public var alternateAction: ActionModelProtocol? + public var required: Bool? + public var fieldKey: String? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case state + case action + case backgroundColor + case required + case fieldKey + case alternateAction + } + + public init(_ state: Bool) { + self.state = state + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { + self.state = state + } + action = try typeContainer.decodeModelIfPresent(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) + alternateAction = try typeContainer.decodeModelIfPresent(codingKey: .alternateAction, typeCodingKey: ActionCodingKey.actionType) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeModelIfPresent(action, forKey: .action) + try container.encodeModelIfPresent(alternateAction, forKey: .alternateAction) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(state, forKey: .state) + try container.encodeIfPresent(required, forKey: .required) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + } +} diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index 1d5d743e..0a382705 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -6,26 +6,24 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -public typealias ButtonBlock = (Button) -> () +public typealias ButtonAction = (Button) -> () - -@objcMembers open class Button: UIButton, MFButtonProtocol { +@objcMembers open class Button: UIButton, MFButtonProtocol, ModelMoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - - public var json: [AnyHashable: Any]? - public var actionMap: [AnyHashable: Any]? + open var model: MoleculeModelProtocol? + open var actionModel: ActionModelProtocol? private var initialSetupPerformed = false - private var buttonBlock: ButtonBlock? + private var buttonAction: ButtonAction? //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- - public weak var buttonDelegate: ButtonDelegateProtocol? + open weak var buttonDelegate: ButtonDelegateProtocol? //-------------------------------------------------- // MARK: - Initializers @@ -50,6 +48,7 @@ public typealias ButtonBlock = (Button) -> () // MARK: - Setup //-------------------------------------------------- + /// Required to be called any init. Ensures setupView() only gets called once public func initialSetup() { if !initialSetupPerformed { @@ -62,61 +61,58 @@ public typealias ButtonBlock = (Button) -> () // MARK: - Methods //-------------------------------------------------- - public func addBlock( event: Event, _ buttonBlock: @escaping ButtonBlock) { - self.buttonBlock = buttonBlock - addTarget(self, action: #selector(callBlock(_:)), for: event) + /// Adds a block to be performed for the given event. + open func addActionBlock(event: Event, _ buttonBlock: @escaping ButtonAction) { + self.buttonAction = buttonBlock + addTarget(self, action: #selector(callActionBlock(_:)), for: event) } - func callBlock(_ sender: Button) { - buttonBlock?(self) + @objc private func callActionBlock(_ sender: Button) { + buttonAction?(self) } - public func setWithActionMap(_ actionMap: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - self.actionMap = actionMap - + open func set(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.actionModel = actionModel buttonDelegate = delegateObject?.buttonDelegate - addBlock(event: .touchUpInside) { [weak self] sender in + addActionBlock(event: .touchUpInside) { [weak self] sender in guard let self = self else { return } - - if self.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { + if let data = try? actionModel.encode(using: JSONEncoder()), + let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any], + delegateObject?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } } -} - -// MARK: - MVMCoreUIMoleculeViewProtocol -extension Button: MVMCoreUIMoleculeViewProtocol { - public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - self.json = json - - guard let dictionary = json else { return } - - if let backgroundColorString = dictionary[KeyBackgroundColor] as? String { - backgroundColor = UIColor.mfGet(forHex: backgroundColorString) - } - - if let title = dictionary[KeyTitle] as? String { - setTitle(title, for: .normal) + // MARK:- ModelMoleculeViewProtocol + open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + self.model = model + if let backgroundColor = model?.backgroundColor { + self.backgroundColor = backgroundColor.uiColor } } - public func reset() { - backgroundColor = .clear + open class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + return model?.moleculeName + } + + open class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return nil + } + + open class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + return nil } } - // MARK: - MVMCoreViewProtocol extension Button: MVMCoreViewProtocol { - public func updateView(_ size: CGFloat) {} + open func updateView(_ size: CGFloat) {} /// Will be called only once. - public func setupView() { - + open func setupView() { translatesAutoresizingMaskIntoConstraints = false insetsLayoutMarginsFromSafeArea = false titleLabel?.numberOfLines = 0 @@ -124,9 +120,15 @@ extension Button: MVMCoreViewProtocol { } } +// MARK: - MVMCoreUIMoleculeViewProtocol +extension Button: MVMCoreUIMoleculeViewProtocol { + open func reset() { + backgroundColor = .clear + } +} + // MARK: AppleGuidelinesProtocol extension Button: AppleGuidelinesProtocol { - override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return Self.acceptablyOutsideBounds(point: point, bounds: bounds) } diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index 5bc80003..d73e0fef 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -8,13 +8,13 @@ import UIKit -@objcMembers open class Control: UIControl { +@objcMembers open class Control: UIControl, ModelMoleculeViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - - public var json: [AnyHashable: Any]? - + open var json: [AnyHashable: Any]? + open var model: MoleculeModelProtocol? + private var initialSetupPerformed = false //-------------------------------------------------- @@ -41,19 +41,36 @@ import UIKit //-------------------------------------------------- public func initialSetup() { - if !initialSetupPerformed { initialSetupPerformed = true setupView() } } + + // MARK:- ModelMoleculeViewProtocol + open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + self.model = model + if let backgroundColor = model?.backgroundColor { + self.backgroundColor = backgroundColor.uiColor + } + } + + open class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + return model?.moleculeName + } + + open class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return nil + } + + open class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + return nil + } } // MARK: - AppleGuidelinesProtocol extension Control: AppleGuidelinesProtocol { - override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - return Self.acceptablyOutsideBounds(point: point, bounds: bounds) } } @@ -61,10 +78,10 @@ extension Control: AppleGuidelinesProtocol { // MARK: - MVMCoreViewProtocol extension Control: MVMCoreViewProtocol { - public func updateView(_ size: CGFloat) {} + open func updateView(_ size: CGFloat) {} /// Will be called only once. - public func setupView() { + open func setupView() { translatesAutoresizingMaskIntoConstraints = false insetsLayoutMarginsFromSafeArea = false } @@ -72,16 +89,15 @@ extension Control: MVMCoreViewProtocol { // MARK: - MVMCoreUIMoleculeViewProtocol extension Control: MVMCoreUIMoleculeViewProtocol { + open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.json = json + + if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { + backgroundColor = UIColor.mfGet(forHex: backgroundColorString) + } + } - public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - self.json = json - - if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { - backgroundColor = UIColor.mfGet(forHex: backgroundColorString) - } - } - - public func reset() { + open func reset() { backgroundColor = .clear } } diff --git a/MVMCoreUI/BaseClasses/View.swift b/MVMCoreUI/BaseClasses/View.swift index 515dc0eb..a117d60a 100644 --- a/MVMCoreUI/BaseClasses/View.swift +++ b/MVMCoreUI/BaseClasses/View.swift @@ -8,12 +8,9 @@ import UIKit -@objcMembers open class View: UIView { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - +@objcMembers open class View: UIView, ModelMoleculeViewProtocol { open var json: [AnyHashable: Any]? + open var model: MoleculeModelProtocol? private var initialSetupPerformed = false @@ -36,12 +33,31 @@ import UIKit } public func initialSetup() { - if !initialSetupPerformed { initialSetupPerformed = true setupView() } } + + // MARK:- ModelMoleculeViewProtocol + open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + self.model = model + if let backgroundColor = model?.backgroundColor { + self.backgroundColor = backgroundColor.uiColor + } + } + + open class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + return model?.moleculeName + } + + open class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return nil + } + + open class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + return nil + } } // MARK:- MVMCoreViewProtocol @@ -53,6 +69,7 @@ extension View: MVMCoreViewProtocol { open func setupView() { translatesAutoresizingMaskIntoConstraints = false insetsLayoutMarginsFromSafeArea = false + MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0) } } @@ -70,4 +87,8 @@ extension View: MVMCoreUIMoleculeViewProtocol { open func reset() { backgroundColor = .clear } + + open func setAsMolecule() { + + } } diff --git a/MVMCoreUI/BaseControllers/MFViewController+Model.swift b/MVMCoreUI/BaseControllers/MFViewController+Model.swift new file mode 100644 index 00000000..6cedf93c --- /dev/null +++ b/MVMCoreUI/BaseControllers/MFViewController+Model.swift @@ -0,0 +1,49 @@ +// +// MFViewController+Model.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +extension MFViewController: MoleculeDelegateProtocol { + public func getModuleWithName(_ name: String?) -> [AnyHashable : Any]? { + guard let name = name else { + return nil + } + return loadObject?.modulesJSON?.optionalDictionaryForKey(name) + } + + public func getModuleWithName(_ moduleName: String) -> MoleculeModelProtocol? { + guard let moduleJSON = loadObject?.modulesJSON?.optionalDictionaryForKey(moduleName), + let moleculeName = moduleJSON.optionalStringForKey("moleculeName"), + let modelType = ModelRegistry.getType(for: moleculeName) as? MoleculeModelProtocol.Type else { + return nil + } + do { + return try modelType.decode(jsonDict: moduleJSON) + } catch { + MVMCoreUILoggingHandler.logDebugMessage(withDelegate: "error: \(error)") + } + return nil + } + + @objc public func moleculeLayoutUpdated(_ molecule: UIView & MVMCoreUIMoleculeViewProtocol) { + + } + + @objc public func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // Do nothing + } + + @objc public func removeMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // Do nothing + } +} + +public extension MFViewController { + @objc func parsePageJSON() throws { + } +} diff --git a/MVMCoreUI/BaseControllers/MFViewController.h b/MVMCoreUI/BaseControllers/MFViewController.h index c1415df4..cd96a07f 100644 --- a/MVMCoreUI/BaseControllers/MFViewController.h +++ b/MVMCoreUI/BaseControllers/MFViewController.h @@ -27,14 +27,13 @@ #import #import #import -#import @class MainMenuViewController; @class MVMCoreUITabBarPageControlViewController; @class MVMAnimationManager; @class DelegateObject; -@interface MFViewController : UIViewController +@interface MFViewController : UIViewController // Stores the load object that this screen was loaded with. @property (nullable, strong, nonatomic) MVMCoreLoadObject *loadObject; diff --git a/MVMCoreUI/BaseControllers/MFViewController.m b/MVMCoreUI/BaseControllers/MFViewController.m index 2c8e3997..09700268 100644 --- a/MVMCoreUI/BaseControllers/MFViewController.m +++ b/MVMCoreUI/BaseControllers/MFViewController.m @@ -97,6 +97,17 @@ self.pageType = loadObject.pageType; self.loadObject = loadObject; + NSError *parseError = nil; + [self parsePageJSONAndReturnError:&parseError]; + if (parseError) { + if (error) { + MVMCoreErrorObject *errorObject = [MVMCoreErrorObject createErrorObjectForNSError:parseError location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + errorObject.messageToDisplay = [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; + *error = errorObject; + } + return false; + } + // Verifies all modules needed are loaded. return [MFViewController verifyRequiredModulesLoadedForLoadObject:loadObject error:error]; } @@ -249,6 +260,8 @@ - (BOOL)newPageLoaded:(nonnull NSDictionary *)page { self.loadObject.pageJSON = page; + NSError *parseError = nil; + [self parsePageJSONAndReturnError:&parseError]; return YES; } @@ -832,15 +845,6 @@ } } -#pragma mark - MoleculeDelegateProtocol - -- (NSDictionary *)getModuleWithName:(NSString *)name { - if (!name) { - return nil; - } - return [self.loadObject.modulesJSON dict:name]; -} - #pragma mark - adobe analytics - (nullable NSArray *)additionalActionsToTrackWithMainActionInformation:(nullable NSDictionary *)actionInformation additionalData:(nullable NSDictionary *)additionalData { diff --git a/MVMCoreUI/Categories/UIColor+Extension.swift b/MVMCoreUI/Categories/UIColor+Extension.swift new file mode 100644 index 00000000..fab1ae6f --- /dev/null +++ b/MVMCoreUI/Categories/UIColor+Extension.swift @@ -0,0 +1,210 @@ +// +// UIColor+Extension.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + + +import UIKit + +public typealias ColorHexTuple = (uiColor: UIColor, hex: String) + +extension UIColor { + + /// Dictionary to access brand approved colors by name. + public static let names: [String: ColorHexTuple] = ["black": (.mvmBlack, "#000000"), + "white": (.mvmWhite, "#FFFFFF"), + "red": (.mvmRed, "#D52B1E"), + "orange": (.mvmOrange, "#CC4D0F"), + "green": (.mvmGreen, "#008631"), + "blue": (.mvmBlue, "#007AB8"), + "blueGradient": (.mvmBlueGradient, "#007AB8"), + "yellow": (.mvmYellow, "#FFBC3D"), + "coolGray1": (.mvmCoolGray1, "#F6F6F6"), + "coolGray3": (.mvmCoolGray3, "#D8DADA"), + "coolGray6": (.mvmCoolGray6, "#747676"), + "vzupGold": (.vzupGold, "#B89B56"), + "vzupYellow1": (.vzupYellow1, "#F9D542"), + "vzupYellow2": (.vzupYellow2, "#F4CA53"), + "vzupYellow3": (.vzupYellow3, "#CC9B2D")] + + //-------------------------------------------------- + // MARK: - Brand + //-------------------------------------------------- + + /// HEX: #000000 + public static let mvmBlack = UIColor.black + + /// HEX: #FFFFFF + public static let mvmWhite = UIColor.white + + /// HEX: #D52B1E + public static let mvmRed = UIColor.color8Bits(red: 213, green: 43, blue: 30) + + /// HEX: #CC4D0F + public static let mvmOrange = UIColor.color8Bits(red: 204, green: 77, blue: 15) + + /// HEX: #008631 + public static let mvmGreen = UIColor.color8Bits(red: 0, green: 134, blue: 49) + + /// HEX: #007AB8 + public static let mvmBlue = UIColor.color8Bits(red: 0, green: 122, blue: 184) + + /// HEX: #007AB8 + public static let mvmBlueGradient = UIColor.color8Bits(red: 0, green: 122, blue: 184) + + /// HEX: #FFBC3D + public static let mvmYellow = UIColor.color8Bits(red: 255, green: 188, blue: 61) + + /// HEX: #F6F6F6 + public static let mvmCoolGray1 = UIColor.grayscale(rgb: 246) + + /// HEX: #D8DADA + public static let mvmCoolGray3 = UIColor.color8Bits(red: 216, green: 218, blue: 218) + + /// HEX: #747676 + public static let mvmCoolGray6 = UIColor.color8Bits(red: 116, green: 118, blue: 118) + + //-------------------------------------------------- + // MARK: - VZ UP Brand + //-------------------------------------------------- + + /// HEX: #B89B56 + public static let vzupGold = UIColor.color8Bits(red: 184, green: 155, blue: 68) + + /// HEX: #F9D542 + public static let vzupYellow1 = UIColor.color8Bits(red: 249, green: 213, blue: 66) + + /// HEX: #F4CA53 + public static let vzupYellow2 = UIColor.color8Bits(red: 244, green: 202, blue: 83) + + /// HEX: #CC9B2D + public static let vzupYellow3 = UIColor.color8Bits(red: 204, green: 155, blue: 45) + + //-------------------------------------------------- + // MARK: - Functions + //-------------------------------------------------- + + /// Convenience to get a grayscale UIColor where the same value is used for red, green, and blue. + public class func grayscale(rgb: Int, alpha: CGFloat = 1.0) -> UIColor { + + let grayscale = CGFloat(rgb) / 255.0 + return UIColor(red: grayscale, green: grayscale, blue: grayscale, alpha: alpha) + } + + /// Convenience to get a UIColor. + public class func color8Bits(red: Int, green: Int, blue: Int, alpha: CGFloat = 1.0) -> UIColor { + + return UIColor(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: alpha) + } + + /// Convenience to get a UIColor. + public class func color8Bits(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1.0) -> UIColor { + + return UIColor(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: alpha) + } + + /// Gets UIColor via an 8 digit hex string. + public class func getColorBy(hex: String) -> UIColor { + + var hexint: UInt64 = 0 + + let scanner = Scanner(string: hex) + scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") + scanner.scanHexInt64(&hexint) + + return UIColor(red: (CGFloat((hexint & 0xFF0000) >> 16)) / 255, + green: (CGFloat((hexint & 0xFF00) >> 8)) / 255, + blue: (CGFloat(hexint & 0xFF)) / 255, + alpha: 1) + } + + /// Gets UIColor via an 8 digit hex string. The last two being the alpha channel. + public class func getColorWithTransparencyBy(hex: String) -> UIColor { + + var hexint: UInt64 = 0 + + let scanner = Scanner(string: hex) + scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") + scanner.scanHexInt64(&hexint) + + return UIColor(red: (CGFloat((hexint & 0xFF000000) >> 24)) / 255, + green: (CGFloat((hexint & 0xFF0000) >> 16)) / 255, + blue: (CGFloat((hexint & 0xFF00) >> 8)) / 255, + alpha: (CGFloat(hexint & 0xFF)) / 255) + } + + public class func gradientColor(_ color: UIColor?) -> UIColor { + + var h: CGFloat = 0 + var s: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + + if color?.getHue(&h, saturation: &s, brightness: &b, alpha: &a) ?? false { + return UIColor(hue: h, saturation: max(s - 0.17, 0.0), brightness: min(b - 0.03, 1.0), alpha: a) + } + + return .white + } + + public class func setBackgroundColor(forNavigationBar color: UIColor, navigationBar: UINavigationBar, transparent: Bool) { + + DispatchQueue.main.async { + + let view = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1)) + view.backgroundColor = color + + UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0) + + if let context = UIGraphicsGetCurrentContext() { + view.layer.render(in: context) + } + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + if transparent { + navigationBar.setBackgroundImage(UIImage(), for: .default) + navigationBar.isTranslucent = false + } else { + navigationBar.setBackgroundImage(image, for: .default) + } + } + } + + /// - parameter color: The UIColor intended to retrieve its hex value. + public class func hexString(for color: UIColor) -> String? { + + guard let components = color.cgColor.components else { return nil } + + if color.cgColor.numberOfComponents >= 3 { + let r = Int(CGFloat(components[0]) * 255) + let g = Int(CGFloat(components[1]) * 255) + let b = Int(CGFloat(components[2]) * 255) + + // If alpha of color is less than 1.0 then alpha hex is relevant. + if color.cgColor.numberOfComponents == 4 && components[3] < 1.0 { + let a = Int(CGFloat(components[3]) * 255) + return String(format: "%02X%02X%02X%02X", r, g, b, a) + } + + return String(format: "%02X%02X%02X", r, g, b) + } + + return nil + } + + public class func getColorAndHexFromName(_ name: String) -> ColorHexTuple? { + + for row in names { + if name == row.key { + return row.value + } + } + + return nil + } +} diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index 353ea861..623b2f47 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -29,7 +29,7 @@ import UIKit let navigationController = self.init() style(navigationController.navigationBar) navigationController.separatorView = Line(pinTo: navigationController.navigationBar, edge: .bottom, useMargin: false) - navigationController.separatorView?.style = .standard + navigationController.separatorView?.setStyle(.standard) MVMCoreUISession.sharedGlobal()?.navigationController = navigationController MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = navigationController MVMCoreNavigationHandler.shared()?.navigationController = navigationController diff --git a/MVMCoreUI/Containers/Views/Container/Container.swift b/MVMCoreUI/Containers/Views/Container/Container.swift new file mode 100644 index 00000000..53abfda3 --- /dev/null +++ b/MVMCoreUI/Containers/Views/Container/Container.swift @@ -0,0 +1,82 @@ +// +// Container.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/11/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class Container: View, ContainerProtocol { + public var view: UIView? + let containerHelper = ContainerHelper() + var containerModel: ContainerModelProtocol? { + get { return model as? ContainerModelProtocol } + } + + // MARK:- ModelMoleculeViewProtocol + override open func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let containerModel = model as? ContainerModelProtocol else { return } + containerHelper.set(with: containerModel, for: view as? MVMCoreUIViewConstrainingProtocol) + } + + // MARK:- ContainerProtocol + public func alignHorizontal(_ alignment: UIStackView.Alignment) { + containerHelper.alignHorizontal(alignment) + } + + public func alignVertical(_ alignment: UIStackView.Alignment) { + containerHelper.alignVertical(alignment) + } + + public func constrainView(_ view: UIView) { + containerHelper.constrainView(view) + } +} + +// MARK: - MVMCoreViewProtocol +public extension Container { + override func updateView(_ size: CGFloat) { + super.updateView(size) + (view as? MVMCoreViewProtocol)?.updateView(size) + containerHelper.updateViewMargins(self, model: containerModel, size: size) + } + + /// Will be called only once. + override func setupView() { + super.setupView() + backgroundColor = .clear + } + + func addAndContain(_ view: UIView) { + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + containerHelper.constrainView(view) + self.view = view + } + + convenience init(andContain view: UIView) { + self.init() + addAndContain(view) + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +public extension Container { + override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + guard let view = view else { return } + containerHelper.set(with: json, for: view) + } + + override func reset() { + super.reset() + (view as? MVMCoreUIMoleculeViewProtocol)?.reset?() + } + + override func setAsMolecule() { + (view as? MVMCoreUIMoleculeViewProtocol)?.setAsMolecule?() + } +} diff --git a/MVMCoreUI/Containers/Container.swift b/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift similarity index 76% rename from MVMCoreUI/Containers/Container.swift rename to MVMCoreUI/Containers/Views/Container/ContainerHelper.swift index 069cd9e3..77226341 100644 --- a/MVMCoreUI/Containers/Container.swift +++ b/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift @@ -1,19 +1,13 @@ // -// Container.swift +// ContainerHelper.swift // MVMCoreUI // -// Created by Scott Pfeil on 12/11/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. +// Created by Scott Pfeil on 1/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. // +// A Helper that consolidates some common container logic -import UIKit - -public protocol ContainerModelProtocol { - var horizontalAlignment: UIStackView.Alignment? { get set } - var verticalAlignment: UIStackView.Alignment? { get set } - var useHorizontalMargins: Bool? { get set } - var useVerticalMargins: Bool? { get set } -} +import Foundation public class ContainerHelper: NSObject { var leftConstraint: NSLayoutConstraint? @@ -145,15 +139,6 @@ public class ContainerHelper: NSObject { } } - func set(with model: ContainerModelProtocol) { - if let horizontalAlignment = model.horizontalAlignment { - alignHorizontal(horizontalAlignment) - } - if let verticalAlignment = model.verticalAlignment { - alignVertical(verticalAlignment) - } - } - static func getAlignment(for string: String) -> UIStackView.Alignment? { switch string { case "leading": @@ -169,6 +154,34 @@ public class ContainerHelper: NSObject { } } + static func getAlignmentString(for alignment: UIStackView.Alignment?) -> String? { + switch alignment { + case .leading: + return "leading" + case .trailing: + return "trailing" + case .center: + return "center" + case .fill: + return "fill" + default: + return nil + } + } + + 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) + } + + func set(with model: ContainerModelProtocol, for contained: MVMCoreUIViewConstrainingProtocol?) { + if let horizontalAlignment = model.horizontalAlignment ?? contained?.horizontalAlignment?() { + alignHorizontal(horizontalAlignment) + } + if let verticalAlignment = model.verticalAlignment ?? contained?.verticalAlignment?() { + alignVertical(verticalAlignment) + } + } + func set(with JSON: [AnyHashable: Any]?, for contained: UIView) { if let horizontalAlignmentString = JSON?.optionalStringForKey("horizontalAlignment"), let alignment = ContainerHelper.getAlignment(for: horizontalAlignmentString) ?? (contained as? MVMCoreUIViewConstrainingProtocol)?.horizontalAlignment?() { alignHorizontal(alignment) @@ -183,57 +196,3 @@ public class ContainerHelper: NSObject { } } } - -open class Container: View { - var model: ContainerModelProtocol? - var view: UIView? - let containerHelper = ContainerHelper() - - var topMarginPadding: CGFloat = 0 - var bottomMarginPadding: CGFloat = 0 -} - -// MARK: - MVMCoreViewProtocol -public extension Container { - override func updateView(_ size: CGFloat) { - super.updateView(size) - (view as? MVMCoreViewProtocol)?.updateView(size) - MFStyler.setMarginsFor(self, size: size, defaultHorizontal: model?.useHorizontalMargins ?? true, top: model?.useHorizontalMargins ?? true ? topMarginPadding : 0, bottom: model?.useHorizontalMargins ?? true ? bottomMarginPadding : 0) - } - - /// Will be called only once. - override func setupView() { - super.setupView() - backgroundColor = .clear - } - - func addAndContain(_ view: UIView) { - view.translatesAutoresizingMaskIntoConstraints = false - addSubview(view) - containerHelper.constrainView(view) - self.view = view - } - - convenience init(andContain view: UIView) { - self.init() - addAndContain(view) - } -} - -// MARK: - MVMCoreUIMoleculeViewProtocol -public extension Container { - override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - guard let view = view else { return } - containerHelper.set(with: json, for: view) - } - - override func reset() { - super.reset() - (view as? MVMCoreUIMoleculeViewProtocol)?.reset?() - } - - func setAsMolecule() { - (view as? MVMCoreUIMoleculeViewProtocol)?.setAsMolecule?() - } -} diff --git a/MVMCoreUI/Containers/Views/Container/ContainerModel.swift b/MVMCoreUI/Containers/Views/Container/ContainerModel.swift new file mode 100644 index 00000000..1b721183 --- /dev/null +++ b/MVMCoreUI/Containers/Views/Container/ContainerModel.swift @@ -0,0 +1,54 @@ +// +// ContainerModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 12/4/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class ContainerModel: ContainerModelProtocol, Codable { + public var horizontalAlignment: UIStackView.Alignment? + public var verticalAlignment: UIStackView.Alignment? + public var useHorizontalMargins: Bool? + + public var useVerticalMargins: Bool? + public var topMarginPadding: CGFloat? + public var bottomMarginPadding: CGFloat? + + private enum CodingKeys: String, CodingKey { + case verticalAlignment + case horizontalAlignment + case useHorizontalMargins + case useVerticalMargins + case topMarginPadding + case bottomMarginPadding + } + + public init() {} + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let verticalAlignmentString = try typeContainer.decodeIfPresent(String.self, forKey: .verticalAlignment) { + verticalAlignment = ContainerHelper.getAlignment(for: verticalAlignmentString) + } + if let horizontalAlignmentString = try typeContainer.decodeIfPresent(String.self, forKey: .horizontalAlignment) { + horizontalAlignment = ContainerHelper.getAlignment(for: horizontalAlignmentString) + } + useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins) + useVerticalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useVerticalMargins) + topMarginPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .topMarginPadding) + bottomMarginPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .bottomMarginPadding) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + 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(useVerticalMargins, forKey: .useVerticalMargins) + try container.encodeIfPresent(topMarginPadding, forKey: .topMarginPadding) + try container.encodeIfPresent(bottomMarginPadding, forKey: .bottomMarginPadding) + } +} diff --git a/MVMCoreUI/Containers/Views/Container/ContainerProtocol.swift b/MVMCoreUI/Containers/Views/Container/ContainerProtocol.swift new file mode 100644 index 00000000..10bac27c --- /dev/null +++ b/MVMCoreUI/Containers/Views/Container/ContainerProtocol.swift @@ -0,0 +1,16 @@ +// +// ContainerProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ContainerProtocol { + var view: UIView? { get set } + func alignHorizontal(_ alignment: UIStackView.Alignment) + func alignVertical(_ alignment: UIStackView.Alignment) + func constrainView(_ view: UIView) +} diff --git a/MVMCoreUI/Containers/views/EntryFieldContainer.swift b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift similarity index 94% rename from MVMCoreUI/Containers/views/EntryFieldContainer.swift rename to MVMCoreUI/Containers/Views/EntryFieldContainer.swift index dd7c79f2..3f80006a 100644 --- a/MVMCoreUI/Containers/views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift @@ -118,7 +118,7 @@ import UIKit //-------------------------------------------------- /// Holds reference to delegateObject to inform molecular tableView of an update. - weak var delegateObject: MVMCoreUIDelegateObject? + var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- // MARK: - Lifecycle @@ -266,14 +266,22 @@ import UIKit let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) bottomBar?.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) - delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) setNeedsDisplay() layoutIfNeeded() } } + + //-------------------------------------------------- + // MARK: - MVMCoreUIMoleculeViewProtocol + //-------------------------------------------------- + + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + self.delegateObject = delegateObject + } } -// MARK:- MVMCoreUIMoleculeViewProtocol extension EntryFieldContainer { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Containers/Views/MoleculeContainer.swift b/MVMCoreUI/Containers/Views/MoleculeContainer.swift new file mode 100644 index 00000000..9a907222 --- /dev/null +++ b/MVMCoreUI/Containers/Views/MoleculeContainer.swift @@ -0,0 +1,69 @@ +// +// MoleculeContainer.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class MoleculeContainer: Container { + + /// Can be overriden to change how the molecule is added to the hierarchy. + public func addMolecule(_ molecule: UIView) { + addAndContain(molecule) + } + + override public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + return + } + if view == nil { + if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: false) { + addAndContain(molecule) + } + } else { + (view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON?(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) + } + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + if let casteModel = model as? MoleculeContainerModel { + if view != nil { + (view as? ModelMoleculeViewProtocol)?.setWithModel(casteModel.molecule, delegateObject, additionalData) + } else { + if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(casteModel.molecule, delegateObject) { + addMolecule(molecule) + } + } + } + super.setWithModel(model, delegateObject, additionalData) + } + + public override static func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let containerModel = model as? MoleculeContainerModel, + let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(containerModel.molecule) as? ModelMoleculeViewProtocol.Type, + let moleculeName = moleculeClass.nameForReuse(containerModel.molecule, delegateObject) else { + return "\(model?.moleculeName ?? "moleculeContainer")<>" + } + return "\(model?.moleculeName ?? "moleculeContainer")<\(moleculeName)>" + } + + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + guard let containerModel = molecule as? MoleculeContainerModel else { return 0 } + guard let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(containerModel.molecule) as? ModelMoleculeViewProtocol.Type, + let moleculeHeight = moleculeClass.estimatedHeight(forRow: containerModel.molecule, delegateObject: delegateObject) else { + return (containerModel.topMarginPadding ?? 0) + (containerModel.bottomMarginPadding ?? 0) + } + return moleculeHeight + (containerModel.topMarginPadding ?? 0) + (containerModel.bottomMarginPadding ?? 0) + } + + public override class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + guard let containerModel = molecule as? MoleculeContainerModel, + let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(containerModel.molecule) as? ModelMoleculeViewProtocol.Type else { return nil } + return moleculeClass.requiredModules(containerModel.molecule, delegateObject: delegateObject, error: error) + } +} diff --git a/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift b/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift new file mode 100644 index 00000000..0421b3d1 --- /dev/null +++ b/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift @@ -0,0 +1,34 @@ +// +// MoleculeContainerModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/6/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class MoleculeContainerModel: ContainerModel { + public var molecule: MoleculeModelProtocol + + private enum CodingKeys: String, CodingKey { + case molecule + } + + public init(with moleculeModel: MoleculeModelProtocol) { + molecule = moleculeModel + super.init() + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + molecule = try typeContainer.decodeMolecule(codingKey: .molecule) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeModel(molecule, forKey: .molecule) + } +} diff --git a/MVMCoreUI/CustomPrimitives/Color.swift b/MVMCoreUI/CustomPrimitives/Color.swift new file mode 100644 index 00000000..d72e1af9 --- /dev/null +++ b/MVMCoreUI/CustomPrimitives/Color.swift @@ -0,0 +1,123 @@ +// +// Color.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 11/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +/* + UIColor is not supported by Codable. This Color class + effectively turns UIColor into a primitive class like + Int and String and can be used the same. + */ +public final class Color: Codable { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public let uiColor: UIColor + + public var cgColor: CGColor { + return uiColor.cgColor + } + + public private(set) var hex: String = "" + public private(set) var name: String = "" + + // Color metadata 🎨 + public private(set) var red: CGFloat = 0 + public private(set) var green: CGFloat = 0 + public private(set) var blue: CGFloat = 0 + public private(set) var alpha: CGFloat = 1 + + public var hexWithHash: String { + return "#" + hex + } + + //-------------------------------------------------- + // MARK: - Error + //-------------------------------------------------- + + enum ColorError: Error { + case badName(reason: String) + } + + //-------------------------------------------------- + // MARK: - Class Initializers + //-------------------------------------------------- + + init(uiColor: UIColor) { + self.uiColor = uiColor + hex = UIColor.hexString(for: uiColor) ?? "" + determineRGBA() + } + + init?(name: String) { + guard let colorTuple = UIColor.getColorAndHexFromName(name) else { return nil } + self.uiColor = colorTuple.uiColor + self.hex = colorTuple.hex + determineRGBA() + } + + //-------------------------------------------------- + // MARK: - Codable Initializers + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let colorString = try container.decode(String.self) + + if colorString.hasPrefix("#") { + hex = colorString.replacingOccurrences(of: "#", with: "") + } else { + guard let hex = UIColor.getColorAndHexFromName(colorString)?.hex else { throw ColorError.badName(reason: "Check the spelling of your color.") } + self.hex = hex.replacingOccurrences(of: "#", with: "") + name = colorString + } + + if hex.count == 8 { + uiColor = UIColor.getColorWithTransparencyBy(hex: hex) + } else { + uiColor = UIColor.getColorBy(hex: hex) + } + + determineRGBA() + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(hexWithHash) + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + public func convertHexToFloat(start: String.Index, end: String.Index) -> CGFloat { + + return CGFloat(Int(hex[start.. @property (nullable, nonatomic, strong) NSDictionary *json; +@property (nullable, nonatomic, strong) id model; // Called in the initialization functions. Can setup ui here. - (void)setupView; diff --git a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h b/MVMCoreUI/Legacy/Views/MVMCoreUIMoleculeViewProtocol.h similarity index 97% rename from MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h rename to MVMCoreUI/Legacy/Views/MVMCoreUIMoleculeViewProtocol.h index a499eb7c..f11a5002 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h +++ b/MVMCoreUI/Legacy/Views/MVMCoreUIMoleculeViewProtocol.h @@ -10,14 +10,15 @@ @import MVMCore.MVMCoreViewProtocol; @class MVMCoreUIDelegateObject; @class MVMCoreErrorObject; +@class MoleculeModelProtocol; @protocol MVMCoreUIMoleculeViewProtocol +@optional + /// Sets up the ui based on the json - (void)setWithJSON:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject additionalData:(nullable NSDictionary *)additionalData; -@optional - /// Called after init to provide an early setter for any molecule specific logic - (void)setAsMolecule; diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.h b/MVMCoreUI/Legacy/Views/MVMCoreUIPageControl.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.h rename to MVMCoreUI/Legacy/Views/MVMCoreUIPageControl.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.m b/MVMCoreUI/Legacy/Views/MVMCoreUIPageControl.m similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.m rename to MVMCoreUI/Legacy/Views/MVMCoreUIPageControl.m diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUIPagingProtocol.h b/MVMCoreUI/Legacy/Views/MVMCoreUIPagingProtocol.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUIPagingProtocol.h rename to MVMCoreUI/Legacy/Views/MVMCoreUIPagingProtocol.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h b/MVMCoreUI/Legacy/Views/MVMCoreUISwitch.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h rename to MVMCoreUI/Legacy/Views/MVMCoreUISwitch.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m b/MVMCoreUI/Legacy/Views/MVMCoreUISwitch.m similarity index 99% rename from MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m rename to MVMCoreUI/Legacy/Views/MVMCoreUISwitch.m index 33874a8d..1c40bc5a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m +++ b/MVMCoreUI/Legacy/Views/MVMCoreUISwitch.m @@ -171,7 +171,7 @@ const CGFloat SwitchShakeIntensity = 2; [self setState:[json boolForKey:@"state"] animated:false]; - NSDictionary *actionMap = [json dict:@"actionMap"]; + NSDictionary *actionMap = [json dict:@"action"]; if (actionMap) { [self addTarget:self action:@selector(addCustomAction) forControlEvents:UIControlEventTouchUpInside]; } @@ -423,16 +423,16 @@ const CGFloat SwitchShakeIntensity = 2; return UIAccessibilityTraitButton; } -- (NSString * _Nullable)formFieldGroupName { - return [self.json string:@"groupName"]; -} - - (NSString *)accessibilityHint { return [MVMCoreUIUtility hardcodedStringWithKey:@"AccToggleHint"]; } #pragma mark FormValidationProtocol +- (NSString * _Nullable)formFieldGroupName { + return [self.json string:@"groupName"]; +} + - (BOOL)isValidField { return self.isOn && [self.json boolForKey:@"required"]; } diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.h b/MVMCoreUI/Legacy/Views/MVMCoreUITextFieldView.h similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.h rename to MVMCoreUI/Legacy/Views/MVMCoreUITextFieldView.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.m b/MVMCoreUI/Legacy/Views/MVMCoreUITextFieldView.m similarity index 100% rename from MVMCoreUI/Atoms/Views/MVMCoreUITextFieldView.m rename to MVMCoreUI/Legacy/Views/MVMCoreUITextFieldView.m diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.h b/MVMCoreUI/Legacy/Views/PrimaryButton.h similarity index 97% rename from MVMCoreUI/Atoms/Buttons/PrimaryButton.h rename to MVMCoreUI/Legacy/Views/PrimaryButton.h index 99929430..e83484a0 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.h +++ b/MVMCoreUI/Legacy/Views/PrimaryButton.h @@ -46,6 +46,9 @@ static CGFloat const PrimaryButtonSmallHeight = 30.0; // set YES to skip highlight method, for customer color button @property (nonatomic) BOOL skipHighlighted; +@property (nonatomic) BOOL validationRequired; +@property (nullable, nonatomic, strong) NSArray *requiredGroupsList; + // The main creation functions, changed to black button for 2.0 for default + (nullable instancetype)primaryButton:(BOOL)enabled; + (nullable instancetype)primarySmallButton:(BOOL)enabled; diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m b/MVMCoreUI/Legacy/Views/PrimaryButton.m similarity index 99% rename from MVMCoreUI/Atoms/Buttons/PrimaryButton.m rename to MVMCoreUI/Legacy/Views/PrimaryButton.m index 2bc9e026..439d5479 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m +++ b/MVMCoreUI/Legacy/Views/PrimaryButton.m @@ -20,8 +20,6 @@ @interface PrimaryButton() -@property (nonatomic) BOOL validationRequired; -@property (nonatomic, strong) NSArray *requiredGroupsList; @property (nonatomic) BOOL smallButton; @property (assign, nonatomic) BOOL tinyButton; @property (nonatomic) CGFloat sizeForSizing; diff --git a/MVMCoreUI/Atoms/Views/TextButtonView.h b/MVMCoreUI/Legacy/Views/TextButtonView.h similarity index 100% rename from MVMCoreUI/Atoms/Views/TextButtonView.h rename to MVMCoreUI/Legacy/Views/TextButtonView.h diff --git a/MVMCoreUI/Atoms/Views/TextButtonView.m b/MVMCoreUI/Legacy/Views/TextButtonView.m similarity index 100% rename from MVMCoreUI/Atoms/Views/TextButtonView.m rename to MVMCoreUI/Legacy/Views/TextButtonView.m diff --git a/MVMCoreUI/Molecules/TopLabelsView.h b/MVMCoreUI/Legacy/Views/TopLabelsView.h similarity index 100% rename from MVMCoreUI/Molecules/TopLabelsView.h rename to MVMCoreUI/Legacy/Views/TopLabelsView.h diff --git a/MVMCoreUI/Molecules/TopLabelsView.m b/MVMCoreUI/Legacy/Views/TopLabelsView.m similarity index 100% rename from MVMCoreUI/Molecules/TopLabelsView.m rename to MVMCoreUI/Legacy/Views/TopLabelsView.m diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Legacy/Views/ViewConstrainingView.h similarity index 92% rename from MVMCoreUI/Atoms/Views/ViewConstrainingView.h rename to MVMCoreUI/Legacy/Views/ViewConstrainingView.h index df443afd..30f5a552 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Legacy/Views/ViewConstrainingView.h @@ -30,6 +30,8 @@ @property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *topPinLow; @property (nullable, strong, nonatomic) IBOutlet NSLayoutConstraint *bottomPinLow; +@property (weak, nullable, nonatomic) UIView *constrainedView; + /// In updateView, will set horizontal padding to default. @property (nonatomic) BOOL updateViewHorizontalDefaults; @property (nonatomic) BOOL updateViewVerticalDefaults; @@ -50,7 +52,7 @@ + (nonnull ViewConstrainingView *)viewConstrainingView:(nonnull UIView *)view; // Can be initialized with a molecule to constrain -- (nullable instancetype)initWithMolecule:(nonnull UIView *)molecule alignment:(UIStackViewAlignment)alignment; +- (nonnull instancetype)initWithMolecule:(nonnull UIView *)molecule alignment:(UIStackViewAlignment)alignment; #pragma mark - Constraining @@ -89,6 +91,9 @@ - (void)alignBottom; - (void)alignFillVertical; +- (void)alignHorizontal:(UIStackViewAlignment)alignment; +- (void)alignVertical:(UIStackViewAlignment)alignment; + /// Convenience function for getting the alignment from a map. + (UIStackViewAlignment)getAlignmentForString:(nullable NSString *)alignmentString defaultAlignment:(UIStackViewAlignment)defaultAlignment; diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Legacy/Views/ViewConstrainingView.m similarity index 97% rename from MVMCoreUI/Atoms/Views/ViewConstrainingView.m rename to MVMCoreUI/Legacy/Views/ViewConstrainingView.m index f7aaf7f6..b98e72a5 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Legacy/Views/ViewConstrainingView.m @@ -18,12 +18,11 @@ #import "MVMCoreUIViewConstrainingProtocol.h" @interface ViewConstrainingView () -@property (weak, nullable, nonatomic) UIView *constrainedView; @end @implementation ViewConstrainingView -- (nullable instancetype)initWithMolecule:(nonnull UIView *)molecule alignment:(UIStackViewAlignment)alignment { +- (nonnull instancetype)initWithMolecule:(nonnull UIView *)molecule alignment:(UIStackViewAlignment)alignment { if (self = [super init]) { if (!molecule.superview) { [self addConstrainedView:molecule alignment:alignment]; @@ -343,8 +342,8 @@ self.updateViewVerticalDefaults = NO; self.topMarginPadding = PaddingDefaultVerticalSpacing3; self.bottomMarginPadding = PaddingDefaultVerticalSpacing3; - if ([self.molecule respondsToSelector:@selector(alignment)]) { - [self alignHorizontal:[(UIView *)self.molecule alignment]]; + if ([self.molecule respondsToSelector:@selector(horizontalAlignment)]) { + [self alignHorizontal:[(UIView *)self.molecule horizontalAlignment]]; } [self alignVertical:UIStackViewAlignmentFill]; if ([self.molecule respondsToSelector:@selector(reset)]) { diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index 795298b3..a71be907 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -20,8 +20,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import #import #import -#import -#import #import #pragma mark - TopAlert diff --git a/MVMCoreUI/Models/Extensions/MoleculeModelHelper.swift b/MVMCoreUI/Models/Extensions/MoleculeModelHelper.swift new file mode 100644 index 00000000..599851bb --- /dev/null +++ b/MVMCoreUI/Models/Extensions/MoleculeModelHelper.swift @@ -0,0 +1,51 @@ +// +// MoleculeModelHelper.swift +// MVMCoreUI +// +// Created by Ryan on 11/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +extension KeyedDecodingContainer where Key : CodingKey { + private enum TypeCodingKey: String, CodingKey { + case moleculeName + } + + /// Decodes the molecule model with the given coding key based on moleculeName + public func decodeMolecule(codingKey: KeyedDecodingContainer.Key) throws -> MoleculeModelProtocol { + return try decodeModel(codingKey: codingKey, typeCodingKey: TypeCodingKey.moleculeName) + } + + /// Decodes the molecule model with the given coding key based on moleculeName, optional + public func decodeMoleculeIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> MoleculeModelProtocol? { + return try decodeModelIfPresent(codingKey: codingKey, typeCodingKey: TypeCodingKey.moleculeName) + } + + /// Decodes the list of molecule models with the given coding key based on moleculeName + public func decodeMolecules(codingKey: KeyedDecodingContainer.Key) throws -> [MoleculeModelProtocol] { + guard let models = try decodeModels(codingKey: codingKey, typeCodingKey: TypeCodingKey.moleculeName) as? [MoleculeModelProtocol] else { + throw ModelRegistry.Error.decoderError + } + return models + } + + /// Decodes the list of molecule models with the given coding key based on moleculeName, optional + public func decodeMoleculesIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> [MoleculeModelProtocol]? { + return try decodeModelsIfPresent(codingKey: codingKey, typeCodingKey: TypeCodingKey.moleculeName) as? [MoleculeModelProtocol] + } + + /// Decodes an array with arrays of molecules based on the identifiers, optional. + public func decodeMolecules2DIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> [[MoleculeModelProtocol]]? { + return try decodeModels2DIfPresent(codingKey: codingKey, typeCodingKey: TypeCodingKey.moleculeName) as? [[MoleculeModelProtocol]] + } + + /// Decodes an array with arrays of models based on the identifiers. + public func decodeMolecules2D(codingKey: KeyedDecodingContainer.Key) throws -> [[MoleculeModelProtocol]] { + guard let models = try decodeModels2D(codingKey: codingKey, typeCodingKey: TypeCodingKey.moleculeName) as? [[MoleculeModelProtocol]] else { + throw ModelRegistry.Error.decoderError + } + return models + } +} diff --git a/MVMCoreUI/Models/ModelProtocols/CarouselItemModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/CarouselItemModelProtocol.swift new file mode 100644 index 00000000..53a6cc10 --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/CarouselItemModelProtocol.swift @@ -0,0 +1,14 @@ +// +// CarouselItemModelProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol CarouselItemModelProtocol: ContainerModelProtocol, MoleculeModelProtocol { + var peakingUI: Bool? {get} + var peakingArrowColor: Color? {get} +} diff --git a/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift new file mode 100644 index 00000000..5f593d3b --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/CarouselPagingModelProtocol.swift @@ -0,0 +1,13 @@ +// +// CarouselPagingModelProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/25/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol CarouselPagingModelProtocol: MoleculeModelProtocol { + var position: Float? {get} +} diff --git a/MVMCoreUI/Models/ModelProtocols/ContainerModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/ContainerModelProtocol.swift new file mode 100644 index 00000000..304a4811 --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/ContainerModelProtocol.swift @@ -0,0 +1,19 @@ +// +// ContainerModelProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ContainerModelProtocol { + var horizontalAlignment: UIStackView.Alignment? { get set } + var verticalAlignment: UIStackView.Alignment? { get set } + var useHorizontalMargins: Bool? { get set } + + var useVerticalMargins: Bool? { get set } + var topMarginPadding: CGFloat? { get set } + var bottomMarginPadding: CGFloat? { get set } +} diff --git a/MVMCoreUI/Models/ModelProtocols/FormModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/FormModelProtocol.swift new file mode 100644 index 00000000..aa298716 --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/FormModelProtocol.swift @@ -0,0 +1,15 @@ +// +// FormModelProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol FormModelProtocol: Model { + var required: Bool? { get } + var fieldKey: String? { get } + var groupName: String? { get } +} diff --git a/MVMCoreUI/Models/ModelProtocols/ListItemModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/ListItemModelProtocol.swift new file mode 100644 index 00000000..9d424a2b --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/ListItemModelProtocol.swift @@ -0,0 +1,35 @@ +// +// ListItemModelProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 12/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ListItemModelProtocol: ContainerModelProtocol, MoleculeModelProtocol { + var line: LineModel? { get set } + var action: ActionModelProtocol? { get set } + var hideArrow: Bool? { get set } + var style: String? { get set } +} + +// Not a strict requirement. +extension ListItemModelProtocol { + public var action: ActionModelProtocol? { + get { + return nil + } + set { + } + } + + public var style: String? { + get { + return nil + } + set { + } + } +} diff --git a/MVMCoreUI/Models/ModelProtocols/MoleculeModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/MoleculeModelProtocol.swift new file mode 100644 index 00000000..85a91bcd --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/MoleculeModelProtocol.swift @@ -0,0 +1,12 @@ +import Foundation + +public protocol MoleculeModelProtocol: Model { + var moleculeName: String? { get } + var backgroundColor: Color? { get set} +} + +extension MoleculeModelProtocol { + public var moleculeName: String? { + get { return Self.identifier } + } +} diff --git a/MVMCoreUI/Models/ModelProtocols/PageModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/PageModelProtocol.swift new file mode 100644 index 00000000..8c6d1557 --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/PageModelProtocol.swift @@ -0,0 +1,15 @@ +// +// PageModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/9/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol PageModelProtocol: Model { + var pageType: String { get set } + var screenHeading: String? { get set } + var isAtomicTabs: Bool? { get set } +} diff --git a/MVMCoreUI/Models/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/TemplateModelProtocol.swift new file mode 100644 index 00000000..66472da6 --- /dev/null +++ b/MVMCoreUI/Models/ModelProtocols/TemplateModelProtocol.swift @@ -0,0 +1,19 @@ +// +// TemplateModelProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/22/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol TemplateModelProtocol: PageModelProtocol { + var template: String { get } +} + +extension TemplateModelProtocol { + public var template: String { + get { return Self.identifier } + } +} diff --git a/MVMCoreUI/Models/Molecules/LabelModel.swift b/MVMCoreUI/Models/Molecules/LabelModel.swift new file mode 100644 index 00000000..859627d5 --- /dev/null +++ b/MVMCoreUI/Models/Molecules/LabelModel.swift @@ -0,0 +1,17 @@ +// +// Label.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + + +import Foundation + +@objcMembers public class LabelModel: MoleculeProtocol { + public static var identifier: String = "label" + public var moleculeName: String? + public var text: String? + public var spacing: Int? +} diff --git a/MVMCoreUI/Models/Molecules/ListItemModel.swift b/MVMCoreUI/Models/Molecules/ListItemModel.swift new file mode 100644 index 00000000..80263c9f --- /dev/null +++ b/MVMCoreUI/Models/Molecules/ListItemModel.swift @@ -0,0 +1,37 @@ +// +// ListItem.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + +import Foundation + +@objcMembers public class ListItemModel: MoleculeContainerModel, MoleculeProtocol { + public static var identifier: String = "listItem" + public var backgroundColor: String? + public var action: ActionModel? + public var line: LineModel? + + enum ListItemCodingKeys: String, CodingKey { + case moleculeName + case action + case line + } + + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + let typeContainer = try decoder.container(keyedBy: ListItemCodingKeys.self) + self.action = try typeContainer.decodeIfPresent(ActionModel.self, forKey: .action) + line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: ListItemCodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(action, forKey: .action) + try container.encodeIfPresent(line, forKey: .line) + } +} diff --git a/MVMCoreUI/Molecules/Doughnut/DoughnutChart.swift b/MVMCoreUI/Molecules/Doughnut/DoughnutChart.swift new file mode 100644 index 00000000..cbb6aeba --- /dev/null +++ b/MVMCoreUI/Molecules/Doughnut/DoughnutChart.swift @@ -0,0 +1,186 @@ +// +// DoughnutChart.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 07/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class DoughnutChart: View { + var doughnutLayer = CALayer() + var titleLabel = Label.commonLabelH3(true) + var subTitleLabel = Label.commonLabelB2(true) + var doughnutChartModel: DoughnutChartModel? { + get { return model as? DoughnutChartModel } + } + var labelContainer = MVMCoreUICommonViewsUtility.commonView() + var labelContainerLeftConstraint: NSLayoutConstraint? + var labelContainerTopConstraint: NSLayoutConstraint? + var labelContainerBottomConstraint: NSLayoutConstraint? + var labelContainerRightConstraint: NSLayoutConstraint? + var heightConstraint: NSLayoutConstraint? + + static let heightConstant: CGFloat = 150 + private var sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: heightConstant)! + var height: CGFloat = heightConstant { + didSet { + if height != oldValue { + sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: height)! + updateContainer() + drawGraph() + } + } + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + titleLabel.updateView(size) + subTitleLabel.updateView(size) + updateContainer() + drawGraph() + } + + open override func reset() { + super.reset() + titleLabel.reset() + subTitleLabel.reset() + clearLayers() + } + + public override func setAsMolecule() { + titleLabel.setAsMolecule() + subTitleLabel.setAsMolecule() + } + + open override func setupView() { + super.setupView() + guard labelContainer.superview == nil else { + return + } + addSubview(labelContainer) + + labelContainer.addSubview(titleLabel) + labelContainer.addSubview(subTitleLabel) + titleLabel.textAlignment = .center + subTitleLabel.textAlignment = .center + + //Make label font size to adjust width if label content is high + titleLabel.numberOfLines = 1 + titleLabel.adjustsFontSizeToFitWidth = true + + layer.addSublayer(doughnutLayer) + heightConstraint = heightAnchor.constraint(equalToConstant: + sizeObject.getValueBasedOnApplicationWidth()) + heightConstraint?.isActive = true + widthAnchor.constraint(equalTo: heightAnchor).isActive = true + + labelContainerLeftConstraint = labelContainer.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor, constant: lineWidth()) + labelContainerLeftConstraint?.isActive = true + labelContainerTopConstraint = labelContainer.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: lineWidth()) + labelContainerTopConstraint?.isActive = true + labelContainerRightConstraint = rightAnchor.constraint(greaterThanOrEqualTo: labelContainer.rightAnchor, constant: lineWidth()) + labelContainerRightConstraint?.isActive = true + labelContainerBottomConstraint = bottomAnchor.constraint(greaterThanOrEqualTo: labelContainer.bottomAnchor, constant: lineWidth()) + labelContainerBottomConstraint?.isActive = true + labelContainer.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + labelContainer.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true + + NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true) + NSLayoutConstraint.constraintPinSubview(subTitleLabel, pinTop: false, pinBottom: true, pinLeft: true, pinRight: true) + _ = NSLayoutConstraint(pinFirstView: titleLabel, toSecondView: subTitleLabel, withConstant: 0, directionVertical: true) + //Rotate view for initial draw + doughnutLayer.transform = CATransform3DMakeRotation(1 * .pi, 0.0, 0.0, 1.0) + } + + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + clearLayers() + guard let doughnutChartModel = doughnutChartModel else { + return + } + titleLabel.setWithModel(doughnutChartModel.title, delegateObject, additionalData) + subTitleLabel.setWithModel(doughnutChartModel.subtitle, delegateObject, additionalData) + titleLabel.textAlignment = .center + subTitleLabel.textAlignment = .center + updateLabelContainer() + drawGraph() + } + + func drawGraph() { + clearLayers() + let widthHeight = sizeObject.getValueBasedOnApplicationWidth() + doughnutLayer.frame = CGRect(x: 0, y: 0, + width: widthHeight, + height: widthHeight) + if let doughnutChart = doughnutChartModel { + var prevPercent: CGFloat = 0.0 + for model in doughnutChart.sections { + prevPercent += drawBar(model, prevPercent) + } + } + } + + func drawBar(_ chartModel: DoughnutChartItemModel, _ prevPercent: CGFloat) -> CGFloat { + + let shapeLayer = CAShapeLayer() + shapeLayer.frame = doughnutLayer.frame + shapeLayer.lineWidth = lineWidth() + shapeLayer.fillColor = nil + shapeLayer.strokeColor = chartModel.color.uiColor.cgColor + + let arcCenter = shapeLayer.position + let radius = shapeLayer.bounds.size.width / 2.0 - lineWidth()/2.0 + + let value: CGFloat = chartModel.percent + let gap: CGFloat = spaceRequired() ? lineGap() : 0.0 + let startAngle = ((prevPercent / 100.0) * 2 * .pi) - (0.5 * .pi) + let endAngle = ((value / 100.0) * 2 * .pi) + startAngle - gap + let circlePath = UIBezierPath(arcCenter: arcCenter, + radius: radius, + startAngle: startAngle, + endAngle: endAngle, + clockwise: true) + + shapeLayer.path = circlePath.cgPath + doughnutLayer.addSublayer(shapeLayer) + return value + } + + func clearLayers() { + doughnutLayer.sublayers?.forEach({ $0.removeFromSuperlayer() }) + } + + func updateContainer() { + heightConstraint?.constant = sizeObject.getValueBasedOnApplicationWidth() + updateLabelContainer() + setNeedsDisplay() + } + + func lineWidth() -> CGFloat { + return 30 + } + + func lineGap() -> CGFloat { + //If array is having the single item then no space required + return doughnutChartModel?.sections.count == 1 ? 0.0 : 0.05 + } + + func spaceRequired() -> Bool { + return doughnutChartModel?.spaceRequired ?? true + } + + func updateLabelContainer() { + labelContainer.setNeedsDisplay() + labelContainer.layoutIfNeeded() + let radius = sizeObject.getValueBasedOnApplicationWidth()/2 - lineWidth() + let labelheight = labelContainer.frame.height/2 + let padding = sizeObject.getValueBasedOnApplicationWidth()/2 - sqrt(max(0, pow(radius, 2) - pow(labelheight, 2))) + + labelContainerLeftConstraint?.constant = padding + labelContainerRightConstraint?.constant = padding + labelContainerTopConstraint?.constant = max(radius - labelheight, labelheight) + labelContainerBottomConstraint?.constant = max(radius - labelheight, labelheight) + } +} diff --git a/MVMCoreUI/Molecules/Doughnut/DoughnutChartModel.swift b/MVMCoreUI/Molecules/Doughnut/DoughnutChartModel.swift new file mode 100644 index 00000000..42acd2f0 --- /dev/null +++ b/MVMCoreUI/Molecules/Doughnut/DoughnutChartModel.swift @@ -0,0 +1,36 @@ +// +// DoughnutChartModel.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 10/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class DoughnutChartModel: MoleculeModelProtocol { + public var backgroundColor: Color? + public static var identifier: String = "doughnutChart" + public var title: LabelModel? + public var subtitle: LabelModel? + public var sections: [DoughnutChartItemModel] + public var spaceRequired: Bool? + + public init(sections: [DoughnutChartItemModel]) { + self.sections = sections + } +} + +@objcMembers public class DoughnutChartItemModel: MoleculeModelProtocol { + public var backgroundColor: Color? + public static var identifier: String = "doughnutChartItem" + public var label: LabelModel + @Percent public var percent: CGFloat + public var color: Color + + public init(percent: CGFloat, color: Color, label: LabelModel) { + self.percent = percent + self.color = color + self.label = label + } +} diff --git a/MVMCoreUI/Molecules/Doughnut/DoughnutChartView.swift b/MVMCoreUI/Molecules/Doughnut/DoughnutChartView.swift new file mode 100644 index 00000000..6bf91a43 --- /dev/null +++ b/MVMCoreUI/Molecules/Doughnut/DoughnutChartView.swift @@ -0,0 +1,160 @@ +// +// DoughnutChartView.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 26/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class DoughnutChartView: View { + var doughnutChart = DoughnutChart(frame: CGRect.zero) + var colorLablesStack = ColorViewLabelsStack() + var doughnutChartModel: DoughnutChartModel? { + get { return model as? DoughnutChartModel } + } + + open override func setupView() { + super.setupView() + guard doughnutChart.superview == nil else { + return + } + doughnutChart.translatesAutoresizingMaskIntoConstraints = false + addSubview(doughnutChart) + colorLablesStack.translatesAutoresizingMaskIntoConstraints = false + addSubview(colorLablesStack) + + doughnutChart.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + doughnutChart.topAnchor.constraint(equalTo: topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: doughnutChart.bottomAnchor).isActive = true + + let doughnutBottomAnchor = bottomAnchor.constraint(equalTo: doughnutChart.bottomAnchor) + doughnutBottomAnchor.priority = UILayoutPriority(rawValue: 200) + doughnutBottomAnchor.isActive = true + + let colorLablesBottomAnchor = bottomAnchor.constraint(equalTo: colorLablesStack.bottomAnchor) + colorLablesBottomAnchor.priority = UILayoutPriority(rawValue: 204) + colorLablesBottomAnchor.isActive = true + + let colorLablesTopAnchor = colorLablesStack.topAnchor.constraint(equalTo: doughnutChart.topAnchor) + colorLablesTopAnchor.priority = .defaultLow + colorLablesTopAnchor.isActive = true + + colorLablesStack.topAnchor.constraint(greaterThanOrEqualTo: doughnutChart.topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: colorLablesStack.bottomAnchor).isActive = true + trailingAnchor.constraint(equalTo: colorLablesStack.trailingAnchor).isActive = true + + let centerY = colorLablesStack.centerYAnchor.constraint(equalTo: doughnutChart.centerYAnchor) + centerY.priority = UILayoutPriority(rawValue: 500) + centerY.isActive = true + + colorLablesStack.leadingAnchor.constraint(equalTo: doughnutChart.trailingAnchor, constant: PaddingThree).isActive = true + colorLablesStack.backgroundColor = .clear + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + doughnutChart.updateView(size) + colorLablesStack.updateView(size) + } + + open override func reset() { + super.reset() + doughnutChart.reset() + colorLablesStack.reset() + } + + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + + guard let model = doughnutChartModel else { return } + doughnutChart.setWithModel(model, delegateObject, additionalData) + + // Create the stack model + var stackItems: [MoleculeStackItemModel] = [] + for item in model.sections { + stackItems.append(MoleculeStackItemModel(with: item)) + } + let stack = MoleculeStackModel(molecules: stackItems) + stack.verticalAlignment = .fill + colorLablesStack.setWithModel(stack, delegateObject, additionalData) + } + + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + guard let json = json, let model = try? Self.decodeJSONToModel(json: json, type: DoughnutChartModel.self) else { return } + setWithModel(model, delegateObject, additionalData) + } +} + +extension DoughnutChartView: MVMCoreUIViewConstrainingProtocol { + open func horizontalAlignment() -> UIStackView.Alignment { + return .leading + } +} + +class ColorViewLabelsStack: MoleculeStackView { + override func createStackItemsFromModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let stackItemModels = stackModel?.molecules else { return } + for model in stackItemModels { + let view = ColorViewWithLabel() + let stackItem = MoleculeStackItem(andContain: view) + stackItem.setWithModel(model, delegateObject, additionalData) + stackItems.append(stackItem) + } + } +} + +class ColorViewWithLabel: View { + + var label = Label.commonLabelB2(true) + var colorView = MVMCoreUICommonViewsUtility.commonView() + private var sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: 8)! + var heightConstraint: NSLayoutConstraint? + + override func setupView() { + super.setupView() + guard colorView.superview == nil else { + return + } + translatesAutoresizingMaskIntoConstraints = false + addSubview(colorView) + addSubview(label) + + heightConstraint = colorView.heightAnchor.constraint(equalToConstant: sizeObject.getValueBasedOnApplicationWidth()) + heightConstraint?.isActive = true + colorView.widthAnchor.constraint(equalTo: colorView.heightAnchor).isActive = true + colorView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + colorView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + + label.leadingAnchor.constraint(equalTo: colorView.trailingAnchor, constant: PaddingOne).isActive = true + label.topAnchor.constraint(equalTo: topAnchor).isActive = true + trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true + bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true + } + + override func updateView(_ size: CGFloat) { + super.updateView(size) + label.updateView(size) + heightConstraint?.constant = sizeObject.getValueBased(onSize: size) + setNeedsDisplay() + } + + override func reset() { + super.reset() + label.reset() + } + + override func setAsMolecule() { + label.setAsMolecule() + } + + override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let chartItemModel = model as? DoughnutChartItemModel else { + return + } + label.setWithModel(chartItemModel.label, delegateObject, additionalData) + colorView.backgroundColor = chartItemModel.color.uiColor + } +} diff --git a/MVMCoreUI/Molecules/FooterModel.swift b/MVMCoreUI/Molecules/FooterModel.swift new file mode 100644 index 00000000..d61d1194 --- /dev/null +++ b/MVMCoreUI/Molecules/FooterModel.swift @@ -0,0 +1,55 @@ +// +// FooterModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/27/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +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 + } + + /// Defaults to set + func setDefaults() { + if useHorizontalMargins == nil { + useHorizontalMargins = true + } + if useVerticalMargins == nil { + useVerticalMargins = true + } + if topMarginPadding == nil { + topMarginPadding = PaddingDefaultVerticalSpacing + } + if bottomMarginPadding == nil { + bottomMarginPadding = PaddingDefaultVerticalSpacing + } + } + + public override init(with moleculeModel: MoleculeModelProtocol) { + super.init(with: moleculeModel) + setDefaults() + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + try super.init(from: decoder) + setDefaults() + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } +} diff --git a/MVMCoreUI/Molecules/StandardFooterView.swift b/MVMCoreUI/Molecules/FooterView.swift similarity index 58% rename from MVMCoreUI/Molecules/StandardFooterView.swift rename to MVMCoreUI/Molecules/FooterView.swift index a83611cc..b9113d1d 100644 --- a/MVMCoreUI/Molecules/StandardFooterView.swift +++ b/MVMCoreUI/Molecules/FooterView.swift @@ -1,5 +1,5 @@ // -// StandardFooterView.swift +// FooterView.swift // MVMCoreUI // // Created by Scott Pfeil on 3/11/19. @@ -8,23 +8,11 @@ import UIKit -open class StandardFooterView: MoleculeContainer { - open override func setupView() { - super.setupView() - topMarginPadding = PaddingDefaultVerticalSpacing - bottomMarginPadding = PaddingDefaultVerticalSpacing - } - +open class FooterView: MoleculeContainer { public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { if let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) { return height + PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing } return 42 } - - open override func reset() { - super.reset() - topMarginPadding = PaddingDefaultVerticalSpacing - bottomMarginPadding = PaddingDefaultVerticalSpacing - } } diff --git a/MVMCoreUI/Molecules/HeaderModel.swift b/MVMCoreUI/Molecules/HeaderModel.swift new file mode 100644 index 00000000..b6f3629f --- /dev/null +++ b/MVMCoreUI/Molecules/HeaderModel.swift @@ -0,0 +1,58 @@ +// +// Header.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + +import Foundation + +@objcMembers public class HeaderModel: MoleculeContainerModel, MoleculeModelProtocol { + public static var identifier: String = "header" + public var backgroundColor: Color? + public var line: LineModel? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case line + case backgroundColor + } + + /// Defaults to set + func setDefaults() { + if useHorizontalMargins == nil { + useHorizontalMargins = true + } + if useVerticalMargins == nil { + useVerticalMargins = true + } + if topMarginPadding == nil { + topMarginPadding = PaddingDefaultVerticalSpacing + } + if bottomMarginPadding == nil { + bottomMarginPadding = PaddingDefaultVerticalSpacing + } + line?.type = .heavy + } + + public override init(with moleculeModel: MoleculeModelProtocol) { + super.init(with: moleculeModel) + setDefaults() + } + + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + setDefaults() + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) ?? LineModel(type: .heavy) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(line, forKey: .line) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + } +} diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/HeaderView.swift similarity index 69% rename from MVMCoreUI/Molecules/StandardHeaderView.swift rename to MVMCoreUI/Molecules/HeaderView.swift index 23cf6c17..c90be96d 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/HeaderView.swift @@ -1,5 +1,5 @@ // -// StandardHeaderView.swift +// HeaderView.swift // MVMCoreUI // // Created by Scott Pfeil on 2/12/19. @@ -8,9 +8,13 @@ import UIKit -public class StandardHeaderView: MoleculeContainer { +public class HeaderView: MoleculeContainer { var line: Line? + var headerModel: HeaderModel? { + get { return model as? HeaderModel } + } + // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) @@ -19,12 +23,10 @@ public class StandardHeaderView: MoleculeContainer { public override func setupView() { super.setupView() - topMarginPadding = PaddingDefaultVerticalSpacing - bottomMarginPadding = PaddingDefaultVerticalSpacing guard line == nil else { return } let line = Line() - line.style = .heavy + line.setStyle(.heavy) addSubview(line) NSLayoutConstraint.pinViewBottom(toSuperview: line, useMargins: false, constant: 0).isActive = true NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true @@ -35,16 +37,26 @@ public class StandardHeaderView: MoleculeContainer { // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - if let separatorJSON = json?.optionalDictionaryForKey("separator") { + if let separatorJSON = json?.optionalDictionaryForKey("line") { line?.setWithJSON(separatorJSON, delegateObject: delegateObject, additionalData: additionalData) } } + + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + + guard let headerModel = model as? HeaderModel else { + return + } + + if let seperatorModel = headerModel.line { + line?.setWithJSON(seperatorModel.toJSON(), delegateObject: delegateObject, additionalData: additionalData) + } + } open override func reset() { super.reset() - line?.style = .heavy - topMarginPadding = PaddingDefaultVerticalSpacing - bottomMarginPadding = PaddingDefaultVerticalSpacing + line?.setStyle(.heavy) } public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift index 6e10a1a0..089352c7 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift @@ -8,7 +8,7 @@ import UIKit -@objcMembers open class ImageHeadlineBody: ViewConstrainingView { +@objcMembers open class ImageHeadlineBody: View { let headlineBody = HeadlineBody(frame: .zero) let imageView = MFLoadImageView() var constraintBetweenImageLabelsConstant: CGFloat = 16 @@ -23,27 +23,24 @@ import UIKit headlineBody.headlineLabel.styleB1(true) headlineBody.spaceBetweenLabelsConstant = 0 - let view = MVMCoreUICommonViewsUtility.commonView() - addSubview(view) - pinView(toSuperView: view) - view.addSubview(headlineBody) - view.addSubview(imageView) + addSubview(headlineBody) + addSubview(imageView) - headlineBody.topAnchor.constraint(equalTo: view.topAnchor).isActive = true - view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor).isActive = true - view.bottomAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor).isActive = true - var constraint = view.bottomAnchor.constraint(equalTo: headlineBody.bottomAnchor) + headlineBody.topAnchor.constraint(equalTo: topAnchor).isActive = true + rightAnchor.constraint(equalTo: headlineBody.rightAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor).isActive = true + var constraint = bottomAnchor.constraint(equalTo: headlineBody.bottomAnchor) constraint.priority = .defaultLow constraint.isActive = true - imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true - imageView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - imageView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor).isActive = true - view.bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor).isActive = true - constraint = view.bottomAnchor.constraint(equalTo: imageView.bottomAnchor) + imageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + imageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + imageView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor).isActive = true + constraint = bottomAnchor.constraint(equalTo: imageView.bottomAnchor) constraint.priority = UILayoutPriority(rawValue: 200) constraint.isActive = true - constraint = imageView.topAnchor.constraint(equalTo: view.topAnchor) + constraint = imageView.topAnchor.constraint(equalTo: topAnchor) constraint.priority = UILayoutPriority(rawValue: 200) constraint.isActive = true @@ -58,22 +55,6 @@ import UIKit } // MARK: - MVMCoreUIMoleculeViewProtocol - - open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) - - let imageJSON = json?.optionalDictionaryForKey("image") - imageView.setWithJSON(imageJSON, delegateObject: delegateObject, additionalData: additionalData) - constraintBetweenImageLabels?.constant = imageJSON != nil ? constraintBetweenImageLabelsConstant : 0 - } - - open override func setAsMolecule() { - super.setAsMolecule() - headlineBody.setAsMolecule() - imageView.setAsMolecule() - } - open override func reset() { super.reset() headlineBody.reset() @@ -82,7 +63,15 @@ import UIKit imageView.reset() } - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + // MARK:- ModelMoleculeViewProtocol + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 95 } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? ImageHeadlineBodyModel else { return } + headlineBody.setWithModel(model.headlineBody, delegateObject, additionalData) + imageView.setWithModel(model.image, delegateObject, additionalData) + } } diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBodyModel.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBodyModel.swift new file mode 100644 index 00000000..4966ce58 --- /dev/null +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBodyModel.swift @@ -0,0 +1,16 @@ +// +// File.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public struct ImageHeadlineBodyModel: MoleculeModelProtocol { + public static var identifier: String = "imageHeadlineBody" + public var backgroundColor: Color? + public var image: ImageViewModel + public var headlineBody: HeadlineBodyModel +} diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TabsModel.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TabsModel.swift new file mode 100644 index 00000000..f45c771d --- /dev/null +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TabsModel.swift @@ -0,0 +1,50 @@ +// +// TabsModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class TabsModel: MoleculeModelProtocol { + public static var identifier: String = "tabs" + public var backgroundColor: Color? + public var tabs: [LabelModel] + public var selectedColor = Color(uiColor: .mfTomatoRed()) + + // Must be capped to 0...(tabs.count - 1) + public var selectedIndex: Int = 0 + + private enum CodingKeys: String, CodingKey { + case tabs + case backgroundColor + case selectedColor + case selectedIndex + } + + public init(with tabs: [LabelModel]) { + self.tabs = tabs + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + tabs = try typeContainer.decode([LabelModel].self, forKey: .tabs) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedColor) { + selectedColor = color + } + if let index = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) { + selectedIndex = index + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(tabs, forKey: .tabs) + try container.encode(backgroundColor, forKey: .backgroundColor) + try container.encode(selectedColor, forKey: .selectedColor) + try container.encode(selectedIndex, forKey: .selectedIndex) + } +} diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift index 2c5f0fe5..5c80bd1a 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift @@ -84,17 +84,19 @@ import UIKit } open func setupConstraintsForViewWithButtons() { - guard let viewForButtons = viewForButtons, let primaryButton = primaryButton, let secondaryButton = secondaryButton else { - return - } + guard let viewForButtons = viewForButtons, + let primaryButton = primaryButton, + let secondaryButton = secondaryButton + else { return } + viewForButtons.addSubview(primaryButton) viewForButtons.addSubview(secondaryButton) secondaryButton.widthAnchor.constraint(equalTo: primaryButton.widthAnchor, multiplier: 1).isActive = true - secondaryButton.topAnchor.constraint(equalTo: viewForButtons.topAnchor).isActive = true - primaryButton.topAnchor.constraint(equalTo: viewForButtons.topAnchor).isActive = true - viewForButtons.bottomAnchor.constraint(equalTo: secondaryButton.bottomAnchor).isActive = true - viewForButtons.bottomAnchor.constraint(equalTo: primaryButton.bottomAnchor).isActive = true - NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[leftButton]-10-[rightButton]-0-|", options: NSLayoutConstraint.FormatOptions.alignAllCenterY, metrics: nil, views: ["leftButton": secondaryButton, "rightButton": primaryButton])) + NSLayoutConstraint.constraintPinSubview(secondaryButton, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) + NSLayoutConstraint.constraintPinSubview(primaryButton, pinTop: true, pinBottom: true, pinLeft: false, pinRight: true) + let constraint = primaryButton.leadingAnchor.constraint(equalTo: secondaryButton.trailingAnchor, constant: 10) + constraint.priority = UILayoutPriority(900) + constraint.isActive = true } func setupWithTwoButtons() { @@ -107,7 +109,6 @@ import UIKit pinView(toSuperView: viewForButtons) alignCenterHorizontal() - createPrimaryButton() createSecondaryButton() setupConstraintsForViewWithButtons() @@ -137,20 +138,25 @@ import UIKit } } + /// Legacy func setupUI(withPrimaryButtonMap primaryButtonMap: [AnyHashable: Any]?, secondaryButtonMap: [AnyHashable: Any]?) { - if primaryButtonMap != nil, secondaryButtonMap != nil { + setupUI(primaryButtonShowing: primaryButtonMap != nil, secondaryButtonShowing: secondaryButtonMap != nil) + } + + func setupUI(primaryButtonShowing: Bool, secondaryButtonShowing: Bool) { + if primaryButtonShowing, secondaryButtonShowing { heightConstraint?.isActive = false if primaryButton == nil || secondaryButton == nil { removeButtons() setupWithTwoButtons() } - } else if primaryButtonMap != nil { + } else if primaryButtonShowing { heightConstraint?.isActive = false if primaryButton == nil || secondaryButton != nil { removeButtons() setupWithPrimaryButton() } - } else if secondaryButtonMap != nil { + } else if secondaryButtonShowing { heightConstraint?.isActive = false if secondaryButton == nil || primaryButton != nil { removeButtons() @@ -251,6 +257,10 @@ import UIKit primaryButton?.isHidden = true secondaryButton?.isHidden = true } + + override open func horizontalAlignment() -> UIStackView.Alignment { + return .center + } } // MARK: - Deprecate @@ -291,3 +301,14 @@ extension TwoButtonView { setup(primaryButtonMap: primaryButtonMap, secondaryButtonMap: secondaryButtonMap, actionDelegate: actionDelegate, additionalData: additionalData, buttonDelegate: buttonDelegate) } } + +extension TwoButtonView: ModelMoleculeViewProtocol { + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? TwoButtonViewModel else { return } + setupUI(primaryButtonShowing: model.primaryButton != nil, secondaryButtonShowing: model.secondaryButton != nil) + setDefaultCustom() + primaryButton?.setWithModel(model.primaryButton, delegateObject, additionalData) + secondaryButton?.setWithModel(model.secondaryButton, delegateObject, additionalData) + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } +} diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift new file mode 100644 index 00000000..a7abde9f --- /dev/null +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -0,0 +1,16 @@ +// +// TwoButtonViewModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public struct TwoButtonViewModel: MoleculeModelProtocol { + public static var identifier: String = "twoButtonView" + public var backgroundColor: Color? + public var primaryButton: ButtonModel? + public var secondaryButton: ButtonModel? +} diff --git a/MVMCoreUI/Molecules/Items/AccordionListItemModel.swift b/MVMCoreUI/Molecules/Items/AccordionListItemModel.swift new file mode 100644 index 00000000..0e2fa531 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/AccordionListItemModel.swift @@ -0,0 +1,48 @@ +// +// AccordionListItemModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +class AccordionListItemModel: MoleculeContainerModel, ListItemModelProtocol { + public static var identifier: String = "accordionListItem" + public var molecules: [ListItemModelProtocol] + public var backgroundColor: Color? + public var hideLineWhenExpanded: Bool = false + public var hideArrow: Bool? = true + public var line: LineModel? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case molecules + case backgroundColor + case hideLineWhenExpanded + case hideArrow + case line + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + molecules = try typeContainer.decodeMolecules(codingKey: .molecules) as! [ListItemModelProtocol] + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) + if let hideLine = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideLineWhenExpanded) { + hideLineWhenExpanded = hideLine + } + 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.encodeModels(molecules, forKey: .molecules) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(hideLineWhenExpanded, forKey: .hideLineWhenExpanded) + try container.encodeIfPresent(line, forKey: .line) + } +} diff --git a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift index 075d33f1..db4e322e 100644 --- a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -9,6 +9,7 @@ import UIKit @objcMembers public class AccordionMoleculeTableViewCell: MoleculeTableViewCell { + var accordionListItemModel: AccordionListItemModel? let accordionButton = createAccordionButton() static func createAccordionButton() -> MFCustomButton { @@ -26,20 +27,20 @@ import UIKit accessoryView = accordionButton } - public override func didSelectCell(atIndex indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + override public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { accordionButton.isSelected = !accordionButton.isSelected accordionButton.setTitle(accordionButton.isSelected ? "-" : "+", for: .normal) - guard let molecules = json?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] else { + guard let molecules = accordionListItemModel?.molecules else { return } if accordionButton.isSelected { - delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: .automatic) + delegateObject?.moleculeDelegate?.addMolecules(molecules, sender: self, animation: .automatic) } else { - delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: .automatic) + delegateObject?.moleculeDelegate?.removeMolecules(molecules, sender: self, animation: .automatic) } - if (json?.boolForKey("hideSeparatorWhenExpanded") ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { + if (accordionListItemModel?.hideLineWhenExpanded ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { bottomSeparatorView?.isHidden = accordionButton.isSelected } } diff --git a/MVMCoreUI/Molecules/Items/CarouselItemModel.swift b/MVMCoreUI/Molecules/Items/CarouselItemModel.swift new file mode 100644 index 00000000..f2c88e9b --- /dev/null +++ b/MVMCoreUI/Molecules/Items/CarouselItemModel.swift @@ -0,0 +1,43 @@ +// +// CarouselItemModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + + +@objcMembers public class CarouselItemModel: MoleculeContainerModel, CarouselItemModelProtocol { + public static var identifier: String = "carouselItem" + public var backgroundColor: Color? + public var peakingUI: Bool? + public var peakingArrowColor: Color? + public var moleculeName: String? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case peakingUI + case peakingArrowColor + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + peakingUI = try typeContainer.decodeIfPresent(Bool.self, forKey: .peakingUI) + peakingArrowColor = try typeContainer.decodeIfPresent(Color.self, forKey: .peakingArrowColor) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(peakingUI, forKey: .peakingUI) + try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor) + } +} diff --git a/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift b/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift index 31a6cc61..46139a37 100644 --- a/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift @@ -9,62 +9,58 @@ import UIKit @objcMembers public class DropDownFilterTableViewCell: TableViewCell { - let dropDown = DropDown(forDropDownWithBothDelegates: nil) + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + var dropDownListItemModel: DropDownListItemModel? + let dropDown = ItemDropdownEntryField() var delegateObject: MVMCoreUIDelegateObject? var previousIndex = NSNotFound - var dropDownSelectionObservation: NSKeyValueObservation? - - // MARK: - MFViewProtocol + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + override public func setupView() { super.setupView() - guard let dropDown = dropDown, dropDown.superview == nil else { - return - } - bottomMarginPadding = 0 - dropDown.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(dropDown) - - NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: dropDown, useMargins: true).values)) - - #warning("Once we have the state manager, we will need to change this logic to only add or remove on user selection...Want to avoid re-use crashes from array size differences") - dropDownSelectionObservation = observe( - \.dropDown?.textField?.text, - options: [.old, .new] - ) { [weak self] object, change in - guard change.newValue != change.oldValue, let self = self, - let options = self.dropDown?.json?.optionalArrayForKey("options") as? [NSString], - let index = options.firstIndex(of: change.newValue!! as NSString), - let moleculesArrays = self.json?.arrayForKey(KeyMolecules) as? [[[AnyHashable: Any]]] else { return } + guard dropDown.superview == nil else { return } + addMolecule(dropDown) + dropDown.observeDropdownChange = { [weak self] oldValue, newValue in + guard newValue != oldValue, + let self = self, + let index = self.dropDown.pickerData.firstIndex(of: newValue), + let dropListItemJSON = self.dropDownListItemModel.toJSON(), + let json2d = dropListItemJSON.optionalArrayForKey("molecules") as? [[[AnyHashable: Any]]] + else { return } + if self.previousIndex != NSNotFound { - let previousMolecules = moleculesArrays[self.previousIndex] - self.delegateObject?.moleculeDelegate?.removeMolecules?(previousMolecules, sender: self, animation: .fade) + self.delegateObject?.moleculeDelegate?.removeMolecules(json2d[self.previousIndex], sender: self, animation: .fade) } - let molecules = moleculesArrays[index] - self.delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: .fade) + + self.delegateObject?.moleculeDelegate?.addMolecules(json2d[index], sender: self, animation: .fade) self.previousIndex = index } } - public override func updateView(_ size: CGFloat) { - super.updateView(size) - dropDown?.updateView(size) - } - - // MARK: - MoleculeDelegateProtocol - public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - bottomSeparatorView?.style = .none + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + dropDownListItemModel = model as? DropDownListItemModel self.delegateObject = delegateObject - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - dropDown?.mfTextFieldDelegate = delegateObject?.uiTextFieldDelegate as? MFTextFieldDelegate - dropDown?.uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate - dropDown?.setWithJSON(json?.optionalDictionaryForKey("dropDown"), delegateObject: delegateObject, additionalData: additionalData) + super.setWithModel(model, delegateObject, additionalData) + + dropDown.setWithModel(dropDownListItemModel?.dropDown, delegateObject, additionalData) + dropDown.observingTextFieldDelegate = delegateObject?.uiTextFieldDelegate as? ObservingTextFieldDelegate + dropDown.uiTextFieldDelegate = delegateObject?.uiTextFieldDelegate } public override func reset() { super.reset() - bottomMarginPadding = 0 - bottomSeparatorView?.style = .none + bottomSeparatorView?.setStyle(.none) + } + + public override static func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 80 } } diff --git a/MVMCoreUI/Molecules/Items/DropDownListItemModel.swift b/MVMCoreUI/Molecules/Items/DropDownListItemModel.swift new file mode 100644 index 00000000..35f69b51 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/DropDownListItemModel.swift @@ -0,0 +1,89 @@ +// +// DropDownListItemModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 12/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class DropDownListItemModel: ContainerModel, ListItemModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "dropDownListItem" + public var molecules: [[ListItemModelProtocol]] + public var dropDown: ItemDropdownEntryFieldModel + public var backgroundColor: Color? + public var line: LineModel? = LineModel(type: .none) + public var hideArrow: Bool? = true + + /// Defaults to set + func setDefaults() { + if useHorizontalMargins == nil { + useHorizontalMargins = true + } + if useVerticalMargins == nil { + useVerticalMargins = true + } + if topMarginPadding == nil { + topMarginPadding = 24 + } + if bottomMarginPadding == nil { + bottomMarginPadding = 0 + } + } + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(molecules: [[ListItemModelProtocol]], dropDown: ItemDropdownEntryFieldModel) { + self.molecules = molecules + self.dropDown = dropDown + super.init() + setDefaults() + } + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case moleculeName + case molecules + case dropDown + case line + case backgroundColor + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + molecules = try typeContainer.decodeMolecules2D(codingKey: .molecules) as? [[ListItemModelProtocol]] ?? [[]] + dropDown = try typeContainer.decode(ItemDropdownEntryFieldModel.self, forKey: .dropDown) + + if let lineModel = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) { + line = lineModel + } + + 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.encodeModels2D(molecules, forKey: .molecules) + try container.encode(dropDown, forKey: .dropDown) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(line, forKey: .line) + } +} diff --git a/MVMCoreUI/Molecules/Items/ListItemModel.swift b/MVMCoreUI/Molecules/Items/ListItemModel.swift new file mode 100644 index 00000000..ffb0eb35 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/ListItemModel.swift @@ -0,0 +1,75 @@ +// +// ListItem.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + +import Foundation +import MVMCore + +@objcMembers public class ListItemModel: MoleculeContainerModel, ListItemModelProtocol { + + public static var identifier: String = "listItem" + public var backgroundColor: Color? + public var action: ActionModelProtocol? + public var hideArrow: Bool? + public var line: LineModel? + public var style: String? = "standard" + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case action + case hideArrow + case line + case style + } + + /// Defaults to set + func setDefaults() { + if useHorizontalMargins == nil { + useHorizontalMargins = true + } + if useVerticalMargins == nil { + useVerticalMargins = true + } + if topMarginPadding == nil { + topMarginPadding = 24 + } + if bottomMarginPadding == nil { + bottomMarginPadding = 24 + } + } + + 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) + action = try typeContainer.decodeModelIfPresent(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) + hideArrow = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideArrow) + line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) + if let style = try typeContainer.decodeIfPresent(String.self, forKey: .style) { + self.style = style + } + 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) + try container.encodeModelIfPresent(action, forKey: .action) + try container.encodeIfPresent(hideArrow, forKey: .hideArrow) + try container.encodeIfPresent(line, forKey: .line) + try container.encodeIfPresent(style, forKey: .style) + } +} + diff --git a/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift index 35b3ed04..9d56f4cd 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift @@ -8,7 +8,8 @@ import UIKit -open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol { +open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeViewProtocol, ModelMoleculeViewProtocol { + open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? open var json: [AnyHashable: Any]? public let containerHelper = ContainerHelper() @@ -65,40 +66,39 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi peakingRightArrow.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true NSLayoutConstraint.scalingPinViewRight(toSuper: peakingRightArrow, ratio: ratio, anchor: contentView.widthAnchor) } - - public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - self.json = json - - if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { + + + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let collectionModel = model as? CarouselItemModel else { + return + } + + if let useHorizontalMargins = collectionModel.useHorizontalMargins { updateViewHorizontalDefaults = useHorizontalMargins } - if let useVerticalMargins = json?.optionalBoolForKey("useVerticalMargins") { + if let useVerticalMargins = collectionModel.useVerticalMargins { updateViewVerticalDefaults = useVerticalMargins } - // Handles peaking. - allowsPeaking = json?.optionalBoolForKey("peakingUI") ?? true - if let peakingArrowColor = json?.optionalStringForKey("peakingArrowColor") { - let color = UIColor.mfGet(forHex: peakingArrowColor) + allowsPeaking = collectionModel.peakingUI ?? false + if let peakingArrowColor = collectionModel.peakingArrowColor { + let color = peakingArrowColor.uiColor peakingLeftArrow.tintColor = color peakingRightArrow.tintColor = color } - - if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { - backgroundColor = UIColor.mfGet(forHex: backgroundColorString) - } - - guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else { - return + + if let backgroundColor = collectionModel.backgroundColor { + self.backgroundColor = backgroundColor.uiColor } + if molecule == nil { - if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: false) { + if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(collectionModel.molecule, delegateObject, false) { contentView.insertSubview(moleculeView, at: 0) containerHelper.constrainView(moleculeView) molecule = moleculeView } } else { - molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) + (molecule as? ModelMoleculeViewProtocol)?.setWithModel(collectionModel.molecule, delegateObject, additionalData) } guard let molecule = molecule else { return } @@ -114,11 +114,13 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi backgroundColor = .white } - public class func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { - guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else { + public class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let molecule = (model as? CarouselItemModel)?.molecule, + let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(molecule) as? ModelMoleculeViewProtocol.Type, + let name = moleculeClass.nameForReuse(molecule, delegateObject) ?? molecule.moleculeName else { return nil } - return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) + return name } public func updateView(_ size: CGFloat) { diff --git a/MVMCoreUI/Molecules/Items/MoleculeStackItem.swift b/MVMCoreUI/Molecules/Items/MoleculeStackItem.swift new file mode 100644 index 00000000..279e7948 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/MoleculeStackItem.swift @@ -0,0 +1,16 @@ +// +// MoleculeStackItem.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// +// A list item that contains a molecule + +import UIKit + +open class MoleculeStackItem: MoleculeContainer { + var stackItemModel: StackItemModel? { + get { return model as? StackItemModel } + } +} diff --git a/MVMCoreUI/Molecules/Items/MoleculeStackItemModel.swift b/MVMCoreUI/Molecules/Items/MoleculeStackItemModel.swift new file mode 100644 index 00000000..4731ef34 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/MoleculeStackItemModel.swift @@ -0,0 +1,47 @@ +// +// MoleculeStackItem.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/4/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + +import Foundation + +@objcMembers public class MoleculeStackItemModel: MoleculeContainerModel, MoleculeModelProtocol, StackItemModelProtocol { + public static var identifier: String = "stackItem" + public var backgroundColor: Color? + public var spacing: CGFloat? + public var percent: Int? + public var gone: Bool = false + + private enum CodingKeys: String, CodingKey { + case moleculeName + case spacing + case percent + case gone + } + + public override init(with moleculeModel: MoleculeModelProtocol) { + super.init(with: moleculeModel) + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) + percent = try typeContainer.decodeIfPresent(Int.self, forKey: .percent) + if let gone = try typeContainer.decodeIfPresent(Bool.self, forKey: .gone) { + self.gone = gone + } + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(spacing, forKey: .spacing) + try container.encodeIfPresent(percent, forKey: .percent) + try container.encode(gone, forKey: .gone) + } +} diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 7cd3a900..56ff1ca9 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -11,34 +11,24 @@ import UIKit @objcMembers open class MoleculeTableViewCell: TableViewCell { // MARK: - MVMCoreUIMoleculeViewProtocol - public override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) - { - guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - return + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? ListItemModel else { return } + if molecule != nil { + (molecule as? ModelMoleculeViewProtocol)?.setWithModel(model.molecule, delegateObject, additionalData) + } else if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(model.molecule, delegateObject, false) { + addMolecule(moleculeView) } - if molecule == nil { - if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: false) { - addMolecule(moleculeView) - } - } else { - molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) - } - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + containerHelper.set(with: model, for: molecule as? MVMCoreUIViewConstrainingProtocol) } - - public override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) else { - return 80 - } - return max(2 * PaddingDefaultVerticalSpacing3, height) - } - - public override class func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { - guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else { + + public override class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let moleculeModel = (model as? ListItemModel)?.molecule else { return "\(self)<>" } - let moleculeName = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) ?? "" + let className = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(moleculeModel) as? ModelMoleculeViewProtocol.Type + let moleculeName = className?.nameForReuse(moleculeModel, delegateObject) ?? moleculeModel.moleculeName ?? "" return "\(self)<\(moleculeName)>" } @@ -49,4 +39,13 @@ import UIKit } return theClass.requiredModules?(moleculeJSON, delegateObject: delegateObject, error: error) } + + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + guard let moleculeModel = (molecule as? MoleculeContainerModel)?.molecule, + let classType = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(moleculeModel) as? ModelMoleculeViewProtocol.Type, + let height = classType.estimatedHeight(forRow: moleculeModel, delegateObject: delegateObject) else { + return 80 + } + return max(2 * PaddingDefaultVerticalSpacing3, height) + } } diff --git a/MVMCoreUI/Molecules/Items/StackItem.swift b/MVMCoreUI/Molecules/Items/StackItem.swift index 6ee5ae88..07cae3d0 100644 --- a/MVMCoreUI/Molecules/Items/StackItem.swift +++ b/MVMCoreUI/Molecules/Items/StackItem.swift @@ -2,55 +2,14 @@ // StackItem.swift // MVMCoreUI // -// Created by Scott Pfeil on 12/13/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. // -import UIKit +import Foundation -open class StackItemModel: ContainerModelProtocol { - public var view: StackItem - public var spacing: CGFloat? - public var percentage: Int? - public var verticalAlignment: UIStackView.Alignment? - public var horizontalAlignment: UIStackView.Alignment? - public var useHorizontalMargins: Bool? = false - public var useVerticalMargins: Bool? = false - public var gone = false - - init(with view: StackItem) { - self.view = view - view.model = self - } - - init(with view: StackItem, json: [AnyHashable: Any]?) { - self.view = view - view.model = self - update(with: json) - } - - func update(with json: [AnyHashable: Any]?) { - gone = json?.boolForKey("gone") ?? (json == nil) - spacing = json?.optionalCGFloatForKey("spacing") - percentage = json?["percent"] as? Int - if let horizontalAlignmentString = json?.optionalStringForKey("horizontalAlignment") { - horizontalAlignment = ContainerHelper.getAlignment(for: horizontalAlignmentString) - } else { - horizontalAlignment = nil - } - - if let verticalAlignmentString = json?.optionalStringForKey("verticalAlignment") { - verticalAlignment = ContainerHelper.getAlignment(for: verticalAlignmentString) - } else { - verticalAlignment = nil - } - - useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") ?? false - useVerticalMargins = json?.optionalBoolForKey("useVerticalMargins") ?? false +open class StackItem: Container { + var stackItemModel: StackItemModel? { + get { return model as? StackItemModel } } } - -open class StackItem: MoleculeContainer { - - -} diff --git a/MVMCoreUI/Molecules/Items/StackItemModel.swift b/MVMCoreUI/Molecules/Items/StackItemModel.swift new file mode 100644 index 00000000..f296986e --- /dev/null +++ b/MVMCoreUI/Molecules/Items/StackItemModel.swift @@ -0,0 +1,22 @@ +// +// StackItemModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class StackItemModel: StackItemModelProtocol, MoleculeModelProtocol { + public static var identifier: String = "simpleStackItem" + public var backgroundColor: Color? + public var spacing: CGFloat? + public var percent: Int? + public var gone: Bool = false + + public convenience init(gone: Bool) { + self.init() + self.gone = gone + } +} diff --git a/MVMCoreUI/Molecules/Items/StackItemModelProtocol.swift b/MVMCoreUI/Molecules/Items/StackItemModelProtocol.swift new file mode 100644 index 00000000..170fce69 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/StackItemModelProtocol.swift @@ -0,0 +1,15 @@ +// +// StackItemModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol StackItemModelProtocol: MoleculeModelProtocol { + var spacing: CGFloat? { get set } + var percent: Int? { get set } + var gone: Bool { get set } +} diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index 91721544..cd8a3cdf 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -8,13 +8,11 @@ import UIKit -@objcMembers open class TableViewCell: UITableViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol { - open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? - open var json: [AnyHashable: Any]? - public let containerHelper = ContainerHelper() +@objcMembers open class TableViewCell: UITableViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol, ModelMoleculeViewProtocol { - // In updateView, will set padding to default. - open var updateViewHorizontalDefaults = true + open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? + open var listItemModel: ListItemModelProtocol? + public let containerHelper = ContainerHelper() // For the accessory view convenience. private var caretView: CaretView? @@ -24,19 +22,10 @@ import UIKit // For separation between cells. public var topSeparatorView: Line? public var bottomSeparatorView: Line? - public enum SeparatorFrequency: String { - case all - case allExceptTop - case allExceptBottom - case between - } /// For subclasses that want to use a custom accessory view. open var customAccessoryView = false - open var topMarginPadding: CGFloat = 24 - open var bottomMarginPadding: CGFloat = 24 - private var heroAccessoryCenter: CGPoint? // MARK: - Styling @@ -60,38 +49,38 @@ import UIKit } open func styleStandard() { - topMarginPadding = 24 - bottomMarginPadding = 24 - topSeparatorView?.style = .none - bottomSeparatorView?.style = .standard + listItemModel?.topMarginPadding = 24 + listItemModel?.bottomMarginPadding = 24 + topSeparatorView?.setStyle(.none) + bottomSeparatorView?.setStyle(.standard) } open func styleTallDivider() { - topMarginPadding = 48 - bottomMarginPadding = 16 - topSeparatorView?.style = .none - bottomSeparatorView?.style = .thin + listItemModel?.topMarginPadding = 48 + listItemModel?.bottomMarginPadding = 16 + topSeparatorView?.setStyle(.none) + bottomSeparatorView?.setStyle(.thin) } open func styleShortDivider() { - topMarginPadding = 32 - bottomMarginPadding = 16 - topSeparatorView?.style = .none - bottomSeparatorView?.style = .thin + listItemModel?.topMarginPadding = 32 + listItemModel?.bottomMarginPadding = 16 + topSeparatorView?.setStyle(.none) + bottomSeparatorView?.setStyle(.thin) } open func styleFooter() { - topMarginPadding = 24 - bottomMarginPadding = 0 - topSeparatorView?.style = .none - bottomSeparatorView?.style = .none + listItemModel?.topMarginPadding = 24 + listItemModel?.bottomMarginPadding = 0 + topSeparatorView?.setStyle(.none) + bottomSeparatorView?.setStyle(.none) } open func styleNone() { - topMarginPadding = 0 - bottomMarginPadding = 0 - topSeparatorView?.style = .none - bottomSeparatorView?.style = .none + listItemModel?.topMarginPadding = 0 + listItemModel?.bottomMarginPadding = 0 + topSeparatorView?.setStyle(.none) + bottomSeparatorView?.setStyle(.none) } /// Adds the molecule to the view. @@ -122,8 +111,8 @@ import UIKit } // MARK: - MFViewProtocol - open func updateView(_ size: CGFloat) { - MFStyler.setMarginsFor(self, size: size, defaultHorizontal: updateViewHorizontalDefaults, top: topMarginPadding, bottom: bottomMarginPadding) + public func updateView(_ size: CGFloat) { + containerHelper.updateViewMargins(self, model: listItemModel, size: size) if accessoryView != nil { // Smaller left margin if accessory view. @@ -148,73 +137,60 @@ import UIKit open func setupView() { selectionStyle = .none insetsLayoutMarginsFromSafeArea = false + preservesSuperviewLayoutMargins = false contentView.insetsLayoutMarginsFromSafeArea = false contentView.preservesSuperviewLayoutMargins = false } - - // MARK: - MVMCoreUIMoleculeViewProtocol - open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - self.json = json - - guard let json = json else { return } - - style(with: json.optionalStringForKey("style")) - - if let useHorizontalMargins = json.optionalBoolForKey("useHorizontalMargins") { - updateViewHorizontalDefaults = useHorizontalMargins + + //TODO: Model, Change to model + public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? ListItemModelProtocol else { + return } - - if (json.optionalBoolForKey("useVerticalMargins") ?? true) == false { - topMarginPadding = 0 - bottomMarginPadding = 0 + + self.listItemModel = model + style(with: model.style) + + if let backgroundColor = model.backgroundColor { + self.backgroundColor = backgroundColor.uiColor } - - if let backgroundColorString = json.optionalStringForKey(KeyBackgroundColor) { - backgroundColor = UIColor.mfGet(forHex: backgroundColorString) - } - + // Add the caret if there is an action and it's not declared hidden. if !customAccessoryView { - if let _ = json.optionalDictionaryForKey("actionMap"), !json.boolForKey("hideArrow") { + if let _ = model.action, !(model.hideArrow ?? false) { addCaretViewAccessory() } else { accessoryView = nil } } - + // override the separator - if let separator = json.optionalDictionaryForKey("separator") { + if let separator = model.line { addSeparatorsIfNeeded() - bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData) + bottomSeparatorView?.setWithModel(separator, nil, nil) } - - guard let molecule = molecule else { return } - containerHelper.set(with: json, for: molecule) } open func reset() { molecule?.reset?() - updateViewHorizontalDefaults = true styleStandard() backgroundColor = .white } - - public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) else { - return 80 - } - return max(2 * PaddingDefaultVerticalSpacing3, height) - } - - public class func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { - return molecule?.optionalStringForKey(KeyMoleculeName) ?? "" + + public class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + return model?.moleculeName } + + public class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return nil + } // MARK: - Arrow /// Adds the standard mvm style caret to the accessory view @objc public func addCaretViewAccessory() { guard accessoryView == nil else { return } caretView = CaretView(lineWidth: 1) + caretView?.translatesAutoresizingMaskIntoConstraints = true caretView?.size = .small(.vertical) caretView?.setConstraints() @@ -260,23 +236,21 @@ import UIKit // MARK: - MoleculeListCellProtocol /// For when the separator between cells shows using json and frequency. Default is type: standard, frequency: allExceptTop. - public func setSeparatorWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, indexPath: IndexPath) { + public func setLines(with model: LineModel?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?, indexPath: IndexPath) { addSeparatorsIfNeeded() - if let json = json { - topSeparatorView?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - bottomSeparatorView?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - if let separatorFrequencyString = json.optionalStringForKey("frequency"), let separatorFrequency = SeparatorFrequency(rawValue: separatorFrequencyString) { - setSeparatorFrequency(separatorFrequency, indexPath: indexPath) - } + if let model = model { + topSeparatorView?.setWithModel(model, delegateObject, additionalData) + bottomSeparatorView?.setWithModel(model, delegateObject, additionalData) } else { - topSeparatorView?.style = .standard - bottomSeparatorView?.style = .standard - setSeparatorFrequency(TableViewCell.SeparatorFrequency.allExceptTop, indexPath: indexPath) + topSeparatorView?.setStyle(.standard) + bottomSeparatorView?.setStyle(.standard) } + setSeparatorFrequency(model?.frequency ?? .allExceptTop, indexPath: indexPath) } - public func didSelectCell(atIndex indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - if let actionMap = json?.optionalDictionaryForKey("actionMap") { + public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + //TODO: Use object when handleAction is rewrote to handle action model + if let actionMap = self.listItemModel?.action?.toJSON() { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } @@ -289,7 +263,7 @@ import UIKit open func addSeparatorsIfNeeded() { if topSeparatorView == nil { let line = Line() - line.style = .none + line.setStyle(.none) addSubview(line) NSLayoutConstraint.pinViewTop(toSuperview: line, useMargins: false, constant: 0).isActive = true NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true @@ -298,7 +272,7 @@ import UIKit } if bottomSeparatorView == nil { let line = Line() - line.style = .none + line.setStyle(.none) addSubview(line) NSLayoutConstraint.pinViewBottom(toSuperview: line, useMargins: false, constant: 0).isActive = true NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true @@ -308,7 +282,7 @@ import UIKit } /// For when the separator between cells shows. - public func setSeparatorFrequency(_ separatorFrequency: SeparatorFrequency, indexPath: IndexPath) { + public func setSeparatorFrequency(_ separatorFrequency: LineModel.Frequency, indexPath: IndexPath) { switch separatorFrequency { case .all: if indexPath.row == 0 { diff --git a/MVMCoreUI/Molecules/Items/TabsListItemModel.swift b/MVMCoreUI/Molecules/Items/TabsListItemModel.swift new file mode 100644 index 00000000..0158774a --- /dev/null +++ b/MVMCoreUI/Molecules/Items/TabsListItemModel.swift @@ -0,0 +1,56 @@ +// +// TabsListItemModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class TabsListItemModel: ContainerModel, ListItemModelProtocol { + public static var identifier: String = "tabsListItem" + var tabs: TabsModel + var molecules: [[ListItemModelProtocol]] + + public var backgroundColor: Color? + public var hideArrow: Bool? = true + public var line: LineModel? = LineModel(type: .standard) + + private enum CodingKeys: String, CodingKey { + case moleculeName + case tabs + case molecules + case backgroundColor + case line + } + + public init(with tabs: TabsModel, molecules: [[ListItemModelProtocol]]) { + self.tabs = tabs + self.molecules = molecules + super.init() + self.topMarginPadding = 8 + self.bottomMarginPadding = 0 + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + tabs = try typeContainer.decode(TabsModel.self, forKey: .tabs) + molecules = try typeContainer.decodeMolecules2D(codingKey: .molecules) as! [[ListItemModelProtocol]] + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + if let lineModel = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) { + line = lineModel + } + 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(tabs, forKey: .tabs) + try container.encodeModels2D(molecules, forKey: .molecules) + try container.encode(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(line, forKey: .line) + } +} diff --git a/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift index 1a1796cd..cf09eb51 100644 --- a/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift @@ -9,6 +9,7 @@ import UIKit @objcMembers public class TabsTableViewCell: TableViewCell { + var tabsListItemModel: TabsListItemModel? let tabs = TopTabbar(frame: .zero) var delegateObject: MVMCoreUIDelegateObject? var previousTabIndex = 0 @@ -20,8 +21,6 @@ import UIKit return } tabs.paddingBeforeFirstTab = false - topMarginPadding = 8 - bottomMarginPadding = 0 tabs.translatesAutoresizingMaskIntoConstraints = false tabs.delegate = self @@ -38,42 +37,42 @@ import UIKit } // MARK: - MoleculeDelegateProtocol - public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) self.delegateObject = delegateObject tabs.reloadData() } - + public override func reset() { super.reset() - topMarginPadding = 8 - bottomMarginPadding = 0 + tabs.reset() } } extension TabsTableViewCell: TopTabbarDelegate { public func shouldSelectItem(at index: Int, topTabbar: TopTabbar) -> Bool { - if let moleculesArrays = json?.arrayForKey(KeyMolecules), let molecules = moleculesArrays[topTabbar.selectedIndex] as? [[AnyHashable: Any]] { - delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: index < tabs.selectedIndex ? .right : .left) + if let molecules = tabsListItemModel?.molecules[topTabbar.selectedIndex] { + delegateObject?.moleculeDelegate?.removeMolecules(molecules, sender: self, animation: index < tabs.selectedIndex ? .right : .left) } previousTabIndex = tabs.selectedIndex return true } - + public func topTabbar(_ topTabbar: TopTabbar, didSelectItemAt index: Int) { - if let moleculesArrays = json?.arrayForKey(KeyMolecules), let molecules = moleculesArrays[index] as? [[AnyHashable: Any]] { - delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: index < previousTabIndex ? .left : .right) + if let molecules = tabsListItemModel?.molecules[index] { + delegateObject?.moleculeDelegate?.addMolecules(molecules, sender: self, animation: index < previousTabIndex ? .left : .right) } } } extension TabsTableViewCell: TopTabbarDataSource { public func number(ofTopTabbarItems topTabbar: TopTabbar) -> Int { - return json?.optionalDictionaryForKey("tabs")?.optionalArrayForKey("tabs")?.count ?? 0 + return tabsListItemModel?.tabs.tabs.count ?? 0 } - + public func topTabbar(_ topTabbar: TopTabbar, titleForItemAt index: Int) -> String? { - guard let tabs = json?.optionalDictionaryForKey("tabs")?.arrayForKey("tabs"), let label = tabs[index] as? [AnyHashable: Any], let title = label.optionalStringForKey(KeyText) else { + guard let title = tabsListItemModel?.tabs.tabs[index].text else { return "Select" } return title diff --git a/MVMCoreUI/Molecules/ActionDetailWithImage.swift b/MVMCoreUI/Molecules/LeftRightViews/ActionDetailWithImage.swift similarity index 81% rename from MVMCoreUI/Molecules/ActionDetailWithImage.swift rename to MVMCoreUI/Molecules/LeftRightViews/ActionDetailWithImage.swift index 4811f20d..bde18777 100644 --- a/MVMCoreUI/Molecules/ActionDetailWithImage.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/ActionDetailWithImage.swift @@ -9,7 +9,7 @@ import UIKit -@objcMembers open class ActionDetailWithImage: ViewConstrainingView { +@objcMembers open class ActionDetailWithImage: View { //------------------------------------------------------ // MARK: - Outlets //------------------------------------------------------ @@ -91,10 +91,6 @@ import UIKit imageLoader.updateView(size) } - public override class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 197 - } - //------------------------------------------------------ // MARK: - Methods //------------------------------------------------------ @@ -117,20 +113,15 @@ import UIKit setDefaultState() } - open override func setAsMolecule() { - super.setAsMolecule() - - headlineBodyButton.setAsMolecule() - imageLoader.setAsMolecule() - setDefaultState() + // MARK:- ModelMoleculeViewProtocol + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 197 } - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - - guard let dictionary = json else { return } - - headlineBodyButton.setWithJSON(dictionary.optionalDictionaryForKey("headlineBodyButton"), delegateObject: delegateObject, additionalData: additionalData) - imageLoader.setWithJSON(dictionary.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? ActionDetailWithImageModel else { return } + headlineBodyButton.setWithModel(model.headlineBodyButton, delegateObject, additionalData) + imageLoader.setWithModel(model.image, delegateObject, additionalData) } } diff --git a/MVMCoreUI/Molecules/LeftRightViews/ActionDetailWithImageModel.swift b/MVMCoreUI/Molecules/LeftRightViews/ActionDetailWithImageModel.swift new file mode 100644 index 00000000..a397c1a1 --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/ActionDetailWithImageModel.swift @@ -0,0 +1,16 @@ +// +// ActionDetailWithImageModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public struct ActionDetailWithImageModel: MoleculeModelProtocol { + public static var identifier: String = "actionDetailWithImage" + public var backgroundColor: Color? + public var headlineBodyButton: HeadlineBodyButtonModel + public var image: ImageViewModel +} diff --git a/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift b/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift index 9b7dc062..680e7089 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift @@ -8,8 +8,8 @@ import UIKit -@objcMembers public class CornerLabels: ViewConstrainingView { - +@objcMembers public class CornerLabels: View { + var middleView: UIView? let topLeftLabel = Label.commonLabelB1(true) let topRightLabel = Label.commonLabelB1(true) let bottomLeftLabel = Label.commonLabelB3(true) @@ -38,18 +38,19 @@ import UIKit var topLabelToMoleculeConstraint: NSLayoutConstraint? var bottomLabelToMoleculeConstraint: NSLayoutConstraint? - public override func addMolecule(_ molecule: UIView) { - insertSubview(molecule, at: 0) + public func addMiddleView(_ view: UIView) { + insertSubview(view, at: 0) topLabelToMoleculeConstraint?.isActive = false bottomLabelToMoleculeConstraint?.isActive = false - molecule.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true - layoutMarginsGuide.rightAnchor.constraint(equalTo: molecule.rightAnchor).isActive = true + view.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true + layoutMarginsGuide.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true - topLabelToMoleculeConstraint = molecule.topAnchor.constraint(equalTo: topLabelsView.bottomAnchor, constant: spaceAboveMolecule) + topLabelToMoleculeConstraint = view.topAnchor.constraint(equalTo: topLabelsView.bottomAnchor, constant: spaceAboveMolecule) topLabelToMoleculeConstraint?.isActive = true - bottomLabelToMoleculeConstraint = bottomLabelsView.topAnchor.constraint(equalTo: molecule.bottomAnchor, constant: spaceBelowMolecule) + bottomLabelToMoleculeConstraint = bottomLabelsView.topAnchor.constraint(equalTo: view.bottomAnchor, constant: spaceBelowMolecule) bottomLabelToMoleculeConstraint?.isActive = true + self.middleView = view } // MARK: - MVMCoreViewProtocol @@ -59,12 +60,11 @@ import UIKit topRightLabel.updateView(size) bottomLeftLabel.updateView(size) bottomRightLabel.updateView(size) + (middleView as? MVMCoreViewProtocol)?.updateView(size) } public override func setupView() { super.setupView() - shouldSetupMoleculeFromJSON = true - guard topLeftLabel.superview == nil else { return } @@ -141,17 +141,6 @@ import UIKit } // MARK: - MVMCoreUIMoleculeViewProtocol - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - topLeftLabel.setWithJSON(json?.optionalDictionaryForKey("topLeftLabel"), delegateObject: delegateObject, additionalData: additionalData) - topRightLabel.setWithJSON(json?.optionalDictionaryForKey("topRightLabel"), delegateObject: delegateObject, additionalData: additionalData) - bottomLeftLabel.setWithJSON(json?.optionalDictionaryForKey("bottomLeftLabel"), delegateObject: delegateObject, additionalData: additionalData) - bottomRightLabel.setWithJSON(json?.optionalDictionaryForKey("bottomRightLabel"), delegateObject: delegateObject, additionalData: additionalData) - - topLabelToMoleculeConstraint?.constant = (molecule != nil && (topLeftLabel.hasText || topRightLabel.hasText)) ? spaceAboveMolecule : 0 - bottomLabelToMoleculeConstraint?.constant = (molecule != nil && (bottomLeftLabel.hasText || bottomRightLabel.hasText)) ? spaceBelowMolecule : 0 - } - public override func setAsMolecule() { super.setAsMolecule() styleDefault() @@ -163,8 +152,7 @@ import UIKit styleDefault() spaceAboveMolecule = 6.0 spaceBelowMolecule = 6.0 - - molecule?.reset?() + (middleView as? MoleculeViewProtocol)?.reset?() } func styleDefault() { @@ -174,7 +162,27 @@ import UIKit bottomRightLabel.styleB3(true) } - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 34 } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? CornerLabelsModel else { return } + if middleView != nil { + (middleView as? ModelMoleculeViewProtocol)?.setWithModel(model, delegateObject, additionalData) + } else { + if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(model.molecule, delegateObject) { + addMiddleView(molecule) + } + } + + topLeftLabel.setWithModel(model.topLeftLabel, delegateObject, additionalData) + topRightLabel.setWithModel(model.topRightLabel, delegateObject, additionalData) + bottomLeftLabel.setWithModel(model.bottomLeftLabel, delegateObject, additionalData) + bottomRightLabel.setWithModel(model.bottomRightLabel, delegateObject, additionalData) + + topLabelToMoleculeConstraint?.constant = (middleView != nil && (topLeftLabel.hasText || topRightLabel.hasText)) ? spaceAboveMolecule : 0 + bottomLabelToMoleculeConstraint?.constant = (middleView != nil && (bottomLeftLabel.hasText || bottomRightLabel.hasText)) ? spaceBelowMolecule : 0 + } } diff --git a/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift b/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift new file mode 100644 index 00000000..ce3796bf --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift @@ -0,0 +1,52 @@ +// +// CornerLabelsModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class CornerLabelsModel: MoleculeModelProtocol { + public static var identifier: String = "cornerLabels" + public var backgroundColor: Color? + public var topLeftLabel: LabelModel? + public var topRightLabel: LabelModel? + public var bottomLeftLabel: LabelModel? + public var bottomRightLabel: LabelModel? + public var molecule: MoleculeModelProtocol + + init(with molecule: MoleculeModelProtocol) { + self.molecule = molecule + } + + private enum CodingKeys: String, CodingKey { + case backgroundColor + case topLeftLabel + case topRightLabel + case bottomLeftLabel + case bottomRightLabel + case molecule + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + molecule = try typeContainer.decodeMolecule(codingKey: .molecule) + topLeftLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .topLeftLabel) + topRightLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .topRightLabel) + bottomLeftLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .bottomLeftLabel) + bottomRightLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .bottomRightLabel) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeModelIfPresent(molecule, forKey: .molecule) + try container.encodeIfPresent(topLeftLabel, forKey: .topLeftLabel) + try container.encodeIfPresent(topRightLabel, forKey: .topRightLabel) + try container.encodeIfPresent(bottomLeftLabel, forKey: .bottomLeftLabel) + try container.encodeIfPresent(bottomRightLabel, forKey: .bottomRightLabel) + } +} diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyLinkToggleModel.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyLinkToggleModel.swift new file mode 100644 index 00000000..5ce04f9c --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyLinkToggleModel.swift @@ -0,0 +1,20 @@ +// +// HeadlineBodyLinkToggleModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public struct HeadlineBodyLinkToggleModel: MoleculeModelProtocol { + public static var identifier: String = "headlineBodyLinkToggle" + public var backgroundColor: Color? + public var headlineBodyLink: HeadlineBodyLinkModel + public var toggle: ToggleModel + + public init(_ headlineBodyLink: HeadlineBodyLinkModel, _ toggle: ToggleModel) { + self.headlineBodyLink = headlineBodyLink + self.toggle = toggle + } +} diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift index 79110460..1dbefcfe 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift @@ -8,54 +8,53 @@ import UIKit -@objcMembers open class HeadlineBodySwitch: ViewConstrainingView { +@objcMembers open class HeadlineBodySwitch: View { public let headlineBody = HeadlineBody(frame: .zero) - public let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() + public let toggle = Toggle() // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) headlineBody.updateView(size) - mvmSwitch.updateView(size) + toggle.updateView(size) } open override func setupView() { super.setupView() - guard mvmSwitch.superview == nil else { + guard toggle.superview == nil else { return } headlineBody.styleListItem() let view = MVMCoreUICommonViewsUtility.commonView() addSubview(view) - pinView(toSuperView: view) + NSLayoutConstraint.constraintPinSubview(toSuperview: view) view.addSubview(headlineBody) - view.addSubview(mvmSwitch) - NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBody, rightView: mvmSwitch) + view.addSubview(toggle) + NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBody, rightView: toggle) + } + + // MARK:- ModelMoleculeViewProtocol + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let headlineBodyToggleModel = model as? HeadlineBodyToggleModel else { + return + } + setWithJSON(headlineBodyToggleModel.toJSON(), delegateObject: delegateObject, additionalData: additionalData) + } + + open class override func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + guard let model = molecule as? HeadlineBodyToggleModel, + let toggleHeight = Toggle.estimatedHeight(forRow: model.toggle, delegateObject: delegateObject), + let headlineBody = HeadlineBody.estimatedHeight(forRow: model.headlineBody, delegateObject: delegateObject) else { return nil } + return max(toggleHeight, headlineBody) } // MARK: - MVMCoreUIMoleculeViewProtocol - open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) - mvmSwitch.setWithJSON(json?.optionalDictionaryForKey("switch"), delegateObject: delegateObject, additionalData: additionalData) - } - - open override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 30 - } - - open override func setAsMolecule() { - super.setAsMolecule() - headlineBody.setAsMolecule() - (mvmSwitch as MVMCoreUIMoleculeViewProtocol).setAsMolecule?() - headlineBody.styleListItem() - } - open override func reset() { super.reset() headlineBody.reset() - (mvmSwitch as MVMCoreUIMoleculeViewProtocol).reset?() + toggle.reset() headlineBody.styleListItem() } } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift index b0cf6956..aa9e1a76 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift @@ -8,55 +8,45 @@ import UIKit -@objcMembers public class HeadlineBodyTextButtonSwitch: ViewConstrainingView { - let headlineBodyTextButton = HeadlineBodyTextButton(frame: .zero) - let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() +@objcMembers public class HeadlineBodyTextButtonSwitch: View { + let headlineBodyLink = HeadlineBodyTextButton(frame: .zero) + let toggle = Toggle() // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) - headlineBodyTextButton.updateView(size) - mvmSwitch.updateView(size) + headlineBodyLink.updateView(size) + toggle.updateView(size) } public override func setupView() { super.setupView() - guard mvmSwitch.superview == nil else { + guard toggle.superview == nil else { return } - let view = MVMCoreUICommonViewsUtility.commonView() - addSubview(view) - pinView(toSuperView: view) - - headlineBodyTextButton.headlineBody.styleListItem() - view.addSubview(headlineBodyTextButton) - view.addSubview(mvmSwitch) - NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBodyTextButton, rightView: mvmSwitch) - } - - // MARK: - MVMCoreUIMoleculeViewProtocol - public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - headlineBodyTextButton.setWithJSON(json?.optionalDictionaryForKey("headlineBodyTextButton"), delegateObject: delegateObject, additionalData: additionalData) - mvmSwitch.setWithJSON(json?.optionalDictionaryForKey("switch"), delegateObject: delegateObject, additionalData: additionalData) - } - - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return HeadlineBodyTextButton.estimatedHeight(forRow: json, delegateObject: delegateObject) - } - - public override func setAsMolecule() { - super.setAsMolecule() - headlineBodyTextButton.setAsMolecule() - (mvmSwitch as MVMCoreUIMoleculeViewProtocol).setAsMolecule?() - headlineBodyTextButton.headlineBody.styleListItem() + headlineBodyLink.headlineBody.styleListItem() + addSubview(headlineBodyLink) + addSubview(toggle) + NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBodyLink, rightView: toggle) } + // MARK: - MVMCoreUIMoleculeViewProtoco public override func reset() { super.reset() - headlineBodyTextButton.reset() - (mvmSwitch as MVMCoreUIMoleculeViewProtocol).reset?() - headlineBodyTextButton.headlineBody.styleListItem() + headlineBodyLink.reset() + toggle.reset() + } + + // MARK:- ModelMoleculeViewProtocol + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? HeadlineBodyLinkToggleModel else { return } + headlineBodyLink.setWithModel(model.headlineBodyLink, delegateObject, additionalData) + toggle.setWithModel(model.toggle, delegateObject, additionalData) + } + + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return HeadlineBodyTextButton.estimatedHeight(forRow: (molecule as? HeadlineBodyLinkToggleModel)?.headlineBodyLink, delegateObject: delegateObject) } } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyToggleModel.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyToggleModel.swift new file mode 100644 index 00000000..dd7c2b1c --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyToggleModel.swift @@ -0,0 +1,22 @@ +// +// HeadlineBodyToggleModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 1/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +open class HeadlineBodyToggleModel: MoleculeModelProtocol { + public static var identifier: String = "headlineBodyToggle" + open var backgroundColor: Color? + open var headlineBody: HeadlineBodyModel + open var toggle: ToggleModel + + public init(_ headlineBody: HeadlineBodyModel, _ toggle: ToggleModel) { + self.headlineBody = headlineBody + self.toggle = toggle + } +} diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift index f60202b3..a7688121 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift @@ -8,54 +8,50 @@ import UIKit -@objcMembers public class LabelSwitch: ViewConstrainingView { - let label = Label.commonLabelB1(true) - let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() +@objcMembers open class LabelSwitch: View { + public let label = Label.commonLabelB1(true) + public let toggle = Toggle() // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) label.updateView(size) - mvmSwitch.updateView(size) + toggle.updateView(size) } - public override func setupView() { + open override func setupView() { super.setupView() - guard mvmSwitch.superview == nil else { + guard toggle.superview == nil else { return } - let view = MVMCoreUICommonViewsUtility.commonView() - addSubview(view) - pinView(toSuperView: view) - view.addSubview(label) - view.addSubview(mvmSwitch) + addSubview(label) + addSubview(toggle) label.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - NSLayoutConstraint.pinSubviewsCenter(leftView: label, rightView: mvmSwitch) + NSLayoutConstraint.pinSubviewsCenter(leftView: label, rightView: toggle) + } + + // MARK:- ModelMoleculeViewProtocol + open override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + guard let model = molecule as? LabelToggleModel, + let toggleHeight = Toggle.estimatedHeight(forRow: model.toggle, delegateObject: delegateObject), + let labelHeight = Label.estimatedHeight(forRow: model.label, delegateObject: delegateObject) else { return nil } + return max(toggleHeight, labelHeight) + } + + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let labelToggleModel = model as? LabelToggleModel else { + return + } + label.setWithModel(labelToggleModel.label, delegateObject, additionalData) + toggle.setWithModel(labelToggleModel.toggle, delegateObject, additionalData) } // MARK: - MVMCoreUIMoleculeViewProtocol - public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - label.setWithJSON(json?.optionalDictionaryForKey("label"), delegateObject: delegateObject, additionalData: additionalData) - mvmSwitch.setWithJSON(json?.optionalDictionaryForKey("switch"), delegateObject: delegateObject, additionalData: additionalData) - } - - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return MVMCoreUISwitch.estimatedHeight(forRow: json, delegateObject: delegateObject) - } - - public override func setAsMolecule() { - super.setAsMolecule() - label.setAsMolecule() - (mvmSwitch as MVMCoreUIMoleculeViewProtocol).setAsMolecule?() - label.styleB1(true) - } - - public override func reset() { + open override func reset() { super.reset() label.reset() - (mvmSwitch as MVMCoreUIMoleculeViewProtocol).reset?() + toggle.reset() label.styleB1(true) } } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelToggleModel.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelToggleModel.swift new file mode 100644 index 00000000..7ed5857e --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelToggleModel.swift @@ -0,0 +1,16 @@ +// +// LabelToggle.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 1/15/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class LabelToggleModel: MoleculeModelProtocol { + public static var identifier: String = "labelToggle" + public var backgroundColor: Color? + public var label: LabelModel + public var toggle: ToggleModel +} diff --git a/MVMCoreUI/Molecules/ModelMoleculeViewProtocol.swift b/MVMCoreUI/Molecules/ModelMoleculeViewProtocol.swift new file mode 100644 index 00000000..972ee175 --- /dev/null +++ b/MVMCoreUI/Molecules/ModelMoleculeViewProtocol.swift @@ -0,0 +1,34 @@ +// +// ModelMoleculeViewProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ModelMoleculeViewProtocol { + func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) + static func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? + static func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? + static func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? +} + +extension ModelMoleculeViewProtocol { + public static func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + return model?.moleculeName + } + public static func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return nil + } + public static func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + return nil + } + // Temporary + public static func decodeJSONToModel(json: [AnyHashable: Any], type: T.Type) throws -> T where T : Decodable { + let data = try JSONSerialization.data(withJSONObject: json) + let decoder = JSONDecoder() + return try decoder.decode(type, from: data) + } +} diff --git a/MVMCoreUI/Molecules/ModuleMolecule.swift b/MVMCoreUI/Molecules/ModuleMolecule.swift index 66e18d47..360410b7 100644 --- a/MVMCoreUI/Molecules/ModuleMolecule.swift +++ b/MVMCoreUI/Molecules/ModuleMolecule.swift @@ -8,64 +8,77 @@ import UIKit -struct ModuleMoleculeModel: ContainerModelProtocol { - var horizontalAlignment: UIStackView.Alignment? = .fill - var verticalAlignment: UIStackView.Alignment? = .fill - var useHorizontalMargins: Bool? = false - var useVerticalMargins: Bool? = false -} - open class ModuleMolecule: Container { + + open var moduleMolecule: (UIView & MVMCoreUIMoleculeViewProtocol & ModelMoleculeViewProtocol)? + var moduleMoleculeModel: ModuleMoleculeModel? { + get { return model as? ModuleMoleculeModel } + } + public override func setupView() { super.setupView() - model = ModuleMoleculeModel() } - // MARK: - MVMCoreUIMoleculeViewProtocol - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - - guard let moduleName = json?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { - // Critical error - return + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + + guard let moduleMoleculeModel = model as? ModuleMoleculeModel, + let moduleModel = delegateObject?.moleculeDelegate?.getModuleWithName(moduleMoleculeModel.moduleName) else { + // Critical error + return } - if view == nil { - if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: module, delegateObject: delegateObject, constrainIfNeeded: false) { - addAndContain(moleculeView) + if moduleMolecule == nil { + if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(moduleModel, delegateObject, false) { + addSubview(moleculeView) + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: false).values)) + moduleMolecule = moleculeView as? (UIView & MVMCoreUIMoleculeViewProtocol & ModelMoleculeViewProtocol) + + isAccessibilityElement = false + if moleculeView.accessibilityElements != nil { + accessibilityElements = moleculeView.accessibilityElements + } else { + accessibilityElements = [moleculeView] + } } } else { - (view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON(module, delegateObject: delegateObject, additionalData: additionalData) + moduleMolecule?.setWithModel(model, delegateObject, additionalData) } } - - public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - guard let moduleName = json?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { - // Critical error - return 0 + + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + + guard let moduleMolecule = molecule as? ModuleMoleculeModel, + let moduleModel = delegateObject?.moleculeDelegate?.getModuleWithName(moduleMolecule.moduleName), + let classType = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(moduleModel) as? ModelMoleculeViewProtocol.Type, + let height = classType.estimatedHeight(forRow: moduleModel, delegateObject: delegateObject)else { + // Critical error + return 0 } - return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: module)?.estimatedHeight?(forRow: module, delegateObject: delegateObject) ?? 0 + return height } - - public class func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { - guard let moduleName = molecule?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { - // Critical error + + public override class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let moduleMolecule = model as? ModuleMoleculeModel, + let moduleModel = delegateObject?.moleculeDelegate?.getModuleWithName(moduleMolecule.moduleName), + let classType = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(moduleModel) as? ModelMoleculeViewProtocol.Type, + let name = classType.nameForReuse(moduleModel, delegateObject) else { + // Critical error return "moduleMolecule<>" } - return "moduleMolecule<" + (MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: module)?.name?(forReuse: module, delegateObject: delegateObject) ?? module.stringForkey(KeyMoleculeName)) + ">" + return name } - - public class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { - let moduleName = json?.optionalStringForKey("moduleName") - if moduleName == nil || delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) == nil { + + public override class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + + guard let moduleName = (molecule as? ModuleMoleculeModel)?.moduleName, + let _ = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { if let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), code: CoreUIErrorCode.ErrorCodeModuleMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) { error?.pointee = errorObject MVMCoreUILoggingHandler.shared()?.addError(toLog: errorObject) } + return nil } - if let moduleName = moduleName { - return [moduleName] - } - return nil + return [moduleName] } } diff --git a/MVMCoreUI/Molecules/ModuleMoleculeModel.swift b/MVMCoreUI/Molecules/ModuleMoleculeModel.swift new file mode 100644 index 00000000..931e7c04 --- /dev/null +++ b/MVMCoreUI/Molecules/ModuleMoleculeModel.swift @@ -0,0 +1,33 @@ +// +// ModuleMoleculeModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class ModuleMoleculeModel: MoleculeModelProtocol { + public var backgroundColor: Color? + public static var identifier: String = "moduleMolecule" + public var moduleName: String + + private enum CodingKeys: String, CodingKey { + case moduleName + } + + public init(_ moduleName: String) { + self.moduleName = moduleName + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + moduleName = try typeContainer.decode(String.self, forKey:.moduleName) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moduleName, forKey: .moduleName) + } +} diff --git a/MVMCoreUI/Molecules/MoleculeContainer.swift b/MVMCoreUI/Molecules/MoleculeContainer.swift deleted file mode 100644 index 14a5bb4a..00000000 --- a/MVMCoreUI/Molecules/MoleculeContainer.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// MoleculeContainer.swift -// MVMCoreUI -// -// Created by Scott Pfeil on 12/12/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -open class MoleculeContainer: Container { - - override public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - return - } - if view == nil { - if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: false) { - addAndContain(molecule) - } - } else { - (view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) - } - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - } -} diff --git a/MVMCoreUI/Molecules/MoleculeViewProtocol.swift b/MVMCoreUI/Molecules/MoleculeViewProtocol.swift new file mode 100644 index 00000000..63fff68c --- /dev/null +++ b/MVMCoreUI/Molecules/MoleculeViewProtocol.swift @@ -0,0 +1,37 @@ +// +// MVMCoreUIMoleculeViewProtocol1.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +import UIKit +import MVMCore.MVMCoreViewProtocol + +@objc public protocol MoleculeViewProtocol: MVMCoreViewProtocol { + + /// Sets up the ui based on the json + @objc func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) + + /// Called after init to provide an early setter for any molecule specific logic + @objc optional func setAsMolecule() + + + /// Resets to default state before set with json is called again. + @objc optional func reset() + + + /// For the molecule list to load more efficiently. + @objc optional static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat + + + /// Allows the molecule to set its name for reuse. Default could be moleculeName. + @objc optional static func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? + + + /// Can return the required modules + @objc optional static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? +} diff --git a/MVMCoreUI/Molecules/Scroller.swift b/MVMCoreUI/Molecules/Scroller.swift index e4289bc3..8ee77816 100644 --- a/MVMCoreUI/Molecules/Scroller.swift +++ b/MVMCoreUI/Molecules/Scroller.swift @@ -8,7 +8,7 @@ import UIKit -@objcMembers open class Scroller: ViewConstrainingView { +@objcMembers open class Scroller: Container { public let scrollView = UIScrollView(frame: .zero) public let contentView = MVMCoreUICommonViewsUtility.commonView() @@ -20,26 +20,27 @@ import UIKit translatesAutoresizingMaskIntoConstraints = false scrollView.translatesAutoresizingMaskIntoConstraints = false addSubview(scrollView) - pinView(toSuperView: scrollView) + NSLayoutConstraint.constraintPinSubview(toSuperview: scrollView) + contentView.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(contentView) NSLayoutConstraint.constraintPinSubview(toSuperview: contentView) let constraint = contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0) constraint.priority = UILayoutPriority(rawValue: 999) constraint.isActive = true } - - open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { - return - } - if molecule == nil { - if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { - contentView.addSubview(moleculeView) - pinView(toSuperView: moleculeView) - molecule = moleculeView + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + if let casteModel = model as? ScrollerModel { + if view != nil { + (view as? ModelMoleculeViewProtocol)?.setWithModel(casteModel.molecule, delegateObject, additionalData) + } else { + if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(casteModel.molecule, delegateObject) { + contentView.addSubview(molecule) + molecule.translatesAutoresizingMaskIntoConstraints = false + containerHelper.constrainView(molecule) + } } - } else { - molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) } + super.setWithModel(model, delegateObject, additionalData) } } diff --git a/MVMCoreUI/Molecules/ScrollerModel.swift b/MVMCoreUI/Molecules/ScrollerModel.swift new file mode 100644 index 00000000..f92fe13b --- /dev/null +++ b/MVMCoreUI/Molecules/ScrollerModel.swift @@ -0,0 +1,14 @@ +// +// ScrollerModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/13/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class ScrollerModel: MoleculeContainerModel, MoleculeModelProtocol { + public static var identifier: String = "scroller" + public var backgroundColor: Color? +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift index 869bd23a..ff8e6cfa 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -8,12 +8,15 @@ import UIKit -@objcMembers open class EyebrowHeadlineBodyLink: ViewConstrainingView { - let stack = MoleculeStackView(frame: .zero) +@objcMembers open class EyebrowHeadlineBodyLink: Container { + let stack = Stack(frame: .zero) let eyebrow = Label.commonLabelB3(true) let headline = Label.commonLabelB1(true) let body = Label.commonLabelB2(true) - let link = MFTextButton(nil, constrainHeight: false, forWidth: MVMCoreUIUtility.getWidth()) + let link = Link() + var casteModel: EyebrowHeadlineBodyLinkModel? { + get { return model as? EyebrowHeadlineBodyLinkModel } + } // MARK: - MFViewProtocol open override func setupView() { @@ -21,17 +24,9 @@ import UIKit guard stack.superview == nil else { return } - stack.spacing = 0 + stack.stackItems = [StackItem(andContain: eyebrow),StackItem(andContain: headline),StackItem(andContain: body),StackItem(andContain: link)] addSubview(stack) - pinView(toSuperView: stack) - stack.addStackItem(StackItemModel(with: StackItem(andContain: eyebrow)), lastItem: false) - stack.addStackItem(StackItemModel(with: StackItem(andContain: headline)), lastItem: false) - stack.addStackItem(StackItemModel(with: StackItem(andContain: body)), lastItem: false) - - // To visually take into account the extra padding in the intrinsic content of a button. - let stackItem = StackItemModel(with: StackItem(andContain: link)) - stackItem.spacing = -6 - stack.addStackItem(stackItem, lastItem: true) + NSLayoutConstraint.constraintPinSubview(toSuperview: stack) } open override func updateView(_ size: CGFloat) { @@ -40,29 +35,31 @@ import UIKit } // MARK: - MVMCoreUIMoleculeViewProtocol - open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - eyebrow.setWithJSON(json?.optionalDictionaryForKey("eyebrow"), delegateObject: delegateObject, additionalData: additionalData) - stack.items[0].gone = !eyebrow.hasText - headline.setWithJSON(json?.optionalDictionaryForKey("headline"), delegateObject: delegateObject, additionalData: additionalData) - stack.items[1].gone = !headline.hasText - body.setWithJSON(json?.optionalDictionaryForKey("body"), delegateObject: delegateObject, additionalData: additionalData) - stack.items[2].gone = !body.hasText - link.setWithJSON(json?.optionalDictionaryForKey("link"), delegateObject: delegateObject, additionalData: additionalData) - stack.items[3].gone = link.titleLabel?.text?.count ?? 0 == 0 - stack.restack() - } - open override func reset() { super.reset() stack.reset() - stack.spacing = 0 + stack.stackModel?.spacing = 0 eyebrow.styleB3(true) headline.styleB1(true) body.styleB2(true) } - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + // MARK:- ModelMoleculeViewProtocol + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + eyebrow.setWithModel(casteModel?.eyeBrow, delegateObject, additionalData) + headline.setWithModel(casteModel?.headline, delegateObject, additionalData) + body.setWithModel(casteModel?.body, delegateObject, additionalData) + link.setWithModel(casteModel?.link, delegateObject, additionalData) + + // Create a stack model to use for the internal stack. + let stackModel = StackModel(molecules: [StackItemModel(gone: !eyebrow.hasText),StackItemModel(gone: !headline.hasText),StackItemModel(gone: !body.hasText),StackItemModel(gone: (link.titleLabel?.text?.count ?? 0) == 0)]) + stackModel.spacing = 0 + stack.model = stackModel + stack.restack() + } + + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 65 } } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift new file mode 100644 index 00000000..e9341535 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift @@ -0,0 +1,19 @@ +// +// EyebrowHeadlineBodyLinkModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +struct EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol { + static var identifier: String = "eyebrowHeadlineBodyLink" + var backgroundColor: Color? + + public var eyeBrow: LabelModel? + public var headline: LabelModel? + public var body: LabelModel? + public var link: LinkModel? +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 9c3dba8a..fc19749a 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -7,24 +7,23 @@ // import Foundation -@objcMembers public class HeadLineBodyCaretLinkImage: ViewConstrainingView { - +@objcMembers public class HeadLineBodyCaretLinkImage: Container { let headlineBody = HeadlineBody(frame: .zero) let caretButton = CaretButton(frame: .zero) - let backgroundImageView = MFLoadImageView() + let backgroundImageView = MFLoadImageView(pinnedEdges: .all) var spaceBetweenConstant: CGFloat = 104.0 - var leftConstraintHeadline : NSLayoutConstraint? - var leftConstraintCaretView : NSLayoutConstraint? - let padding = MFStyler.defaultHorizontalPaddingForApplicationWidth() - let maxWidth : CGFloat = 350.0 + let maxWidth: CGFloat = 350.0 + static let heightConstant: CGFloat = 320.0 + var heightConstraint: NSLayoutConstraint? + // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { - super.updateView(size) - headlineBody.updateView(size) - caretButton.updateView(size) - backgroundImageView.updateView(size) - leftConstraintHeadline?.constant = MFStyler.defaultHorizontalPadding(forSize: size) - leftConstraintCaretView?.constant = MFStyler.defaultHorizontalPadding(forSize: size) + super.updateView(size) + headlineBody.updateView(size) + caretButton.updateView(size) + backgroundImageView.updateView(size) + backgroundImageView.alignFillHorizontal() + backgroundImageView.alignFillVertical() } open override func setupView() { @@ -32,36 +31,39 @@ import Foundation guard subviews.count == 0 else { return } - let view = MVMCoreUICommonViewsUtility.commonView() - addSubview(view) - pinView(toSuperView: view) - view.addSubview(headlineBody) - view.addSubview(caretButton) + heightConstraint = heightAnchor.constraint(equalToConstant: Self.heightConstant) + heightConstraint?.isActive = true + + let container = MVMCoreUICommonViewsUtility.commonView() + addAndContain(container) + + container.addSubview(headlineBody) + container.addSubview(caretButton) //Headline view - leftConstraintHeadline = headlineBody.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) - leftConstraintHeadline?.isActive = true - headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: PaddingDefault).isActive = true + headlineBody.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true + headlineBody.topAnchor.constraint(equalTo: container.topAnchor, constant: 0).isActive = true - let headLineBodyWidth = headlineBody.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85) + let headLineBodyWidth = headlineBody.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.85) headLineBodyWidth.priority = .defaultHigh headLineBodyWidth.isActive = true headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true //Caret view caretButton.translatesAutoresizingMaskIntoConstraints = false - leftConstraintCaretView = caretButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding) - leftConstraintCaretView?.isActive = true - view.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor, constant: PaddingDefault).isActive = true + caretButton.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true + container.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor, constant: 0).isActive = true caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant).isActive = true //Background image view backgroundImageView.translatesAutoresizingMaskIntoConstraints = false backgroundImageView.imageView.contentMode = .scaleAspectFill - view.addSubview(backgroundImageView) + backgroundImageView.alignFillHorizontal() + backgroundImageView.alignFillVertical() + addSubview(backgroundImageView) NSLayoutConstraint.constraintPinSubview(toSuperview: backgroundImageView) - view.sendSubviewToBack(backgroundImageView) + sendSubviewToBack(backgroundImageView) } // MARK: - MVMCoreUIMoleculeViewProtocol @@ -78,7 +80,20 @@ import Foundation backgroundImageView.reset() } - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + // MARK:- ModelMoleculeViewProtocol + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 320 } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? HeadlineBodyCaretLinkImageModel else { return } + headlineBody.setWithModel(model.headlineBody, delegateObject, additionalData) + caretButton.setWithModel(model.caretLink, delegateObject, additionalData) + caretButton.isHidden = model.caretLink == nil + backgroundImageView.setWithModel(model.image, delegateObject, additionalData) + backgroundImageView.alignFillHorizontal() + backgroundImageView.alignFillVertical() + } } + diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift index d236fc37..3a270ad5 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -8,7 +8,7 @@ import UIKit -open class HeadlineBody: ViewConstrainingView { +open class HeadlineBody: View { let headlineLabel = Label.commonLabelH2(true) let messageLabel = Label.commonLabelB2(true) var spaceBetweenLabelsConstant = PaddingTwo @@ -81,7 +81,7 @@ open class HeadlineBody: ViewConstrainingView { let view = MVMCoreUICommonViewsUtility.commonView() addSubview(view) - pinView(toSuperView: view) + NSLayoutConstraint.constraintPinSubview(toSuperview: view) view.addSubview(headlineLabel) view.addSubview(messageLabel) @@ -118,6 +118,24 @@ open class HeadlineBody: ViewConstrainingView { spaceBetweenLabels?.constant = 0 } } + + // MARK:- ModelMoleculeViewProtocol + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 58 + } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + + super.setWithModel(model, delegateObject, additionalData) + guard let headlineBodyModel = model as? HeadlineBodyModel else { + return + } + + style(with: headlineBodyModel.style) + + headlineLabel.setWithModel(headlineBodyModel.headline, delegateObject, additionalData) + messageLabel.setWithModel(headlineBodyModel.body, delegateObject, additionalData) + } // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { @@ -127,6 +145,7 @@ open class HeadlineBody: ViewConstrainingView { let headlineJSON = json?.optionalDictionaryForKey("headline") headlineLabel.setWithJSON(headlineJSON, delegateObject: delegateObject, additionalData: additionalData) + let bodyJSON = json?.optionalDictionaryForKey("body") messageLabel.setWithJSON(bodyJSON, delegateObject: delegateObject, additionalData: additionalData) } @@ -136,7 +155,7 @@ open class HeadlineBody: ViewConstrainingView { stylePageHeader() } - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return 58 } } diff --git a/MVMCoreUI/Molecules/HeadlineBodyButton.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift similarity index 75% rename from MVMCoreUI/Molecules/HeadlineBodyButton.swift rename to MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift index 87efc9b6..a0f22f34 100644 --- a/MVMCoreUI/Molecules/HeadlineBodyButton.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift @@ -9,7 +9,7 @@ import UIKit -@objcMembers open class HeadlineBodyButton: ViewConstrainingView { +@objcMembers open class HeadlineBodyButton: View { //------------------------------------------------------ // MARK: - Outlets //------------------------------------------------------ @@ -33,8 +33,8 @@ import UIKit // MARK: - Initialization //------------------------------------------------------ - public init() { - super.init(frame: .zero) + public convenience init() { + self.init(frame: .zero) } public override init(frame: CGRect) { @@ -107,29 +107,16 @@ import UIKit defaultState() } - open override func setAsMolecule() { - super.setAsMolecule() - - headlineBody.setAsMolecule() - button.setAsMolecule() - defaultState() + // MARK:- ModelMoleculeViewProtocol + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 320 } - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - - guard let dictionary = json else { return } - - if let padding = dictionary.optionalCGFloatForKey("buttonHeadlinePadding") { - buttonHeadlinePadding = padding - } - - headlineBody.setWithJSON(dictionary.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) - - if let buttonDictionary = dictionary.optionalDictionaryForKey("button") { - button.setWithJSON(buttonDictionary, delegateObject: delegateObject, additionalData: additionalData) - } else { - button.isHidden = true - } + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? HeadlineBodyButtonModel else { return } + buttonHeadlinePadding = model.buttonHeadlinePadding + headlineBody.setWithModel(model.headlineBody, delegateObject, additionalData) + button.setWithModel(model.button, delegateObject, additionalData) } } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift new file mode 100644 index 00000000..74e85ea9 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift @@ -0,0 +1,18 @@ +// +// HeadlineBodyButtonModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public struct HeadlineBodyButtonModel: MoleculeModelProtocol { + public static var identifier: String = "headlineBodyButton" + public var backgroundColor: Color? + + public var headlineBody: HeadlineBodyModel + public var button: ButtonModel + public var buttonHeadlinePadding: CGFloat +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift new file mode 100644 index 00000000..eb8d7c60 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift @@ -0,0 +1,71 @@ +// +// headlineBodyCaretLinkImageModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/14/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProtocol { + public static var identifier: String = "headlineBodyCaretLinkImage" + public var backgroundColor: Color? + public var caretLink: CaretLinkModel? + public var headlineBody: HeadlineBodyModel + public var image: ImageViewModel + + init(headlineBody: HeadlineBodyModel, image: ImageViewModel) { + self.headlineBody = headlineBody + self.image = image + super.init() + setDefaults() + } + + /// Defaults to set + func setDefaults() { + if useHorizontalMargins == nil { + useHorizontalMargins = true + } + if useVerticalMargins == nil { + useVerticalMargins = true + } + if topMarginPadding == nil { + topMarginPadding = PaddingDefault + } + if bottomMarginPadding == nil { + bottomMarginPadding = PaddingDefault + } + if image.height == nil { + image.height = HeadLineBodyCaretLinkImage.heightConstant + } + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case headlineBody + case image + case caretLink + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + headlineBody = try typeContainer.decode(HeadlineBodyModel.self, forKey: .headlineBody) + 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 { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(HeadlineBodyCaretLinkImageModel.identifier, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(headlineBody, forKey: .headlineBody) + try container.encode(image, forKey: .image) + try container.encodeIfPresent(caretLink, forKey: .caretLink) + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyLinkModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyLinkModel.swift new file mode 100644 index 00000000..9eae8e03 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyLinkModel.swift @@ -0,0 +1,22 @@ +// +// HeadlineBodyLinkModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public struct HeadlineBodyLinkModel: MoleculeModelProtocol { + public static var identifier: String = "headlineBodyLink" + public var headlineBody: HeadlineBodyModel + public var link: LinkModel + public var backgroundColor: Color? + + public init(headlineBody: HeadlineBodyModel, link: LinkModel) { + self.headlineBody = headlineBody + self.link = link + } +} + diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift new file mode 100644 index 00000000..62199654 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -0,0 +1,23 @@ +// +// HeadlineBody.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + +import Foundation + +@objcMembers public class HeadlineBodyModel: MoleculeModelProtocol { + public static var identifier: String = "headlineBody" + public var moleculeName: String? + public var headline: LabelModel? + public var body: LabelModel? + public var style: String? + public var backgroundColor: Color? + + public init(headline: LabelModel) { + self.headline = headline + } +} + diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift index 7c24cff4..55c3e795 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift @@ -8,10 +8,10 @@ import UIKit -@objcMembers public class HeadlineBodyTextButton: ViewConstrainingView { +@objcMembers public class HeadlineBodyTextButton: View { let headlineBody = HeadlineBody(frame: .zero) - let textButton = MFTextButton(nil, constrainHeight: true, forWidth: MVMCoreUIUtility.getWidth()) + let link = Link() var spaceBetweenConstant: CGFloat = 0.0 var spaceBetween: NSLayoutConstraint? @@ -19,7 +19,7 @@ import UIKit open override func updateView(_ size: CGFloat) { super.updateView(size) headlineBody.updateView(size) - textButton.updateView(size) + link.updateView(size) setSpacing() } @@ -28,34 +28,30 @@ import UIKit guard subviews.count == 0 else { return } - let view = MVMCoreUICommonViewsUtility.commonView() - addSubview(view) - pinView(toSuperView: view) - - view.addSubview(headlineBody) - view.addSubview(textButton) + addSubview(headlineBody) + addSubview(link) headlineBody.styleListItem() - headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true - headlineBody.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) + headlineBody.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true + headlineBody.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + var constraint = rightAnchor.constraint(equalTo: headlineBody.rightAnchor) constraint.priority = .defaultHigh constraint.isActive = true - spaceBetween = textButton.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) + spaceBetween = link.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) spaceBetween?.isActive = true - textButton.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - view.bottomAnchor.constraint(equalTo: textButton.bottomAnchor).isActive = true - view.rightAnchor.constraint(greaterThanOrEqualTo: textButton.rightAnchor).isActive = true - constraint = view.rightAnchor.constraint(equalTo: textButton.rightAnchor) + link.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + bottomAnchor.constraint(equalTo: link.bottomAnchor).isActive = true + rightAnchor.constraint(greaterThanOrEqualTo: link.rightAnchor).isActive = true + constraint = rightAnchor.constraint(equalTo: link.rightAnchor) constraint.priority = .defaultHigh constraint.isActive = true } // MARK: - Constraining public func setSpacing() { - if headlineBody.hasText() && (textButton.titleLabel?.text?.count ?? 0) > 0 { + if headlineBody.hasText() && (link.titleLabel?.text?.count ?? 0) > 0 { spaceBetween?.constant = spaceBetweenConstant } else { spaceBetween?.constant = 0 @@ -63,20 +59,22 @@ import UIKit } // MARK: - MVMCoreUIMoleculeViewProtocol - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) - textButton.setWithJSON(json?.optionalDictionaryForKey("textButton"), delegateObject: delegateObject, additionalData: additionalData) - } - open override func reset() { super.reset() headlineBody.reset() headlineBody.styleListItem() - textButton.reset() + link.reset() } - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + // MARK:- ModelMoleculeViewProtocol + open override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? HeadlineBodyLinkModel else { return } + headlineBody.setWithModel(model.headlineBody, delegateObject, additionalData) + link.setWithModel(model.link, delegateObject, additionalData) + } + + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 60 } } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedList.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedList.swift new file mode 100644 index 00000000..8f977ed7 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedList.swift @@ -0,0 +1,14 @@ +// +// NumberedList.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 03/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class NumberedList: StringAndMoleculeStack { +} + + diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift new file mode 100644 index 00000000..795eec7d --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/NumberedListModel.swift @@ -0,0 +1,48 @@ +// +// NumberedListModel.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 10/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class NumberedListModel: MoleculeStackModel { + public override class var identifier: String { + return "numberedList" + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case list + } + + // Numbered list model comes in the from of list = [MoleculeModelProtocol] + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + + let list = try typeContainer.decodeMolecules(codingKey: .list) + var models: [MoleculeStackItemModel] = [] + for (index, molecule) in list.enumerated() { + models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: "\(index+1).", molecule: molecule))) + } + super.init(molecules: models) + spacing = 0 + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(moleculeName, forKey: .moleculeName) + + var models: [MoleculeModelProtocol] = [] + for molecule in molecules { + models.append(molecule.molecule) + } + try container.encodeModels(models, forKey: .list) + } +} + + diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift new file mode 100644 index 00000000..caa00021 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeModel.swift @@ -0,0 +1,42 @@ +// +// StringAndMoleculeModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/17/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class StringAndMoleculeModel: MoleculeModelProtocol { + public static var identifier: String = "stringAndMoleculeModel" + public var backgroundColor: Color? + public var string: String + public var molecule: MoleculeModelProtocol + + public init(string: String, molecule: MoleculeModelProtocol) { + self.string = string + self.molecule = molecule + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case string + case molecule + } + + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + string = try typeContainer.decode(String.self, forKey: .string) + molecule = try typeContainer.decodeMolecule(codingKey: .molecule) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(string, forKey: .string) + try container.encodeModel(molecule, forKey: .molecule) + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift new file mode 100644 index 00000000..ac6613ea --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift @@ -0,0 +1,28 @@ +// +// ListMoleculeContainer.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 03/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +// This class is only temporarily necessary. Eventually we will have initWithModel instad of just init for moleculeviews, which will remove this need. +open class StringAndMoleculeStack: MoleculeStackView { + override func createStackItemsFromModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let model = stackModel else { return } + for stackItemModel in model.molecules { + guard let stringAndMoleculeModel = stackItemModel.molecule as? StringAndMoleculeModel, + let moleculeName = stringAndMoleculeModel.molecule.moleculeName, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forName: moleculeName) as? (UIView & ModelMoleculeViewProtocol) else { + // Throw error + return + } + let view = StringAndMoleculeView(string: stringAndMoleculeModel.string, molecule: molecule) + let stackItem = MoleculeStackItem(andContain: view) + stackItem.setWithModel(stackItemModel, delegateObject, nil) + stackItems.append(stackItem) + } + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift new file mode 100644 index 00000000..ffd23b0a --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift @@ -0,0 +1,92 @@ +// +// StringAndMoleculeView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/17/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class StringAndMoleculeView: View { + var label = Label.commonLabelB2(true) + var string: String + var molecule: UIView & ModelMoleculeViewProtocol + + var leftWidthConstraint: NSLayoutConstraint? + @Percent var percentage: CGFloat = 5 + + var constraintBetweenViews: NSLayoutConstraint? + var spaceBetweenViews: CGFloat = 0 { + didSet { + if spaceBetweenViews != oldValue { + constraintBetweenViews?.constant = spaceBetweenViews + setNeedsDisplay() + } + } + } + + // MARK: - Inits + public init(string: String, molecule: UIView & ModelMoleculeViewProtocol) { + self.string = string + self.molecule = molecule + super.init(frame: .zero) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func setupView() { + super.setupView() + guard subviews.count == 0 else { + return + } + + translatesAutoresizingMaskIntoConstraints = false + addSubview(label) + addSubview(molecule) + + NSLayoutConstraint.constraintPinSubview(label, pinTop: true, pinBottom: false, pinLeft: true, pinRight: false) + bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true + + let lowBottomConstraint = bottomAnchor.constraint(equalTo: label.bottomAnchor) + lowBottomConstraint.priority = UILayoutPriority(rawValue: 200) + lowBottomConstraint.isActive = true + + NSLayoutConstraint.constraintPinSubview(molecule, pinTop: true, pinBottom: true, pinLeft: false, pinRight: true) + constraintBetweenViews = molecule.leftAnchor.constraint(equalTo: label.rightAnchor, constant: spaceBetweenViews) + constraintBetweenViews?.priority = .required + constraintBetweenViews?.isActive = true + + setContentHuggingPriority(.defaultHigh, for: .vertical) + setContentHuggingPriority(.defaultHigh, for: .horizontal) + updateLeftViewWidthConstraint(percentage) + } + + override open func updateView(_ size: CGFloat) { + super.updateView(size) + (molecule as? MVMCoreViewProtocol)?.updateView(size) + label.updateView(size) + } + + override open func reset() { + super.reset() + label.reset() + (molecule as? MoleculeViewProtocol)?.reset?() + } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? StringAndMoleculeModel else { return } + label.text = model.string + molecule.setWithModel(model.molecule, delegateObject, additionalData) + } + + func updateLeftViewWidthConstraint(_ percent: CGFloat) { + percentage = percent + leftWidthConstraint?.isActive = false + leftWidthConstraint = label.widthAnchor.constraint(equalTo: widthAnchor, multiplier: CGFloat(percent/100), constant: 0) + leftWidthConstraint?.isActive = true + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedList.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedList.swift new file mode 100644 index 00000000..b393866c --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedList.swift @@ -0,0 +1,12 @@ +// +// UnOrderedList.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 03/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class UnOrderedList: StringAndMoleculeStack { +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift new file mode 100644 index 00000000..87d57927 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/Lists/UnOrderedListModel.swift @@ -0,0 +1,52 @@ +// +// UnOrderedListModel.swift +// MVMCoreUI +// +// Created by Murugan, Vimal on 10/01/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class UnOrderedListModel: MoleculeStackModel { + public override class var identifier: String { + return "unOrderedList" + } + public var bulletChar = "•" + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case list + case bulletChar + } + + // Numbered list model comes in the from of list = [MoleculeModelProtocol] + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let bulletChar = try typeContainer.decodeIfPresent(String.self, forKey: .bulletChar) { + self.bulletChar = bulletChar + } + + let list = try typeContainer.decodeMolecules(codingKey: .list) + var models: [MoleculeStackItemModel] = [] + for molecule in list { + models.append(MoleculeStackItemModel(with: StringAndMoleculeModel(string: bulletChar, molecule: molecule))) + } + super.init(molecules: models) + spacing = 0 + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(bulletChar, forKey: .bulletChar) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(moleculeName, forKey: .moleculeName) + + var models: [MoleculeModelProtocol] = [] + for molecule in molecules { + models.append(molecule.molecule) + } + try container.encodeModels(models, forKey: .list) + } +} diff --git a/MVMCoreUI/Organisms/Carousel.swift b/MVMCoreUI/Organisms/Carousel.swift index d0efcce3..7262e122 100644 --- a/MVMCoreUI/Organisms/Carousel.swift +++ b/MVMCoreUI/Organisms/Carousel.swift @@ -8,7 +8,8 @@ import UIKit -open class Carousel: ViewConstrainingView { +open class Carousel: View { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) /// The current index of the collection view. Includes dummy cells when looping. @@ -28,13 +29,13 @@ open class Carousel: ViewConstrainingView { var numberOfPages = 0 /// The json for the molecules. - var molecules: [[AnyHashable: Any]]? + var molecules: [MoleculeModelProtocol]? /// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%. var itemAlignment = UICollectionView.ScrollPosition.left /// From 0-1. The item width as a percent of the carousel width. - var itemWidthPercent: CGFloat = 1 + var itemWidthPercent: Float = 1 /// The height of the carousel. Default is 300. var collectionViewHeight: NSLayoutConstraint? @@ -46,6 +47,9 @@ open class Carousel: ViewConstrainingView { var loop = false private var dragging = false + // For adding pager + private var bottomPin: NSLayoutConstraint? + // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() @@ -59,7 +63,7 @@ open class Carousel: ViewConstrainingView { collectionView.backgroundColor = .clear collectionView.isAccessibilityElement = false addSubview(collectionView) - pinView(toSuperView: collectionView) + bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) collectionViewHeight?.isActive = false @@ -79,50 +83,50 @@ open class Carousel: ViewConstrainingView { } // MARK: - MVMCoreUIMoleculeViewProtocol - open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let carouselModel = model as? CarouselModel else { return } collectionView.backgroundColor = backgroundColor collectionView.layer.borderColor = backgroundColor?.cgColor - collectionView.layer.borderWidth = (json?.boolForKey("border") ?? false) ? 1 : 0 + collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0 backgroundColor = .white - registerCells(with: json, delegateObject: delegateObject) - setupLayout(with: json) - prepareMolecules(with: json) - itemWidthPercent = (json?.optionalCGFloatForKey("itemWidthPercent") ?? 100) / 100 - setAlignment(with: json?.optionalStringForKey("itemAlignment")) - if let height = json?.optionalCGFloatForKey("height") { - collectionViewHeight?.constant = height + + registerCells(with: carouselModel, delegateObject: delegateObject) + setupLayout(with: carouselModel) + prepareMolecules(with: carouselModel) + itemWidthPercent = (carouselModel.itemWidthPercent ?? 100) / 100 + setAlignment(with: carouselModel.itemAlignment) + + if let height = carouselModel.height { + collectionViewHeight?.constant = CGFloat(height) collectionViewHeight?.isActive = true } - setupPagingMolecule(json: json?.optionalDictionaryForKey("pagingMolecule"), delegateObject: delegateObject) + + setupPagingMolecule(carouselModel.pagingMolecule, delegateObject: delegateObject) collectionView.reloadData() } - open override func shouldSetHorizontalMargins(_ shouldSet: Bool) { - super.shouldSetHorizontalMargins(shouldSet) - updateViewHorizontalDefaults = false - } - // MARK: - JSON Setters /// Updates the layout being used - func setupLayout(with json:[AnyHashable: Any]?) { + + func setupLayout(with carouselModel: CarouselModel?) { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal - layout.minimumLineSpacing = json?["spacing"] as? CGFloat ?? 1 + layout.minimumLineSpacing = CGFloat(carouselModel?.spacing ?? 1) layout.minimumInteritemSpacing = 0 collectionView.collectionViewLayout = layout } - - func prepareMolecules(with json: [AnyHashable: Any]?) { - guard let newMolecules = json?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] else { + + func prepareMolecules(with carouselModel: CarouselModel?) { + guard let newMolecules = carouselModel?.molecules else { numberOfPages = 0 molecules = nil return } - + numberOfPages = newMolecules.count molecules = newMolecules - if json?.boolForKey("loop") ?? false && newMolecules.count > 2 { + if carouselModel?.loop ?? false && newMolecules.count > 2 { // Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell). loop = true molecules?.insert(newMolecules.last!, at: 0) @@ -132,36 +136,33 @@ open class Carousel: ViewConstrainingView { } pageIndex = 0 } - + + /// Sets up the paging molecule + open func setupPagingMolecule(_ molecule: CarouselPagingModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) { + var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil + if let molecule = molecule { + pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(molecule, delegateObject, false) as? (UIView & MVMCoreUIPagingProtocol) + } + addPaging(view: pagingView, position: (CGFloat(molecule?.position ?? 20))) + } + /// Registers the cells with the collection view - func registerCells(with json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) { - if let molecules = json?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] { - for molecule in molecules { - if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) { - collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier) - } + func registerCells(with carouselModel: CarouselModel, delegateObject: MVMCoreUIDelegateObject?) { + for molecule in carouselModel.molecules { + if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) { + collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier) } } } - - /// Sets up the paging molecule - open func setupPagingMolecule(json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) { - var pagingView: (UIView & MVMCoreUIPagingProtocol)? = nil - if let json = json { - pagingView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: json, delegateObject: delegateObject, constrainIfNeeded: true) as? (UIView & MVMCoreUIPagingProtocol) - } - addPaging(view: pagingView, position: (json?.optionalCGFloatForKey("position") ?? 20)) - } // MARK: - Convenience /// Returns the (identifier, class) of the molecule for the given map. - func getMoleculeInfo(with molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: [AnyHashable: Any])? { - guard let molecule = molecule, - let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule), - let moleculeName = moleculeClass.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) else { - return nil + func getMoleculeInfo(with molecule: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject?) -> (identifier: String, class: AnyClass, molecule: MoleculeModelProtocol)? { + guard let className = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(molecule) , + let moleculeName = (className as? ModelMoleculeViewProtocol.Type)?.nameForReuse(molecule, delegateObject) ?? molecule.moleculeName else { + return nil } - return (moleculeName, moleculeClass, molecule) + return (moleculeName, className, molecule) } /// Sets the alignment from the string. @@ -252,7 +253,7 @@ open class Carousel: ViewConstrainingView { extension Carousel: UICollectionViewDelegateFlowLayout { public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let itemWidth = collectionView.bounds.width * itemWidthPercent + let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) return CGSize(width: itemWidth, height: collectionView.bounds.height) } @@ -272,9 +273,9 @@ extension Carousel: UICollectionViewDataSource { return UICollectionViewCell() } let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath) - if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol { + if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol & ModelMoleculeViewProtocol { protocolCell.reset?() - protocolCell.setWithJSON(moleculeInfo.molecule, delegateObject: nil, additionalData: nil) + protocolCell.setWithModel(moleculeInfo.molecule, nil, nil) protocolCell.updateView(collectionView.bounds.width) } setAccessiblity(cell, index: indexPath.row) @@ -322,7 +323,7 @@ extension Carousel: UIScrollViewDelegate { } // Checks if the user is not paging but attempting to drag endlessly and goes out of bounds. Caps the index. if let separatorWidth = (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing { - let itemWidth = collectionView.bounds.width * itemWidthPercent + let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) let index = scrollView.contentOffset.x / (itemWidth + separatorWidth) let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 if index < 1 { @@ -359,7 +360,7 @@ extension Carousel: UIScrollViewDelegate { } // We switch cards if we pass the velocity threshold or position threshold (currently 50%). - let itemWidth = collectionView.bounds.width * itemWidthPercent + let itemWidth = collectionView.bounds.width * CGFloat(itemWidthPercent) var cellToSwipeTo = Int(scrollView.contentOffset.x/(itemWidth + separatorWidth) + 0.5) let lastCellIndex = collectionView(collectionView, numberOfItemsInSection: 0) - 1 let velocityThreshold: CGFloat = 1.1 diff --git a/MVMCoreUI/Organisms/CarouselModel.swift b/MVMCoreUI/Organisms/CarouselModel.swift new file mode 100644 index 00000000..e43e6caa --- /dev/null +++ b/MVMCoreUI/Organisms/CarouselModel.swift @@ -0,0 +1,67 @@ +// +// CarouselModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/25/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class CarouselModel: MoleculeModelProtocol { + public static var identifier: String = "carousel" + public var backgroundColor: Color? + public var molecules: [CarouselItemModel] + + public var spacing: Float? + public var border: Bool? + public var loop: Bool? + public var height: Float? + public var itemWidthPercent: Float? + public var itemAlignment: String? + public var pagingMolecule: CarouselPagingModelProtocol? + + public init(molecules: [CarouselItemModel]){ + self.molecules = molecules + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case molecules + case spacing + case border + case loop + case height + case itemWidthPercent + case itemAlignment + case pagingMolecule + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + self.molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) + self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + self.spacing = try typeContainer.decode(Float.self, forKey: .spacing) + self.border = try typeContainer.decode(Bool.self, forKey: .border) + self.loop = try typeContainer.decode(Bool.self, forKey: .loop) + self.height = try typeContainer.decode(Float.self, forKey: .height) + self.itemWidthPercent = try typeContainer.decode(Float.self, forKey: .itemWidthPercent) + self.itemAlignment = try typeContainer.decode(String.self, forKey: .itemAlignment) + self.pagingMolecule = try typeContainer.decodeMoleculeIfPresent(codingKey: .pagingMolecule) as? CarouselPagingModelProtocol + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(molecules, forKey: .molecules) + try container.encode(spacing, forKey: .spacing) + try container.encode(border, forKey: .border) + try container.encode(loop, forKey: .loop) + try container.encode(height, forKey: .height) + try container.encode(itemWidthPercent, forKey: .itemWidthPercent) + try container.encode(itemAlignment, forKey: .itemAlignment) + try container.encodeModelIfPresent(pagingMolecule, forKey: .pagingMolecule) + } +} diff --git a/MVMCoreUI/Organisms/MoleculeStackModel.swift b/MVMCoreUI/Organisms/MoleculeStackModel.swift new file mode 100644 index 00000000..134cc701 --- /dev/null +++ b/MVMCoreUI/Organisms/MoleculeStackModel.swift @@ -0,0 +1,54 @@ +// +// MoleculeStack.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// +// A stack that has a list molecule stack items. + +import Foundation + +@objcMembers public class MoleculeStackModel: ContainerModel, MoleculeModelProtocol, StackModelProtocol { + public class var identifier: String { + return "stack" + } + public var backgroundColor: Color? + public var molecules: [MoleculeStackItemModel] + public var axis: NSLayoutConstraint.Axis = .vertical + public var spacing: CGFloat = 16.0 + public var useStackSpacingBeforeFirstItem = false + + public init(molecules: [MoleculeStackItemModel]) { + self.molecules = molecules + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case molecules + case axis + case spacing + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + molecules = try typeContainer.decode([MoleculeStackItemModel].self, forKey: .molecules) + if let axisString = try typeContainer.decodeIfPresent(String.self, forKey: .axis), let optionalAxis = NSLayoutConstraint.Axis(rawValue: axisString) { + axis = optionalAxis + } + if let spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) { + self.spacing = spacing + } + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(molecules, forKey: .molecules) + try container.encodeIfPresent(axis.rawValueString, forKey: .axis) + try container.encodeIfPresent(spacing, forKey: .spacing) + } +} diff --git a/MVMCoreUI/Organisms/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift index 45e002be..d6f934a7 100644 --- a/MVMCoreUI/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -8,273 +8,37 @@ import UIKit -open class MoleculeStackView: Container { - var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() - var items: [StackItemModel] = [] - var useStackSpacingBeforeFirstItem = false - - var moleculesShouldSetHorizontalMargins = false - var moleculesShouldSetVerticalMargins = false - - /// For setting the direction of the stack - var axis: NSLayoutConstraint.Axis = .vertical { - didSet { - if axis != oldValue { - restack() - } - } +open class MoleculeStackView: Stack { + override var stackModel: MoleculeStackModel? { + get { return model as? MoleculeStackModel } } - /// The spacing to use between each item in the stack. - var spacing: CGFloat = 16 { - didSet { - if spacing != oldValue { - restack() - } + /// Convenience function, adds a molecule to a MoleculeStackItem to the MoleculeStack + func setup(with views: [View], lastItem: Bool) { + var models: [MoleculeStackItemModel] = [] + for view in views { + guard let model = view.model else { return } + let stackItemModel = MoleculeStackItemModel(with: model) + let stackItem = MoleculeStackItem(andContain: view) + stackItems.append(stackItem) + models.append(stackItemModel) } - } - - // MARK: - Helpers - public func setAxisWithJSON(_ json: [AnyHashable: Any]?) { - switch json?.optionalStringForKey("axis") { - case "horizontal": - axis = .horizontal - default: - axis = .vertical - } - } - - public func pinView(_ view: UIView, toView: UIView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, priority: UILayoutPriority, constant: CGFloat) { - let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: relation, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant) - constraint.priority = priority - constraint.isActive = true - } - - /// Restacks the existing items. - func restack() { - setWithStackItems(items) - } - - /// Removes all stack items views from the view. - func removeAllItemViews() { - for item in items { - item.view.removeFromSuperview() - } - } - - // MARK: - Inits - public override init(frame: CGRect) { - super.init(frame: frame) - } - - public init(withJSON json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.init(frame: CGRect.zero) - setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - } - - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - MFViewProtocol - public override func setupView() { - super.setupView() - guard contentView.superview == nil else { - return - } - MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) - translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .clear - addSubview(contentView) - containerHelper.constrainView(contentView) - contentView.setContentHuggingPriority(.defaultHigh, for: .vertical) - contentView.setContentHuggingPriority(.defaultHigh, for: .horizontal) - } - - public override func updateView(_ size: CGFloat) { - super.updateView(size) - directionalLayoutMargins.leading = 0 - directionalLayoutMargins.trailing = 0 - for item in items { - item.view.updateView(size) - } - } - - // MARK: - MVMCoreUIMoleculeViewProtocol - public override func reset() { - super.reset() - backgroundColor = .clear - for item in items { - item.view.reset() - } - } - - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - let previousJSON = self.json - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - removeAllItemViews() - - // If the items in the stack are the same, just update previous items instead of re-allocating. - var items: [StackItemModel]? - if MoleculeStackView.name(forReuse: previousJSON, delegateObject: delegateObject) == MoleculeStackView.name(forReuse: json, delegateObject: delegateObject) { - items = self.items - } - self.items = [] - - guard let molecules = json?.arrayForKey(KeyMolecules) as? [[String: Any]] else { - return - } - - // Sets the stack attributes - setAxisWithJSON(json) - spacing = json?.optionalCGFloatForKey("spacing") ?? 16 - - // Adds the molecules and sets the json. - for (index, map) in molecules.enumerated() { - var view: UIView? - var stackItemModel: StackItemModel - if let item = items?[index] { - stackItemModel = item - item.update(with: map) - view = item.view - (view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON(map, delegateObject: delegateObject, additionalData: nil) - addStackItem(item, lastItem: index == molecules.count - 1) - } else { - let stackItem = StackItem() - stackItem.setWithJSON(map, delegateObject: delegateObject, additionalData: additionalData) - view = stackItem - stackItemModel = StackItemModel(with: stackItem, json: map) - addStackItem(stackItemModel, lastItem: index == molecules.count - 1) - } - - stackItemModel.useHorizontalMargins = moleculesShouldSetHorizontalMargins - stackItemModel.useVerticalMargins = moleculesShouldSetVerticalMargins - } - } - - public class func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { - // This will aggregate names of molecules to make an id. - guard let molecules = molecule?.optionalArrayForKey(KeyMolecules) else { - return "stack<>" - } - var name = "stack<" - for case let item as [AnyHashable: Any] in molecules { - if let molecule = item.optionalDictionaryForKey(KeyMolecule), let moleculeName = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) { - name.append(moleculeName + ",") - } - } - name.append(">") - return name - } - - public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - guard let items = json?.optionalArrayForKey(KeyMolecules) else { - return 0 - } - let horizontal = json?.optionalStringForKey("axis") == "horizontal" - var estimatedHeight: CGFloat = 0 - for case let item as [AnyHashable: AnyHashable] in items { - if let molecule = item.optionalDictionaryForKey(KeyMolecule) { - let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.estimatedHeight?(forRow: molecule, delegateObject: delegateObject) - if !horizontal { - // Vertical stack aggregates the items - let spacing = item.optionalCGFloatForKey("spacing") ?? (estimatedHeight != 0 ? (json?.optionalCGFloatForKey("spacing") ?? 16) : 0) - estimatedHeight += ((height ?? 0) + spacing) - } else if let height = height { - // Horizontal stack takes the tallest item. - estimatedHeight = max(estimatedHeight, height) - } - } - } - return estimatedHeight - } - - public class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { - guard let items = json?.optionalArrayForKey(KeyMolecules) else { - return nil - } - var modules: [String] = [] - for case let item as [AnyHashable: AnyHashable] in items { - if let molecule = item.optionalDictionaryForKey(KeyMolecule), let modulesForMolecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.requiredModules?(molecule, delegateObject: delegateObject, error: error) { - modules += modulesForMolecule - } - } - return modules.count > 0 ? modules : nil - } - - // MARK: - Adding to stack - /// Adds the view to the stack. - func addView(_ view: UIView, lastItem: Bool) { - addStackItem(StackItemModel(with: StackItem(andContain: view)), lastItem: lastItem) - } - - /// Adds the stack item to the stack. - func addStackItem(_ stackItem: StackItemModel, lastItem: Bool) { - guard !stackItem.gone else { - items.append(stackItem) - return - } - let view = stackItem.view - contentView.addSubview(view) - view.translatesAutoresizingMaskIntoConstraints = false - - let spacing = stackItem.spacing ?? self.spacing - let verticalAlignment = stackItem.verticalAlignment ?? (stackItem.percentage == nil && axis == .vertical ? .fill : (axis == .vertical ? .leading : .center)) - let horizontalAlignment = stackItem.horizontalAlignment ?? (view.view as? MVMCoreUIViewConstrainingProtocol)?.horizontalAlignment?() ?? (axis == .vertical || stackItem.percentage == nil ? .fill : .leading) - view.containerHelper.alignHorizontal(horizontalAlignment) - view.containerHelper.alignVertical(verticalAlignment) - - let first = items.first { !$0.gone } == nil - if axis == .vertical { - if first { - pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : stackItem.spacing ?? 0) - } else if let previousView = items.last(where: { stackItem in - return !stackItem.gone - })?.view { - view.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: spacing).isActive = true - } - pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) - pinView(contentView, toView: view, attribute: .trailing, relation: .equal, priority: .required, constant: 0) - if let percent = stackItem.percentage { - view.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: CGFloat(percent)/100.0).isActive = true - } - if lastItem { - pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) - } + if let stackModel = stackModel { + stackModel.molecules = models } else { - if first { - // First horizontal item has no spacing by default unless told otherwise. - pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : stackItem.spacing ?? 0) - } else if let previousView = items.last(where: { stackItem in - return !stackItem.gone - })?.view { - view.leftAnchor.constraint(equalTo: previousView.rightAnchor, constant: spacing).isActive = true - } - pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) - pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) - if let percent = stackItem.percentage { - view.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: CGFloat(percent)/100.0).isActive = true - } + model = MoleculeStackModel(molecules: models) } - if lastItem { - pinView(contentView, toView: view, attribute: .right, relation: .equal, priority: .required, constant: 0) - } - items.append(stackItem) + restack() } - - func setWithStackItems(_ items: [StackItemModel]) { - removeAllItemViews() - self.items.removeAll() - var previousPresentItem: StackItemModel? = nil - for item in items { - if !item.gone { - previousPresentItem = item + + // MARK: - Adding to stack + /// Creates all of the stackItems for the stackItemModels + override func createStackItemsFromModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let stackItemModels = stackModel?.molecules else { return } + for model in stackItemModels { + if let stackItem = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(model, delegateObject) as? MoleculeStackItem { + stackItems.append(stackItem) } - addStackItem(item, lastItem: false) - } - if let lastView = previousPresentItem?.view { - let attribute: NSLayoutConstraint.Attribute = axis == .vertical ? .bottom : .right - pinView(contentView, toView: lastView, attribute: attribute, relation: .equal, priority: .required, constant: 0) } } } diff --git a/MVMCoreUI/Organisms/Stack.swift b/MVMCoreUI/Organisms/Stack.swift new file mode 100644 index 00000000..2ab77421 --- /dev/null +++ b/MVMCoreUI/Organisms/Stack.swift @@ -0,0 +1,251 @@ +// +// Stack.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class Stack: Container where T: StackModelProtocol { + var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() + var stackModel: T? { + get { return model as? T } + } + var stackItems: [UIView] = [] + + // MARK: - Helpers + public func pinView(_ view: UIView, toView: UIView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, priority: UILayoutPriority, constant: CGFloat) { + let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: relation, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant) + constraint.priority = priority + constraint.isActive = true + } + + /// Restacks the existing items. + func restack() { + removeAllItemViews() + guard let stackModel = stackModel else { return } + let stackItems = self.stackItems + self.stackItems = [] + let lastItemIndex = stackModel.molecules.lastIndex(where: { (item) -> Bool in + return !item.gone + }) + + // Adds the views + for (index, view) in stackItems.enumerated() { + addView(view, stackModel.molecules[index], percentModifier: getPercentModifier(), lastItem: lastItemIndex == index) + } + } + + /// Removes all stack items views from the view. + func removeAllItemViews() { + for item in stackItems { + item.removeFromSuperview() + } + } + + // MARK: - Inits + public override init(frame: CGRect) { + super.init(frame: frame) + } + + public init(withJSON json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.init(frame: CGRect.zero) + setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - MFViewProtocol + public override func setupView() { + super.setupView() + guard contentView.superview == nil else { + return + } + MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .clear + addSubview(contentView) + containerHelper.constrainView(contentView) + contentView.setContentHuggingPriority(.defaultHigh, for: .vertical) + contentView.setContentHuggingPriority(.defaultHigh, for: .horizontal) + } + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + for item in stackItems { + (item as? MVMCoreViewProtocol)?.updateView(size) + } + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + public override func reset() { + super.reset() + backgroundColor = .clear + for item in stackItems { + (item as? MoleculeViewProtocol)?.reset?() + } + } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + let previousModel = self.model + super.setWithModel(model, delegateObject, additionalData) + removeAllItemViews() + + // If the items in the stack are different, clear them, create new ones. + if (previousModel == nil) || Self.nameForReuse(previousModel, delegateObject) != Self.nameForReuse(model, delegateObject) { + stackItems = [] + createStackItemsFromModel(model, delegateObject, additionalData) + } else { + setStackItemsFromModel(model, delegateObject, additionalData) + } + + restack() + } + + public override class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + // This will aggregate names of molecules to make an id. + guard let model = model as? T else { + return "stack<>" + } + var name = "stack<" + for case let item in model.molecules { + if let moleculeName = item.moleculeName { + if let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping[moleculeName] as? ModelMoleculeViewProtocol.Type, + let nameForReuse = moleculeClass.nameForReuse(item, delegateObject) { + name.append(nameForReuse + ",") + } else { + name.append(moleculeName + ",") + } + } + } + name.append(">") + return name + } + + // Need to update to take into account first spacing flag + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + guard let model = molecule as? T else { return 0 } + let horizontal = model.axis == .horizontal + var estimatedHeight: CGFloat = 0 + for case let item in model.molecules { + if item.gone { continue } + let height = (MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(item) as? ModelMoleculeViewProtocol.Type)?.estimatedHeight(forRow: item, delegateObject: delegateObject) ?? 0 + if !horizontal { + // Vertical stack aggregates the items + let spacing = item.spacing ?? model.spacing + estimatedHeight += (height + spacing) + } else { + // Horizontal stack takes the tallest item. + estimatedHeight = max(estimatedHeight, height) + } + } + return estimatedHeight + } + + public override class func requiredModules(_ molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + guard let model = molecule as? T else { return nil } + var modules: [String] = [] + for case let item in model.molecules { + if let modulesForMolecule = (MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(item) as? ModelMoleculeViewProtocol.Type)?.requiredModules(item, delegateObject: delegateObject, error: error) { + modules += modulesForMolecule + } + } + return modules.count > 0 ? modules : nil + } + + // MARK: - Subclassables + + /// Can be subclassed to create views when we get stack item models and have no views yet + func createStackItemsFromModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + } + + /// Can be subclassed to set stack items with model when we already have views + func setStackItemsFromModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + guard let models = stackModel?.molecules else { return } + for (index, element) in models.enumerated() { + (stackItems[index] as? ModelMoleculeViewProtocol)?.setWithModel(element, delegateObject, additionalData) + } + } + + // MARK: - Adding to stack + /// Gets the percent modifier. This value is used to help properly calculate percent for stack items when spacing is involved. + private func getPercentModifier() -> CGFloat { + guard let stackModel = stackModel else { return 0.0 } + var totalSpace: CGFloat = 0.0 + var totalViews = 0 + var firstMoleculeFound = false + for stackItemModel in stackModel.molecules { + guard !stackItemModel.gone else { continue } + totalViews += 1 + let spacing = stackItemModel.spacing ?? stackModel.spacing + if firstMoleculeFound { + totalSpace += spacing + } else { + firstMoleculeFound = true + totalSpace += (stackModel.useStackSpacingBeforeFirstItem ? spacing : stackItemModel.spacing ?? 0) + } + } + return (totalViews > 0 ? -(totalSpace / CGFloat(totalViews)) : 0) + } + + /// Adds the stack item view + private func addView(_ view: UIView,_ model: StackItemModelProtocol, percentModifier: CGFloat, lastItem: Bool) { + guard let stackModel = self.stackModel else { return } + guard !model.gone else { + // Gone views do not show + stackItems.append(view) + return + } + contentView.addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false + + let spacing = model.spacing ?? stackModel.spacing + if let container = view as? ContainerProtocol { + let verticalAlignment = (model as? ContainerModelProtocol)?.verticalAlignment ?? (container.view as? MVMCoreUIViewConstrainingProtocol)?.verticalAlignment?() ?? (model.percent == nil && stackModel.axis == .vertical ? .fill : (stackModel.axis == .vertical ? .leading : .center)) + let horizontalAlignment = (model as? ContainerModelProtocol)?.horizontalAlignment ?? (container.view as? MVMCoreUIViewConstrainingProtocol)?.horizontalAlignment?() ?? (stackModel.axis == .vertical || model.percent == nil ? .fill : .leading) + container.alignHorizontal(horizontalAlignment) + container.alignVertical(verticalAlignment) + } + + let first = contentView.subviews.count == 1 + if stackModel.axis == .vertical { + if first { + pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: stackModel.useStackSpacingBeforeFirstItem ? spacing : model.spacing ?? 0) + } else if let previousView = stackItems.last(where: { item in + return !model.gone + }) { + view.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: spacing).isActive = true + } + pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) + pinView(contentView, toView: view, attribute: .trailing, relation: .equal, priority: .required, constant: 0) + if let percent = model.percent { + view.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: CGFloat(percent)/100.0, constant: percentModifier).isActive = true + } + if lastItem { + pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) + } + } else { + if first { + // First horizontal item has no spacing by default unless told otherwise. + pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: stackModel.useStackSpacingBeforeFirstItem ? spacing : model.spacing ?? 0) + } else if let previousView = stackItems.last(where: { item in + return !model.gone + }) { + view.leftAnchor.constraint(equalTo: previousView.rightAnchor, constant: spacing).isActive = true + } + pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) + pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) + if let percent = model.percent { + view.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: CGFloat(percent)/100.0, constant: percentModifier).isActive = true + } + if lastItem { + pinView(contentView, toView: view, attribute: .right, relation: .equal, priority: .required, constant: 0) + } + } + stackItems.append(view) + } +} diff --git a/MVMCoreUI/Organisms/StackModel.swift b/MVMCoreUI/Organisms/StackModel.swift new file mode 100644 index 00000000..741946c5 --- /dev/null +++ b/MVMCoreUI/Organisms/StackModel.swift @@ -0,0 +1,49 @@ +// +// StackModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class StackModel: StackModelProtocol, MoleculeModelProtocol { + public static var identifier: String = "simpleStack" + public var backgroundColor: Color? + public var molecules: [StackItemModel] + public var axis: NSLayoutConstraint.Axis = .vertical + public var spacing: CGFloat = 16.0 + public var useStackSpacingBeforeFirstItem = false + + public init(molecules: [StackItemModel]) { + self.molecules = molecules + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case molecules + case axis + case spacing + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + + molecules = try typeContainer.decode([StackItemModel].self, forKey: .molecules) + if let axisString = try typeContainer.decodeIfPresent(String.self, forKey: .axis), let optionalAxis = NSLayoutConstraint.Axis(rawValue: axisString) { + axis = optionalAxis + } + if let spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) { + self.spacing = spacing + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(molecules, forKey: .molecules) + try container.encodeIfPresent(axis.rawValueString, forKey: .axis) + try container.encodeIfPresent(spacing, forKey: .spacing) + } +} diff --git a/MVMCoreUI/Organisms/StackModelProtocol.swift b/MVMCoreUI/Organisms/StackModelProtocol.swift new file mode 100644 index 00000000..b7385466 --- /dev/null +++ b/MVMCoreUI/Organisms/StackModelProtocol.swift @@ -0,0 +1,18 @@ +// +// StackModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/16/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol StackModelProtocol { + associatedtype AnyStackItemModel: StackItemModelProtocol + + var molecules: [AnyStackItemModel] { get set } + var axis: NSLayoutConstraint.Axis { get set } + var spacing: CGFloat { get set } + var useStackSpacingBeforeFirstItem: Bool { get set } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index af0f5023..d60cdd6a 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -10,7 +10,7 @@ import UIKit @objcMembers open class CoreUIObject: MVMCoreObject { public var moleculeMap: MVMCoreUIMoleculeMappingObject? - + open override func defaultInitialSetup() { cache = MVMCoreCache() sessionHandler = MVMCoreSessionTimeHandler() @@ -19,5 +19,6 @@ import UIKit viewControllerMapping = MVMCoreUIViewControllerMappingObject() loggingDelegate = MVMCoreUILoggingHandler() moleculeMap = MVMCoreUIMoleculeMappingObject() + MoleculeObjectMapping.registerObjects() } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIDelegateObject.swift b/MVMCoreUI/OtherHandlers/MVMCoreUIDelegateObject.swift index c0c8211f..f64162db 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIDelegateObject.swift +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIDelegateObject.swift @@ -12,8 +12,8 @@ open class MVMCoreUIDelegateObject: DelegateObject { public weak var formValidationProtocol: FormValidationProtocol? public weak var buttonDelegate: ButtonDelegateProtocol? public weak var uiTextFieldDelegate: UITextFieldDelegate? - public weak var moleculeDelegate: MoleculeDelegateProtocol? - + public var moleculeDelegate: MoleculeDelegateProtocol? + open override func setAll(withDelegate delegate: Any) { super.setAll(withDelegate: delegate) formValidationProtocol = delegate as? FormValidationProtocol @@ -26,3 +26,4 @@ open class MVMCoreUIDelegateObject: DelegateObject { return controller?.delegateObject?() as? MVMCoreUIDelegateObject } } + diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject+ModelExtension.swift b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject+ModelExtension.swift new file mode 100644 index 00000000..4796b2a2 --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject+ModelExtension.swift @@ -0,0 +1,53 @@ +// +// MVMCoreUIMoleculeMappingObject+ModelExtension.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/24/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public extension MVMCoreUIMoleculeMappingObject { + + func register(viewClass: V.Type, viewModelClass: M.Type) { + ModelRegistry.register(viewModelClass) + MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(viewClass, forKey: viewModelClass.identifier as NSString) + } + + func getMoleculeClass(_ model: MoleculeModelProtocol) -> AnyClass? { + if let moleculeName = model.moleculeName { + return moleculeMapping.object(forKey: moleculeName) as? AnyClass + } + return nil + } + + func createMolecule(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> (UIView & MVMCoreUIMoleculeViewProtocol)? { + return createMolecule(model, delegateObject, false) + } + + func createMolecule(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ constrainIfNeeded: Bool) -> (UIView & MVMCoreUIMoleculeViewProtocol)? { + guard let moleculeName = model.moleculeName, + let molecule = createMolecule(forName: moleculeName) else { + return nil + } + + let setData = {() in + if let molecule = molecule as? ModelMoleculeViewProtocol { + molecule.setWithModel(model, delegateObject, nil) + } else { + molecule.setWithJSON?(model.toJSON(), delegateObject: delegateObject, additionalData: nil) + } + } + + if constrainIfNeeded, let castMolecule = molecule as? MVMCoreUIViewConstrainingProtocol, + castMolecule.needsToBeConstrained?() ?? false { + let view = ViewConstrainingView(molecule: molecule, alignment: castMolecule.horizontalAlignment?() ?? .fill) + setData() + return view + } else { + setData() + return molecule + } + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h index 7d85a2ea..776566c8 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h @@ -25,6 +25,9 @@ #pragma mark - Molecule Creation +/// Creates the molecule for the molecule name. +- (nullable UIView *)createMoleculeForName:(nonnull NSString *)name; + /// Creates the molecule for the molecule json. - (nullable UIView *)createMoleculeForJSON:(nonnull NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject; diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 6140ed18..ccaa9115 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -16,6 +16,7 @@ #import "MVMCoreUIPageControl.h" #import "MVMCoreUIViewConstrainingProtocol.h" + @implementation MVMCoreUIMoleculeMappingObject - (NSMutableDictionary *)moleculeMapping { @@ -24,60 +25,11 @@ static dispatch_once_t onceToken; static NSMutableDictionary *mapping; dispatch_once(&onceToken, ^{ - mapping = [@{ - @"label": Label.class, - @"line": Line.class, - @"button": PrimaryButton.class, - @"textButton": MFTextButton.class, - @"header": StandardHeaderView.class, - @"moleculeStack": MoleculeStackView.class, - @"twoButtonView": TwoButtonView.class, - @"footer": StandardFooterView.class, - @"caretView": CaretView.class, - @"caretButton": CaretButton.class, - @"textField": MFTextField.class, - @"dropDown": DropDown.class, - @"digitTextField": MFDigitTextField.class, - @"digitEntryField": DigitEntryField.class, - @"textEntryField": TextEntryField.class, - @"itemDropdownEntryField": ItemDropdownEntryField.class, - @"dateDropdownEntryField": DateDropdownEntryField.class, - @"checkbox": Checkbox.class, - @"checkboxWithLabel": CheckboxWithLabelView.class, - @"cornerLabels" : CornerLabels.class, - @"progressbar": ProgressBar.class, - @"circleProgress": GraphView.class, - @"multiProgressBar": MultiProgress.class, - @"radioButton": RadioButton.class, - @"radioButtonLabel": RadioButtonLabel.class, - @"listItem": MoleculeTableViewCell.class, - @"accordionListItem": AccordionMoleculeTableViewCell.class, - @"toggle": Toggle.class, - @"leftRightLabelView": LeftRightLabelView.class, - @"actionDetailWithImage": ActionDetailWithImage.class, - @"image": MFLoadImageView.class, - @"moduleMolecule": ModuleMolecule.class, - @"headlineBody": HeadlineBody.class, - @"carousel": Carousel.class, - @"carouselItem": MoleculeCollectionViewCell.class, - @"barsPager": MVMCoreUIPageControl.class, - @"scroller": Scroller.class, - @"imageHeadlineBody": ImageHeadlineBody.class, - @"labelSwitch": LabelSwitch.class, - @"headlineBodySwitch": HeadlineBodySwitch.class, - @"headlineBodyTextButton": HeadlineBodyTextButton.class, - @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class, - @"tabsListItem": TabsTableViewCell.class, - @"dropDownListItem": DropDownFilterTableViewCell.class, - @"headlineBodyButton": HeadlineBodyButton.class, - @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class, - @"headLineBodyCaretLinkImage" : HeadLineBodyCaretLinkImage.class - } mutableCopy]; + mapping = [@{} mutableCopy]; }); return mapping; } - + (nullable instancetype)sharedMappingObject { return [MVMCoreActionUtility initializerClassCheck:[CoreUIObject sharedInstance].moleculeMap classToVerify:self]; } @@ -118,7 +70,7 @@ // Check if we need to constrain this view. UIView *castMolecule = [molecule conformsToProtocol:@protocol(MVMCoreUIViewConstrainingProtocol)] ? (UIView *)molecule : nil; if (constrainIfNeeded && [castMolecule respondsToSelector:@selector(needsToBeConstrained)] && [castMolecule needsToBeConstrained]) { - molecule = [[ViewConstrainingView alloc] initWithMolecule:molecule alignment:[castMolecule respondsToSelector:@selector(alignment)] ? [castMolecule alignment] : UIStackViewAlignmentFill]; + molecule = [[ViewConstrainingView alloc] initWithMolecule:molecule alignment:[castMolecule respondsToSelector:@selector(horizontalAlignment)] ? [castMolecule horizontalAlignment] : UIStackViewAlignmentFill]; } [molecule setWithJSON:json delegateObject:delegateObject additionalData:nil]; return molecule; diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h b/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h index a2ac2340..12aaacdb 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h @@ -15,9 +15,6 @@ /// Notifies the creator that the view may not be suitable to be shown on its own and it needs to be added to a view to help with layout. - (BOOL)needsToBeConstrained; -/// The alignment if constrained. -- (UIStackViewAlignment)alignment __deprecated; - /// The alignment if constrained. - (UIStackViewAlignment)horizontalAlignment; diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m index 2bc1a0dd..7bdccc8e 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m @@ -19,9 +19,9 @@ static NSMutableDictionary *viewControllerMapping; dispatch_once(&onceToken, ^{ viewControllerMapping = [@{ - @"moleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackTemplate class]], + @"stack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackTemplate class]], @"centerMoleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackCenteredTemplate class]], - @"moleculeList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]], + @"list" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]], @"threeLayer" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ThreeLayerTemplate class]] } mutableCopy]; diff --git a/MVMCoreUI/OtherHandlers/ModelMoleculeDelegateProtocol.swift b/MVMCoreUI/OtherHandlers/ModelMoleculeDelegateProtocol.swift new file mode 100644 index 00000000..8fe1caa5 --- /dev/null +++ b/MVMCoreUI/OtherHandlers/ModelMoleculeDelegateProtocol.swift @@ -0,0 +1,50 @@ +// +// MoleculeDelegateProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol MoleculeDelegateProtocol { + + /// returns a module for the corresponding module name. + func getModuleWithName(_ name: String?) -> [AnyHashable : Any]? + func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? + + /// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. + func moleculeLayoutUpdated(_ molecule: UIView & MVMCoreUIMoleculeViewProtocol) //optional + + /// Asks the delegate to add or remove molecules. + //optional + func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) + func removeMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) + + //optional + func addMolecules(_ molecules: [ListItemModelProtocol], sender: UITableViewCell, animation: UITableView.RowAnimation) + func removeMolecules(_ molecules: [ListItemModelProtocol], sender: UITableViewCell, animation: UITableView.RowAnimation) +} + +extension MoleculeDelegateProtocol { + public func moleculeLayoutUpdated(_ molecule: UIView & MVMCoreUIMoleculeViewProtocol) { + // Do Nothing + } + + public func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // Do nothing + } + + public func removeMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // Do nothing + } + + public func addMolecules(_ molecules: [ListItemModelProtocol], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // Do nothing + } + + public func removeMolecules(_ molecules: [ListItemModelProtocol], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // Do nothing + } +} diff --git a/MVMCoreUI/OtherHandlers/ModuleDelegateProtocol.swift b/MVMCoreUI/OtherHandlers/ModuleDelegateProtocol.swift new file mode 100644 index 00000000..70c271ce --- /dev/null +++ b/MVMCoreUI/OtherHandlers/ModuleDelegateProtocol.swift @@ -0,0 +1,14 @@ +// +// ModuleDelegateProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 12/6/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + + +public protocol ModuleDelegateProtocol { + func getModuleWithName(_ moleculeName: String) -> Model? +} diff --git a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift new file mode 100644 index 00000000..ae62a765 --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift @@ -0,0 +1,109 @@ +// +// MVMCoreUIMoleculeMappingObject+ModelMapping.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 10/28/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class MoleculeObjectMapping: NSObject { + public static func registerObjects() { + // Stacks + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: MoleculeStackView.self, viewModelClass: MoleculeStackModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Stack.self, viewModelClass: StackModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: UnOrderedList.self, viewModelClass: UnOrderedListModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: NumberedList.self, viewModelClass: NumberedListModel.self) + + // Label + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Label.self, viewModelClass: LabelModel.self) + //need to move labelattributemodel to different method + ModelRegistry.register(LabelAttributeFontModel.self) + ModelRegistry.register(LabelAttributeColorModel.self) + //ModelRegistry.register(LabelAttributeImageModel.self) // We need to separate the registry by types due to collisions... + ModelRegistry.register(LabelAttributeUnderlineModel.self) + ModelRegistry.register(LabelAttributeStrikeThroughModel.self) + ModelRegistry.register(LabelAttributeActionModel.self) + + // Buttons + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: PrimaryButton.self, viewModelClass: ButtonModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: TwoButtonView.self, viewModelClass: TwoButtonViewModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Link.self, viewModelClass: LinkModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CaretButton.self, viewModelClass: CaretLinkModel.self) + + // Entry Field + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: TextEntryField.self, viewModelClass: TextEntryFieldModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: MdnEntryField.self, viewModelClass: MdnEntryFieldModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: DigitEntryField.self, viewModelClass: DigitEntryFieldModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: ItemDropdownEntryField.self, viewModelClass: ItemDropdownEntryFieldModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: DateDropdownEntryField.self, viewModelClass: DateDropdownEntryFieldModel.self) + + // Other Atoms + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Label.self, viewModelClass: LabelModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: ProgressBar.self, viewModelClass: ProgressBarModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: MultiProgress.self, viewModelClass: MultiProgressBarModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CaretView.self, viewModelClass: CaretViewModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: DashLine.self, viewModelClass: DashLineModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: MFLoadImageView.self, viewModelClass: ImageViewModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Line.self, viewModelClass: LineModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: GraphView.self, viewModelClass: CircleProgressModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Toggle.self, viewModelClass: ToggleModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Checkbox.self, viewModelClass: CheckboxModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CheckboxWithLabelView.self, viewModelClass: CheckboxLabelModel.self) + + // Horizontal Combination Molecules + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: StringAndMoleculeView.self, viewModelClass: StringAndMoleculeModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: ImageHeadlineBody.self, viewModelClass: ImageHeadlineBodyModel.self) + + // Vertical Combination Molecules + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: HeadlineBody.self, viewModelClass: HeadlineBodyModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: HeadLineBodyCaretLinkImage.self, viewModelClass: HeadlineBodyCaretLinkImageModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: EyebrowHeadlineBodyLink.self, viewModelClass: EyebrowHeadlineBodyLinkModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: HeadlineBodyTextButton.self, viewModelClass: HeadlineBodyLinkModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: HeadlineBodyButton.self, viewModelClass: HeadlineBodyButtonModel.self) + + // Left Right Molecules + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CornerLabels.self, viewModelClass: CornerLabelsModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: LeftRightLabelView.self, viewModelClass: LeftRightLabelModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: LabelSwitch.self, viewModelClass: LabelToggleModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: HeadlineBodySwitch.self, viewModelClass: HeadlineBodyToggleModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: HeadlineBodyTextButtonSwitch.self, viewModelClass: HeadlineBodyLinkToggleModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: ActionDetailWithImage.self, viewModelClass: ActionDetailWithImageModel.self) + + // List items + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: MoleculeTableViewCell.self, viewModelClass: ListItemModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: DropDownFilterTableViewCell.self, viewModelClass: DropDownListItemModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: AccordionMoleculeTableViewCell.self, viewModelClass: AccordionListItemModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: TabsTableViewCell.self, viewModelClass: TabsListItemModel.self) + + // Other Items + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: MoleculeStackItem.self, viewModelClass: MoleculeStackItemModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: StackItem.self, viewModelClass: StackItemModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: MoleculeCollectionViewCell.self, viewModelClass: CarouselItemModel.self) + + // Other Container Molecules + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: HeaderView.self, viewModelClass: HeaderModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: FooterView.self, viewModelClass: FooterModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Scroller.self, viewModelClass: ScrollerModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: ModuleMolecule.self, viewModelClass: ModuleMoleculeModel.self) + + // Other Molecules + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: DoughnutChartView.self, viewModelClass: DoughnutChartModel.self) + + // Other Organisms + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Carousel.self, viewModelClass: CarouselModel.self) + + // TODO: Need model + MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(DigitEntryField.self, forKey: "digitTextField" as NSString) + MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(DateDropdownEntryField.self, forKey: "dateDropdownEntryField" as NSString) + MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(Checkbox.self, forKey: "checkbox" as NSString) + MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(CheckboxWithLabelView.self, forKey: "checkboxLabel" as NSString) + MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(RadioButton.self, forKey: "radioButton" as NSString) + MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(RadioButtonLabel.self, forKey: "radioButtonLabel" as NSString) + MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping.setObject(MVMCoreUIPageControl.self, forKey: "barsPager" as NSString) + + // TODO: Need View + ModelRegistry.register(TabsModel.self) + } +} diff --git a/MVMCoreUI/Templates/ListPageTemplateModel.swift b/MVMCoreUI/Templates/ListPageTemplateModel.swift new file mode 100644 index 00000000..fe9c12c5 --- /dev/null +++ b/MVMCoreUI/Templates/ListPageTemplateModel.swift @@ -0,0 +1,63 @@ +// +// ListPageTemplate.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/22/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class ListPageTemplateModel: TemplateModelProtocol { + + public static var identifier: String = "list" + + public var pageType: String + public var screenHeading: String? + public var isAtomicTabs: Bool? + + public var header: MoleculeModelProtocol? + public var molecules: [ListItemModelProtocol]? + public var footer: MoleculeModelProtocol? + public var line: LineModel? + + public init(pageType: String, screenHeading: String?, molecules: [ListItemModelProtocol]) { + self.pageType = pageType + self.screenHeading = screenHeading + self.molecules = molecules + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case pageType + case screenHeading + case molecules + case header + case footer + case line + case isAtomicTabs + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + pageType = try typeContainer.decode(String.self, forKey: .pageType) + screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) + molecules = try typeContainer.decodeMoleculesIfPresent(codingKey: .molecules) as? [ListItemModelProtocol] + isAtomicTabs = try typeContainer.decodeIfPresent(Bool.self, forKey: .isAtomicTabs) + header = try typeContainer.decodeMoleculeIfPresent(codingKey: .header) + footer = try typeContainer.decodeMoleculeIfPresent(codingKey: .footer) + line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(pageType, forKey: .pageType) + try container.encodeIfPresent(screenHeading, forKey: .screenHeading) + try container.encodeModelsIfPresent(molecules, forKey: .molecules) + try container.encodeIfPresent(isAtomicTabs, forKey: .isAtomicTabs) + try container.encodeModelIfPresent(header, forKey: .header) + try container.encodeModelIfPresent(footer, forKey: .footer) + try container.encode(line, forKey: .line) + } +} + diff --git a/MVMCoreUI/Templates/MoleculeListCellProtocol.h b/MVMCoreUI/Templates/MoleculeListCellProtocol.h deleted file mode 100644 index 3fa19a02..00000000 --- a/MVMCoreUI/Templates/MoleculeListCellProtocol.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// MoleculeListCellProtocol.h -// MVMCoreUI -// -// Created by Scott Pfeil on 5/22/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// -#import -@class MVMCoreUIDelegateObject; - -@protocol MoleculeListCellProtocol -@optional - -/// Can set the separator according to what the moleculeList commands. -- (void)setSeparatorWithJSON:(nullable NSDictionary *)json delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject additionalData:(nullable NSDictionary *)additionalData indexPath:(nonnull NSIndexPath *)indexPath; - -/// Handle action -- (void)didSelectCellAtIndex:(nonnull NSIndexPath *)indexPath delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject additionalData:(nullable NSDictionary *)additionalData; - -- (void)willDisplay; - -@end diff --git a/MVMCoreUI/Templates/MoleculeListCellProtocol.swift b/MVMCoreUI/Templates/MoleculeListCellProtocol.swift new file mode 100644 index 00000000..5d9b9525 --- /dev/null +++ b/MVMCoreUI/Templates/MoleculeListCellProtocol.swift @@ -0,0 +1,32 @@ +// +// MoleculeListCellProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 1/10/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol MoleculeListCellProtocol { + /// Can set the separator according to what the moleculeList commands. + func setLines(with model: LineModel?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, indexPath: IndexPath) + + /// Handle action when cell is pressed + func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) + + /// Called by the list when the cell will display. + func willDisplay() +} + +// Default implementation does nothing +extension MoleculeListCellProtocol { + public func setLines(with model: LineModel?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, indexPath: IndexPath) { + } + + public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + } + + func willDisplay() { + } +} diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 2aed3c65..75ef7d2f 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -8,10 +8,16 @@ import UIKit -open class MoleculeListTemplate: ThreeLayerTableViewController { - public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])]? +open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol { + + public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: ListItemModelProtocol)]? var observer: NSKeyValueObservation? + public var templateModel: ListPageTemplateModel? + @objc public override func parsePageJSON() throws { + try parseTemplateJSON() + } + open override var loadObject: MVMCoreLoadObject? { didSet { if loadObject != oldValue { @@ -25,23 +31,41 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { } } } - + open override func viewForTop() -> UIView { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), - let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { - return super.viewForTop() + guard let headerModel = templateModel?.header, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(headerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { + return super.viewForTop() + } + + // Temporary, Default the horizontal padding + if var container = templateModel?.header as? ContainerModelProtocol, container.useHorizontalMargins == nil { + container.useHorizontalMargins = true } return molecule } override open func viewForBottom() -> UIView { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), - let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { - return super.viewForBottom() + guard let footerModel = templateModel?.footer, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(footerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { + return super.viewForBottom() } return molecule } + open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer) -> Bool { + guard super.shouldFinishProcessingLoad(loadObject, error: error) else { return false } + // This template requires atleast one of the three layers. + if templateModel?.header == nil, + templateModel?.molecules?.count ?? 0 == 0, + templateModel?.footer == nil, + let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), messageToLog: "List template requires atleast one of the following: header, footer, molecules", code: CoreUIErrorCode.ErrorCodeListMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) { + error.pointee = errorObject + return false + } + return true + } + open override func newDataBuildScreen() { super.newDataBuildScreen() setup() @@ -60,7 +84,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { open override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { guard let moleculeInfo = moleculesInfo?[indexPath.row], - let estimatedHeight = moleculeInfo.class.estimatedHeight?(forRow: moleculeInfo.molecule, delegateObject: delegateObject() as? MVMCoreUIDelegateObject) else { + let estimatedHeight = (moleculeInfo.class as? ModelMoleculeViewProtocol.Type)?.estimatedHeight(forRow: moleculeInfo.molecule, delegateObject: delegateObject() as? MVMCoreUIDelegateObject) else { return 0 } return estimatedHeight @@ -73,29 +97,29 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let moleculeInfo = moleculesInfo?[indexPath.row], let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.identifier) else { - return UITableViewCell() + return UITableViewCell() } let delegate = delegateObject() as? MVMCoreUIDelegateObject let moleculeCell = cell as? MVMCoreUIMoleculeViewProtocol moleculeCell?.reset?() if let protocolCell = cell as? MoleculeListCellProtocol { - protocolCell.setSeparatorWithJSON?(loadObject?.pageJSON?.optionalDictionaryForKey("separator"), delegateObject: delegate, additionalData: nil, indexPath: indexPath) + protocolCell.setLines(with: templateModel?.line, delegateObject: delegate, additionalData: nil, indexPath: indexPath) } - moleculeCell?.setWithJSON(moleculeInfo.molecule, delegateObject: delegate, additionalData: nil) - moleculeCell?.updateView(tableView.bounds.width) + (moleculeCell as? ModelMoleculeViewProtocol)?.setWithModel(moleculeInfo.molecule, delegate, nil) + moleculeCell?.updateView(tableView.bounds.width) return cell } open override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if let protocolCell = cell as? MoleculeListCellProtocol { - protocolCell.willDisplay?() + protocolCell.willDisplay() } } open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let cell = tableView.cellForRow(at: indexPath) as? MoleculeListCellProtocol { - cell.didSelectCell?(atIndex: indexPath, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil) + cell.didSelectCell(at: indexPath, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil) } } @@ -122,7 +146,59 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { } } - open override func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + public override func addMolecules(_ molecules: [[AnyHashable: Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + + var tmpMolecules = [ListItemModelProtocol]() + + molecules.forEach { molecule in + if let data = try? JSONSerialization.data(withJSONObject: molecule), let listItemModel = try? JSONDecoder().decode(ListItemModel.self, from: data) { + tmpMolecules.append(listItemModel) + } + } + + DispatchQueue.main.async { + guard let indexPath = self.tableView?.indexPath(for: sender) else { return } + var indexPaths: [IndexPath] = [] + for molecule in tmpMolecules { + if let info = self.getMoleculeInfo(with: molecule) { + self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + let index = indexPath.row + 1 + indexPaths.count + self.moleculesInfo?.insert(info, at: index) + indexPaths.append(IndexPath(row: index, section: 0)) + } + } + self.tableView?.insertRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() + } + } + + public override func removeMolecules(_ molecules: [[AnyHashable: Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + + var tmpMolecules = [ListItemModelProtocol]() + + molecules.forEach { molecule in + if let data = try? JSONSerialization.data(withJSONObject: molecule), let listItemModel = try? JSONDecoder().decode(ListItemModel.self, from: data) { + tmpMolecules.append(listItemModel) + } + } + + var indexPaths: [IndexPath] = [] + //TODO: cehck for molecule protocola eqality + for molecule in tmpMolecules { + if let removeIndex = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in + return molecule.toJSONString() == moleculeInfo.molecule.toJSONString() + }) { + moleculesInfo?.remove(at: removeIndex) + indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) + } + } + self.tableView?.deleteRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() + } + + public func addMolecules(_ molecules: [ListItemModelProtocol], sender: UITableViewCell, animation: UITableView.RowAnimation) { // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. DispatchQueue.main.async { guard let indexPath = self.tableView?.indexPath(for: sender) else { return } @@ -141,11 +217,12 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { } } - open override func removeMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + public func removeMolecules(_ molecules: [ListItemModelProtocol], sender: UITableViewCell, animation: UITableView.RowAnimation) { var indexPaths: [IndexPath] = [] + //TODO: cehck for molecule protocola eqality for molecule in molecules { if let removeIndex = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in - return NSDictionary(dictionary: molecule).isEqual(to: moleculeInfo.molecule) + return molecule.toJSONString() == moleculeInfo.molecule.toJSONString() }) { moleculesInfo?.remove(at: removeIndex) indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) @@ -158,19 +235,19 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { // MARK: - Convenience /// Returns the (identifier, class) of the molecule for the given map. - func getMoleculeInfo(with molecule: [AnyHashable: Any]?) -> (identifier: String, class: AnyClass, molecule: [AnyHashable: Any])? { - guard let molecule = molecule, - let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule), - let moleculeName = moleculeClass.name?(forReuse: molecule, delegateObject: delegateObject() as? MVMCoreUIDelegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) else { - return nil + func getMoleculeInfo(with listItem: ListItemModelProtocol?) -> (identifier: String, class: AnyClass, molecule: ListItemModelProtocol)? { + guard let listItem = listItem, + let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(listItem), + let moleculeName = (moleculeClass as? ModelMoleculeViewProtocol.Type)?.nameForReuse(listItem, delegateObject() as? MVMCoreUIDelegateObject) ?? listItem.moleculeName else { + return nil } - return (moleculeName, moleculeClass, molecule) + return (moleculeName, moleculeClass, listItem) } /// Sets up the molecule list and ensures no errors loading all content. - func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])]? { - var moleculeList: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])] = [] - if let molecules = loadObject?.pageJSON?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] { + func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: ListItemModelProtocol)]? { + var moleculeList: [(identifier: String, class: AnyClass, molecule: ListItemModelProtocol)] = [] + if let molecules = templateModel?.molecules { for molecule in molecules { if let info = getMoleculeInfo(with: molecule) { moleculeList.append(info) @@ -182,15 +259,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { /// Sets up the header, footer, molecule list and ensures no errors loading all content. func setup() { - var moleculeList: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])] = [] - if let molecules = loadObject?.pageJSON?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] { - for molecule in molecules { - if let info = getMoleculeInfo(with: molecule) { - moleculeList.append(info) - } - } - } - moleculesInfo = moleculeList + moleculesInfo = getMoleculeInfoList() } /// Adds modules from requiredModules() to the MVMCoreViewControllerMapping.requiredModules map. diff --git a/MVMCoreUI/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Templates/MoleculeStackTemplate.swift index b0881321..03d21c6b 100644 --- a/MVMCoreUI/Templates/MoleculeStackTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeStackTemplate.swift @@ -8,8 +8,13 @@ import UIKit -open class MoleculeStackTemplate: ThreeLayerViewController { +open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { + var observer: NSKeyValueObservation? + public var templateModel: StackPageTemplateModel? + public override func parsePageJSON() throws { + try parseTemplateJSON() + } open override var loadObject: MVMCoreLoadObject? { didSet { @@ -30,26 +35,30 @@ open class MoleculeStackTemplate: ThreeLayerViewController { } open override func viewForTop() -> UIView? { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { - return nil + guard let headerModel = templateModel?.header, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(headerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { + return nil } return molecule } open override func viewForMiddle() -> UIView? { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("moleculeStack") else { - return nil + + guard let moleculeStackModel = templateModel?.moleculeStack else { + return nil } + let stack = MoleculeStackView(frame: .zero) - stack.useStackSpacingBeforeFirstItem = true - stack.moleculesShouldSetHorizontalMargins = true - stack.setWithJSON(moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil) + moleculeStackModel.useStackSpacingBeforeFirstItem = true + moleculeStackModel.useHorizontalMargins = true + stack.setWithModel(moleculeStackModel, delegateObject() as? MVMCoreUIDelegateObject, nil) return stack } override open func viewForBottom() -> UIView? { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { - return nil + guard let footerModel = templateModel?.footer, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(footerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { + return nil } return molecule } @@ -79,7 +88,7 @@ open class MoleculeStackTemplate: ThreeLayerViewController { let delegate = delegateObject() as? MVMCoreUIDelegateObject MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate, moduleList: modules, errorList: nil) MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate, moduleList: modules, errorList: nil) - MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("moleculeStack"), delegateObject: delegate, moduleList: modules, errorList: nil) + MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("stack"), delegateObject: delegate, moduleList: modules, errorList: nil) return modules as? [Any] } } diff --git a/MVMCoreUI/Templates/StackCenteredPageTemplateModel.swift b/MVMCoreUI/Templates/StackCenteredPageTemplateModel.swift new file mode 100644 index 00000000..86c09b6d --- /dev/null +++ b/MVMCoreUI/Templates/StackCenteredPageTemplateModel.swift @@ -0,0 +1,17 @@ +// +// StackCenteredPageTemplate.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/22/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class StackCenteredPageTemplateModel: TemplateModelProtocol { + + public static var identifier: String = "stackCenterTemplate" + public var pageType: String + public var screenHeading: String? + public var isAtomicTabs: Bool? +} diff --git a/MVMCoreUI/Templates/StackPageTemplateModel.swift b/MVMCoreUI/Templates/StackPageTemplateModel.swift new file mode 100644 index 00000000..b9a54125 --- /dev/null +++ b/MVMCoreUI/Templates/StackPageTemplateModel.swift @@ -0,0 +1,56 @@ +// +// StackPageTemplate.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/22/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + + +@objcMembers public class StackPageTemplateModel: TemplateModelProtocol { + public static var identifier: String = "stack" + + public var pageType: String + public var screenHeading: String? + public var isAtomicTabs: Bool? + + public var header: MoleculeModelProtocol? + public var moleculeStack: MoleculeStackModel + public var footer: MoleculeModelProtocol? + + public init(pageType: String, moleculeStack: MoleculeStackModel) { + self.pageType = pageType + self.moleculeStack = moleculeStack + } + + private enum CodingKeys: String, CodingKey { + case pageType + case screenHeading + case header + case footer + case moleculeStack + case isAtomicTabs + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + pageType = try typeContainer.decode(String.self, forKey: .pageType) + moleculeStack = try typeContainer.decode(MoleculeStackModel.self, forKey: .moleculeStack) + screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) + isAtomicTabs = try typeContainer.decodeIfPresent(Bool.self, forKey: .isAtomicTabs) + header = try typeContainer.decodeMoleculeIfPresent(codingKey: .header) + footer = try typeContainer.decodeMoleculeIfPresent(codingKey: .footer) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(pageType, forKey: .pageType) + try container.encode(moleculeStack, forKey: .moleculeStack) + try container.encodeIfPresent(screenHeading, forKey: .screenHeading) + try container.encodeIfPresent(isAtomicTabs, forKey: .isAtomicTabs) + try container.encodeModelIfPresent(header, forKey: .header) + try container.encodeModelIfPresent(footer, forKey: .footer) + } +} diff --git a/MVMCoreUI/Templates/TemplateProtocol.swift b/MVMCoreUI/Templates/TemplateProtocol.swift new file mode 100644 index 00000000..0498ec5d --- /dev/null +++ b/MVMCoreUI/Templates/TemplateProtocol.swift @@ -0,0 +1,24 @@ +// +// ModelTemplateProtocol.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/25/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol TemplateProtocol: class { + associatedtype TemplateModel: TemplateModelProtocol + var templateModel: TemplateModel? { get set } +} + +public extension TemplateProtocol where Self: MFViewController { + func parseTemplateJSON() throws { + guard let pageJSON = self.loadObject?.pageJSON else { return } + let data = try JSONSerialization.data(withJSONObject: pageJSON) + let decoder = JSONDecoder() + let templateModel = try decoder.decode(TemplateModel.self, from: data) + self.templateModel = templateModel + } +} diff --git a/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift b/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift new file mode 100644 index 00000000..fa7548f3 --- /dev/null +++ b/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift @@ -0,0 +1,57 @@ +// +// ThreeLayerPageTemplateModel.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 11/22/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class ThreeLayerPageTemplateModel: TemplateModelProtocol { + public static var identifier: String = "threeLayer" + + public var pageType: String + public var screenHeading: String? + public var isAtomicTabs: Bool? + + public var header: MoleculeModelProtocol? + public var middle: MoleculeModelProtocol? + public var footer: MoleculeModelProtocol? + + public init(pageType: String, header: MoleculeModelProtocol?, middle: MoleculeModelProtocol?, footer: MoleculeModelProtocol?) { + self.pageType = pageType + self.header = header + self.middle = middle + self.footer = footer + } + + private enum CodingKeys: String, CodingKey { + case pageType + case screenHeading + case header + case footer + case middle + case isAtomicTabs + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + pageType = try typeContainer.decode(String.self, forKey: .pageType) + screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) + isAtomicTabs = try typeContainer.decodeIfPresent(Bool.self, forKey: .isAtomicTabs) + header = try typeContainer.decodeMoleculeIfPresent(codingKey: .header) + header = try typeContainer.decodeMoleculeIfPresent(codingKey: .middle) + footer = try typeContainer.decodeMoleculeIfPresent(codingKey: .footer) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(pageType, forKey: .pageType) + try container.encodeIfPresent(screenHeading, forKey: .screenHeading) + try container.encodeIfPresent(isAtomicTabs, forKey: .isAtomicTabs) + try container.encodeModelIfPresent(header, forKey: .header) + try container.encodeModelIfPresent(header, forKey: .middle) + try container.encodeModelIfPresent(footer, forKey: .footer) + } +} diff --git a/MVMCoreUI/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Templates/ThreeLayerTemplate.swift index 20475a6b..785d1055 100644 --- a/MVMCoreUI/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Templates/ThreeLayerTemplate.swift @@ -8,8 +8,13 @@ import UIKit -@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController { +@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController, TemplateProtocol { + public var templateModel: ThreeLayerPageTemplateModel? + @objc public override func parsePageJSON() throws { + try parseTemplateJSON() + } + override open func viewDidLoad() { super.viewDidLoad() bottomViewOutsideOfScroll = true @@ -20,24 +25,27 @@ import UIKit super.newDataBuildScreen() heightConstraint?.isActive = true } - + open override func viewForTop() -> UIView? { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { - return nil + guard let headerModel = templateModel?.header, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(headerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { + return nil } return molecule } open override func viewForMiddle() -> UIView? { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("middle"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { - return nil + guard let middleModel = templateModel?.middle, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(middleModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { + return nil } return molecule } override open func viewForBottom() -> UIView? { - guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, constrainIfNeeded: true) else { - return nil + guard let footerModel = templateModel?.footer, + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(footerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { + return nil } return molecule } diff --git a/MVMCoreUI/Utility/MVMCoreUIConstants.h b/MVMCoreUI/Utility/MVMCoreUIConstants.h index e5587745..4c0a3553 100644 --- a/MVMCoreUI/Utility/MVMCoreUIConstants.h +++ b/MVMCoreUI/Utility/MVMCoreUIConstants.h @@ -22,6 +22,7 @@ extern NSString * const KeyDisableButton; extern NSString * const KeyValue; extern NSString * const KeyLabel; extern NSString * const KeyDisable; +extern NSString * const KeyEnabled; extern NSString * const KeyFieldName; extern NSString * const KeyHideMainMenu; diff --git a/MVMCoreUI/Utility/MVMCoreUIConstants.m b/MVMCoreUI/Utility/MVMCoreUIConstants.m index 6211cfee..5f5a9a45 100644 --- a/MVMCoreUI/Utility/MVMCoreUIConstants.m +++ b/MVMCoreUI/Utility/MVMCoreUIConstants.m @@ -21,6 +21,7 @@ NSString * const KeyDisableButton = @"disableAction"; NSString * const KeyValue = @"value"; NSString * const KeyLabel = @"label"; NSString * const KeyDisable = @"disable"; +NSString * const KeyEnabled = @"enabled"; NSString * const KeyFieldName = @"fieldName"; NSString * const KeyFieldKey = @"fieldKey"; NSString * const KeyRequired = @"required"; diff --git a/MVMCoreUI/Utility/NSLayoutConstraintAxis+Extension.swift b/MVMCoreUI/Utility/NSLayoutConstraintAxis+Extension.swift new file mode 100644 index 00000000..9e82f9f9 --- /dev/null +++ b/MVMCoreUI/Utility/NSLayoutConstraintAxis+Extension.swift @@ -0,0 +1,77 @@ +// +// NSLayoutConstraintAxis+Extension.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/20/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +/** + When using this class in codable for a String value from server. + + Example use case.... + + var axis: NSLayoutConstraint.Axis + + enum CodingKeys: String, CodingKey { + case axis + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let word = try container.decode(String.self, forKey: .axis) + axis = NSLayoutConstraint.Axis(rawValue: axis)! + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(axis.rawValueString, forKey: .axis) + } + */ + +enum AxisError: Error { + case notAnAxis +} + +extension NSLayoutConstraint.Axis: RawRepresentable { + + init?(rawValue: String) { + switch rawValue { + case "horizontal": + self = .horizontal + case "vertical": + self = .vertical + default: + return nil + } + } + + var rawValueString: String { + switch self { + case .horizontal: + return "horizontal" + case .vertical: + return "vertical" + @unknown default: + return "" + } + } +} + +extension NSLayoutConstraint.Axis: Codable { + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.singleValueContainer() + let string = try typeContainer.decode(String.self) + guard let axis = NSLayoutConstraint.Axis(rawValue: string) else { + throw AxisError.notAnAxis + } + self = axis + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValueString) + } +} diff --git a/MVMCoreUI/Utility/UIStackViewAlignment+Extension.swift b/MVMCoreUI/Utility/UIStackViewAlignment+Extension.swift new file mode 100644 index 00000000..b2c4b82b --- /dev/null +++ b/MVMCoreUI/Utility/UIStackViewAlignment+Extension.swift @@ -0,0 +1,93 @@ +// +// UIStackViewAlignment+Extension.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 12/16/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +/** + When using this class in codable for a String value from server. + + Example use case.... + + var alignment: UIStackView.Alignment + + enum CodingKeys: String, CodingKey { + case alignment + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let word = try container.decode(String.self, forKey: .alignment) + alignment = UIStackView.Alignment(rawValue: word)! + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(alignment.rawValueString, forKey: .alignment) + } + */ + +enum AlignmentError: Error { + case notAnAlignment +} + +extension UIStackView.Alignment: RawRepresentable { + + init?(rawValue: String) { + switch rawValue { + case "fill": + self = .fill + case "leading": + self = .leading + case "firstBaseline": + self = .firstBaseline + case "center": + self = .center + case "trailing": + self = .trailing + case "lastBaseline": + self = .lastBaseline + default: + return nil + } + } + + var rawValueString: String { + switch self { + case .fill: + return "fill" + case .leading: + return "leading" + case .firstBaseline: + return "firstBaseline" + case .center: + return "center" + case .trailing: + return "trailing" + case .lastBaseline: + return "lastBaseline" + @unknown default: + return "" + } + } +} + +extension UIStackView.Alignment: Codable { + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.singleValueContainer() + let string = try typeContainer.decode(String.self) + guard let alignment = UIStackView.Alignment(rawValue: string) else { + throw AlignmentError.notAnAlignment + } + self = alignment + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(rawValueString) + } +}