diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 2d45fd63..49776618 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -11,21 +11,26 @@ 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 */; }; 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */; }; + 01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */; }; + 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D902327ECE600EF99AA /* CornerLabels.swift */; }; + 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D922327ECFB00EF99AA /* ProgressBar.swift */; }; + 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */; }; 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 */; }; - 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, ); }; }; @@ -176,7 +181,6 @@ D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */; }; D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */; }; D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; }; - D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */; }; D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */; }; D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */; }; DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; @@ -192,20 +196,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 = ""; }; 0116A4E4228B19640094F3ED /* RadioButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButtonModel.swift; sourceTree = ""; }; + 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeTableViewCell.swift; sourceTree = ""; }; + 01509D902327ECE600EF99AA /* CornerLabels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; + 01509D922327ECFB00EF99AA /* ProgressBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; + 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlineBodyTextButtonSwitch.swift; sourceTree = ""; }; 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 = ""; }; - 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 = ""; }; @@ -360,7 +369,6 @@ D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scroller.swift; sourceTree = ""; }; D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTemplate.swift; sourceTree = ""; }; D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIDelegateObject.swift; sourceTree = ""; }; - D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeTableViewCell.swift; sourceTree = ""; }; D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTableViewController.swift; sourceTree = ""; }; D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListTemplate.swift; sourceTree = ""; }; DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftRightLabelView.swift; sourceTree = ""; }; @@ -395,6 +403,62 @@ path = FormUIHelpers; sourceTree = ""; }; + D224798823142BF2003FCCF9 /* SwitchMolecules */ = { + isa = PBXGroup; + children = ( + 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */, + D22479892314445E003FCCF9 /* LabelSwitch.swift */, + D224798B231450C8003FCCF9 /* HeadlineBodySwitch.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 = ( + 01509D902327ECE600EF99AA /* CornerLabels.swift */, + D224798823142BF2003FCCF9 /* SwitchMolecules */, + ); + path = LeftRightViews; + sourceTree = ""; + }; + D22479902316A9CB003FCCF9 /* Organisms */ = { + isa = PBXGroup; + children = ( + D2A5145E2211DDC100345BFB /* MoleculeStackView.swift */, + D2A6390022CBB1820052ED1F /* Carousel.swift */, + ); + path = Organisms; + sourceTree = ""; + }; + D22479912316A9EF003FCCF9 /* Items */ = { + isa = PBXGroup; + children = ( + 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */, + D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, + D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, + ); + path = Items; + sourceTree = ""; + }; D22D1F582204D2590077CEC0 /* LegacyControllers */ = { isa = PBXGroup; children = ( @@ -440,6 +504,7 @@ D29DF11E21E6851E003B2FB9 /* TopAlert */, D29DF10D21E67A70003B2FB9 /* Atoms */, D29DF10E21E67A77003B2FB9 /* Molecules */, + D22479902316A9CB003FCCF9 /* Organisms */, D29DF0DF21E418B2003B2FB9 /* Templates */, D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */, D29DF0D021E404D4003B2FB9 /* Info.plist */, @@ -482,34 +547,21 @@ 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 */, 01004F2F22721C3800991ECC /* RadioButton.swift */, - D2E1FADC2268B25E00AEFD8C /* MoleculeTableViewCell.swift */, - B8200E142280C4CF007245F4 /* ProgressBar.swift */, - B8200E182281DC1A007245F4 /* ProgressBarWithLabel.swift */, - B8200E182281DC1A007245F4 /* ProgressBarView.swift */, 0116A4E4228B19640094F3ED /* RadioButtonModel.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 = ""; @@ -542,6 +594,7 @@ D29DF11321E6805F003B2FB9 /* UIColor+MFConvenience.m */, D29DF11221E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h */, D29DF11421E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m */, + D22479932316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift */, ); path = Categories; sourceTree = ""; @@ -620,6 +673,7 @@ D29DF17021E69E1F003B2FB9 /* MFCustomButton.m */, D29DF16C21E69E1F003B2FB9 /* PrimaryButton.h */, D29DF17121E69E1F003B2FB9 /* PrimaryButton.m */, + D282AACA2243C61700C46919 /* ButtonView.swift */, D29DF16D21E69E1F003B2FB9 /* MFTextButton.h */, D29DF17221E69E1F003B2FB9 /* MFTextButton.m */, ); @@ -629,6 +683,10 @@ D29DF17D21E69E26003B2FB9 /* Views */ = { isa = PBXGroup; children = ( + 01509D922327ECFB00EF99AA /* ProgressBar.swift */, + D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, + D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, + D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, DBC4391622442196001AB423 /* CaretView.swift */, DBC4391722442197001AB423 /* DashLine.swift */, DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */, @@ -946,6 +1004,7 @@ D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, + D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, 01004F3022721C3800991ECC /* RadioButton.swift in Sources */, D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, @@ -953,6 +1012,7 @@ 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 */, @@ -967,20 +1027,21 @@ D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */, - B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, D2A514672213885800345BFB /* StandardHeaderView.swift in Sources */, DBEFFA04225A829700230692 /* Label.swift in Sources */, D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, + 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, 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 */, - D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, + D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */, @@ -996,12 +1057,12 @@ D2A6390522CBCE160052ED1F /* MoleculeCollectionViewCell.swift in Sources */, D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */, D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */, - 016A1071228122180009D605 /* SwitchLineItem.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 */, @@ -1019,7 +1080,6 @@ 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 */, @@ -1029,11 +1089,13 @@ D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, - B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */, D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, + 01509D8F2327EC6F00EF99AA /* MoleculeTableViewCell.swift in Sources */, 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */, + 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */, D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */, D29DF2CB21E7BFCC003B2FB9 /* MFSizeThreshold.m in Sources */, + 01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */, D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; 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 3e229092..f7d8803c 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/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..a21dd55f 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h @@ -8,10 +8,11 @@ #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 +24,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/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/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 100% rename from MVMCoreUI/Molecules/ImageHeadlineBody.swift rename to MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift 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..b188d45b --- /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, senderIndexPath: indexPath) + } else { + delegateObject?.moleculeDelegate?.removeMolecules?(molecules, senderIndexPath: indexPath) + } + + 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..210eac6f 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") ?? 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/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..ffab9920 --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift @@ -0,0 +1,57 @@ +// +// 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() + addSubview(headlineBody) + 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..754f508b --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift @@ -0,0 +1,59 @@ +// +// 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 + } + headlineBodyTextButton.headlineBody.styleListItem() + addSubview(headlineBodyTextButton) + 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..017eee99 --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift @@ -0,0 +1,57 @@ +// +// 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 + } + addSubview(label) + 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/HeadlineBody.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift similarity index 82% rename from MVMCoreUI/Molecules/HeadlineBody.swift rename to MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift index d3c37261..1077ac0e 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) @@ -87,6 +123,8 @@ open class HeadlineBody: ViewConstrainingView { 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 +133,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..bb638624 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift @@ -0,0 +1,78 @@ +// +// 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 + } + addSubview(headlineBody) + addSubview(textButton) + headlineBody.styleListItem() + + headlineBody.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0).isActive = true + headlineBody.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true + var constraint = layoutMarginsGuide.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: layoutMarginsGuide.leftAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(equalTo: textButton.bottomAnchor).isActive = true + layoutMarginsGuide.rightAnchor.constraint(greaterThanOrEqualTo: textButton.rightAnchor).isActive = true + constraint = layoutMarginsGuide.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 100% rename from MVMCoreUI/Molecules/MoleculeStackView.swift rename to MVMCoreUI/Organisms/MoleculeStackView.swift diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 27112b1a..a3656148 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -41,12 +41,11 @@ @"checkbox" : MVMCoreUICheckBox.class, @"cornerLabels" : CornerLabels.class, @"progressBar": ProgressBar.class, - @"textField": MFTextField.class, @"checkbox": MVMCoreUICheckBox.class, @"radioButton": RadioButton.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, @@ -56,7 +55,11 @@ @"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 } mutableCopy]; }); return mapping; diff --git a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h index fa91e0fc..cfa1504f 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 senderIndexPath:(nonnull NSIndexPath *)indexPath; +- (void)removeMolecules:(nonnull NSArray *)molecules senderIndexPath:(nonnull NSIndexPath *)indexPath; + @end diff --git a/MVMCoreUI/Styles/MFStyler.h b/MVMCoreUI/Styles/MFStyler.h index ea2d4afa..5b73c8e2 100644 --- a/MVMCoreUI/Styles/MFStyler.h +++ b/MVMCoreUI/Styles/MFStyler.h @@ -94,6 +94,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 6dda12a1..18246f7f 100644 --- a/MVMCoreUI/Styles/MFStyler.m +++ b/MVMCoreUI/Styles/MFStyler.m @@ -97,13 +97,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 d73da1ad..b9d21b67 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..7ad011b3 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -117,6 +117,36 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { } } + open override func addMolecules(_ molecules: [[AnyHashable: Any]], senderIndexPath indexPath: IndexPath) { + var indexPaths: [IndexPath] = [] + var moleculeList: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])] = [] + for (index, molecule) in molecules.enumerated() { + if let info = getMoleculeInfo(with: molecule) { + moleculeList.append(info) + indexPaths.append(IndexPath(row: indexPath.row + 1 + index, section: 0)) + tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + } + } + moleculesInfo?.insert(contentsOf: moleculeList, at: indexPath.row + 1) + tableView?.insertRows(at: indexPaths, with: .automatic) + } + + open override func removeMolecules(_ molecules: [[AnyHashable: Any]], senderIndexPath indexPath: IndexPath) { + guard let moleculesList = moleculesInfo else { + return + } + var indexPaths: [IndexPath] = [] + for (index, moleculeInfo) in moleculesList.enumerated() { + if molecules.contains(where: { (molecule) -> Bool in + return NSDictionary(dictionary: molecule).isEqual(to: moleculeInfo.molecule) + }) { + indexPaths.append(IndexPath(row: index, section: 0)) + moleculesInfo?.remove(at: index) + } + } + tableView?.deleteRows(at: indexPaths, with: .automatic) + } + // 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 {