diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 16458819..f606284e 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -10,21 +10,25 @@ 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 */; }; - 016A1071228122180009D605 /* SwitchLineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016A1070228122180009D605 /* SwitchLineItem.swift */; }; + 016A1071228122180009D605 /* HeadlineBodyTextButtonSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016A1070228122180009D605 /* HeadlineBodyTextButtonSwitch.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 */; }; - 01CA51B5229716F60071A6EE /* Switch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CA51B4229716F60071A6EE /* Switch.swift */; }; 01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */; }; 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; }; 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; - 0A804A3D2316CB79009A8656 /* HeadlineBodyPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A804A3C2316CB79009A8656 /* HeadlineBodyPrimaryButton.swift */; }; + 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; }; D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.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 */; }; + D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */; }; + D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */; }; D22D1F1A220341F60077CEC0 /* MVMCoreUICheckBox.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F18220341F50077CEC0 /* MVMCoreUICheckBox.h */; settings = {ATTRIBUTES = (Public, ); }; }; D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F19220341F50077CEC0 /* MVMCoreUICheckBox.m */; }; D22D1F1E220343560077CEC0 /* MVMCoreUICheckMarkView.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F1C220343560077CEC0 /* MVMCoreUICheckMarkView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -37,6 +41,7 @@ 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, ); }; }; D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; }; + D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.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 */; }; @@ -190,20 +195,25 @@ 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 = ""; }; - 016A1070228122180009D605 /* SwitchLineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchLineItem.swift; sourceTree = ""; }; + 016A1070228122180009D605 /* HeadlineBodyTextButtonSwitch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyTextButtonSwitch.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 = ""; }; - 01CA51B4229716F60071A6EE /* Switch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = ""; }; 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; 0A804A3C2316CB79009A8656 /* HeadlineBodyPrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyPrimaryButton.swift; sourceTree = ""; }; + 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUINavigationController.h; sourceTree = ""; }; D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUINavigationController.m; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.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 = ""; }; + D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyTextButton.swift; sourceTree = ""; }; + D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccordionMoleculeTableViewCell.swift; sourceTree = ""; }; D22D1F18220341F50077CEC0 /* MVMCoreUICheckBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUICheckBox.h; sourceTree = ""; }; D22D1F19220341F50077CEC0 /* MVMCoreUICheckBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUICheckBox.m; sourceTree = ""; }; D22D1F1C220343560077CEC0 /* MVMCoreUICheckMarkView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUICheckMarkView.h; sourceTree = ""; }; @@ -216,6 +226,7 @@ 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 = ""; }; D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = ""; }; + D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewCell.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 = ""; }; @@ -393,6 +404,63 @@ path = FormUIHelpers; sourceTree = ""; }; + D224798823142BF2003FCCF9 /* SwitchMolecules */ = { + isa = PBXGroup; + children = ( + D22479892314445E003FCCF9 /* LabelSwitch.swift */, + D224798B231450C8003FCCF9 /* HeadlineBodySwitch.swift */, + 016A1070228122180009D605 /* HeadlineBodyTextButtonSwitch.swift */, + ); + path = SwitchMolecules; + sourceTree = ""; + }; + D224798D2316A988003FCCF9 /* VerticalCombinationViews */ = { + isa = PBXGroup; + children = ( + D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, + D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */, + ); + path = VerticalCombinationViews; + sourceTree = ""; + }; + D224798E2316A995003FCCF9 /* HorizontalCombinationViews */ = { + isa = PBXGroup; + children = ( + D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */, + D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */, + ); + path = HorizontalCombinationViews; + sourceTree = ""; + }; + D224798F2316A99F003FCCF9 /* LeftRightViews */ = { + isa = PBXGroup; + children = ( + D224798823142BF2003FCCF9 /* SwitchMolecules */, + B8200E182281DC1A007245F4 /* CornerLabels.swift */, + ); + path = LeftRightViews; + sourceTree = ""; + }; + D22479902316A9CB003FCCF9 /* Organisms */ = { + isa = PBXGroup; + children = ( + D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */, + D2A6390022CBB1820052ED1F /* Carousel.swift */, + ); + path = Organisms; + sourceTree = ""; + }; + D22479912316A9EF003FCCF9 /* Items */ = { + isa = PBXGroup; + children = ( + D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, + D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */, + D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, + D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */, + ); + path = Items; + sourceTree = ""; + }; D22D1F582204D2590077CEC0 /* LegacyControllers */ = { isa = PBXGroup; children = ( @@ -438,6 +506,7 @@ D29DF11E21E6851E003B2FB9 /* TopAlert */, D29DF10D21E67A70003B2FB9 /* Atoms */, D29DF10E21E67A77003B2FB9 /* Molecules */, + D22479902316A9CB003FCCF9 /* Organisms */, D29DF0DF21E418B2003B2FB9 /* Templates */, D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */, D29DF0D021E404D4003B2FB9 /* Info.plist */, @@ -480,30 +549,19 @@ D29DF10E21E67A77003B2FB9 /* Molecules */ = { isa = PBXGroup; children = ( + D22479912316A9EF003FCCF9 /* Items */, + D224798F2316A99F003FCCF9 /* LeftRightViews */, + D224798E2316A995003FCCF9 /* HorizontalCombinationViews */, + D224798D2316A988003FCCF9 /* VerticalCombinationViews */, + D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */, 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */, D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */, D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, - D282AACA2243C61700C46919 /* ButtonView.swift */, - 016A1070228122180009D605 /* SwitchLineItem.swift */, - 01CA51B4229716F60071A6EE /* Switch.swift */, - D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */, D2A514662213885800345BFB /* StandardHeaderView.swift */, - D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, - D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */, D274CA322236A78900B01B62 /* StandardFooterView.swift */, - D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */, - B8200E142280C4CF007245F4 /* ProgressBar.swift */, - B8200E182281DC1A007245F4 /* CornerLabels.swift */, D29B770F22C281F400D6ACE0 /* ModuleMolecule.swift */, - D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, - D2A6390022CBB1820052ED1F /* Carousel.swift */, - D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, - D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, - D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, - D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */, - D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */, ); path = Molecules; sourceTree = ""; @@ -536,6 +594,7 @@ D29DF11321E6805F003B2FB9 /* UIColor+MFConvenience.m */, D29DF11221E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h */, D29DF11421E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m */, + D22479932316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift */, ); path = Categories; sourceTree = ""; @@ -615,6 +674,7 @@ D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */, D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */, D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */, + D282AACA2243C61700C46919 /* ButtonView.swift */, D29DF16D21E69E1F003B2FB9 /* MFTextButton.h */, D29DF17221E69E1F003B2FB9 /* MFTextButton.m */, ); @@ -624,6 +684,11 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, + D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, + D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, + B8200E142280C4CF007245F4 /* ProgressBar.swift */, + 948DB67D2326DCD90011F916 /* MultiProgress.swift */, DBC4391622442196001AB423 /* CaretView.swift */, DBC4391722442197001AB423 /* DashLine.swift */, DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */, @@ -940,12 +1005,15 @@ DBC4391922442197001AB423 /* DashLine.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, + D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */, + D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, + D224798C231450C8003FCCF9 /* HeadlineBodySwitch.swift in Sources */, D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, @@ -971,9 +1039,11 @@ D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, + D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */, D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, + D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, @@ -990,12 +1060,13 @@ D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */, D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */, D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */, - 016A1071228122180009D605 /* SwitchLineItem.swift in Sources */, + 016A1071228122180009D605 /* HeadlineBodyTextButtonSwitch.swift in Sources */, D29DF29521E7ADB8003B2FB9 /* ProgrammaticScrollViewController.m in Sources */, D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */, D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */, + D224798A2314445E003FCCF9 /* LabelSwitch.swift in Sources */, D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, @@ -1007,13 +1078,13 @@ D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, + 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, - 01CA51B5229716F60071A6EE /* Switch.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, diff --git a/MVMCoreUI/Molecules/ButtonView.swift b/MVMCoreUI/Atoms/Buttons/ButtonView.swift similarity index 100% rename from MVMCoreUI/Molecules/ButtonView.swift rename to MVMCoreUI/Atoms/Buttons/ButtonView.swift diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.h b/MVMCoreUI/Atoms/TextFields/MFTextField.h index 72adf682..6b047c6c 100644 --- a/MVMCoreUI/Atoms/TextFields/MFTextField.h +++ b/MVMCoreUI/Atoms/TextFields/MFTextField.h @@ -33,6 +33,7 @@ @property (nullable, weak, nonatomic) UIView *view; @property (nullable, weak, nonatomic) IBOutlet UIView *textFieldContainerView; +@property (nullable, weak, nonatomic) IBOutlet UIView *backgroundView; @property (nullable, weak, nonatomic) IBOutlet UITextField *textField; @property (nullable, weak, nonatomic) IBOutlet Label *formLabel; @property (nullable, weak, nonatomic) IBOutlet UIView *separatorView;//make it public so outsider class can know the posistion of it. diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.xib b/MVMCoreUI/Atoms/TextFields/MFTextField.xib index e839533f..cb9821f1 100644 --- a/MVMCoreUI/Atoms/TextFields/MFTextField.xib +++ b/MVMCoreUI/Atoms/TextFields/MFTextField.xib @@ -11,6 +11,7 @@ + diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index 06ddbed5..ed99fcac 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -9,14 +9,14 @@ import MVMCore -public typealias ActionBlock = () -> Void +public typealias ActionBlock = () -> () @objcMembers open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol { //------------------------------------------------------ - // MARK: - General Properties + // MARK: - Properties //------------------------------------------------------ - + public var makeWholeViewClickable = false /// Set this property if you want updateView to update the font based on this standard and the size passed in. @@ -39,19 +39,39 @@ public typealias ActionBlock = () -> Void //------------------------------------------------------ public var clauses: [ActionableClause] = [] { - didSet { isUserInteractionEnabled = !clauses.isEmpty } + didSet { + isUserInteractionEnabled = !clauses.isEmpty + if clauses.count > 1 { + clauses.sort { first, second in + guard let firstLocation = first.range?.location, + let secondLocation = second.range?.location + else { return false } + return firstLocation < secondLocation + } + } + } } /// Used for tappable links in the text. public struct ActionableClause { var range: NSRange? var actionBlock: ActionBlock? + var accessibilityID: Int = 0 func performAction() { actionBlock?() } } + //------------------------------------------------------ + // MARK: - Convenience Setter For objective-C + //------------------------------------------------------ + + /// Sets the clauses array to empty. + @objc public func setEmptyClauses() { + clauses = [] + } + //------------------------------------------------------ // MARK: - Initialization //------------------------------------------------------ @@ -62,6 +82,8 @@ public typealias ActionBlock = () -> Void numberOfLines = 0 lineBreakMode = .byWordWrapping translatesAutoresizingMaskIntoConstraints = false + clauses = [] + accessibilityCustomActions = [] let tapGesture = UITapGestureRecognizer(target: self, action: #selector(textLinkTapped(_:))) tapGesture.numberOfTapsRequired = 1 @@ -113,6 +135,7 @@ public typealias ActionBlock = () -> Void return label } + /// H32 -> Head @objc public static func commonLabelH32(_ scale: Bool) -> Label { let label = Label.label() label.styleH32(scale) @@ -140,18 +163,20 @@ public typealias ActionBlock = () -> Void return label } + /// B20 -> Body @objc public static func commonLabelB20(_ scale: Bool) -> Label { let label = Label.label() label.styleB20(scale) return label } + /// Default @objc open class func label() -> Label { return Label(frame: .zero) } //------------------------------------------------------ - // MARK: - Functions + // MARK: - Style //------------------------------------------------------ @objc public static func setLabel(_ label: UILabel?, withHTML html: String?) { @@ -189,6 +214,10 @@ public typealias ActionBlock = () -> Void } } + if let wholeViewIsClickable = json?.boolForKey("makeWholeViewClickable") { + (label as? Label)?.makeWholeViewClickable = wholeViewIsClickable + } + if let backgroundColorHex = json?.optionalStringForKey(KeyBackgroundColor), !backgroundColorHex.isEmpty { label.backgroundColor = UIColor.mfGet(forHex: backgroundColorHex) } @@ -229,11 +258,28 @@ public typealias ActionBlock = () -> Void case "strikethrough": attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) attributedString.addAttribute(.baselineOffset, value: 0, range: range) + case "color": if let colorHex = attribute.optionalStringForKey(KeyTextColor), !colorHex.isEmpty { attributedString.removeAttribute(.foregroundColor, range: range) attributedString.addAttribute(.foregroundColor, value: UIColor.mfGet(forHex: colorHex), range: range) } + case "image": + let fontSize = attribute["size"] as? CGFloat ?? label.font.pointSize + let imageName = attribute["name"] as? String ?? "externalLink" + let imageURL = attribute["URL"] as? String + let imageAttachment: NSTextAttachment + + if let url = imageURL, let label = label as? Label { + imageAttachment = Label.getTextAttachmentFrom(url: url, dimension: fontSize, label: label) + } else { + imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize) + } + + let mutableString = NSMutableAttributedString() + mutableString.append(NSAttributedString(attachment: imageAttachment)) + attributedString.insert(mutableString, at: location) + case "font": if let fontStyle = attribute.optionalStringForKey("style") { let styles = MFStyler.styleGetAttributedString("0", withStyle: fontStyle) @@ -243,13 +289,13 @@ public typealias ActionBlock = () -> Void } else { let fontSize = attribute["size"] as? CGFloat var font: UIFont? - + if let fontName = attribute.optionalStringForKey("name") { font = MFFonts.mfFont(withName: fontName, size: fontSize ?? label.font.pointSize) } else if let fontSize = fontSize { font = label.font.withSize(fontSize) } - + if let font = font { attributedString.removeAttribute(.font, range: range) attributedString.addAttribute(.font, value: font, range: range) @@ -259,10 +305,9 @@ public typealias ActionBlock = () -> Void guard let actionLabel = label as? Label else { continue } actionLabel.addActionAttributes(range: range, string: attributedString) - actionLabel.clauses.append(ActionableClause(range: range, - actionBlock: actionLabel.createActionBlockFrom(actionMap: json, - additionalData: additionalData, - delegateObject: delegate))) + let actionBlock = actionLabel.createActionBlockFrom(actionMap: json, additionalData: additionalData, delegateObject: delegate) + actionLabel.appendActionableClause(range: range, actionBlock: actionBlock) + default: continue } @@ -271,8 +316,6 @@ public typealias ActionBlock = () -> Void } } - - //------------------------------------------------------ // MARK: - Methods //------------------------------------------------------ @@ -323,15 +366,28 @@ public typealias ActionBlock = () -> Void if let originalAttributedString = originalAttributedString { let attributedString = NSMutableAttributedString(attributedString: originalAttributedString) attributedString.removeAttribute(.font, range: NSRange(location: 0, length: attributedString.length)) - originalAttributedString.enumerateAttribute(.font, in: NSRange(location: 0, length: originalAttributedString.length), options: [], using: { value, range, stop in - // Loop the original attributed string, resize the fonts. + + // Loop the original attributed string, resize the fonts. + originalAttributedString.enumerateAttribute(.font, in: NSRange(location: 0, length: originalAttributedString.length), options: []) { value, range, stop in + if let fontObj = value as? UIFont, let stylerSize = MFStyler.sizeObjectGeneric(forCurrentDevice: fontObj.pointSize)?.getValueBased(onSize: size) { attributedString.addAttribute(.font, value: fontObj.withSize(stylerSize) as Any, range: range) } - }) + } + + // Loop the original attributed string, resize the image attachments. + originalAttributedString.enumerateAttribute(.attachment, in: NSRange(location: 0, length: originalAttributedString.length), options: []) { value, range, stop in + if let attachment = value as? NSTextAttachment, + let stylerSize = MFStyler.sizeObjectGeneric(forCurrentDevice: attachment.bounds.width)?.getValueBased(onSize: size) { + + let dimension = round(stylerSize) + attachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension) + } + } + attributedText = attributedString - } else if !MVMCoreGetterUtility.fequal(a: Float(standardFontSize), b: 0.0), let sizeObject: MFSizeObject = self.sizeObject ?? MFStyler.sizeObjectGeneric(forCurrentDevice: standardFontSize) { - self.font = self.font.withSize(sizeObject.getValueBased(onSize: size)) + } else if !MVMCoreGetterUtility.fequal(a: Float(standardFontSize), b: 0.0), let sizeObject = sizeObject ?? MFStyler.sizeObjectGeneric(forCurrentDevice: standardFontSize) { + font = font.withSize(sizeObject.getValueBased(onSize: size)) } } @@ -353,23 +409,98 @@ public typealias ActionBlock = () -> Void } } + /** + Appends an external link image to the end of the attributed string. + Will provide one whitespace to the left of the icon; adds 2 chars to the end of the string. + */ + @objc public func appendExternalLinkIcon() { + + guard let attributedText = attributedText else { return } + + let mutableString = NSMutableAttributedString(attributedString: attributedText) + + mutableString.append(NSAttributedString(string: " ")) + mutableString.append(NSAttributedString(attachment: Label.getTextAttachmentImage(dimension: font.pointSize))) + self.attributedText = mutableString + } - ///Appends an external link image to the end of the attributed string. - public func addExternalLinkIcon() { + /** + Insert external link icon anywhere within text of Label. + + - Note: Each icon insertion adds 1 additional characters to the overall text length. + Therefore, you **MUST** insert icons and links in the order they would appear. + - parameter index: Location within the associated text to insert an external Link Icon + */ + public func insertExternalLinkIcon(at index: Int) { - let size = round(font.pointSize * 0.8) + guard let attributedText = attributedText, index <= attributedText.string.count && index >= 0 else { return } - guard let attributedText = self.attributedText else { return } + let mutableString = NSMutableAttributedString(attributedString: attributedText) + mutableString.insert(NSAttributedString(attachment: Label.getTextAttachmentImage(dimension: font.pointSize)), at: index) - let fullString = NSMutableAttributedString(attributedString: attributedText) + self.attributedText = mutableString + } + + /* + Retrieves an NSTextAttachment for NSAttributedString that is prepped to be inserted with the text. + + - parameter name: The Asset name of the image. DEFAULT: "externalLink" + - parameter dimension: length of the height and width of the image. Will be 80% the passed magnitude. + */ + static func getTextAttachmentImage(name: String = "externalLink", dimension: CGFloat) -> NSTextAttachment { + + let dimension = round(dimension * 0.8) let imageAttachment = NSTextAttachment() - imageAttachment.image = MVMCoreUIUtility.imageNamed("externalLink") - imageAttachment.bounds = CGRect(x: 0, y: 0, width: size, height: size) + imageAttachment.image = MVMCoreUIUtility.imageNamed(name) + imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension) - fullString.append(NSAttributedString(string: " ")) - fullString.append(NSAttributedString(attachment: imageAttachment)) - self.attributedText = fullString + return imageAttachment + } + + static func getTextAttachmentFrom(url: String, dimension: CGFloat, label: Label) -> NSTextAttachment { + + let dimension = round(dimension * 0.8) + + let imageAttachment = NSTextAttachment() + imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension) + + DispatchQueue.global(qos: .default).async { + + guard let url = URL(string: url), + let data = try? Data(contentsOf: url) + else { return } + + DispatchQueue.main.sync { + imageAttachment.image = UIImage(data: data) + label.setNeedsDisplay() + } + } + + return imageAttachment + } + + /// Call to detect in the attributedText contains an NSTextAttachment. + func textContainsTextAttachment() -> Bool { + + guard let attributedText = attributedText else { return false } + + var containsAttachment = false + + attributedText.enumerateAttribute(.attachment, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, stop in + if value is NSTextAttachment { + containsAttachment = true + return + } + } + + return containsAttachment + } + + func appendActionableClause(range: NSRange, actionBlock: @escaping ActionBlock) { + + let accessibleAction = customAccessibilityAction(range: range) + clauses.append(ActionableClause(range: range, actionBlock: actionBlock, accessibilityID: accessibleAction?.hash ?? -1)) } } @@ -382,6 +513,8 @@ extension Label { textAlignment = .left originalAttributedString = nil styleB2(true) + accessibilityCustomActions = [] + clauses = [] } @objc public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { @@ -410,7 +543,7 @@ extension Label { // MARK: - Multi-Action Functionality extension Label { - /// Reseting to default Label values. + /// Applied to existing text. Removes underlines of tappable links and assoated actionable clauses. @objc public func clearActionableClauses() { guard let attributedText = attributedText else { return } @@ -422,6 +555,7 @@ extension Label { } self.attributedText = mutableAttributedString + accessibilityElements = [] clauses = [] } @@ -452,38 +586,36 @@ extension Label { Provides an actionable range of text. - Attention: This method expects text to be set first. Otherwise, it will do nothing. - - Parameters: - - range: The range of text to be tapped. - - actionBlock: The code triggered when tapping the range of text. + - parameter range: The range of text to be tapped. + - parameter actionBlock: The code triggered when tapping the range of text. */ @objc public func addTappableLinkAttribute(range: NSRange, actionBlock: @escaping ActionBlock) { setActionAttributes(range: range) - clauses.append(ActionableClause(range: range, actionBlock: actionBlock)) + appendActionableClause(range: range, actionBlock: actionBlock) } /** Provides an actionable range of text. - Attention: This method expects text to be set first. Otherwise, it will do nothing. - - Parameters: - - range: The range of text to be tapped. - - actionMap: - - delegate: - - additionalData: + - parameter range: The range of text to be tapped. + - parameter actionMap: + - parameter delegate: + - parameter additionalData: */ @objc public func addTappableLinkAttribute(range: NSRange, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { setActionAttributes(range: range) - clauses.append(ActionableClause(range: range, - actionBlock: createActionBlockFrom(actionMap: actionMap, - additionalData: additionalData, - delegateObject: delegateObject))) + let actionBlock = createActionBlockFrom(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) + appendActionableClause(range: range, actionBlock: actionBlock) } @objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) { for clause in clauses { + + // This determines if we tapped on the desired range of text. if let range = clause.range, gesture.didTapAttributedTextInLabel(self, inRange: range) { clause.performAction() return @@ -497,15 +629,23 @@ extension UITapGestureRecognizer { func didTapAttributedTextInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool { + // There would only ever be one clause to act on. if label.makeWholeViewClickable { return true } + // Must configure the attributed string to translate what would appear on screen to accurately analyze. guard let attributedText = label.attributedText else { return false } + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = label.textAlignment + + let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText) + stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) + + let textStorage = NSTextStorage(attributedString: stagedAttributedString) let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: .zero) - let textStorage = NSTextStorage(attributedString: attributedText) layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) @@ -515,8 +655,37 @@ extension UITapGestureRecognizer { textContainer.maximumNumberOfLines = label.numberOfLines textContainer.size = label.bounds.size - let indexOfCharacter = layoutManager.characterIndex(for: location(in: label), in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - - return NSLocationInRange(indexOfCharacter, targetRange) + let indexOfGlyph = layoutManager.glyphIndex(for: location(in: label), in: textContainer) + + return NSLocationInRange(indexOfGlyph, targetRange) + } +} + +// MARK: - Accessibility +extension Label { + + func customAccessibilityAction(range: NSRange) -> UIAccessibilityCustomAction? { + + guard let text = text else { return nil } + + if accessibilityHint == nil { + accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "swipe_to_select_with_action_hint") + } + + let actionText = NSString(string: text).substring(with: range) + let accessibleAction = UIAccessibilityCustomAction(name: actionText, target: self, selector: #selector(accessibilityCustomAction(_:))) + accessibilityCustomActions?.append(accessibleAction) + + return accessibleAction + } + + @objc public func accessibilityCustomAction(_ action: UIAccessibilityCustomAction) { + + for clause in clauses { + if action.hash == clause.accessibilityID { + clause.performAction() + return + } + } } } diff --git a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift index 5cc6d50b..bc1c2219 100644 --- a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift +++ b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift @@ -64,7 +64,15 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt if newActionBlock == nil { label?.clearActionableClauses() } else { - label?.clauses = [Label.ActionableClause(range: actionRange, actionBlock: newActionBlock)] + guard let label = label else { return } + + let accessibleAction = UIAccessibilityCustomAction(name: actionText ?? "", target: label, selector: #selector(label.accessibilityCustomAction(_:))) + label.clauses = [Label.ActionableClause(range: actionRange, actionBlock: newActionBlock, accessibilityID: accessibleAction.hash)] + label.accessibilityCustomActions = [accessibleAction] + + if label.accessibilityHint == nil { + label.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "swipe_to_select_with_action_hint") + } } } } diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h index fc76ed71..f1638f6a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h @@ -12,7 +12,7 @@ @class Label; @class MFSizeObject; -@interface MVMCoreUICheckBox : UIControl +@interface MVMCoreUICheckBox : UIControl @property (nullable, weak, nonatomic) MVMCoreUICheckMarkView *checkMark; @property (readonly, nonatomic) BOOL isSelected; diff --git a/MVMCoreUI/Molecules/MVMCoreUIPageControl.h b/MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.h similarity index 100% rename from MVMCoreUI/Molecules/MVMCoreUIPageControl.h rename to MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.h diff --git a/MVMCoreUI/Molecules/MVMCoreUIPageControl.m b/MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.m similarity index 100% rename from MVMCoreUI/Molecules/MVMCoreUIPageControl.m rename to MVMCoreUI/Atoms/Views/MVMCoreUIPageControl.m diff --git a/MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h b/MVMCoreUI/Atoms/Views/MVMCoreUIPagingProtocol.h similarity index 100% rename from MVMCoreUI/Molecules/MVMCoreUIPagingProtocol.h rename to MVMCoreUI/Atoms/Views/MVMCoreUIPagingProtocol.h diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h index 5591f595..32e98f2a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h @@ -8,10 +8,12 @@ #import +#import +#import @import MVMCore.MVMCoreViewProtocol; typedef void(^ValueChangeBlock)(void); -@interface MVMCoreUISwitch : UIControl +@interface MVMCoreUISwitch : UIControl @property (assign, nonatomic, getter=isOn) BOOL on; @property (nullable, strong, nonatomic) UIColor *onTintColor; @@ -23,8 +25,8 @@ typedef void(^ValueChangeBlock)(void); @property (nonatomic) BOOL shouldTouchToSwitch; @property (nullable, copy, nonatomic) ValueChangeBlock valueChangedBlock; -+ (nullable instancetype)mvmSwitchDefault; -+ (nullable instancetype)mvmSwitchDefaultWithValueChangeBlock:(nullable ValueChangeBlock)block; ++ (nonnull instancetype)mvmSwitchDefault; ++ (nonnull instancetype)mvmSwitchDefaultWithValueChangeBlock:(nullable ValueChangeBlock)block; - (void)setState:(BOOL)state animated:(BOOL)animated; diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m index 9b9e0b96..f994b320 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m @@ -14,6 +14,7 @@ #import #import #import +#import const CGFloat SwitchWidth = 42; const CGFloat SwitchHeight = 22; @@ -21,7 +22,7 @@ const CGFloat SwitchKnobWidth = 20; const CGFloat SwitchKnobHeight = 20; const CGFloat SwitchShakeIntensity = 2; -@interface MVMCoreUISwitch () +@interface MVMCoreUISwitch () @property (weak, nonatomic) UIView *baseView; @property (weak, nonatomic) UIView *knobView; @@ -37,6 +38,9 @@ const CGFloat SwitchShakeIntensity = 2; @property (nonatomic) BOOL canChangeValue; +@property (strong, nonatomic, nullable) NSDictionary *json; +@property (nullable, strong, nonatomic) DelegateObject *delegate; + @end @implementation MVMCoreUISwitch @@ -86,7 +90,7 @@ const CGFloat SwitchShakeIntensity = 2; return mySwitch; } -+ (nullable instancetype)mvmSwitchDefaultWithValueChangeBlock:(nullable ValueChangeBlock)block { ++ (nonnull instancetype)mvmSwitchDefaultWithValueChangeBlock:(nullable ValueChangeBlock)block { MVMCoreUISwitch *mySwitch = [[self alloc] initWithFrame:CGRectZero]; mySwitch.valueChangedBlock = block; return mySwitch; @@ -137,6 +141,50 @@ const CGFloat SwitchShakeIntensity = 2; } } +#pragma mark - MVMCoreUIMoleculeViewProtocol + +- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { + self.json = json; + + [FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol]; + + NSString *color = [json string:@"onTintColor"]; + if (color) { + self.onTintColor = [UIColor mfGetColorForHex:color]; + } + + color = [json string:@"offTintColor"]; + if (color) { + self.offTintColor = [UIColor mfGetColorForHex:color]; + } + + color = [json string:@"onKnobTintColor"]; + if (color) { + self.onKnobTintColor = [UIColor mfGetColorForHex:color]; + } + + color = [json string:@"offKnobTintColor"]; + if (color) { + self.offKnobTintColor = [UIColor mfGetColorForHex:color]; + } + + [self setState:[json boolForKey:@"state"] animated:false]; +} + ++ (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject { + return [self getSwitchHeight]; +} + +#pragma mark - MVMCoreUIMoleculeViewProtocol + +- (BOOL)needsToBeConstrained { + return YES; +} + +- (UIStackViewAlignment)alignment { + return UIStackViewAlignmentTrailing; +} + #pragma mark - UIResponder overide - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { @@ -280,6 +328,11 @@ const CGFloat SwitchShakeIntensity = 2; if (self.valueChangedBlock) { self.valueChangedBlock(); } + + if (self.delegate && [self.delegate respondsToSelector:@selector(formValidationProtocol)] && [[self.delegate performSelector:@selector(formValidationProtocol)] respondsToSelector:@selector(formValidatorModel)]) { + FormValidator *formValidator = [[self.delegate performSelector:@selector(formValidationProtocol)] performSelector:@selector(formValidatorModel)]; + [formValidator enableByValidation]; + } } - (void)setState:(BOOL)state withoutBlockAnimated:(BOOL)animated { @@ -308,7 +361,7 @@ const CGFloat SwitchShakeIntensity = 2; } -- (BOOL)changeValue{ +- (BOOL)changeValue { self.on^=YES; self.shouldTouchToSwitch = NO; [self setState:self.on animated:YES]; @@ -349,7 +402,8 @@ const CGFloat SwitchShakeIntensity = 2; } #pragma mark - Accessibility -- (BOOL)isAccessibilityElement{ + +- (BOOL)isAccessibilityElement { return YES; } @@ -361,4 +415,18 @@ const CGFloat SwitchShakeIntensity = 2; return [MVMCoreUIUtility hardcodedStringWithKey:@"AccToggleHint"]; } +#pragma mark FormValidationProtocol + +- (BOOL)isValidField { + return self.isOn && [self.json boolForKey:@"required"]; +} + +- (NSString *)formFieldName { + return [self.json string:KeyFieldKey]; +} + +- (id)formFieldValue { + return @(self.isOn); +} + @end diff --git a/MVMCoreUI/Atoms/Views/MultiProgress.swift b/MVMCoreUI/Atoms/Views/MultiProgress.swift new file mode 100644 index 00000000..619e31e7 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/MultiProgress.swift @@ -0,0 +1,93 @@ +// +// MultiProgress.swift +// MVMCoreUI +// +// Created by Ryan on 9/9/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +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: MFView { + ///passing value to progressList creates corresponding progress bars + var progressList: Array? { + didSet { + for subview in subviews { + subview.removeFromSuperview() + } + guard (progressList?.count ?? 0) > 0 else { + return + } + var previous: UIView? + for progressObject in progressList! { + guard progressObject.progress > 0.0 else { + continue + } + let view = MFView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + view.backgroundColor = progressObject.color + view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: progressObject.progress).isActive = true + view.leadingAnchor.constraint(equalTo: previous?.trailingAnchor ?? leadingAnchor).isActive = true + previous = view + NSLayoutConstraint.constraintPinSubview(view, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false) + } + } + } + + var roundedRect: Bool = false { + didSet { + if roundedRect { + layer.cornerRadius = (thicknessConstraint?.constant ?? defaultHeight)/2 + } else { + layer.cornerRadius = 0 + } + } + } + var thicknessConstraint: NSLayoutConstraint? + let defaultHeight: CGFloat = 8 + + override open func setupView() { + super.setupView() + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .mfSilver() + clipsToBounds = true + if thicknessConstraint == nil { + thicknessConstraint = heightAnchor.constraint(equalToConstant: defaultHeight) + thicknessConstraint?.isActive = true + } + } + + 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]]) + } +} diff --git a/MVMCoreUI/Molecules/ProgressBar.swift b/MVMCoreUI/Atoms/Views/ProgressBar.swift similarity index 100% rename from MVMCoreUI/Molecules/ProgressBar.swift rename to MVMCoreUI/Atoms/Views/ProgressBar.swift diff --git a/MVMCoreUI/Atoms/Views/SeparatorView.h b/MVMCoreUI/Atoms/Views/SeparatorView.h index 89a9197d..40489b16 100644 --- a/MVMCoreUI/Atoms/Views/SeparatorView.h +++ b/MVMCoreUI/Atoms/Views/SeparatorView.h @@ -41,4 +41,7 @@ typedef enum : NSUInteger { - (void)setAsLight; - (void)setAsMedium; +/// Returns if the separator should be visible based on the type. +- (BOOL)shouldBeVisible; + @end diff --git a/MVMCoreUI/Atoms/Views/SeparatorView.m b/MVMCoreUI/Atoms/Views/SeparatorView.m index a666d450..9f85c311 100644 --- a/MVMCoreUI/Atoms/Views/SeparatorView.m +++ b/MVMCoreUI/Atoms/Views/SeparatorView.m @@ -162,6 +162,10 @@ [self setNeedsLayout]; [self layoutIfNeeded]; } + +- (BOOL)shouldBeVisible { + return ![[self.json string:KeyType] isEqualToString:@"none"]; +} #pragma mark - Molecule diff --git a/MVMCoreUI/Categories/NSLayoutConstraintExtension.swift b/MVMCoreUI/Categories/NSLayoutConstraintExtension.swift new file mode 100644 index 00000000..bf6c8f65 --- /dev/null +++ b/MVMCoreUI/Categories/NSLayoutConstraintExtension.swift @@ -0,0 +1,44 @@ +// +// NSLayoutConstraintExtension.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 8/28/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + +public extension NSLayoutConstraint { + static func pinSubviewsCenter(leftView: UIView, rightView: UIView) { + guard let superView = leftView.superview else { + return + } + leftView.centerYAnchor.constraint(equalTo: superView.centerYAnchor).isActive = true + leftView.leftAnchor.constraint(equalTo: superView.layoutMarginsGuide.leftAnchor).isActive = true + leftView.topAnchor.constraint(greaterThanOrEqualTo: superView.layoutMarginsGuide.topAnchor).isActive = true + superView.layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: leftView.bottomAnchor).isActive = true + + var constraint = leftView.topAnchor.constraint(equalTo: superView.layoutMarginsGuide.topAnchor) + constraint.priority = .defaultLow + constraint.isActive = true + + constraint = superView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: leftView.bottomAnchor) + constraint.priority = .defaultLow + constraint.isActive = true + + rightView.leftAnchor.constraint(greaterThanOrEqualTo: leftView.rightAnchor, constant: 16).isActive = true + + rightView.centerYAnchor.constraint(equalTo: superView.centerYAnchor).isActive = true + superView.layoutMarginsGuide.rightAnchor.constraint(equalTo: rightView.rightAnchor).isActive = true + rightView.topAnchor.constraint(greaterThanOrEqualTo: superView.layoutMarginsGuide.topAnchor).isActive = true + superView.layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: rightView.bottomAnchor).isActive = true + + constraint = rightView.topAnchor.constraint(equalTo: superView.layoutMarginsGuide.topAnchor) + constraint.priority = .defaultLow + constraint.isActive = true + + constraint = superView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: rightView.bottomAnchor) + constraint.priority = .defaultLow + constraint.isActive = true + } +} diff --git a/MVMCoreUI/Containers/TabBarController/TopTabbar.h b/MVMCoreUI/Containers/TabBarController/TopTabbar.h index e63911e7..d851294b 100644 --- a/MVMCoreUI/Containers/TabBarController/TopTabbar.h +++ b/MVMCoreUI/Containers/TabBarController/TopTabbar.h @@ -30,6 +30,9 @@ @property (nonatomic, readonly) NSInteger selectedIndex; +/// A flag for if there should be padding before the first item. +@property (nonatomic) BOOL paddingBeforeFirstTab; + //method to set the height - (void)pinHeight:(CGFloat)height; diff --git a/MVMCoreUI/Containers/TabBarController/TopTabbar.m b/MVMCoreUI/Containers/TabBarController/TopTabbar.m index c52e9d66..e9d77c77 100644 --- a/MVMCoreUI/Containers/TabBarController/TopTabbar.m +++ b/MVMCoreUI/Containers/TabBarController/TopTabbar.m @@ -75,6 +75,7 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (void)setupView { + self.paddingBeforeFirstTab = YES; self.maxHeight = BAR_HEIGHT; self.selectedIndex = 0; self.backgroundColor = [UIColor whiteColor]; @@ -229,6 +230,9 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + if (!self.paddingBeforeFirstTab && section == 0) { + return UIEdgeInsetsMake(SECTION_TOPPIN, 0,SECTION_BOTPIN, 0); + } return UIEdgeInsetsMake(SECTION_TOPPIN, SECTION_PADDING,SECTION_BOTPIN, 0); } @@ -341,7 +345,7 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (void)selectIndex:(NSInteger)index animated:(BOOL)animated { - if (self.collectionView) { + if (self.collectionView && [self.datasource numberOfTopTabbarItems:self] > 0) { [MVMCoreDispatchUtility performBlockOnMainThread:^{ NSInteger currentIndex = self.selectedIndex; self.selectedIndex = index; diff --git a/MVMCoreUI/Molecules/ActionDetailWithImage.swift b/MVMCoreUI/Molecules/ActionDetailWithImage.swift index 4792dbd6..ec91c479 100644 --- a/MVMCoreUI/Molecules/ActionDetailWithImage.swift +++ b/MVMCoreUI/Molecules/ActionDetailWithImage.swift @@ -14,8 +14,7 @@ import UIKit // MARK: - Outlets //------------------------------------------------------ - let title = Label.commonLabelH3(true) - let message = Label.commonLabelB3(true) + let header = HeadlineBody(frame: .zero) let button = PrimaryButton.primaryTinyButton(false)! let imageLoader = MFLoadImageView(pinnedEdges: .all) let leftContainer = ViewConstrainingView.empty() @@ -24,7 +23,7 @@ import UIKit // MARK: - Properties //------------------------------------------------------ - var bottomTitlePadding: CGFloat = PaddingOne + var buttonHeaderPadding: CGFloat = PaddingTwo //------------------------------------------------------ // MARK: - Constraints @@ -32,7 +31,6 @@ import UIKit var imageLeadingConstraint: NSLayoutConstraint? var buttonTopConstraint: NSLayoutConstraint? - var messageTopConstraint: NSLayoutConstraint? //------------------------------------------------------ // MARK: - Initialization @@ -68,8 +66,7 @@ import UIKit addSubview(leftContainer) addSubview(imageLoader) - leftContainer.addSubview(title) - leftContainer.addSubview(message) + leftContainer.addSubview(header) leftContainer.addSubview(button) leftContainer.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true @@ -80,17 +77,11 @@ import UIKit leftContainerBottom.priority = UILayoutPriority(249) leftContainerBottom.isActive = true - title.topAnchor.constraint(equalTo: leftContainer.topAnchor).isActive = true - title.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true - leftContainer.trailingAnchor.constraint(equalTo: title.trailingAnchor).isActive = true - - messageTopConstraint = message.topAnchor.constraint(equalTo: title.bottomAnchor, constant: PaddingOne) - messageTopConstraint?.isActive = true - - message.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true - leftContainer.trailingAnchor.constraint(equalTo: message.trailingAnchor).isActive = true - - buttonTopConstraint = button.topAnchor.constraint(equalTo: message.bottomAnchor, constant: PaddingTwo) + header.topAnchor.constraint(equalTo: leftContainer.topAnchor).isActive = true + header.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true + leftContainer.trailingAnchor.constraint(equalTo: header.trailingAnchor).isActive = true + + buttonTopConstraint = button.topAnchor.constraint(equalTo: header.bottomAnchor, constant: buttonHeaderPadding) buttonTopConstraint?.isActive = true button.leadingAnchor.constraint(equalTo: leftContainer.leadingAnchor).isActive = true @@ -112,15 +103,12 @@ import UIKit override open func updateView(_ size: CGFloat) { super.updateView(size) - title.updateView(size) - message.updateView(size) + header.updateView(size) button.updateView(size) imageLoader.updateView(size) leftContainer.updateView(size) - - - messageTopConstraint?.constant = title.hasText && message.hasText ? bottomTitlePadding : 0 - buttonTopConstraint?.constant = message.hasText || title.hasText ? PaddingTwo : 0 + + buttonTopConstraint?.constant = header.hasText() ? buttonHeaderPadding : 0 } public override static func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { @@ -133,35 +121,31 @@ import UIKit private func setDefaultState() { - title.font = MFStyler.fontH3() - message.font = MFStyler.fontB3() + header.headlineLabel.font = MFStyler.fontH3() + header.messageLabel.font = MFStyler.fontB3() button.setAsSecondaryCustom() button.isHidden = false imageLoader.imageView.contentMode = .scaleAspectFit imageLoader.addSizeConstraintsForAspectRatio = true - bottomTitlePadding = PaddingOne + buttonHeaderPadding = PaddingTwo } override open func reset() { super.reset() - title.reset() - message.reset() + header.reset() button.reset() imageLeadingConstraint?.constant = 16 imageLoader.reset() - setDefaultState() } open override func setAsMolecule() { super.setAsMolecule() - title.setAsMolecule() - message.setAsMolecule() + header.setAsMolecule() button.setAsMolecule() imageLoader.setAsMolecule() - setDefaultState() } @@ -170,13 +154,13 @@ import UIKit guard let dictionary = json else { return } - if let padding = dictionary.optionalCGFloatForKey("bottomTitlePadding") { - bottomTitlePadding = padding + if let padding = dictionary.optionalCGFloatForKey("buttonHeaderPadding") { + buttonHeaderPadding = padding } - title.setWithJSON(dictionary.optionalDictionaryForKey("title"), delegateObject: delegateObject, additionalData: additionalData) - message.setWithJSON(dictionary.optionalDictionaryForKey("message"), delegateObject: delegateObject, additionalData: additionalData) + header.setWithJSON(dictionary.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) imageLoader.setWithJSON(dictionary.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) + if let buttonDictionary = dictionary.optionalDictionaryForKey("button") { button.setWithJSON(buttonDictionary, delegateObject: delegateObject, additionalData: additionalData) } else { diff --git a/MVMCoreUI/Molecules/ImageHeadlineBody.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift similarity index 68% rename from MVMCoreUI/Molecules/ImageHeadlineBody.swift rename to MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift index f1e295fe..aca450aa 100644 --- a/MVMCoreUI/Molecules/ImageHeadlineBody.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift @@ -20,27 +20,30 @@ import UIKit guard subviews.count == 0 else { return } - MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0) headlineBody.headlineLabel.styleB1(true) headlineBody.spaceBetweenLabelsConstant = 0 - addSubview(headlineBody) - addSubview(imageView) - NSLayoutConstraint.pinViewTop(toSuperview: headlineBody, useMargins: true, constant: 0).isActive = true - NSLayoutConstraint.pinViewRight(toSuperview: headlineBody, useMargins: true, constant: 0).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor).isActive = true - var constraint = NSLayoutConstraint.pinViewBottom(toSuperview: headlineBody, useMargins: true, constant: 0) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + view.addSubview(headlineBody) + view.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) constraint.priority = .defaultLow constraint.isActive = true - imageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - NSLayoutConstraint.pinViewLeft(toSuperview: imageView, useMargins: true, constant: 0).isActive = true - imageView.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor).isActive = true - constraint = NSLayoutConstraint.pinViewBottom(toSuperview: imageView, useMargins: true, constant: 0) + 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) constraint.priority = UILayoutPriority(rawValue: 200) constraint.isActive = true - constraint = NSLayoutConstraint.pinViewTop(toSuperview: imageView, useMargins: true, constant: 0) + constraint = imageView.topAnchor.constraint(equalTo: view.topAnchor) constraint.priority = UILayoutPriority(rawValue: 200) constraint.isActive = true diff --git a/MVMCoreUI/Molecules/TwoButtonView.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift similarity index 96% rename from MVMCoreUI/Molecules/TwoButtonView.swift rename to MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift index 4b4f64d9..28ad6dc4 100644 --- a/MVMCoreUI/Molecules/TwoButtonView.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift @@ -8,12 +8,13 @@ import UIKit -@objcMembers open class TwoButtonView: ButtonView { - open var secondaryButton: PrimaryButton? +@objcMembers open class TwoButtonView: ViewConstrainingView { + open var primaryButton: PrimaryButton? = PrimaryButton.button() + open var secondaryButton: PrimaryButton? = PrimaryButton.button() open var viewForButtons: UIView? public var heightConstraint: NSLayoutConstraint? - public override init() { + public init() { super.init(frame: .zero) } @@ -34,28 +35,32 @@ import UIKit open override func updateView(_ size: CGFloat) { super.updateView(size) MVMCoreDispatchUtility.performBlock(onMainThread: { + self.primaryButton?.updateView(size) self.secondaryButton?.updateView(size) }) } + open override func setupView() { + super.setupView() + setupWithTwoButtons() + secondaryButton?.bordered = true + } + // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) let primaryButtonMap = json?.optionalDictionaryForKey("primaryButton") let secondaryButtonMap = json?.optionalDictionaryForKey("secondaryButton") set(primaryButtonJSON: primaryButtonMap, secondaryButtonJSON: secondaryButtonMap, delegateObject: delegateObject, additionalData: additionalData) + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } open override func reset() { super.reset() + primaryButton?.setAsStandardCustom() secondaryButton?.setAsSecondaryCustom() } // MARK: - Constraining - override func setupButton() { - setupWithTwoButtons() - } - func createPrimaryButton() { if primaryButton == nil { primaryButton = PrimaryButton.button() diff --git a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift new file mode 100644 index 00000000..075d33f1 --- /dev/null +++ b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -0,0 +1,46 @@ +// +// AccordionMoleculeTableViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 8/30/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class AccordionMoleculeTableViewCell: MoleculeTableViewCell { + let accordionButton = createAccordionButton() + + static func createAccordionButton() -> MFCustomButton { + let accordionButton = MFCustomButton(type: .custom) + accordionButton.setTitle("+", for: .normal) + accordionButton.setTitleColor(.black, for: .normal) + accordionButton.titleLabel?.font = UIFont.systemFont(ofSize: 40, weight: .ultraLight) + accordionButton.frame = CGRect(x: 0, y: 0, width: 20, height: 20) + return accordionButton + } + + override public func setupView() { + customAccessoryView = true + super.setupView() + accessoryView = accordionButton + } + + public override func didSelectCell(atIndex indexPath: 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 { + return + } + + if accordionButton.isSelected { + delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: .automatic) + } else { + delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: .automatic) + } + + if (json?.boolForKey("hideSeparatorWhenExpanded") ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { + bottomSeparatorView?.isHidden = accordionButton.isSelected + } + } +} diff --git a/MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift similarity index 100% rename from MVMCoreUI/Molecules/MoleculeCollectionViewCell.swift rename to MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift diff --git a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift similarity index 83% rename from MVMCoreUI/Molecules/MoleculeTableViewCell.swift rename to MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index c1054e33..641b3721 100644 --- a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -15,7 +15,6 @@ import UIKit // In updateView, will set padding to default. open var updateViewHorizontalDefaults = true - open var updateViewVerticalDefaults = true // For the accessory view convenience. public var caretView: CaretView? @@ -32,6 +31,48 @@ import UIKit case Between = "between" } + /// For subclasses that want to use a custom accessory view. + open var customAccessoryView = false + + public var topMarginPadding: CGFloat = 24 + public var bottomMarginPadding: CGFloat = 24 + + // MARK: - Styling + func style(with styleString: String?) { + guard let styleString = styleString else { + return + } + switch styleString { + case "standard": + styleStandard() + case "header": + styleHeader() + case "none": + styleNone() + default: break + } + } + + func styleStandard() { + topMarginPadding = 24 + bottomMarginPadding = 24 + bottomSeparatorView?.show() + bottomSeparatorView?.setAsLight() + } + + func styleHeader() { + topMarginPadding = 48 + bottomMarginPadding = 16 + bottomSeparatorView?.show() + bottomSeparatorView?.setAsRegular() + } + + func styleNone() { + topMarginPadding = 0 + bottomMarginPadding = 0 + bottomSeparatorView?.hide() + } + // MARK: - Inits public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -45,7 +86,7 @@ import UIKit // MARK: - MFViewProtocol public func updateView(_ size: CGFloat) { - MFStyler.setDefaultMarginsFor(self, size: size, horizontal: updateViewHorizontalDefaults, vertical: updateViewVerticalDefaults) + MFStyler.setMarginsFor(self, size: size, defaultHorizontal: updateViewHorizontalDefaults, top: topMarginPadding, bottom: bottomMarginPadding) if #available(iOS 11.0, *) { if accessoryView != nil { // Smaller left margin if accessory view. @@ -91,11 +132,14 @@ import UIKit public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json; + style(with: json?.optionalStringForKey("style")) + if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { updateViewHorizontalDefaults = useHorizontalMargins } - if let useVerticalMargins = json?.optionalBoolForKey("useVerticalMargins") { - updateViewVerticalDefaults = useVerticalMargins + if (json?.optionalBoolForKey("useVerticalMargins") ?? true) == false { + topMarginPadding = 0 + bottomMarginPadding = 0 } if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { @@ -103,10 +147,12 @@ import UIKit } // Add the caret if there is an action and it's not declared hidden. - if let _ = json?.optionalDictionaryForKey("actionMap"), !json!.boolForKey("hideArrow") { - addCaretViewAccessory() - } else { - accessoryView = nil + if !customAccessoryView { + if let _ = json?.optionalDictionaryForKey("actionMap"), !json!.boolForKey("hideArrow") { + addCaretViewAccessory() + } else { + accessoryView = nil + } } // override the separator @@ -138,8 +184,8 @@ import UIKit public func reset() { molecule?.reset?() - updateViewVerticalDefaults = true updateViewHorizontalDefaults = true + styleStandard() backgroundColor = .white } @@ -152,9 +198,10 @@ import UIKit public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else { - return nil + return "\(self)<>" } - return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) + let moleculeName = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) ?? "" + return "\(self)<\(moleculeName)>" } public static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { diff --git a/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift new file mode 100644 index 00000000..1c509a8f --- /dev/null +++ b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift @@ -0,0 +1,81 @@ +// +// TabsTableViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/6/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class TabsTableViewCell: MoleculeTableViewCell { + let tabs = TopTabbar(frame: .zero) + var delegateObject: MVMCoreUIDelegateObject? + var previousTabIndex = 0 + + // MARK: - MFViewProtocol + override public func setupView() { + super.setupView() + guard tabs.superview == nil else { + return + } + tabs.paddingBeforeFirstTab = false + topMarginPadding = 8 + bottomMarginPadding = 0 + + tabs.translatesAutoresizingMaskIntoConstraints = false + tabs.delegate = self + tabs.datasource = self + contentView.addSubview(tabs) + + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: tabs, useMargins: true).values)) + tabs.reloadData() + } + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + tabs.updateView(size) + } + + // MARK: - MoleculeDelegateProtocol + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + tabs.reloadData() + } + + public override func reset() { + super.reset() + topMarginPadding = 8 + bottomMarginPadding = 0 + } +} + +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) + } + 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) + } + } +} + +extension TabsTableViewCell: TopTabbarDataSource { + public func number(ofTopTabbarItems topTabbar: TopTabbar) -> Int { + return json?.optionalDictionaryForKey("tabs")?.optionalArrayForKey("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 { + return "Select" + } + return title + } +} diff --git a/MVMCoreUI/Molecules/CornerLabels.swift b/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift similarity index 100% rename from MVMCoreUI/Molecules/CornerLabels.swift rename to MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift new file mode 100644 index 00000000..d373ce87 --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift @@ -0,0 +1,61 @@ +// +// HeadlineBodySwitch.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 8/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class HeadlineBodySwitch: ViewConstrainingView { + let headlineBody = HeadlineBody(frame: .zero) + let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() + + // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + mvmSwitch.updateView(size) + } + + public override func setupView() { + super.setupView() + guard mvmSwitch.superview == nil else { + return + } + headlineBody.styleListItem() + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineBody) + view.addSubview(mvmSwitch) + NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBody, rightView: mvmSwitch) + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + public 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) + } + + public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 30 + } + + public override func setAsMolecule() { + super.setAsMolecule() + headlineBody.setAsMolecule() + (mvmSwitch as MVMCoreUIMoleculeViewProtocol).setAsMolecule?() + headlineBody.styleListItem() + } + + public override func reset() { + super.reset() + headlineBody.reset() + (mvmSwitch as MVMCoreUIMoleculeViewProtocol).reset?() + headlineBody.styleListItem() + } +} diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift new file mode 100644 index 00000000..0f184544 --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift @@ -0,0 +1,63 @@ +// +// SwitchLineItem.swift +// MVMCoreUI +// +// Created by Priya on 5/6/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class HeadlineBodyTextButtonSwitch: ViewConstrainingView { + let headlineBodyTextButton = HeadlineBodyTextButton(frame: .zero) + let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() + + // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBodyTextButton.updateView(size) + mvmSwitch.updateView(size) + } + + public override func setupView() { + super.setupView() + guard mvmSwitch.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 static 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() + } + + public override func reset() { + super.reset() + headlineBodyTextButton.reset() + (mvmSwitch as MVMCoreUIMoleculeViewProtocol).reset?() + headlineBodyTextButton.headlineBody.styleListItem() + } +} + + diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift new file mode 100644 index 00000000..d47cf44c --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift @@ -0,0 +1,61 @@ +// +// LabelSwitch.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 8/26/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class LabelSwitch: ViewConstrainingView { + let label = Label.commonLabelB1(true) + let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() + + // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + label.updateView(size) + mvmSwitch.updateView(size) + } + + public override func setupView() { + super.setupView() + guard mvmSwitch.superview == nil else { + return + } + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(label) + view.addSubview(mvmSwitch) + label.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + NSLayoutConstraint.pinSubviewsCenter(leftView: label, rightView: mvmSwitch) + } + + // 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 static 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() { + super.reset() + label.reset() + (mvmSwitch as MVMCoreUIMoleculeViewProtocol).reset?() + label.styleB1(true) + } +} diff --git a/MVMCoreUI/Molecules/Switch.swift b/MVMCoreUI/Molecules/Switch.swift deleted file mode 100644 index 5b23b1d5..00000000 --- a/MVMCoreUI/Molecules/Switch.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// Switch.swift -// MVMCoreUI -// -// Created by Priya on 5/23/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -@objcMembers public class Switch: ViewConstrainingView, FormValidationProtocol{ - public var mvmSwitch = MVMCoreUISwitch() - var isRequired = false - var delegateObject: DelegateObject? - - @objc func switchChanged() { - let delegate = delegateObject as? MVMCoreUIDelegateObject - if let delegate = delegate { - let formValidator = delegate.formValidationProtocol?.formValidatorModel?() - formValidator?.enableByValidation() - } - } - - open override func setupView() { - super.setupView() - mvmSwitch.addTarget(self, action: #selector(Switch.switchChanged), for: .valueChanged) - self.clipsToBounds = true - addSubview(mvmSwitch) - mvmSwitch.translatesAutoresizingMaskIntoConstraints = false - setupContainerConstraints() - } - - public override func updateView(_ size: CGFloat) { - super.updateView(size) - mvmSwitch.updateView(size) - } - - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - isRequired = json?[KeyRequired] as? Bool ?? false - self.delegateObject = delegateObject - if let delegateObject = delegateObject { - FormValidator.setupValidation(molecule: self, delegate: delegateObject.formValidationProtocol) - } - if let onColorString = json?.optionalStringForKey("onTintColor") { - mvmSwitch.onTintColor = .mfGet(forHex: onColorString) - } - if let offColorString = json?.optionalStringForKey("offTintColor") { - mvmSwitch.offTintColor = .mfGet(forHex: offColorString) - } - if let onKnobColorString = json?.optionalStringForKey("onKnobTintColor") { - mvmSwitch.onKnobTintColor = .mfGet(forHex: onKnobColorString) - } - if let offKnobColorString = json?.optionalStringForKey("offKnobTintColor") { - mvmSwitch.offKnobTintColor = .mfGet(forHex: offKnobColorString) - } - mvmSwitch.setState(json?.optionalBoolForKey("state") ?? false, animated: true) - } - - func setupContainerConstraints() { - mvmSwitch.topAnchor.constraint(equalTo: topAnchor).isActive = true - mvmSwitch.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - mvmSwitch.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true - mvmSwitch.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true - } - - public func isValidField() -> Bool { - return (isRequired == false) ? true : mvmSwitch.isOn - } - - public func formFieldName() -> String? { - return json?.optionalStringForKey(KeyFieldKey) - } - - public func formFieldValue() -> Any? { - return mvmSwitch.isOn - } - - public override func needsToBeConstrained() -> Bool { - return true - } - - public override func alignment() -> UIStackView.Alignment { - return UIStackView.Alignment.leading - } - - public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return MVMCoreUISwitch.getHeight() - } -} - diff --git a/MVMCoreUI/Molecules/SwitchLineItem.swift b/MVMCoreUI/Molecules/SwitchLineItem.swift deleted file mode 100644 index 8c15e8ec..00000000 --- a/MVMCoreUI/Molecules/SwitchLineItem.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// SwitchLineItem.swift -// MVMCoreUI -// -// Created by Priya on 5/6/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import UIKit - -@objcMembers public class SwitchLineItem: ViewConstrainingView, FormValidationProtocol{ - public var mvmSwitch = Switch() - public var label = Label() - public var leftContainerView = UIView() - public var mfTextButton = MFTextButton(nil, constrainHeight: true, forWidth: 0) - var isRequired = false - var delegateObject: DelegateObject? - - @objc func switchChanged() { - let delegate = delegateObject as? MVMCoreUIDelegateObject - if let delegate = delegate { - let formValidator = delegate.formValidationProtocol?.formValidatorModel?() - formValidator?.enableByValidation() - } - } - - open override func setupView() { - super.setupView() - leftContainerView.addSubview(label) - leftContainerView.addSubview(mfTextButton) - addSubview(leftContainerView) - addSubview(mvmSwitch) - - leftContainerView.translatesAutoresizingMaskIntoConstraints = false - mvmSwitch.translatesAutoresizingMaskIntoConstraints = false - mfTextButton.translatesAutoresizingMaskIntoConstraints = false - label.translatesAutoresizingMaskIntoConstraints = false - setupContainerConstraints() - } - - public override func updateView(_ size: CGFloat) { - super.updateView(size) - label.updateView(size) - mvmSwitch.updateView(size) - mfTextButton.updateView(size) - } - - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - mvmSwitch.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - label.setWithJSON(json?.optionalDictionaryForKey("label"), delegateObject: delegateObject, additionalData: additionalData) - mfTextButton.setWithJSON(json?.optionalDictionaryForKey("textButton"), delegateObject: delegateObject, additionalData: additionalData) - if (label.text?.count ?? 0) <= 0 && (mfTextButton.titleLabel?.text?.count ?? 0) <= 0 { - mvmSwitch.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true - } - } - - func setupContainerConstraints() { - leftContainerView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true - - var constraint = leftContainerView.topAnchor.constraint(equalTo: topAnchor) - constraint.priority = UILayoutPriority(249) - constraint.isActive = true - - mvmSwitch.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true - - constraint = mvmSwitch.topAnchor.constraint(equalTo: topAnchor) - constraint.priority = UILayoutPriority(249) - constraint.isActive = true - - trailingAnchor.constraint(equalTo: mvmSwitch.trailingAnchor).isActive = true - - constraint = bottomAnchor.constraint(equalTo: mvmSwitch.bottomAnchor) - constraint.priority = UILayoutPriority(249) - constraint.isActive = true - - bottomAnchor.constraint(greaterThanOrEqualTo: mvmSwitch.bottomAnchor).isActive = true - - constraint = bottomAnchor.constraint(equalTo: leftContainerView.bottomAnchor) - constraint.isActive = true - - bottomAnchor.constraint(greaterThanOrEqualTo: leftContainerView.bottomAnchor).isActive = true - leftContainerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - - NSLayoutConstraint.constraintPinSubview(leftContainerView, pinCenterX: false, pinCenterY: true) - constraint = mvmSwitch.leadingAnchor.constraint(greaterThanOrEqualTo: leftContainerView.trailingAnchor) - constraint.priority = UILayoutPriority(999) - constraint.isActive = true - NSLayoutConstraint.constraintPinSubview(mvmSwitch, pinCenterX: false, pinCenterY: true) - - leftContainerView.topAnchor.constraint(equalTo: label.topAnchor).isActive = true - leftContainerView.trailingAnchor.constraint(greaterThanOrEqualTo: label.trailingAnchor).isActive = true - - constraint = leftContainerView.trailingAnchor.constraint(equalTo: label.trailingAnchor) - constraint.priority = UILayoutPriority(249) - constraint.isActive = true - - leftContainerView.trailingAnchor.constraint(greaterThanOrEqualTo: mfTextButton.trailingAnchor).isActive = true - - constraint = leftContainerView.trailingAnchor.constraint(equalTo: mfTextButton.trailingAnchor) - constraint.priority = UILayoutPriority(249) - constraint.isActive = true - - leftContainerView.bottomAnchor.constraint(equalTo: mfTextButton.bottomAnchor).isActive = true - mfTextButton.leadingAnchor.constraint(equalTo: leftContainerView.leadingAnchor).isActive = true - label.leadingAnchor.constraint(equalTo: leftContainerView.leadingAnchor).isActive = true - mfTextButton.topAnchor.constraint(equalTo: label.bottomAnchor).isActive = true - leftContainerView.setContentHuggingPriority(.defaultHigh, for: .horizontal) - mvmSwitch.setContentHuggingPriority(.defaultLow, for: .horizontal) - } - - public override func needsToBeConstrained() -> Bool { - return true - } - - public override func alignment() -> UIStackView.Alignment { - return UIStackView.Alignment.leading - } -} - - diff --git a/MVMCoreUI/Molecules/HeadlineBody.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift similarity index 69% rename from MVMCoreUI/Molecules/HeadlineBody.swift rename to MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift index d3c37261..db30c8d6 100644 --- a/MVMCoreUI/Molecules/HeadlineBody.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -18,6 +18,42 @@ open class HeadlineBody: ViewConstrainingView { var leftConstraintMessage: NSLayoutConstraint? var rightConstraintMessage: NSLayoutConstraint? + func hasText() -> Bool { + return headlineLabel.hasText || messageLabel.hasText + } + + // MARK: - Styling + func style(with styleString: String?) { + guard let styleString = styleString else { + return + } + switch styleString { + case "header": + stylePageHeader() + case "item": + styleListItem() + default: break + } + } + + func styleLandingPageHeader() { + headlineLabel.styleH1(true) + messageLabel.styleB2(true) + spaceBetweenLabelsConstant = PaddingTwo + } + + func stylePageHeader() { + headlineLabel.styleH2(true) + messageLabel.styleB2(true) + spaceBetweenLabelsConstant = PaddingTwo + } + + func styleListItem() { + headlineLabel.styleB1(true) + messageLabel.styleB2(true) + spaceBetweenLabelsConstant = 0 + } + // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) @@ -34,33 +70,37 @@ open class HeadlineBody: ViewConstrainingView { translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear clipsToBounds = true - - addSubview(headlineLabel) - addSubview(messageLabel) + + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineLabel) + view.addSubview(messageLabel) headlineLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) messageLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - topPin = headlineLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0) + topPin = headlineLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 0) topPin?.isActive = true spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: spaceBetweenLabelsConstant) spaceBetweenLabels?.isActive = true - leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: leftAnchor) + leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: view.leftAnchor) leftConstraintTitle?.isActive = true - rightConstraintTitle = rightAnchor.constraint(equalTo: headlineLabel.rightAnchor) + rightConstraintTitle = view.rightAnchor.constraint(equalTo: headlineLabel.rightAnchor) rightConstraintTitle?.isActive = true - leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: leftAnchor) + leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: view.leftAnchor) leftConstraintMessage?.isActive = true - rightConstraintMessage = rightAnchor.constraint(equalTo: messageLabel.rightAnchor) + rightConstraintMessage = view.rightAnchor.constraint(equalTo: messageLabel.rightAnchor) rightConstraintMessage?.isActive = true - bottomPin = bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0) + bottomPin = view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0) bottomPin?.isActive = true } @@ -73,20 +113,12 @@ open class HeadlineBody: ViewConstrainingView { } } - open override func setLeftPinConstant(_ constant: CGFloat) { - leftConstraintTitle?.constant = constant - leftConstraintMessage?.constant = constant - } - - open override func setRightPinConstant(_ constant: CGFloat) { - rightConstraintTitle?.constant = constant - rightConstraintMessage?.constant = constant - } - // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + style(with: json?.optionalStringForKey("style")) + let headlineJSON = json?.optionalDictionaryForKey("headline") headlineLabel.setWithJSON(headlineJSON, delegateObject: delegateObject, additionalData: additionalData) let bodyJSON = json?.optionalDictionaryForKey("body") @@ -95,9 +127,7 @@ open class HeadlineBody: ViewConstrainingView { open override func reset() { super.reset() - headlineLabel.styleH2(true) - messageLabel.styleB2(true) - spaceBetweenLabelsConstant = PaddingTwo + stylePageHeader() } public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift new file mode 100644 index 00000000..009189cb --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift @@ -0,0 +1,82 @@ +// +// HeadlineBodyTextButton.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 8/28/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class HeadlineBodyTextButton: ViewConstrainingView { + + let headlineBody = HeadlineBody(frame: .zero) + let textButton = MFTextButton(nil, constrainHeight: true, forWidth: MVMCoreUIUtility.getWidth()) + var spaceBetweenConstant: CGFloat = 0.0 + var spaceBetween: NSLayoutConstraint? + + // MARK: - MVMCoreViewProtocol + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + textButton.updateView(size) + setSpacing() + } + + open override func setupView() { + super.setupView() + guard subviews.count == 0 else { + return + } + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineBody) + view.addSubview(textButton) + 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) + constraint.priority = .defaultHigh + constraint.isActive = true + + spaceBetween = textButton.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) + constraint.priority = .defaultHigh + constraint.isActive = true + } + + // MARK: - Constraining + public func setSpacing() { + if headlineBody.hasText() && (textButton.titleLabel?.text?.count ?? 0) > 0 { + spaceBetween?.constant = spaceBetweenConstant + } else { + spaceBetween?.constant = 0 + } + } + + // 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() + } + + public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 60 + } +} diff --git a/MVMCoreUI/Molecules/Carousel.swift b/MVMCoreUI/Organisms/Carousel.swift similarity index 100% rename from MVMCoreUI/Molecules/Carousel.swift rename to MVMCoreUI/Organisms/Carousel.swift diff --git a/MVMCoreUI/Molecules/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift similarity index 97% rename from MVMCoreUI/Molecules/MoleculeStackView.swift rename to MVMCoreUI/Organisms/MoleculeStackView.swift index ad395eec..b2f70e6d 100644 --- a/MVMCoreUI/Molecules/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -27,11 +27,15 @@ public class StackItem { func update(with json: [AnyHashable: Any]) { spacing = json.optionalCGFloatForKey("spacing") percentage = json["percent"] as? Int - if let alignment = json.optionalStringForKey("verticalAlignment") { + if let alignment = json.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"verticalAlignment"]) { verticalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) + } else { + verticalAlignment = nil } - if let alignment = json.optionalStringForKey("horizontalAlignment") { + if let alignment = json.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"horizontalAlignment"]) { horizontalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) + } else { + horizontalAlignment = nil } } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 0dffc356..86267332 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -41,11 +41,11 @@ @"checkbox" : MVMCoreUICheckBox.class, @"cornerLabels" : CornerLabels.class, @"progressBar": ProgressBar.class, - @"textField": MFTextField.class, + @"multiProgressBar": MultiProgress.class, @"checkbox": MVMCoreUICheckBox.class, @"listItem": MoleculeTableViewCell.class, - @"switchLineItem": SwitchLineItem.class, - @"switch": Switch.class, + @"accordionListItem": AccordionMoleculeTableViewCell.class, + @"switch": MVMCoreUISwitch.class, @"leftRightLabelView": LeftRightLabelView.class, @"actionDetailWithImage": ActionDetailWithImage.class, @"image": MFLoadImageView.class, @@ -55,7 +55,12 @@ @"carouselItem": MoleculeCollectionViewCell.class, @"barsPager": MVMCoreUIPageControl.class, @"scroller": Scroller.class, - @"imageHeadlineBody": ImageHeadlineBody.class + @"imageHeadlineBody": ImageHeadlineBody.class, + @"labelSwitch": LabelSwitch.class, + @"headlineBodySwitch": HeadlineBodySwitch.class, + @"headlineBodyTextButton": HeadlineBodyTextButton.class, + @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class, + @"tabsListItem": TabsTableViewCell.class } mutableCopy]; }); return mapping; diff --git a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h index fa91e0fc..57eb63f5 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h +++ b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h @@ -16,4 +16,8 @@ /// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. - (void)moleculeLayoutUpdated:(nonnull UIView *)molecule; +/// Asks the delegate to add or remove molecules. +- (void)addMolecules:(nonnull NSArray *)molecules sender:(nonnull UITableViewCell *)sender animation:(UITableViewRowAnimation)animation; +- (void)removeMolecules:(nonnull NSArray *)molecules sender:(nonnull UITableViewCell *)sender animation:(UITableViewRowAnimation)animation; + @end diff --git a/MVMCoreUI/Styles/MFStyler.h b/MVMCoreUI/Styles/MFStyler.h index 2ab3c2f2..267a0377 100644 --- a/MVMCoreUI/Styles/MFStyler.h +++ b/MVMCoreUI/Styles/MFStyler.h @@ -93,6 +93,7 @@ B3 -> Legal + (CGFloat)defaultVerticalPaddingForSize:(CGFloat)size; + (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size; + (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size horizontal:(BOOL)horizontal vertical:(BOOL)vertical; ++ (void)setMarginsForView:(nullable UIView *)view size:(CGFloat)size defaultHorizontal:(BOOL)horizontal top:(CGFloat)top bottom:(CGFloat)bottom; //------------------------------------------------- // Returns the fonts for these styles. Scales them as needed by default diff --git a/MVMCoreUI/Styles/MFStyler.m b/MVMCoreUI/Styles/MFStyler.m index dfc9909a..abe6c960 100644 --- a/MVMCoreUI/Styles/MFStyler.m +++ b/MVMCoreUI/Styles/MFStyler.m @@ -96,13 +96,20 @@ CGFloat const LabelWithInternalButtonLineSpace = 2; } + (void)setDefaultMarginsForView:(nullable UIView *)view size:(CGFloat)size horizontal:(BOOL)horizontal vertical:(BOOL)vertical { + CGFloat horizontalPadding = horizontal ? [MFStyler defaultHorizontalPaddingForSize:size] : 0; + CGFloat verticalPadding = vertical ? PaddingDefaultVerticalSpacing3 : 0; [MVMCoreDispatchUtility performBlockOnMainThread:^{ - CGFloat horizontalPadding = horizontal ? [MFStyler defaultHorizontalPaddingForSize:size] : 0; - CGFloat verticalPadding = vertical ? PaddingDefaultVerticalSpacing3 : 0; [MVMCoreUIUtility setMarginsForView:view leading:horizontalPadding top:verticalPadding trailing:horizontalPadding bottom:verticalPadding]; }]; } ++ (void)setMarginsForView:(nullable UIView *)view size:(CGFloat)size defaultHorizontal:(BOOL)horizontal top:(CGFloat)top bottom:(CGFloat)bottom { + CGFloat horizontalPadding = horizontal ? [MFStyler defaultHorizontalPaddingForSize:size] : 0; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + [MVMCoreUIUtility setMarginsForView:view leading:horizontalPadding top:top trailing:horizontalPadding bottom:bottom]; + }]; +} + #pragma mark - 2.0 fonts + (nullable UIFont *)fontH1:(BOOL)genericScaling { diff --git a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings index 2e7677f3..4812d697 100644 --- a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings @@ -8,6 +8,7 @@ //// Accessibility "AccCloseButton" = "Close"; +"swipe_to_select_with_action_hint" = "swipe up or down to select action, then double tap to select."; // Tab "AccTab" = ", tab"; "AccTabHint" = "Double tap to select."; diff --git a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings index bf4f4e47..d6505503 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings @@ -6,6 +6,9 @@ Copyright © 2017 myverizon. All rights reserved. */ +// Accessibility +"swipe_to_select_with_action_hint" = "deslízate hacia arriba o hacia abajo para seleccionar la acción, luego toca dos veces para seleccionar."; + "AccCloseButton" = "Cerrar"; // Tab "AccTab" = ", pestaña"; diff --git a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings index bf4f4e47..d6505503 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings @@ -6,6 +6,9 @@ Copyright © 2017 myverizon. All rights reserved. */ +// Accessibility +"swipe_to_select_with_action_hint" = "deslízate hacia arriba o hacia abajo para seleccionar la acción, luego toca dos veces para seleccionar."; + "AccCloseButton" = "Cerrar"; // Tab "AccTab" = ", pestaña"; diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index abec1b01..0270cb63 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -117,6 +117,42 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { } } + open override func addMolecules(_ molecules: [[AnyHashable : Any]], 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 cell = sender as? MoleculeTableViewCell, let indexPath = self.tableView?.indexPath(for: cell) else { + return + } + var indexPaths: [IndexPath] = [] + for molecule in molecules { + 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() + } + } + + open override func removeMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + var indexPaths: [IndexPath] = [] + for molecule in molecules { + if let removeIndex = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in + return NSDictionary(dictionary: molecule).isEqual(to: moleculeInfo.molecule) + }) { + 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() + } + // 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])? { diff --git a/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m b/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m index 8583cc2e..e51ffc83 100644 --- a/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m +++ b/MVMCoreUI/TopAlert/MVMCoreUITopAlertMainView.m @@ -173,6 +173,7 @@ if (closeButton && !self.closeButton) { self.closeButton = [self addCloseButtonWithAnimationDelegate:animationDelegate]; + [self.closeButton setTintColor:self.contentColor ?:[UIColor whiteColor]]; } else if (!closeButton && self.closeButton) { [self.closeButton removeFromSuperview]; self.closeButton = nil; @@ -235,7 +236,7 @@ if (color) { self.button.layer.borderColor = color.CGColor; [self.button setTitleColor:color forState:UIControlStateNormal]; - [self.closeButton setTitleColor:color forState:UIControlStateNormal]; + [self.closeButton setTintColor:color]; } }]; } diff --git a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m index 41237640..94c6ca85 100644 --- a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m @@ -181,8 +181,9 @@ static const CGFloat VertialShadowOffset = 6; + (nonnull MFCustomButton *)addCloseButtonToView:(UIView *)view action:(ButtonTapBlock)actionBlock verticalCentered:(BOOL)verticalCentered { MFCustomButton *button = [[MFCustomButton alloc] initWithFrame:CGRectZero]; button.translatesAutoresizingMaskIntoConstraints = NO; - [button setImage:[MVMCoreUIUtility imageNamed:@"closeXBlack"] forState:UIControlStateNormal]; - button.titleLabel.font = [MFStyler fontForHeadlineAlternative]; + UIImage *image = [[MVMCoreUIUtility imageNamed:@"closeXBlack"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [button setImage:image forState:UIControlStateNormal]; + [button setTintColor:[UIColor blackColor]]; //accessibility button.accessibilityLabel = [MVMCoreUIUtility hardcodedStringWithKey:@"AccCloseButton"]; diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index e8523a61..4f150783 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -119,7 +119,7 @@ #pragma mark - Sizing + (CGFloat)getWidth { - UIViewController *controller = [MVMCoreUISession sharedGlobal].splitViewController ?: [MVMCoreUISession sharedGlobal].navigationController; + UIViewController *controller = [MVMCoreUISession sharedGlobal].splitViewController.navigationController ?: [MVMCoreUISession sharedGlobal].navigationController; if (controller) { return CGRectGetWidth(controller.view.bounds); } else {