diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index aed43ac1..7fda38f1 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -19,8 +19,6 @@ D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */; }; D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */; }; - D22D1F5B2204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F592204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D22D1F5C2204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F5A2204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.m */; }; D28B4F8A21FF967C00712C7A /* MVMCoreUIObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D28B4F8821FF967C00712C7A /* MVMCoreUIObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D28B4F8921FF967C00712C7A /* MVMCoreUIObject.m */; }; D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */; }; @@ -34,8 +32,6 @@ D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */ = {isa = PBXBuildFile; fileRef = D29770FA21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m */; }; D29770FD21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29770FB21F7C77400B2F0D0 /* MVMCoreUITextFieldView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF0D121E404D4003B2FB9 /* MVMCoreUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF0E221E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF0E021E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D29DF0E321E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF0E121E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.m */; }; D29DF0E621E4F3C7003B2FB9 /* MVMCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */; }; D29DF11521E6805F003B2FB9 /* UIColor+MFConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF11121E6805F003B2FB9 /* UIColor+MFConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF11621E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF11221E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -151,6 +147,15 @@ D29DF32521ED0DA2003B2FB9 /* TextButtonView.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF32321ED0DA2003B2FB9 /* TextButtonView.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF32C21EE8736003B2FB9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D29DF32821EE8736003B2FB9 /* Localizable.strings */; }; D29DF32E21EE8C3D003B2FB9 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D29DF32D21EE8C3D003B2FB9 /* Media.xcassets */; }; + D2A514582211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */; }; + D2A5145D2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D2A5145F2211DDC100345BFB /* MVMCoreUIMoleculeStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5145E2211DDC100345BFB /* MVMCoreUIMoleculeStackView.swift */; }; + D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */; }; + D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */; }; + D2A514672213885800345BFB /* MVMCoreUIHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A514662213885800345BFB /* MVMCoreUIHeaderView.swift */; }; + D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; }; + D2A5146D2214C1E400345BFB /* LegacyLargeHeaderSingleLabelTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A5146C2214C1E400345BFB /* LegacyLargeHeaderSingleLabelTemplate.swift */; }; D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */; }; D2C5001D21F8EE67001DA659 /* LabelWithInternalButton.h in Headers */ = {isa = PBXBuildFile; fileRef = D2C5001B21F8EE66001DA659 /* LabelWithInternalButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -170,8 +175,6 @@ D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUISwitch.m; sourceTree = ""; }; D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIStackableViewController.h; sourceTree = ""; }; D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIStackableViewController.m; sourceTree = ""; }; - D22D1F592204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIThreeLayerViewController.h; sourceTree = ""; }; - D22D1F5A2204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIThreeLayerViewController.m; sourceTree = ""; }; D28B4F8821FF967C00712C7A /* MVMCoreUIObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIObject.h; sourceTree = ""; }; D28B4F8921FF967C00712C7A /* MVMCoreUIObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIObject.m; sourceTree = ""; }; D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopLabelsView.m; sourceTree = ""; }; @@ -187,8 +190,6 @@ D29DF0CC21E404D4003B2FB9 /* MVMCoreUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MVMCoreUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D29DF0CF21E404D4003B2FB9 /* MVMCoreUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUI.h; sourceTree = ""; }; D29DF0D021E404D4003B2FB9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D29DF0E021E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUILargeHeaderSingleLabelTemplate.h; sourceTree = ""; }; - D29DF0E121E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUILargeHeaderSingleLabelTemplate.m; sourceTree = ""; }; D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MVMCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D29DF11121E6805F003B2FB9 /* UIColor+MFConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIColor+MFConvenience.h"; sourceTree = ""; }; D29DF11221E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSLayoutConstraint+MFConvenience.h"; sourceTree = ""; }; @@ -306,6 +307,15 @@ D29DF32A21EE8736003B2FB9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; D29DF32B21EE8736003B2FB9 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; D29DF32D21EE8C3D003B2FB9 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; + D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIMoleculeMappingObject.h; sourceTree = ""; }; + D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIMoleculeMappingObject.m; sourceTree = ""; }; + D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIMoleculeViewProtocol.h; sourceTree = ""; }; + D2A5145E2211DDC100345BFB /* MVMCoreUIMoleculeStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIMoleculeStackView.swift; sourceTree = ""; }; + D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackTemplate.swift; sourceTree = ""; }; + D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeStackCenteredTemplate.swift; sourceTree = ""; }; + D2A514662213885800345BFB /* MVMCoreUIHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIHeaderView.swift; sourceTree = ""; }; + D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerViewController.swift; sourceTree = ""; }; + D2A5146C2214C1E400345BFB /* LegacyLargeHeaderSingleLabelTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyLargeHeaderSingleLabelTemplate.swift; sourceTree = ""; }; D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewControllerMappingObject.h; sourceTree = ""; }; D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIViewControllerMappingObject.m; sourceTree = ""; }; D2C5001B21F8EE66001DA659 /* LabelWithInternalButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LabelWithInternalButton.h; sourceTree = ""; }; @@ -380,8 +390,9 @@ isa = PBXGroup; children = ( 01DF566F21FA5AB300CC099B /* MVMCoreUITextFieldListFormViewController.swift */, - D29DF0E021E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.h */, - D29DF0E121E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.m */, + D2A5146C2214C1E400345BFB /* LegacyLargeHeaderSingleLabelTemplate.swift */, + D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */, + D2A514622213643100345BFB /* MoleculeStackCenteredTemplate.swift */, ); path = Templates; sourceTree = ""; @@ -413,6 +424,9 @@ D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, D29770F621F7C73800B2F0D0 /* PrimaryButtonView.h */, D29770F721F7C73800B2F0D0 /* PrimaryButtonView.m */, + D2A514662213885800345BFB /* MVMCoreUIHeaderView.swift */, + D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, + D2A5145E2211DDC100345BFB /* MVMCoreUIMoleculeStackView.swift */, ); path = Molecules; sourceTree = ""; @@ -432,8 +446,7 @@ D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */, D29DF2CC21E7C104003B2FB9 /* MFLoadingViewController.h */, D29DF2CD21E7C104003B2FB9 /* MFLoadingViewController.m */, - D22D1F592204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.h */, - D22D1F5A2204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.m */, + D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */, ); path = BaseControllers; sourceTree = ""; @@ -620,6 +633,8 @@ D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */, D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */, D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */, + D2A514562211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h */, + D2A514572211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m */, ); path = OtherHandlers; sourceTree = ""; @@ -703,6 +718,7 @@ D29DF29B21E7ADB9003B2FB9 /* StackableViewController.h in Headers */, D29770F421F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.h in Headers */, D29DF15421E69760003B2FB9 /* MVMCoreUIPanelButtonProtocol.h in Headers */, + D2A514582211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.h in Headers */, D29DF0D121E404D4003B2FB9 /* MVMCoreUI.h in Headers */, D29DF29A21E7ADB8003B2FB9 /* MFProgrammaticTableViewController.h in Headers */, D29DF25621E6A177003B2FB9 /* MFTextFieldSubclassExtension.h in Headers */, @@ -710,7 +726,6 @@ D29DF2BC21E7BEA4003B2FB9 /* TopTabbar.h in Headers */, D29DF25921E6A22D003B2FB9 /* MFButtonProtocol.h in Headers */, D22D1F46220496A30077CEC0 /* MVMCoreUISwitch.h in Headers */, - D22D1F5B2204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.h in Headers */, D22D1F1E220343560077CEC0 /* MVMCoreUICheckMarkView.h in Headers */, D29DF28421E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.h in Headers */, D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */, @@ -720,7 +735,6 @@ D29DF28B21E7AC2B003B2FB9 /* ViewConstrainingView.h in Headers */, D29DF2B321E7B76D003B2FB9 /* MFLoadingSpinner.h in Headers */, D29DF28921E7AC2B003B2FB9 /* MFLabel.h in Headers */, - D29DF0E221E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.h in Headers */, D29DF32521ED0DA2003B2FB9 /* TextButtonView.h in Headers */, D29DF25021E6A177003B2FB9 /* MFDigitTextBox.h in Headers */, D29DF2C621E7BF57003B2FB9 /* MFTabBarInteractor.h in Headers */, @@ -736,6 +750,7 @@ D29DF16221E69996003B2FB9 /* MFViewController.h in Headers */, D29DF13121E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.h in Headers */, D29DF2C421E7BF57003B2FB9 /* MFTabBarSwipeAnimator.h in Headers */, + D2A5145D2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h in Headers */, D28B4F8A21FF967C00712C7A /* MVMCoreUIObject.h in Headers */, D29DF2CA21E7BFC8003B2FB9 /* MFSizeThreshold.h in Headers */, D29770F821F7C73800B2F0D0 /* PrimaryButtonView.h in Headers */, @@ -857,18 +872,20 @@ D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, 01DF567021FA5AB300CC099B /* MVMCoreUITextFieldListFormViewController.swift in Sources */, + D2A5145F2211DDC100345BFB /* MVMCoreUIMoleculeStackView.swift in Sources */, D29DF27621E79E81003B2FB9 /* MVMCoreUILoggingHandler.m in Sources */, D29DF24D21E6A177003B2FB9 /* MFTextField.m in Sources */, D29DF2A221E7AF4E003B2FB9 /* MVMCoreUIUtility.m in Sources */, D29DF12B21E6851E003B2FB9 /* MVMCoreUITopAlertExpandableView.m in Sources */, D29DF25421E6A177003B2FB9 /* MFMdnTextField.m in Sources */, D29DF26521E6A9D9003B2FB9 /* MFTransparentGIFView.m in Sources */, + D2A514672213885800345BFB /* MVMCoreUIHeaderView.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, - D22D1F5C2204D2C00077CEC0 /* MVMCoreUIThreeLayerViewController.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, + D2A5146D2214C1E400345BFB /* LegacyLargeHeaderSingleLabelTemplate.swift in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */, D29DF17A21E69E1F003B2FB9 /* MFCustomButton.m in Sources */, @@ -879,6 +896,7 @@ D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationControllerViewController.m in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, 01DF55E021F8FAA800CC099B /* MFTextFieldListView.swift in Sources */, + D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, D29DF2C921E7BFC6003B2FB9 /* MFSizeObject.m in Sources */, D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */, D29DF2A521E7B2A0003B2FB9 /* MFCaretView.m in Sources */, @@ -892,18 +910,20 @@ D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, + D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D29DF17821E69E1F003B2FB9 /* MFCaretButton.m in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, - D29DF0E321E4F3B6003B2FB9 /* MVMCoreUILargeHeaderSingleLabelTemplate.m in Sources */, D2C5001E21F8EE67001DA659 /* LabelWithInternalButton.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */, + D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */, D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, + D2A514592211C53C00345BFB /* MVMCoreUIMoleculeMappingObject.m in Sources */, D22D1F1B220341F60077CEC0 /* MVMCoreUICheckBox.m in Sources */, D29DF2CB21E7BFCC003B2FB9 /* MFSizeThreshold.m in Sources */, D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */, diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.h b/MVMCoreUI/Atoms/Buttons/PrimaryButton.h index b451a196..ba1c06d2 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.h +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton.h @@ -10,6 +10,7 @@ #import #import #import +#import typedef enum : NSUInteger { PrimaryButtonTypeRed, @@ -19,15 +20,24 @@ typedef enum : NSUInteger { // 2.0 PrimaryButtonTypeBlack, PrimaryButtonTypeWhite, - + PrimaryButtonTypeCustom } PrimaryButtonType; static CGFloat const PrimaryButtonHeight = 42.0; static CGFloat const PrimaryButtonSmallHeight = 30.0; -@interface PrimaryButton : MFCustomButton +@interface PrimaryButton : MFCustomButton + +@property (nonatomic, readonly, assign) PrimaryButtonType primaryButtonType; //use reset function to set + +// For custom type. +@property (nonatomic, strong, nullable) UIColor *fillColor; +@property (nonatomic, strong, nullable) UIColor *borderColor; +@property (nonatomic, strong, nullable) UIColor *textColor; +@property (nonatomic, strong, nullable) UIColor *disabledFillColor; +@property (nonatomic, strong, nullable) UIColor *disabledBorderColor; +@property (nonatomic, strong, nullable) UIColor *disabledTextColor; -@property (nonatomic, readonly, assign) PrimaryButtonType primaryButtonType; //use reset function to set @property (nullable, copy, nonatomic) BOOL(^extraValidationBlock)(void); // Set as no to make button filled, set as yes to make button bordered. diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m b/MVMCoreUI/Atoms/Buttons/PrimaryButton.m index c780aef8..3e44f2bd 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton.m @@ -13,6 +13,7 @@ #import "UIColor+MFConvenience.h" @import MVMCore.MVMCoreDispatchUtility; @import MVMCore.MVMCoreGetterUtility; +@import MVMCore.NSDictionary_MFConvenience; @interface PrimaryButton () @@ -32,6 +33,8 @@ @implementation PrimaryButton +#pragma mark - Sizing + - (MFSizeObject *)innerPadding { return [MFSizeObject sizeObjectWithStandardSize:24.0 standardiPadPortraitSize:32.0 iPadProLandscapeSize:36.0]; } @@ -110,7 +113,11 @@ } else { [self setAsInactiveGray]; } + break; } + case PrimaryButtonTypeCustom: + [self setAsCustom]; + break; default: break; } @@ -215,6 +222,10 @@ } else { [self setAsInactiveGray]; } + break; + case PrimaryButtonTypeCustom: + [self setAsCustom]; + break; default: break; } @@ -394,9 +405,39 @@ } } +- (void)setAsCustom { + if (self.enabled) { + self.backgroundColor = self.fillColor ?: [UIColor clearColor]; + if (self.bordered) { + self.layer.borderWidth = 1; + self.layer.borderColor = self.borderColor.CGColor ?: [UIColor blackColor].CGColor; + [self setTitleColor:(self.textColor ?: [UIColor colorWithCGColor:self.layer.borderColor]) forState:UIControlStateNormal]; + } else { + self.layer.borderWidth = 0; + self.layer.borderColor = [UIColor clearColor].CGColor; + [self setTitleColor:(self.textColor ?: [UIColor blackColor]) forState:UIControlStateNormal]; + } + } else { + if (self.disabledBorderColor || self.bordered) { + self.layer.borderWidth = 1; + self.layer.borderColor = self.disabledBorderColor.CGColor ?: [UIColor mfPrimaryWhiteButtonDisableColor].CGColor; + self.backgroundColor = self.disabledFillColor ?: [UIColor clearColor]; + [self setTitleColor:(self.disabledTextColor ?: [UIColor colorWithCGColor:self.layer.borderColor]) forState:UIControlStateNormal]; + } else { + self.layer.borderWidth = 0; + self.layer.borderColor = [UIColor clearColor].CGColor; + self.backgroundColor = self.disabledFillColor ?: [UIColor mfPrimaryWhiteButtonDisableColor]; + [self setTitleColor:(self.disabledTextColor ?: [UIColor blackColor]) forState:UIControlStateNormal]; + } + } +} + - (void)setBordered:(BOOL)bordered { if (bordered != _bordered) { - if (bordered) { + if (self.buttonType == PrimaryButtonTypeCustom) { + _bordered = bordered; + [self setAsCustom]; + } else if (bordered) { self.layer.borderWidth = 1; [self setTitleColor:self.backgroundColor forState:UIControlStateNormal]; self.layer.borderColor = self.backgroundColor.CGColor; @@ -521,6 +562,25 @@ #pragma mark - For Subclassing +- (void)setWithJSON:(NSDictionary *)json delegate:(NSObject *)delegate additionalData:(nullable NSDictionary *)additionalData { + self.primaryButtonType = PrimaryButtonTypeCustom; + NSString *color = [json string:@"fillColor"]; + self.fillColor = (color ? [UIColor mfGetColorForHex:color] : nil); + color = [json string:@"textColor"]; + self.textColor = (color ? [UIColor mfGetColorForHex:color] : nil); + color = [json string:@"borderColor"]; + self.borderColor = (color ? [UIColor mfGetColorForHex:color] : nil); + _bordered = self.borderColor != nil; + color = [json string:@"disabledFillColor"]; + self.disabledFillColor = (color ? [UIColor mfGetColorForHex:color] : nil); + color = [json string:@"disabledTextColor"]; + self.disabledTextColor = (color ? [UIColor mfGetColorForHex:color] : nil); + color = [json string:@"disabledBorderColor"]; + self.disabledBorderColor = (color ? [UIColor mfGetColorForHex:color] : nil); + [self setAsSmallButton:[json boolForKey:@"small"]]; + [self setWithActionMap:json actionDelegate:([delegate conformsToProtocol:@protocol(MVMCoreActionDelegateProtocol)] ? (NSObject *)delegate : nil) additionalData:additionalData buttonDelegate:([delegate conformsToProtocol:@protocol(ButtonDelegateProtocol)] ? (id )delegate : nil)]; +} + - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self pinHeight]; diff --git a/MVMCoreUI/Atoms/Views/MFView.h b/MVMCoreUI/Atoms/Views/MFView.h index e985d1a9..eb76b858 100644 --- a/MVMCoreUI/Atoms/Views/MFView.h +++ b/MVMCoreUI/Atoms/Views/MFView.h @@ -7,9 +7,10 @@ // #import +#import @import MVMCore.MVMCoreViewProtocol; -@interface MFView : UIView +@interface MFView : UIView // Called in the initialization functions. Can setup ui here. - (void)setupView; diff --git a/MVMCoreUI/Atoms/Views/MFView.m b/MVMCoreUI/Atoms/Views/MFView.m index ffc25cfe..afe3476a 100644 --- a/MVMCoreUI/Atoms/Views/MFView.m +++ b/MVMCoreUI/Atoms/Views/MFView.m @@ -41,5 +41,7 @@ - (void)updateView:(CGFloat)size { } +- (void)setWithJSON:(NSDictionary *)json delegate:(NSObject *)delegate additionalData:(nullable NSDictionary *)additionalData { +} @end diff --git a/MVMCoreUI/BaseControllers/MVMCoreUIThreeLayerViewController.h b/MVMCoreUI/BaseControllers/MVMCoreUIThreeLayerViewController.h deleted file mode 100644 index 4304fd23..00000000 --- a/MVMCoreUI/BaseControllers/MVMCoreUIThreeLayerViewController.h +++ /dev/null @@ -1,71 +0,0 @@ -// -// MVMCoreUIThreeLayerViewController.h -// MVMCoreUI -// -// Created by Scott Pfeil on 2/1/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -#import -@class TopLabelsView; -@class PrimaryButton; - -@interface MVMCoreUIThreeLayerViewController : MVMCoreUIStackableViewController - -@property (nullable, weak, nonatomic) TopLabelsView *topLabelsView; -@property (nullable, weak, nonatomic) PrimaryButton *primaryButton; -@property (nullable, weak, nonatomic) PrimaryButton *secondaryButton; -@property (nullable, weak, nonatomic, readonly) UIView *bottomView; -@property (nullable, weak, nonatomic, readonly) UIView *topView; -@property (nullable, weak, nonatomic) UIView *viewInScroll; -@property (nullable, weak, nonatomic) UIView *viewOutOfScroll; -@property (nullable, strong, nonatomic) UIView *safeAreaView; -@property (nonatomic, readonly) BOOL customBottomView; - -// Set to overwrite which view is the top edge and/or bottom edge of the between view. must be added to the ui and constrained before buildViewsBetweenLabelsAndButtons. -// Use these to create views that are pinned near the labels or buttons and are separate from any centered content. Add and set in buildInAdditionalViewsBeforeCenteredContent. -@property (nullable, weak, nonatomic) UIView *topBetweenEdgeView; -@property (nullable, weak, nonatomic) UIView *bottomBetweenEdgeView; - -// The constraint connecting the topLabelsView to the content view. -@property (nullable, strong, nonatomic) NSLayoutConstraint *topConstraintForTopView; - -#pragma mark - Subclass - -// Allow you to add any additional ui before buildViewsBetweenLabelsAndButtons gets called. Can use this to set the topBetweenEdgeView or bottomBetweenEdgeView -- (void)buildInAdditionalViewsBeforeCenteredContent; - -// For subclassing. Should return all the views that will be in between labels and buttons. Override standardSpaceAroundUIObject to handle spacing. -- (nullable NSArray *)buildViewsBetweenLabelsAndButtons; - -//********* -// If both are subclassed to return a value, then the buttons will not be pinned towards the bottom. - -// If anything is returned, the class will fill in the space between the top labels and views with the passed in value. -- (nullable NSNumber *)spaceAboveBetweenView; - -// If anything is returned, the class will fill in the space between the views and bottom buttons with the passed in value. -- (nullable NSNumber *)spaceBelowBetweenView; - -// Can overwrite the default padding for labels and buttons. -- (void)updateTopLabelsPadding:(CGFloat)size; -- (void)updateBottomButtonsPadding:(CGFloat)size; - -// default button map will automatically get from response, you can also overide this to have your own button map -- (nullable NSDictionary *)secondaryButtonMap; -- (nullable NSDictionary *)primaryButtonMap; - -// Should not sub class it, for most cases, the headline and message will be in page map, but who knows how server wants to send them in certain pages -- (nullable NSDictionary *)mapForTopLabels; - -// Use these if you want to replace the top labels or bottom button views with your own views. -- (nullable UIView *)useCustomViewInsteadOfLabels; -- (nullable UIView *)useCustomViewInsteadOfButtons; - -// Can override if the buttons should be outside of the scroll or not. Default is no. -- (BOOL)bottomViewOutsideOfScroll; - -// Build above the button view -- (nullable UIView *)buttonsAccessoryView; - -@end diff --git a/MVMCoreUI/BaseControllers/MVMCoreUIThreeLayerViewController.m b/MVMCoreUI/BaseControllers/MVMCoreUIThreeLayerViewController.m deleted file mode 100644 index 532515ad..00000000 --- a/MVMCoreUI/BaseControllers/MVMCoreUIThreeLayerViewController.m +++ /dev/null @@ -1,402 +0,0 @@ -// -// MVMCoreUIThreeLayerViewController.m -// MVMCoreUI -// -// Created by Scott Pfeil on 2/1/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -#import "MVMCoreUIThreeLayerViewController.h" -#import "ViewConstrainingView.h" -#import "MFStyler.h" -#import "MFSizeObject.h" -#import "TopLabelsView.h" -#import "PrimaryButtonView.h" -#import "MVMCoreUICommonViewsUtility.h" -#import "UIColor+MFConvenience.h" -#import "NSLayoutConstraint+MFConvenience.h" -#import "MVMCoreUIConstants.h" -@import MVMAnimationFramework; - -@interface MVMCoreUIThreeLayerViewController () - -@property (nullable, strong, nonatomic) NSLayoutConstraint *heightConstraint; -@property (nullable, weak, nonatomic) UIView *topView; -@property (nullable, weak, nonatomic) UIView *bottomView; -@property (nonatomic, readwrite) BOOL customBottomView; -@property (nullable, weak, nonatomic) NSArray *middleViews; - -@property (nullable, weak, nonatomic) UIView *betweenView; -@property (nullable, weak, nonatomic) ViewConstrainingView *bottomAccessoryView; - -// Adds the button view to the screen. Out of the scroll or in. -- (void)addViewOutsideOfScrollView:(UIView *)bottomView; -- (void)addViewToContentView:(UIView *)bottomView; - -@end - -@implementation MVMCoreUIThreeLayerViewController - -- (void)updateViews { - [super updateViews]; - - // Update top labels and bottom buttons. - CGFloat width = CGRectGetWidth(self.view.bounds); - if ([self.topView respondsToSelector:@selector(updateView:)]) { - [((UIView *)self.topView) updateView:width]; - } - if ([self.bottomView respondsToSelector:@selector(updateView:)]) { - [((UIView *)self.bottomView) updateView:width]; - } - [self updateTopLabelsPadding:width]; - [self updateBottomButtonsPadding:width]; - [self.bottomAccessoryView updateView:width]; - [self.bottomAccessoryView setPinConstantsWithInsets:[self spaceAroundUIObject:self.bottomAccessoryView size:width]]; - -} - -- (void)updateTopLabelsPadding:(CGFloat)size { - if (self.topLabelsView) { - CGFloat horizontalPadding = [MFStyler defaultHorizontalPaddingForSize:size]; - self.topLabelsView.topLabelConstraint.constant = PaddingFive; - self.topLabelsView.bottomLabelConstraint.constant = PaddingFive; - [self.topLabelsView setLeftConstant:horizontalPadding]; - [self.topLabelsView setRightConstant:horizontalPadding]; - } -} - -- (void)updateBottomButtonsPadding:(CGFloat)size { - if (!self.customBottomView) { - PrimaryButtonView *buttonView = (PrimaryButtonView *)self.bottomView; - if (self.secondaryButton || self.primaryButton) { - // Smaller space for smaller devices. Also, top is 0 by default when in scroll. - CGFloat verticalSpacing = [[MFSizeObject sizeObjectWithStandardSize:PaddingDefaultVerticalSpacing smalliPhoneSize:PaddingDefault] getValueBasedOnScreenSize]; - buttonView.leftPin.constant = PaddingDefaultHorizontalSpacing; - buttonView.rightPin.constant = PaddingDefaultHorizontalSpacing; - buttonView.topPin.constant = [self bottomViewOutsideOfScroll] ? verticalSpacing : 0; - buttonView.bottomPin.constant = verticalSpacing; - } else { - buttonView.topPin.constant = 0; - buttonView.bottomPin.constant = 0; - buttonView.leftPin.constant = 0; - buttonView.rightPin.constant = 0; - } - } -} - -- (void)newDataBuildScreen { - [super newDataBuildScreen]; - - // Removes the bottom view out of scroll if it is there. - [self.viewOutOfScroll removeFromSuperview]; - [MVMCoreUIStackableViewController removeUIViews:[self.contentView subviews]]; - - // Checks if we are using a different object than top labels. - UIView *topView = [self useCustomViewInsteadOfLabels]; - self.topView = topView; - if (!topView) { - // Sets up the labels toward the top of the screen. - TopLabelsView *topLabelsView = [[TopLabelsView alloc] initWithFrame:CGRectZero]; - topLabelsView.separatorView.hidden = NO; - self.topLabelsView = topLabelsView; - topView = topLabelsView; - self.topView = topView; - [topLabelsView setWithJSON:[self mapForTopLabels]]; - } - [self.contentView addSubview:topView]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topView)]]; - self.topConstraintForTopView = [NSLayoutConstraint constraintWithItem:topView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; - self.topConstraintForTopView.active = YES; - - // Checks if we are using a different object than the bottom buttons. - UIView *bottomView = [self useCustomViewInsteadOfButtons]; - self.customBottomView = (bottomView != nil); - if (!self.customBottomView) { - - // Sets up the buttons/button. - NSDictionary *primaryButtonDictionary = [self primaryButtonMap]; - NSDictionary *secondaryButtonDictionary = [self secondaryButtonMap]; - PrimaryButtonView *buttonView = [[PrimaryButtonView alloc] initWithPrimaryButtonMap:primaryButtonDictionary secondaryButtonMap:secondaryButtonDictionary actionDelegate:self additionalData:nil buttonDelegate:self]; - self.secondaryButton = buttonView.secondaryButton; - self.primaryButton = buttonView.primaryButton; - bottomView = buttonView; - self.bottomView = bottomView; - } else { - self.bottomView = bottomView; - } - - // Adds the bottom view outside the scroll if directed. - [self.safeAreaView removeFromSuperview]; - if ([self bottomViewOutsideOfScroll]) { - - if (!self.customBottomView) { - bottomView.backgroundColor = [UIColor mfBackgroundGray]; - } - [self addViewOutsideOfScrollView:bottomView]; - self.viewOutOfScroll = bottomView; - - // Sets this so that the button view added to the content view will be empty. - bottomView = [ViewConstrainingView emptyView]; - } - - // Adds either the bottom view or a blank view inside the scroll. - self.viewInScroll = bottomView; - [self addViewToContentView:bottomView]; - - - // Allows addition of any custom ui before custom center view. - [self buildInAdditionalViewsBeforeCenteredContent]; - - - // Positions the views in between the labels and buttons. - UIView *viewBetween = nil; - NSArray *views = [self buildViewsBetweenLabelsAndButtons]; - self.middleViews = views; - if (views.count > 0) { - - viewBetween = [MVMCoreUICommonViewsUtility commonView]; - [self generateFormView:viewBetween withUIArrayForConstrainingViews:views]; - [self.contentView addSubview:viewBetween]; - - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[viewBetween]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewBetween)]]; - [viewBetween setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - } - self.betweenView = viewBetween; - - // This whole section is for handling vertical spacing. - //------------------------------------------------------------- - UIView *topBetweenEdgeView = topView; - if (self.topBetweenEdgeView) { - topBetweenEdgeView = self.topBetweenEdgeView; - } - - UIView *bottomBetweenEdgeView = bottomView; - if (self.bottomBetweenEdgeView) { - bottomBetweenEdgeView = self.bottomBetweenEdgeView; - } - - // Sets the height so when there is not enough content, the bottom buttons will stay pinned. But if there is too much content, the constraint has low priority so the screen will scroll. Only made active in certain conditions. - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]; - heightConstraint.priority = UILayoutPriorityDefaultLow; - self.heightConstraint = heightConstraint; - - if (viewBetween) { - - // These spacer views are what causes the buttons to be pinned to the bottom. - NSNumber *spaceAbove = [self spaceAboveBetweenView]; - NSNumber *spaceBelow = [self spaceBelowBetweenView]; - - if (spaceAbove && spaceBelow) { - - // Both top and bottom space set, buttons not pinned to bottom. - NSDictionary *verticalMetrics = @{@"top":spaceAbove,@"bottom":spaceBelow}; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-top-[viewBetween]-bottom-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,viewBetween,bottomBetweenEdgeView)]]; - } else { - - // Needs the height constraint - heightConstraint.active = YES; - - if (!spaceAbove && !spaceBelow) { - - // No set space above or below, make the spacers the same height with a default minimum. - UIView *topSpacer = [MVMCoreUICommonViewsUtility commonView]; - UIView *bottomSpacer = [MVMCoreUICommonViewsUtility commonView]; - [self.contentView addSubview:topSpacer]; - [self.contentView addSubview:bottomSpacer]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topSpacer)]]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomSpacer)]]; - - NSLayoutConstraint *sameHeightSpacer = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]; - sameHeightSpacer.active = YES; - - NSLayoutConstraint *minimumHeightSpacer = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing]; - minimumHeightSpacer.active = YES; - - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[topSpacer]-0-[viewBetween]-0-[bottomSpacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topBetweenEdgeView,topSpacer,viewBetween,bottomSpacer,bottomBetweenEdgeView)]]; - } else if (spaceAbove) { - - // Space above is set, space below is free. - UIView *bottomSpacer = [MVMCoreUICommonViewsUtility commonView]; - [self.contentView addSubview:bottomSpacer]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomSpacer)]]; - - NSLayoutConstraint *bottomSpacerHeight = [NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing]; - bottomSpacerHeight.active = YES; - - NSDictionary *verticalMetrics = @{@"top":spaceAbove}; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-top-[viewBetween]-0-[bottomSpacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,viewBetween,bottomSpacer,bottomBetweenEdgeView)]]; - } else if (spaceBelow) { - - // Space below is set, space above is free. - UIView *topSpacer = [MVMCoreUICommonViewsUtility commonView]; - [self.contentView addSubview:topSpacer]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topSpacer)]]; - - NSLayoutConstraint *topSpacerHeight = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing]; - topSpacerHeight.active = YES; - - NSDictionary *verticalMetrics = @{@"bottom":spaceBelow}; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[topSpacer]-0-[viewBetween]-bottom-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,topSpacer,viewBetween,bottomBetweenEdgeView)]]; - } - } - } else { - - // No views in between. Just messages and buttons - UIView *spacer = [MVMCoreUICommonViewsUtility commonView]; - [self.contentView addSubview:spacer]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[spacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(spacer)]]; - - // Needs the height constraint - heightConstraint.active = YES; - - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[spacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topBetweenEdgeView,spacer,bottomBetweenEdgeView)]]; - } -} - -- (void)updateViewConstraints { - [super updateViewConstraints]; - - // Updates for ios 11 - if (@available(iOS 11.0, *)) { - if (self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { - self.heightConstraint.constant = -self.scrollView.adjustedContentInset.top - self.scrollView.adjustedContentInset.bottom; - } else { - self.heightConstraint.constant = -self.scrollView.contentInset.top - self.scrollView.contentInset.bottom; - } - } else { - self.heightConstraint.constant = -self.scrollView.contentInset.top - self.scrollView.contentInset.bottom; - } -} - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do any additional setup after loading the view. -} - -- (UIEdgeInsets)spaceAroundUIObject:(nullable id)object size:(CGFloat)size { - CGFloat horizontal = [MFStyler defaultHorizontalPaddingForSize:size]; - if (self.bottomAccessoryView == object) { - return UIEdgeInsetsMake(0, horizontal, PaddingDefaultVerticalSpacing, horizontal); - } - return UIEdgeInsetsMake(0, horizontal, 0, horizontal); -} - -- (void)addViewToContentView:(UIView *)bottomView { - - self.bottomConstraint.active = YES; - - // Buttons will be at the bottom of the content view. - [self.contentView addSubview:bottomView]; - - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[bottomView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]]; - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]]; -} - -- (void)addViewOutsideOfScrollView:(UIView *)bottomView { - - self.bottomConstraint.active = NO; - - // Buttons will be outside of the scrolling view. - [self.view addSubview:bottomView]; - - UIScrollView *scrollview = self.scrollView; - if (@available(iOS 11.0, *)) { - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]]; - [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:bottomView.bottomAnchor].active = YES; - - UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; - safeAreaView.backgroundColor = bottomView.backgroundColor; - self.safeAreaView = safeAreaView; - } else { - // Fallback on earlier versions - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]]; - } - [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]]; -} - -#pragma mark - Can Subclass These - -- (nullable UIView *)buttonsAccessoryView { - return nil; -} - -- (void)buildInAdditionalViewsBeforeCenteredContent { - - UIView *accessoryView = [self buttonsAccessoryView]; - if (accessoryView) { - accessoryView.translatesAutoresizingMaskIntoConstraints = NO; - ViewConstrainingView *constrainingView = [ViewConstrainingView viewConstrainingView:accessoryView]; - constrainingView.updateViewHorizontalDefaults = YES; - [self.contentView addSubview:constrainingView]; - self.bottomBetweenEdgeView = constrainingView; - self.bottomAccessoryView = constrainingView; - - [NSLayoutConstraint constraintPinLeftSubview:constrainingView leftConstant:0]; - [NSLayoutConstraint constraintPinRightSubview:constrainingView rightConstant:0]; - [NSLayoutConstraint constraintPinFirstView:constrainingView toSecondView:self.viewInScroll - withConstant:0 directionVertical:YES]; - } else { - self.bottomAccessoryView = nil; - } -} - -- (nullable NSArray *)buildViewsBetweenLabelsAndButtons { - return nil; -} - -- (nullable NSNumber *)spaceAboveBetweenView { - return nil; -} - -- (nullable NSNumber *)spaceBelowBetweenView { - return nil; -} - -- (NSDictionary *)primaryButtonMap { - NSDictionary *buttonMap = [self.loadObject.pageJSON dict:KeyButtonMap]; - return [buttonMap dict:KeyPrimaryButton]; -} - -- (NSDictionary *)secondaryButtonMap { - NSDictionary *buttonMap = [self.loadObject.pageJSON dict:KeyButtonMap]; - return [buttonMap dict:KeySecondaryButton]; -} - -- (nullable NSDictionary *)mapForTopLabels { - return self.loadObject.pageJSON; -} - -- (nullable UIView *)useCustomViewInsteadOfLabels { - return nil; -} - -- (nullable UIView *)useCustomViewInsteadOfButtons { - return nil; -} - -- (BOOL)bottomViewOutsideOfScroll { - return NO; -} - -#pragma mark - Animations -- (void)setupIntroAnimations { - if (self.topView.subviews) { - [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.topView]]; - } - - if (self.topBetweenEdgeView.subviews.count) { - [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.topBetweenEdgeView]]; - } - - [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.betweenView]]; - - if (self.bottomBetweenEdgeView.subviews.count) { - [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.bottomBetweenEdgeView]]; - } - - if (self.bottomView.subviews) { - [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.bottomView]]; - } -} - -@end diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift new file mode 100644 index 00000000..5d802e7b --- /dev/null +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -0,0 +1,260 @@ +// +// MVMCoreUIThreeLayerViewController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 2/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// +// This class has three views, a top, middle, and bottom. The top view is just the first view in the scrollview. The middle can be aligned center, top, or bottom depending on if spaceBetweenTopAndMiddle() or spaceBetweenMiddleAndBottom() return values. The bottom view, if inside the scrollview, appears pinned to the bottom unless content pushes it off the screen. If outside the scroll, it's pinned to the bottom under the scrollview. + +import UIKit +import MVMAnimationFramework + +public class ThreeLayerViewController: ProgrammaticScrollViewController { + + // The three main views + var topView: UIView? + var middleView: UIView? + var bottomView: UIView? + + // The bottom view can be put outside of the scrolling area. + var bottomViewOutsideOfScroll = false + + private var safeAreaView: UIView? + private var heightConstraint: NSLayoutConstraint? + + public override func updateViews() { + super.updateViews() + let width = view.bounds.width + if let topView = topView as? MVMCoreViewProtocol { + topView.updateView(width) + } + if let middleView = middleView as? MVMCoreViewProtocol { + middleView.updateView(width) + } + if let bottomView = bottomView as? MVMCoreViewProtocol { + bottomView.updateView(width) + } + } + + public override func updateViewConstraints() { + super.updateViewConstraints() + guard let scrollView = scrollView else { + return + } + + if #available(iOS 11.0, *), scrollView.contentInsetAdjustmentBehavior == UIScrollView.ContentInsetAdjustmentBehavior.automatic { + heightConstraint?.constant = -scrollView.adjustedContentInset.top - scrollView.adjustedContentInset.bottom + } else { + heightConstraint?.constant = -scrollView.contentInset.top - scrollView.contentInset.bottom + } + } + + public override func loadView() { + super.loadView() + // The height is used to keep the bottom view at the bottom. + if let contentView = contentView, let scrollView = scrollView { + heightConstraint = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.0) + heightConstraint?.priority = UILayoutPriority.defaultLow + } + } + + public override func newDataBuildScreen() { + super.newDataBuildScreen() + + // Removes the views + topView?.removeFromSuperview() + middleView?.removeFromSuperview() + bottomView?.removeFromSuperview() + safeAreaView?.removeFromSuperview() + MVMCoreUIStackableViewController.remove(contentView?.subviews) + + // Reset constraints + bottomConstraint?.isActive = true + heightConstraint?.isActive = false + + setupLayers() + } + + //MARK:-Functions to subclass + // Subclass for a top view. + public func viewForTop() -> UIView? { + return nil + } + + // Subclass for a middle view. + public func viewForMiddle() -> UIView? { + return nil + } + + // Subclass for a bottom view. + public func viewForBottom() -> UIView? { + return nil + } + + // If a value is set, the middle view is pinned this value below the top view, if not, space is left to fill. + public func spaceBetweenTopAndMiddle() -> CGFloat? { + return nil + } + + // If a value is set, the middle view is pinned this value above the bottom view, if not, space is left to fill. + public func spaceBetweenMiddleAndBottom() -> CGFloat? { + return nil + } +} + +//MARK:-Setup +extension ThreeLayerViewController { + func setupViewAsTop() -> UIView? { + if let topView = viewForTop() { + self.topView = topView + } else { + topView = MVMCoreUICommonViewsUtility.commonView() + topView?.heightAnchor.constraint(equalToConstant: 0).isActive = true + } + guard let topView = topView, let contentView = contentView else { + return nil + } + contentView.addSubview(topView) + topView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true + contentView.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true + topView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true + return topView + } + + func setupViewAsMiddle() -> UIView? { + if let middleView = viewForMiddle() { + self.middleView = middleView + } else { + middleView = MVMCoreUICommonViewsUtility.commonView() + middleView?.heightAnchor.constraint(equalToConstant: 0).isActive = true + } + guard let middleView = middleView, let contentView = contentView else { + return nil + } + contentView.addSubview(middleView) + middleView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true + contentView.rightAnchor.constraint(equalTo: middleView.rightAnchor).isActive = true + middleView.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + return middleView + } + + func setupViewAsBottom() -> UIView? { + if let bottomView = viewForBottom() { + self.bottomView = bottomView + } else { + bottomView = MVMCoreUICommonViewsUtility.commonView() + bottomView?.heightAnchor.constraint(equalToConstant: 0).isActive = true + } + guard let bottomView = bottomView else { + return nil + } + + // Adds the bottom view outside the scroll if directed. + if bottomViewOutsideOfScroll { + bottomConstraint?.isActive = false; + addViewInsideOfScrollViewBottom(ViewConstrainingView.empty()) + addViewOutsideOfScrollViewBottom(bottomView) + } else { + bottomConstraint?.isActive = true; + addViewInsideOfScrollViewBottom(bottomView) + } + return bottomView + } + + func setupLayers() { + guard let contentView = contentView, let topView = setupViewAsTop(), let middleView = setupViewAsMiddle(), let bottomView = setupViewAsBottom() else { + return + } + let spaceAbove = spaceBetweenTopAndMiddle() + let spaceBelow = spaceBetweenMiddleAndBottom() + if let spaceAbove = spaceAbove, let spaceBelow = spaceBelow { + // Both top and bottom space set, buttons not pinned to bottom. + middleView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceAbove).isActive = true + bottomView.topAnchor.constraint(equalTo: middleView.bottomAnchor, constant: spaceBelow).isActive = true + } else { + heightConstraint?.isActive = true + + if let spaceAbove = spaceAbove { + // Space above is set, space below is free. + let bottomSpacer = MVMCoreUICommonViewsUtility.commonView() + contentView.addSubview(bottomSpacer) + bottomSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true + contentView.rightAnchor.constraint(equalTo: bottomSpacer.rightAnchor).isActive = true + bottomSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingDefaultVerticalSpacing).isActive = true + middleView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceAbove).isActive = true + bottomSpacer.topAnchor.constraint(equalTo: middleView.bottomAnchor).isActive = true + bottomView.topAnchor.constraint(equalTo: bottomSpacer.bottomAnchor).isActive = true + } else if let spaceBelow = spaceBelow { + // Space below is set, space above is free. + let topSpacer = MVMCoreUICommonViewsUtility.commonView() + contentView.addSubview(topSpacer) + topSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true + contentView.rightAnchor.constraint(equalTo: topSpacer.rightAnchor).isActive = true + topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingDefaultVerticalSpacing).isActive = true + topSpacer.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true + middleView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor).isActive = true + bottomView.topAnchor.constraint(equalTo: middleView.bottomAnchor, constant: spaceBelow).isActive = true + } else { + // No set space above or below, make the spacers the same height with a default minimum. + let topSpacer = MVMCoreUICommonViewsUtility.commonView() + let bottomSpacer = MVMCoreUICommonViewsUtility.commonView() + contentView.addSubview(topSpacer) + contentView.addSubview(bottomSpacer) + topSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true + contentView.rightAnchor.constraint(equalTo: topSpacer.rightAnchor).isActive = true + bottomSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true + contentView.rightAnchor.constraint(equalTo: bottomSpacer.rightAnchor).isActive = true + topSpacer.heightAnchor.constraint(equalTo: bottomSpacer.heightAnchor).isActive = true + topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingDefaultVerticalSpacing).isActive = true + topSpacer.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true + middleView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor).isActive = true + bottomSpacer.topAnchor.constraint(equalTo: middleView.bottomAnchor).isActive = true + bottomView.topAnchor.constraint(equalTo: bottomSpacer.bottomAnchor).isActive = true + } + } + } + + func addViewInsideOfScrollViewBottom(_ view: UIView) { + guard let contentView = contentView else { + return + } + contentView.addSubview(view); + contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + contentView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + view.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true + } + + func addViewOutsideOfScrollViewBottom(_ view: UIView) { + self.view?.addSubview(view) + if let scrollView = scrollView, let parentView = self.view { + view.topAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true + view.leftAnchor.constraint(equalTo: parentView.leftAnchor).isActive = true + parentView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + if #available(iOS 11.0, *) { + parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + if let safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: parentView) { + safeAreaView.backgroundColor = bottomView?.backgroundColor + self.safeAreaView = safeAreaView + } + } else { + parentView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + } + } + } +} + +//MARK:-Animation +extension ThreeLayerViewController { + public override func setupIntroAnimations() { + if let topView = topView, topView.subviews.count > 0 { + introAnimationManager?.addAnimation(animation: MVMAnimations.fadeUpAnimation(view: topView)) + } + if let middleView = middleView, middleView.subviews.count > 0 { + introAnimationManager?.addAnimation(animation: MVMAnimations.fadeUpAnimation(view: middleView)) + } + if let bottomView = bottomView, bottomView.subviews.count > 0 { + introAnimationManager?.addAnimation(animation: MVMAnimations.fadeUpAnimation(view: bottomView)) + } + } +} diff --git a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m index 0d4f7ebe..e68fda0f 100644 --- a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m +++ b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m @@ -145,7 +145,7 @@ } - (void)setHeadlineAndMessage { - [self.topLabelsView setWithJSON:[self mapForTopLabels]]; + [self.topLabelsView setHeadlineString:[[self mapForTopLabels] stringForKey:KeyTitle] messageString:[[self mapForTopLabels] stringForKey:KeyMessage]]; } - (void)updateViewConstraints { diff --git a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.h b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.h index 82fb809a..7d15d543 100644 --- a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.h +++ b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.h @@ -7,22 +7,70 @@ // // Has top labels docked on top, buttons docked on bottom, and anything you'd like in between when subclassed. -#import -#import #import +#import #import -@class LabelView; -@interface TopLabelsAndBottomButtonsViewController : MVMCoreUIThreeLayerViewController +@class LabelView; +@class PrimaryButton; + +@interface TopLabelsAndBottomButtonsViewController : StackableViewController + +@property (nullable, weak, nonatomic) TopLabelsView *topLabelsView; +@property (nullable, weak, nonatomic) PrimaryButton *primaryButton; +@property (nullable, weak, nonatomic) PrimaryButton *secondaryButton; +@property (nullable, weak, nonatomic, readonly) UIView *bottomView; +@property (nullable, weak, nonatomic, readonly) UIView *topView; +@property (nullable, weak, nonatomic) UIView *viewInScroll; +@property (nullable, weak, nonatomic) UIView *viewOutOfScroll; +@property (nullable, strong, nonatomic) UIView *safeAreaView; + +// Set to overwrite which view is the top edge and/or bottom edge of the between view. must be added to the ui and constrained before buildViewsBetweenLabelsAndButtons. +// Use these to create views that are pinned near the labels or buttons and are separate from any centered content. Add and set in buildInAdditionalViewsBeforeCenteredContent. +@property (nullable, weak, nonatomic) UIView *topBetweenEdgeView; +@property (nullable, weak, nonatomic) UIView *bottomBetweenEdgeView; + +// The constraint connecting the topLabelsView to the content view. +@property (nullable, strong, nonatomic) NSLayoutConstraint *topConstraintForTopView; // Can override. This is put in because to cover 90% of the screens for initial ipad release, need to rebuild ui (newDataBuildScreen) of size of view change in updateViews. Disable this to handle manually with more finess. @property (nonatomic) BOOL rebuildUIOnSizeChange; +#pragma mark - Subclass + +// Allow you to add any additional ui before buildViewsBetweenLabelsAndButtons gets called. Can use this to set the topBetweenEdgeView or bottomBetweenEdgeView +- (void)buildInAdditionalViewsBeforeCenteredContent; +// For subclassing. Should return all the views that will be in between labels and buttons. Override standardSpaceAroundUIObject to handle spacing. +- (nullable NSArray *)buildViewsBetweenLabelsAndButtons; + +//********* +// If both are subclassed to return a value, then the buttons will not be pinned towards the bottom. + +// If anything is returned, the class will fill in the space between the top labels and views with the passed in value. +- (nullable NSNumber *)spaceAboveBetweenView; + +// If anything is returned, the class will fill in the space between the views and bottom buttons with the passed in value. +- (nullable NSNumber *)spaceBelowBetweenView; + // Can overwrite the default padding for labels and buttons. - (UIEdgeInsets)paddingForTopLabels; - (UIEdgeInsets)paddingForBottomButtons; -// Returns the spacing that should surround the passed in item. Subclass this to change spacing. -- (UIEdgeInsets)spaceAroundUIObject:(nullable id)object; +// default button map will automatically get from response, you can also overide this to have your own button map +- (nullable NSDictionary *)secondaryButtonMap; +- (nullable NSDictionary *)primaryButtonMap; + +// Should not sub class it, for most cases, the headline and message will be in page map, but who knows how server wants to send them in certain pages +- (nullable NSDictionary *)mapForTopLabels; + +// Use these if you want to replace the top labels or bottom button views with your own views. +- (nullable UIView *)useCustomViewInsteadOfLabels; +- (nullable UIView *)useCustomViewInsteadOfButtons; + +// Can override if the buttons should be outside of the scroll or not. Default is no. +- (BOOL)bottomViewOutsideOfScroll; + +// Build above the button view +- (nullable UIView *)buttonsAccessoryView; @end diff --git a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m index 2f169286..4d2bc3c6 100644 --- a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m +++ b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m @@ -14,8 +14,6 @@ #import #import #import -#import -#import #import #import #import @@ -25,6 +23,19 @@ @interface TopLabelsAndBottomButtonsViewController () +@property (nullable, strong, nonatomic) NSLayoutConstraint *heightConstraint; +@property (nullable, weak, nonatomic) UIView *topView; +@property (nullable, weak, nonatomic) UIView *bottomView; +@property (nonatomic) BOOL customBottemView; +@property (nullable, weak, nonatomic) NSArray *middleViews; + +@property (nullable, weak, nonatomic) UIView *betweenView; +@property (nullable, weak, nonatomic) ViewConstrainingView *bottomAccessoryView; + +// Adds the button view to the screen. Out of the scroll or in. +- (void)addViewOutsideOfScrollView:(UIView *)bottomView; +- (void)addViewToContentView:(UIView *)bottomView; + @end @implementation TopLabelsAndBottomButtonsViewController @@ -35,33 +46,31 @@ } - (void)updateViews { - if ([self screenSizeChanged] && self.rebuildUIOnSizeChange) { - - // If the screen size changed.... just rebuild everything.... this will cover all sizes and help for ipad slide over. - [self newDataBuildScreen]; - } [super updateViews]; + + if ([self screenSizeChanged]) { + if (self.rebuildUIOnSizeChange) { + + // If the screen size changed.... just rebuild everything.... this will cover all sizes and help for ipad slide over. + [self newDataBuildScreen]; + } else { + + // Update top labels and bottom buttons. + CGFloat width = CGRectGetWidth(self.view.bounds); + if ([self.topView respondsToSelector:@selector(updateView:)]) { + [((UIView *)self.topView) updateView:width]; + } + if ([self.bottomView respondsToSelector:@selector(updateView:)]) { + [((UIView *)self.bottomView) updateView:width]; + } + [self updateTopLabelsBottomButtonsPadding]; + [self.bottomAccessoryView updateView:width]; + [self resetSpaceForFormArrayWithConstrainingViews]; + } + } } -- (UIEdgeInsets)spaceAroundUIObject:(nullable id)object { - return [super spaceAroundUIObject:object size:[MVMCoreUIUtility getWidth]]; -} - -- (UIEdgeInsets)spaceAroundUIObject:(id)object size:(CGFloat)size { - return [self spaceAroundUIObject:object]; -} - -- (UIEdgeInsets)paddingForTopLabels { - return UIEdgeInsetsMake(PaddingFive, [MFStyler defaultHorizontalPaddingForApplicationWidth], PaddingDefaultVerticalSpacing, [MFStyler defaultHorizontalPaddingForApplicationWidth]); -} - -- (UIEdgeInsets)paddingForBottomButtons { - // Smaller space for smaller devices. Also, top is 0 by default when in scroll. - CGFloat verticalSpacing = [[MFSizeObject sizeObjectWithStandardSize:PaddingDefaultVerticalSpacing smalliPhoneSize:PaddingDefault] getValueBasedOnScreenSize]; - return UIEdgeInsetsMake(([self bottomViewOutsideOfScroll] ? verticalSpacing : 0), PaddingDefaultHorizontalSpacing, verticalSpacing, PaddingDefaultHorizontalSpacing); -} - -- (void)updateTopLabelsPadding:(CGFloat)size { +- (void)updateTopLabelsBottomButtonsPadding { if (self.topLabelsView) { UIEdgeInsets paddingForTopLabels = [self paddingForTopLabels]; self.topLabelsView.topLabelConstraint.constant = paddingForTopLabels.top; @@ -69,10 +78,8 @@ [self.topLabelsView setLeftConstant:paddingForTopLabels.left]; [self.topLabelsView setRightConstant:paddingForTopLabels.right]; } -} - -- (void)updateBottomButtonsPadding:(CGFloat)size { - if (!self.customBottomView) { + + if (!self.customBottemView) { PrimaryButtonView *buttonView = (PrimaryButtonView *)self.bottomView; if (self.secondaryButton || self.primaryButton) { UIEdgeInsets paddingForBottomButtons = [self paddingForBottomButtons]; @@ -89,4 +96,352 @@ } } +- (void)newDataBuildScreen { + [super newDataBuildScreen]; + + // Removes the bottom view out of scroll if it is there. + [self.viewOutOfScroll removeFromSuperview]; + [StackableViewController removeUIViews:[self.contentView subviews]]; + + // Checks if we are using a different object than top labels. + UIView *topView = [self useCustomViewInsteadOfLabels]; + self.topView = topView; + if (!topView) { + // Sets up the labels toward the top of the screen. + TopLabelsView *topLabelsView = [[TopLabelsView alloc] initWithFrame:CGRectZero]; + topLabelsView.separatorView.hidden = NO; + self.topLabelsView = topLabelsView; + topView = topLabelsView; + self.topView = topView; + } + [self.contentView addSubview:topView]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topView)]]; + self.topConstraintForTopView = [NSLayoutConstraint constraintWithItem:topView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]; + self.topConstraintForTopView.active = YES; + + // Checks if we are using a different object than the bottom buttons. + UIView *bottomView = [self useCustomViewInsteadOfButtons]; + self.customBottemView = (bottomView != nil); + if (!self.customBottemView) { + + // Sets up the buttons/button. + NSDictionary *primaryButtonDictionary = [self primaryButtonMap]; + NSDictionary *secondaryButtonDictionary = [self secondaryButtonMap]; + PrimaryButtonView *buttonView = [[PrimaryButtonView alloc] initWithPrimaryButtonMap:primaryButtonDictionary secondaryButtonMap:secondaryButtonDictionary actionDelegate:self additionalData:nil buttonDelegate:self]; + self.secondaryButton = buttonView.secondaryButton; + self.primaryButton = buttonView.primaryButton; + bottomView = buttonView; + self.bottomView = bottomView; + } else { + self.bottomView = bottomView; + } + [self updateTopLabelsBottomButtonsPadding]; + + // Adds the bottom view outside the scroll if directed. + [self.safeAreaView removeFromSuperview]; + if ([self bottomViewOutsideOfScroll]) { + + if (!self.customBottemView) { + bottomView.backgroundColor = [UIColor mfBackgroundGray]; + } + [self addViewOutsideOfScrollView:bottomView]; + self.viewOutOfScroll = bottomView; + + // Sets this so that the button view added to the content view will be empty. + bottomView = [ViewConstrainingView emptyView]; + } + + // Adds either the bottom view or a blank view inside the scroll. + self.viewInScroll = bottomView; + [self addViewToContentView:bottomView]; + + + // Allows addition of any custom ui before custom center view. + [self buildInAdditionalViewsBeforeCenteredContent]; + + + // Positions the views in between the labels and buttons. + UIView *viewBetween = nil; + NSArray *views = [self buildViewsBetweenLabelsAndButtons]; + self.middleViews = views; + if (views.count > 0) { + + viewBetween = [MVMCoreUICommonViewsUtility commonView]; + [self generateFormView:viewBetween withUIArrayForConstrainingViews:views]; + [self.contentView addSubview:viewBetween]; + + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[viewBetween]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(viewBetween)]]; + [viewBetween setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + } + self.betweenView = viewBetween; + + // This whole section is for handling vertical spacing. + //------------------------------------------------------------- + UIView *topBetweenEdgeView = topView; + if (self.topBetweenEdgeView) { + topBetweenEdgeView = self.topBetweenEdgeView; + } + + UIView *bottomBetweenEdgeView = bottomView; + if (self.bottomBetweenEdgeView) { + bottomBetweenEdgeView = self.bottomBetweenEdgeView; + } + + // Sets the height so when there is not enough content, the bottom buttons will stay pinned. But if there is too much content, the constraint has low priority so the screen will scroll. Only made active in certain conditions. + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.contentView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]; + heightConstraint.priority = UILayoutPriorityDefaultLow; + self.heightConstraint = heightConstraint; + + if (viewBetween) { + + // These spacer views are what causes the buttons to be pinned to the bottom. + NSNumber *spaceAbove = [self spaceAboveBetweenView]; + NSNumber *spaceBelow = [self spaceBelowBetweenView]; + + if (spaceAbove && spaceBelow) { + + // Both top and bottom space set, buttons not pinned to bottom. + NSDictionary *verticalMetrics = @{@"top":spaceAbove,@"bottom":spaceBelow}; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-top-[viewBetween]-bottom-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,viewBetween,bottomBetweenEdgeView)]]; + } else { + + // Needs the height constraint + heightConstraint.active = YES; + + if (!spaceAbove && !spaceBelow) { + + // No set space above or below, make the spacers the same height with a default minimum. + UIView *topSpacer = [MVMCoreUICommonViewsUtility commonView]; + UIView *bottomSpacer = [MVMCoreUICommonViewsUtility commonView]; + [self.contentView addSubview:topSpacer]; + [self.contentView addSubview:bottomSpacer]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topSpacer)]]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomSpacer)]]; + + NSLayoutConstraint *sameHeightSpacer = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]; + sameHeightSpacer.active = YES; + + NSLayoutConstraint *minimumHeightSpacer = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing]; + minimumHeightSpacer.active = YES; + + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[topSpacer]-0-[viewBetween]-0-[bottomSpacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topBetweenEdgeView,topSpacer,viewBetween,bottomSpacer,bottomBetweenEdgeView)]]; + } else if (spaceAbove) { + + // Space above is set, space below is free. + UIView *bottomSpacer = [MVMCoreUICommonViewsUtility commonView]; + [self.contentView addSubview:bottomSpacer]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomSpacer)]]; + + NSLayoutConstraint *bottomSpacerHeight = [NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing]; + bottomSpacerHeight.active = YES; + + NSDictionary *verticalMetrics = @{@"top":spaceAbove}; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-top-[viewBetween]-0-[bottomSpacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,viewBetween,bottomSpacer,bottomBetweenEdgeView)]]; + } else if (spaceBelow) { + + // Space below is set, space above is free. + UIView *topSpacer = [MVMCoreUICommonViewsUtility commonView]; + [self.contentView addSubview:topSpacer]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topSpacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topSpacer)]]; + + NSLayoutConstraint *topSpacerHeight = [NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:PaddingDefaultVerticalSpacing]; + topSpacerHeight.active = YES; + + NSDictionary *verticalMetrics = @{@"bottom":spaceBelow}; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[topSpacer]-0-[viewBetween]-bottom-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:verticalMetrics views:NSDictionaryOfVariableBindings(topBetweenEdgeView,topSpacer,viewBetween,bottomBetweenEdgeView)]]; + } + } + } else { + + // No views in between. Just messages and buttons + UIView *spacer = [MVMCoreUICommonViewsUtility commonView]; + [self.contentView addSubview:spacer]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[spacer]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(spacer)]]; + + // Needs the height constraint + heightConstraint.active = YES; + + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topBetweenEdgeView]-0-[spacer]-0-[bottomBetweenEdgeView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(topBetweenEdgeView,spacer,bottomBetweenEdgeView)]]; + } + + if (self.topLabelsView) { + [self.topLabelsView setHeadlineString:[[self mapForTopLabels] stringForKey:KeyTitle] messageString:[[self mapForTopLabels] stringForKey:KeyMessage]]; + } + +} + +- (void)updateViewConstraints { + [super updateViewConstraints]; + + // Updates for ios 11 + if (@available(iOS 11.0, *)) { + if (self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { + self.heightConstraint.constant = -self.scrollView.adjustedContentInset.top - self.scrollView.adjustedContentInset.bottom; + } else { + self.heightConstraint.constant = -self.scrollView.contentInset.top - self.scrollView.contentInset.bottom; + } + } else { + self.heightConstraint.constant = -self.scrollView.contentInset.top - self.scrollView.contentInset.bottom; + } +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + +- (UIEdgeInsets)spaceAroundUIObject:(nullable id)object { + CGFloat horizontal = [MFStyler defaultHorizontalPaddingForApplicationWidth]; + if (self.bottomAccessoryView == object) { + return UIEdgeInsetsMake(0, horizontal, PaddingDefaultVerticalSpacing, horizontal); + } + return UIEdgeInsetsMake(0, horizontal, 0, horizontal); +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)addViewToContentView:(UIView *)bottomView { + + self.bottomConstraint.active = YES; + + // Buttons will be at the bottom of the content view. + [self.contentView addSubview:bottomView]; + + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[bottomView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]]; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]]; +} + +- (void)addViewOutsideOfScrollView:(UIView *)bottomView { + + self.bottomConstraint.active = NO; + + // Buttons will be outside of the scrolling view. + [self.view addSubview:bottomView]; + + UIScrollView *scrollview = self.scrollView; + if (@available(iOS 11.0, *)) { + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]]; + [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:bottomView.bottomAnchor].active = YES; + + UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; + safeAreaView.backgroundColor = bottomView.backgroundColor; + self.safeAreaView = safeAreaView; + } else { + // Fallback on earlier versions + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[scrollview]-0-[bottomView]-0-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(scrollview,bottomView)]]; + } + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]]; +} + +#pragma mark - Can Subclass These + + +- (nullable UIView *)buttonsAccessoryView { + return nil; +} + +- (void)buildInAdditionalViewsBeforeCenteredContent { + + UIView *accessoryView = [self buttonsAccessoryView]; + if (accessoryView) { + accessoryView.translatesAutoresizingMaskIntoConstraints = NO; + ViewConstrainingView *constrainingView = [ViewConstrainingView viewConstrainingView:accessoryView]; + constrainingView.updateViewHorizontalDefaults = YES; + [self.contentView addSubview:constrainingView]; + self.bottomBetweenEdgeView = constrainingView; + self.bottomAccessoryView = constrainingView; + + [constrainingView setPinConstantsWithInsets:[self spaceAroundUIObject:constrainingView]]; + + [NSLayoutConstraint constraintPinLeftSubview:constrainingView leftConstant:0]; + [NSLayoutConstraint constraintPinRightSubview:constrainingView rightConstant:0]; + [NSLayoutConstraint constraintPinFirstView:constrainingView toSecondView:self.viewInScroll + withConstant:0 directionVertical:YES]; + } else { + self.bottomAccessoryView = nil; + } +} + +- (nullable NSArray *)buildViewsBetweenLabelsAndButtons { + return nil; +} + +- (nullable NSNumber *)spaceAboveBetweenView { + return nil; +} + +- (nullable NSNumber *)spaceBelowBetweenView { + return nil; +} + +- (UIEdgeInsets)paddingForTopLabels { + return UIEdgeInsetsMake(PaddingFive, [MFStyler defaultHorizontalPaddingForApplicationWidth], PaddingDefaultVerticalSpacing, [MFStyler defaultHorizontalPaddingForApplicationWidth]); +} + +- (UIEdgeInsets)paddingForBottomButtons { + + // Smaller space for smaller devices. Also, top is 0 by default when in scroll. + CGFloat verticalSpacing = [[MFSizeObject sizeObjectWithStandardSize:PaddingDefaultVerticalSpacing smalliPhoneSize:PaddingDefault] getValueBasedOnScreenSize]; + return UIEdgeInsetsMake(([self bottomViewOutsideOfScroll] ? verticalSpacing : 0), PaddingDefaultHorizontalSpacing, verticalSpacing, PaddingDefaultHorizontalSpacing); +} + +- (NSDictionary*)primaryButtonMap { + NSDictionary *buttonMap = [self.loadObject.pageJSON dict:KeyButtonMap]; + return [buttonMap dict:KeyPrimaryButton]; +} + +- (NSDictionary*)secondaryButtonMap { + NSDictionary *buttonMap = [self.loadObject.pageJSON dict:KeyButtonMap]; + return [buttonMap dict:KeySecondaryButton]; +} + +- (nullable NSDictionary *)mapForTopLabels { + return self.loadObject.pageJSON; +} + +- (nullable UIView *)useCustomViewInsteadOfLabels { + return nil; +} + +- (nullable UIView *)useCustomViewInsteadOfButtons { + return nil; +} + +- (BOOL)bottomViewOutsideOfScroll { + return NO; +} + + + +#pragma mark - Animations +-(void)setupIntroAnimations { + if (self.topView.subviews) { + [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.topView]]; + } + + if (self.topBetweenEdgeView.subviews.count) { + [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.topBetweenEdgeView]]; + } + + // NSMutableArray *centerAnimationObjects = [NSMutableArray new]; + // for (int i = 0; i < self.middleViews.count; i++) { + // MVMAnimationObject *aobj =[MVMAnimations fadeUpAnimationWithView:self.middleViews[i]]; + // [centerAnimationObjects addObject:aobj]; + // } + // [self.introAnimationManager addAnimationGroupWithAnimations:centerAnimationObjects]; + + [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.betweenView]]; + + if (self.bottomBetweenEdgeView.subviews.count) { + [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.bottomBetweenEdgeView]]; + } + + if (self.bottomView.subviews) { + [self.introAnimationManager addAnimationWithAnimation:[MVMAnimations fadeUpAnimationWithView:self.bottomView]]; + } +} + @end diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index 3ec411ae..79b9c734 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -15,7 +15,12 @@ FOUNDATION_EXPORT double MVMCoreUIVersionNumber; FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import +#pragma mark - OtherHandlers +#import #import +#import +#import +#import #pragma mark - TopAlert #import @@ -24,11 +29,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import #import -#pragma mark - OtherHandlers -#import -#import -#import - #pragma mark - Categories #import #import @@ -50,7 +50,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import #import #import -#import #import #import #import @@ -109,6 +108,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #pragma mark - Molecules #import #import +#import #pragma mark - Templates -#import diff --git a/MVMCoreUI/Molecules/MVMCoreUIHeaderView.swift b/MVMCoreUI/Molecules/MVMCoreUIHeaderView.swift new file mode 100644 index 00000000..82babc58 --- /dev/null +++ b/MVMCoreUI/Molecules/MVMCoreUIHeaderView.swift @@ -0,0 +1,110 @@ +// +// MVMCoreUIHeaderView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 2/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class MVMCoreUIHeaderView: ViewConstrainingView { + let headlineLabel = MFLabel.commonLabelH2(true) + let messageLabel = MFLabel.commonLabelB2(true) + var separatorView: SeparatorView? + var spaceBetweenLabels: NSLayoutConstraint? + var leftConstraintTitle: NSLayoutConstraint? + var rightConstraintTitle: NSLayoutConstraint? + var leftConstraintMessage: NSLayoutConstraint? + var rightConstraintMessage: NSLayoutConstraint? + private var heightConstraint: NSLayoutConstraint? + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineLabel.updateView(size) + messageLabel.updateView(size) + separatorView?.updateView(size) + } + + public override func setupView() { + super.setupView() + if separatorView == nil { + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .clear + clipsToBounds = true + updateViewHorizontalDefaults = true + + addSubview(headlineLabel) + 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: PaddingFive) + topPin?.isActive = true + + spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: PaddingTwo) + spaceBetweenLabels?.isActive = true + + leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: leftAnchor) + leftConstraintTitle?.isActive = true + + rightConstraintTitle = rightAnchor.constraint(equalTo: headlineLabel.rightAnchor) + rightConstraintTitle?.isActive = true + + leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: leftAnchor) + leftConstraintMessage?.isActive = true + + rightConstraintMessage = rightAnchor.constraint(equalTo: messageLabel.rightAnchor) + rightConstraintMessage?.isActive = true + + bottomPin = bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: PaddingDefaultVerticalSpacing) + bottomPin?.isActive = true + + heightConstraint = heightAnchor.constraint(equalToConstant: 0) + heightConstraint?.priority = UILayoutPriority(rawValue: 950) + + if let separatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot, withHorizontalPadding: 0) { + separatorView.setAsHeavy() + separatorView.isHidden = true + addSubview(separatorView) + self.separatorView = separatorView + } + } + } + + public override func setLeftPinConstant(_ constant: CGFloat) { + leftConstraintTitle?.constant = constant + leftConstraintMessage?.constant = constant + separatorView?.leftPin?.constant = constant + } + + public override func setRightPinConstant(_ constant: CGFloat) { + rightConstraintTitle?.constant = constant + rightConstraintMessage?.constant = constant + separatorView?.rightPin?.constant = constant + } + + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegate: NSObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegate: delegate, additionalData: additionalData) + headlineLabel.text = json?.optionalStringForKey(KeyTitle) + messageLabel.text = json?.optionalStringForKey(KeyMessage) + separatorView?.isHidden = !(json?.boolForKey("showSeparator") ?? false) + if let colorString = json?.optionalStringForKey("backgroundColor") { + backgroundColor = .mfGet(forHex: colorString) + } + if let colorString = json?.optionalStringForKey("contentColor") { + let color = UIColor.mfGet(forHex: colorString) + headlineLabel.textColor = color + messageLabel.textColor = color + separatorView?.backgroundColor = color + } + + if separatorView?.isHidden ?? true { + bottomPin?.constant = 0 + } else { + bottomPin?.constant = PaddingDefaultVerticalSpacing + } + } +} diff --git a/MVMCoreUI/Molecules/MVMCoreUIMoleculeStackView.swift b/MVMCoreUI/Molecules/MVMCoreUIMoleculeStackView.swift new file mode 100644 index 00000000..af336673 --- /dev/null +++ b/MVMCoreUI/Molecules/MVMCoreUIMoleculeStackView.swift @@ -0,0 +1,77 @@ +// +// MVMCoreUIMoleculeStackView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 2/11/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class MVMCoreUIMoleculeStackView: MFView { + var spacingBlock: ((Any) -> UIEdgeInsets)? + var moleculesArray: [UIView]? + + init(withJSON json: [AnyHashable : Any]?, delegate: NSObject?, additionalData: [AnyHashable : Any]?) { + super.init(frame: CGRect.zero) + setWithJSON(json, delegate: delegate, additionalData: additionalData) + } + + convenience init(withJSON json: [AnyHashable : Any]?, delegate: NSObject?, spacingBlock: ((Any) -> UIEdgeInsets)?) { + self.init(withJSON: json, delegate: delegate, additionalData: nil) + self.spacingBlock = spacingBlock + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func setupView() { + super.setupView() + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .clear + self.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + self.setContentCompressionResistancePriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + } + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + for view in subviews { + if let mvmView = view as? MVMCoreViewProtocol { + mvmView.updateView(size) + } + } + } + + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegate: NSObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegate: delegate, additionalData: additionalData) + guard let molecules = json?.arrayForKey("molecules") as? [[String: Any]] else { + return + } + + // Create the molecules and set the json. + var moleculesArray = [] as [UIView] + for moleculeJSON in molecules { + if let name = moleculeJSON.optionalStringForKey("moleculeName"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForName(name) { + molecule.setWithJSON(moleculeJSON, delegate: delegate, additionalData: additionalData) + moleculesArray.append(molecule) + } + } + + guard moleculesArray.count > 0 else { + return + } + + if let spacingBlock = spacingBlock { + MVMCoreUIStackableViewController.populateView(self, withUIArray: moleculesArray, withSpacingBlock: spacingBlock) + } else { + MVMCoreUIStackableViewController.populateView(self, withUIArray: moleculesArray) { (object) -> UIEdgeInsets in + if object as AnyObject? === moleculesArray.first { + return UIEdgeInsets.zero + } else { + return UIEdgeInsets.init(top: PaddingTwo, left: 0, bottom: 0, right: 0) + } + } + } + } +} diff --git a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h new file mode 100644 index 00000000..cfb28c3f --- /dev/null +++ b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h @@ -0,0 +1,18 @@ +// +// MVMCoreUIMoleculeViewProtocol.h +// MVMCoreUI +// +// Created by Scott Pfeil on 2/11/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +#import + +@protocol MVMCoreUIMoleculeViewProtocol + +// Sets up the ui based on the json +- (void)setWithJSON:(nullable NSDictionary *)json delegate:(nullable NSObject *)delegate additionalData:(nullable NSDictionary *)additionalData; + +@end + + diff --git a/MVMCoreUI/Molecules/PrimaryButtonView.h b/MVMCoreUI/Molecules/PrimaryButtonView.h index ec474c00..7140a0d3 100644 --- a/MVMCoreUI/Molecules/PrimaryButtonView.h +++ b/MVMCoreUI/Molecules/PrimaryButtonView.h @@ -28,7 +28,10 @@ - (nonnull instancetype)initButtonSmall:(BOOL)small buttonMap:(nullable NSDictionary *)buttonMap actionDelegate:(nullable NSObject *)actionDelegate additionalData:(nullable NSDictionary *)additionalData buttonDelegate:(nullable id )buttonDelegate; - (nonnull instancetype)initWithPrimaryButtonMap:(nullable NSDictionary *)primaryButtonMap secondaryButtonMap:(nullable NSDictionary *)secondaryButtonMap actionDelegate:(nullable NSObject *)actionDelegate additionalData:(nullable NSDictionary *)additionalData buttonDelegate:(nullable id )buttonDelegate; -// Sets up with whatever is in the passed in button map. (could be 0, 1, or 2 buttons) +// Sets up with whatever is in the passed in. (could be 0, 1, or 2 buttons) +- (void)setupWithFirstButtonJSON:(nullable NSDictionary *)firstButtonJSON secondButtonJSON:(nullable NSDictionary *)secondButtonJSON additionalData:(nullable NSDictionary *)additionalData actionDelegate:(nullable NSObject *)actionDelegate buttonDelegate:(nullable id )buttonDelegate; + +// Legacy: Sets up with whatever is in the passed in button map. (could be 0, 1, or 2 buttons) - (void)setupWithButtonMap:(nullable NSDictionary *)buttonMap actionDelegate:(nullable NSObject *)actionDelegate additionalData:(nullable NSDictionary *)additionalData buttonDelegate:(nullable id )buttonDelegate; - (void)setupWithPrimaryButtonMap:(nullable NSDictionary *)primaryButtonMap secondaryButtonMap:(nullable NSDictionary *)secondaryButtonMap actionDelegate:(nullable NSObject *)actionDelegate additionalData:(nullable NSDictionary *)additionalData buttonDelegate:(nullable id )buttonDelegate; @@ -50,4 +53,6 @@ - (void)showBothPrimaryButtons; - (void)hideBothPrimaryButtons; +- (void)removeSubviews; + @end diff --git a/MVMCoreUI/Molecules/PrimaryButtonView.m b/MVMCoreUI/Molecules/PrimaryButtonView.m index 97178560..818f6785 100644 --- a/MVMCoreUI/Molecules/PrimaryButtonView.m +++ b/MVMCoreUI/Molecules/PrimaryButtonView.m @@ -12,6 +12,7 @@ #import #import "MVMCoreUICommonViewsUtility.h" #import "MVMCoreUIConstants.h" +#import "UIColor+MFConvenience.h" @interface PrimaryButtonView () @@ -26,6 +27,25 @@ @end @implementation PrimaryButtonView + +- (void)updateView:(CGFloat)size { + [super updateView:size]; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + [self.primaryButton updateView:size]; + [self.secondaryButton updateView:size]; + }]; +} + +- (void)setWithJSON:(NSDictionary *)json delegate:(NSObject *)delegate additionalData:(nullable NSDictionary *)additionalData { + [super setWithJSON:json delegate:delegate additionalData:additionalData]; + NSString *backgroundColorString = [json string:@"backgroundColor"]; + if (backgroundColorString) { + self.backgroundColor = [UIColor mfGetColorForHex:backgroundColorString]; + } + [self setupWithFirstButtonJSON:[json dict:@"FirstButton"] secondButtonJSON:[json dict:@"SecondButton"] additionalData:nil actionDelegate:([delegate conformsToProtocol:@protocol(MVMCoreActionDelegateProtocol)] ? (NSObject *)delegate : nil) buttonDelegate:([delegate conformsToProtocol:@protocol(ButtonDelegateProtocol)] ? (id )delegate : nil)]; +} + +#pragma mark - Inits - (instancetype)init { if (self = [super init]) { @@ -85,6 +105,8 @@ return self; } +#pragma mark - Setup + - (void)setupWithButtonMap:(nullable NSDictionary *)buttonMap actionDelegate:(nullable NSObject *)actionDelegate additionalData:(nullable NSDictionary *)additionalData buttonDelegate:(nullable id )buttonDelegate { NSDictionary *secondaryButtonMap = [buttonMap dict:KeySecondaryButton]; @@ -92,8 +114,15 @@ [self setupWithPrimaryButtonMap:primaryButtonMap secondaryButtonMap:secondaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; } +- (void)setupWithFirstButtonJSON:(nullable NSDictionary *)firstButtonJSON secondButtonJSON:(nullable NSDictionary *)secondButtonJSON additionalData:(nullable NSDictionary *)additionalData actionDelegate:(nullable NSObject *)actionDelegate buttonDelegate:(nullable id )buttonDelegate { + [self setupWithPrimaryButtonMap:firstButtonJSON secondaryButtonMap:secondButtonJSON additionalData:additionalData actionDelegate:actionDelegate buttonDelegate:buttonDelegate legacyJSON:NO]; +} + - (void)setupWithPrimaryButtonMap:(nullable NSDictionary *)primaryButtonMap secondaryButtonMap:(nullable NSDictionary *)secondaryButtonMap actionDelegate:(nullable NSObject *)actionDelegate additionalData:(nullable NSDictionary *)additionalData buttonDelegate:(nullable id )buttonDelegate { - + [self setupWithPrimaryButtonMap:primaryButtonMap secondaryButtonMap:secondaryButtonMap additionalData:additionalData actionDelegate:actionDelegate buttonDelegate:buttonDelegate legacyJSON:YES]; +} + +- (void)setupWithPrimaryButtonMap:(nullable NSDictionary *)primaryButtonMap secondaryButtonMap:(nullable NSDictionary *)secondaryButtonMap additionalData:(nullable NSDictionary *)additionalData actionDelegate:(nullable NSObject *)actionDelegate buttonDelegate:(nullable id )buttonDelegate legacyJSON:(BOOL)legacyJSON { if (primaryButtonMap && secondaryButtonMap) { self.height.active = NO; @@ -103,11 +132,16 @@ self.twoButtonView = nil; [self setupWithTwoButtons]; } - [self.primaryButton setWithActionMap:primaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; - [self.secondaryButton setWithActionMap:secondaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; + if (legacyJSON) { + [self.primaryButton setWithActionMap:primaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; + [self.secondaryButton setWithActionMap:secondaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; + } else { + [self.primaryButton setWithJSON:primaryButtonMap delegate:actionDelegate additionalData:additionalData]; + [self.secondaryButton setWithJSON:secondaryButtonMap delegate:actionDelegate additionalData:additionalData]; + } } else if (primaryButtonMap || secondaryButtonMap) { self.height.active = NO; - + // Setup with one button. if (!self.primaryButton || self.secondaryButton) { [self removeSubviews]; @@ -117,16 +151,20 @@ } [self alignCenter]; - if (primaryButtonMap) { - - // Only primary button - [self.primaryButton setWithActionMap:primaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; - self.primaryButton.bordered = NO; + if (legacyJSON) { + if (primaryButtonMap) { + + // Only primary button + [self.primaryButton setWithActionMap:primaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; + self.primaryButton.bordered = NO; + } else { + + // Only secondary button + [self.primaryButton setWithActionMap:secondaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; + self.primaryButton.bordered = YES; + } } else { - - // Only secondary button - [self.primaryButton setWithActionMap:secondaryButtonMap actionDelegate:actionDelegate additionalData:additionalData buttonDelegate:buttonDelegate]; - self.primaryButton.bordered = YES; + [self.primaryButton setWithJSON:primaryButtonMap delegate:actionDelegate additionalData:additionalData]; } } else { [self removeSubviews]; @@ -230,13 +268,7 @@ centerRightPin.active = YES; } -- (void)updateView:(CGFloat)size { - [super updateView:size]; - [MVMCoreDispatchUtility performBlockOnMainThread:^{ - [self.primaryButton updateView:size]; - [self.secondaryButton updateView:size]; - }]; -} +#pragma mark - Configuring - (void)alignLeft { self.alignCenterPin.active = NO; diff --git a/MVMCoreUI/Molecules/TopLabelsView.h b/MVMCoreUI/Molecules/TopLabelsView.h index 04c2d8be..e4e6494a 100644 --- a/MVMCoreUI/Molecules/TopLabelsView.h +++ b/MVMCoreUI/Molecules/TopLabelsView.h @@ -50,7 +50,4 @@ - (void)setLeftConstant:(CGFloat)leftConstant; - (void)setRightConstant:(CGFloat)rightConstant; -// Sets up the ui based on the json -- (void)setWithJSON:(nullable NSDictionary *)json; - @end diff --git a/MVMCoreUI/Molecules/TopLabelsView.m b/MVMCoreUI/Molecules/TopLabelsView.m index 5452871f..04c9b758 100644 --- a/MVMCoreUI/Molecules/TopLabelsView.m +++ b/MVMCoreUI/Molecules/TopLabelsView.m @@ -14,7 +14,6 @@ #import #import #import -#import @interface TopLabelsView () @@ -215,34 +214,4 @@ self.rightConstraintSeparator.constant = rightConstant; } -- (void)setWithJSON:(nullable NSDictionary *)json { - [self setHeadlineString:[json stringForKey:KeyTitle] messageString:[json stringForKey:KeyMessage]]; - UIColor *textColor = [self colorForTopLabelsWithJSON:json]; - self.headlineLabel.textColor = textColor; - self.messageLabel.textColor = textColor; - self.backgroundColor = [self colorForBackgroundWithJSON:json]; - NSNumber *showBottomLine = [json objectForKey:@"showBottomLine" ofType:[NSNumber class]]; - if (showBottomLine) { - self.separatorView.hidden = ![showBottomLine boolValue]; - } -} - -- (nonnull UIColor *)colorForTopLabelsWithJSON:(nullable NSDictionary *)json { - NSString *titleColor = [json string:@"titleBgColor"]; - if (titleColor) { - return [UIColor mfGetColorForHex:titleColor]; - } else { - return [UIColor blackColor]; - } -} - -- (nonnull UIColor *)colorForBackgroundWithJSON:(nullable NSDictionary *)json { - NSString *color = [json string:@"backgroundColor"]; - if (color) { - return [UIColor mfGetColorForHex:color]; - } else { - return [UIColor clearColor]; - } -} - @end diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h new file mode 100644 index 00000000..7d229bbe --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h @@ -0,0 +1,24 @@ +// +// MVMCoreUIMoleculeMappingObject.h +// MVMCoreUI +// +// Created by Scott Pfeil on 2/11/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +#import +#import + +@interface MVMCoreUIMoleculeMappingObject : NSObject + +// Maps molecule name to class. +@property (nullable, strong, nonatomic) NSMutableDictionary *moleculeMapping; + +// Returns the shared instance ++ (nullable instancetype)sharedMappingObject; + +// Returns the molecule for the given name. +- (nullable UIView *)getMoleculeForName:(nonnull NSString *)name; +- (nullable UIView *)getMoleculeForJSON:(nonnull NSDictionary *)json delegate:(nullable NSObject *)delegate; + +@end diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m new file mode 100644 index 00000000..7693e93b --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -0,0 +1,55 @@ +// +// MVMCoreUIMoleculeMappingObject.m +// MVMCoreUI +// +// Created by Scott Pfeil on 2/11/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +#import "MVMCoreUIMoleculeMappingObject.h" +@import MVMCore.MVMCoreActionUtility; +@import MVMCore.NSDictionary_MFConvenience; +#import "MVMCoreUIObject.h" +#import + +@implementation MVMCoreUIMoleculeMappingObject + +- (NSMutableDictionary *)moleculeMapping { + + // Keeps a mapping of the given page type + static dispatch_once_t onceToken; + static NSMutableDictionary *mapping; + dispatch_once(&onceToken, ^{ + mapping = [@{ + @"StandardHeader": MVMCoreUIHeaderView.class, + @"MoleculeStack": MVMCoreUIMoleculeStackView.class, + @"TwoButtonView": PrimaryButtonView.class + } mutableCopy]; + }); + return mapping; +} + + ++ (nullable instancetype)sharedMappingObject { + return [MVMCoreActionUtility initializerClassCheck:[MVMCoreUIObject sharedInstance].moleculeMap classToVerify:self]; +} + +- (nullable UIView *)getMoleculeForName:(nonnull NSString *)name { + Class class = [self.moleculeMapping objectForKey:name]; + if (class) { + return [[class alloc] init]; + } + return nil; +} + +- (nullable UIView *)getMoleculeForJSON:(nonnull NSDictionary *)json delegate:(nullable NSObject *)delegate { + NSString *moleculeName = [json string:@"moleculeName"]; + if (!moleculeName) { + return nil; + } + UIView *molecule = [self getMoleculeForName:moleculeName]; + [molecule setWithJSON:json delegate:delegate additionalData:nil]; + return molecule; +} + +@end diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIObject.h b/MVMCoreUI/OtherHandlers/MVMCoreUIObject.h index f9a3510d..080bd457 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIObject.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIObject.h @@ -7,11 +7,14 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN @interface MVMCoreUIObject : MVMCoreObject +@property (nullable, nonatomic, strong) MVMCoreUIMoleculeMappingObject *moleculeMap; + @end NS_ASSUME_NONNULL_END diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIObject.m index 4c601e46..cea769d1 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIObject.m @@ -21,6 +21,7 @@ self.session = [[MVMCoreUISession alloc] init]; self.viewControllerMapping = [[MVMCoreUIViewControllerMappingObject alloc] init]; self.loggingDelegate = [[MVMCoreUILoggingHandler alloc] init]; + self.moleculeMap = [[MVMCoreUIMoleculeMappingObject alloc] init]; } @end diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m index 255cade5..7a574980 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m @@ -8,7 +8,6 @@ #import "MVMCoreUIViewControllerMappingObject.h" #import -#import "MVMCoreUILargeHeaderSingleLabelTemplate.h" #import @implementation MVMCoreUIViewControllerMappingObject @@ -20,8 +19,10 @@ static NSMutableDictionary *viewControllerMapping; dispatch_once(&onceToken, ^{ viewControllerMapping = [@{ - @"LargeHeaderSingleLabel": [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MVMCoreUILargeHeaderSingleLabelTemplate class]], - @"TextFieldListForm" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MVMCoreUITextFieldListFormViewController class]] + @"LegacyLargeHeaderSingleLabel": [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[LegacyLargeHeaderSingleLabelTemplate class]], + @"TextFieldListForm" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MVMCoreUITextFieldListFormViewController class]], + @"MoleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackTemplate class]], + @"CenterMoleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackCenteredTemplate class]] } mutableCopy]; }); return viewControllerMapping; diff --git a/MVMCoreUI/Templates/LegacyLargeHeaderSingleLabelTemplate.swift b/MVMCoreUI/Templates/LegacyLargeHeaderSingleLabelTemplate.swift new file mode 100644 index 00000000..8c4c1576 --- /dev/null +++ b/MVMCoreUI/Templates/LegacyLargeHeaderSingleLabelTemplate.swift @@ -0,0 +1,61 @@ +// +// LegacyLargeHeaderSingleLabelTemplate.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 2/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class LegacyLargeHeaderSingleLabelTemplate: TopLabelsAndBottomButtonsViewController { + + // A label that can go below the top labels. Can be a normal label or one with internal buttons. + var labelViewUnderTopLabels: LabelView? + var labelUnderTopLabelsWithInternalButton: LabelWithInternalButton? + + public override func newDataBuildScreen() { + super.newDataBuildScreen() + topLabelsView?.headlineLabel?.setFontH1(true) + topLabelsView?.separatorView?.hide() + } + + public override func buildViewsBetweenLabelsAndButtons() -> [UIView]? { + var views = [UIView]() + if let labelActionMap = actionMapForLabelUnderTopLabelsWithInternalButton(), let labelUnderTopLabelsWithInternalButton = LabelWithInternalButton(actionMap: labelActionMap, additionalData: nil, actionDelegate: self) { + labelUnderTopLabelsWithInternalButton.translatesAutoresizingMaskIntoConstraints = false + labelUnderTopLabelsWithInternalButton.setAlignment(NSTextAlignment.left) + views.append(labelUnderTopLabelsWithInternalButton) + self.labelUnderTopLabelsWithInternalButton = labelUnderTopLabelsWithInternalButton + } else if let labelString = stringForLabelUnderTopLabels() { + let labelView = LabelView(frame: CGRect.zero) + labelView.translatesAutoresizingMaskIntoConstraints = false + labelView.alignLeft() + labelView.label?.styleB2(true) + labelView.label?.text = labelString + labelView.label?.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + views.append(labelView) + self.labelViewUnderTopLabels = labelView + } + return views; + } + + public func actionMapForLabelUnderTopLabelsWithInternalButton() -> [AnyHashable : Any]? { + return loadObject?.pageJSON?.optionalDictionaryWithChainOfKeysOrIndexes([KeyButtonMap,"Link"]) + + } + + public func stringForLabelUnderTopLabels() -> String? { + return loadObject?.pageJSON?.optionalStringForKey("description") + } + + public override func spaceAboveBetweenView() -> NSNumber? { + return 0 as NSNumber + } + + public override func paddingForTopLabels() -> UIEdgeInsets { + var padding = super.paddingForTopLabels() + padding.bottom = PaddingTwo + return padding + } +} diff --git a/MVMCoreUI/Templates/MVMCoreUILargeHeaderSingleLabelTemplate.h b/MVMCoreUI/Templates/MVMCoreUILargeHeaderSingleLabelTemplate.h deleted file mode 100644 index 4957f306..00000000 --- a/MVMCoreUI/Templates/MVMCoreUILargeHeaderSingleLabelTemplate.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// MVMCoreUILargeHeaderSingleLabelTemplate.h -// MVMCoreUI -// -// Created by Scott Pfeil on 1/8/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -#import -#import -@class LabelView; -@class LabelWithInternalButton; - -@interface MVMCoreUILargeHeaderSingleLabelTemplate : MVMCoreUIThreeLayerViewController - -// A label that can go below the top labels. Can be a normal label or one with internal buttons. -@property (nullable, weak, nonatomic) LabelView *labelViewUnderTopLabels; -@property (nullable, weak, nonatomic) LabelWithInternalButton *labelUnderTopLabelsWithInternalButton; - -// Override to specifiy the label under top label. Default uses the value for key "description" in the page json. -- (nullable NSString *)stringForLabelUnderTopLabels; - -// Override to specifiy the label under top label with internal button. Default uses the value for key "Link" in the "ButtonMap" of the page json. -- (nullable NSDictionary *)actionMapForLabelUnderTopLabelsWithInternalButton; - -@end diff --git a/MVMCoreUI/Templates/MVMCoreUILargeHeaderSingleLabelTemplate.m b/MVMCoreUI/Templates/MVMCoreUILargeHeaderSingleLabelTemplate.m deleted file mode 100644 index 563fdbdd..00000000 --- a/MVMCoreUI/Templates/MVMCoreUILargeHeaderSingleLabelTemplate.m +++ /dev/null @@ -1,80 +0,0 @@ -// -// MVMCoreUILargeHeaderSingleLabelTemplate.m -// MVMCoreUI -// -// Created by Scott Pfeil on 1/8/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -#import "MVMCoreUILargeHeaderSingleLabelTemplate.h" -#import "LabelView.h" -#import "LabelWithInternalButton.h" -#import "UIColor+MFConvenience.h" -#import "TopLabelsView.h" - -@interface MVMCoreUILargeHeaderSingleLabelTemplate () - -@end - -@implementation MVMCoreUILargeHeaderSingleLabelTemplate - -- (void)newDataBuildScreen { - [super newDataBuildScreen]; - [self.topLabelsView.headlineLabel setFontH1:YES]; - [self.topLabelsView.separatorView hide]; -} - -- (nullable NSArray *)buildViewsBetweenLabelsAndButtons { - NSDictionary *labelActionMap = [self actionMapForLabelUnderTopLabelsWithInternalButton]; - NSString *labelString = [self stringForLabelUnderTopLabels]; - NSMutableArray *views = [[NSMutableArray alloc] init]; - if (labelActionMap.count > 0) { - // Use label with internal button. - LabelWithInternalButton *labelWithInternalButton = [[LabelWithInternalButton alloc] initWithActionMap:labelActionMap additionalData:nil actionDelegate:self buttonDelegate:self]; - labelWithInternalButton.translatesAutoresizingMaskIntoConstraints = NO; - [labelWithInternalButton setAlignment:NSTextAlignmentLeft]; - self.labelUnderTopLabelsWithInternalButton = labelWithInternalButton; - [views addObject:labelWithInternalButton]; - } else if (labelString.length > 0){ - // Use regular label - LabelView *labelView = [[LabelView alloc] init]; - labelView.translatesAutoresizingMaskIntoConstraints = NO; - [labelView alignLeft]; - [labelView.label styleB2:YES]; - labelView.label.text = labelString; - [labelView.label setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - self.labelViewUnderTopLabels = labelView; - [views addObject:labelView]; - } - - return views; -} - -- (nullable NSString *)stringForLabelUnderTopLabels { - return [self.loadObject.pageJSON stringForKey:@"description"]; -} - -- (nullable NSDictionary *)actionMapForLabelUnderTopLabelsWithInternalButton { - return [self.loadObject.pageJSON dictWithChainOfKeysOrIndexes:@[KeyButtonMap,@"Link"]]; -} - -- (NSNumber *)spaceAboveBetweenView { - return @(0); -} - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do any additional setup after loading the view. -} - -- (void)didReceiveMemoryWarning { - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -- (void)updateTopLabelsPadding:(CGFloat)size { - [super updateTopLabelsPadding:size]; - self.topLabelsView.bottomLabelConstraint.constant = PaddingTwo; -} - -@end diff --git a/MVMCoreUI/Templates/MoleculeStackCenteredTemplate.swift b/MVMCoreUI/Templates/MoleculeStackCenteredTemplate.swift new file mode 100644 index 00000000..0a781bc6 --- /dev/null +++ b/MVMCoreUI/Templates/MoleculeStackCenteredTemplate.swift @@ -0,0 +1,32 @@ +// +// MoleculeStackCenteredTemplate.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 2/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class MoleculeStackCenteredTemplate: ThreeLayerViewController { + + public override func viewForMiddle() -> UIView? { + let molecule = loadObject?.pageJSON?.optionalDictionaryForKey("MoleculeStack") + let moleculeStack = MVMCoreUIMoleculeStackView(withJSON: molecule, delegate: self, additionalData: nil) + return moleculeStack + } + + public override func viewForTop() -> UIView? { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("Header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForJSON(moleculeJSON, delegate: self) else { + return nil + } + return molecule + } + + override public func viewForBottom() -> UIView? { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("Footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForJSON(moleculeJSON, delegate: self) else { + return nil + } + return molecule + } +} diff --git a/MVMCoreUI/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Templates/MoleculeStackTemplate.swift new file mode 100644 index 00000000..c31d9c4e --- /dev/null +++ b/MVMCoreUI/Templates/MoleculeStackTemplate.swift @@ -0,0 +1,38 @@ +// +// MoleculeStackTemplate.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 2/11/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +public class MoleculeStackTemplate: ThreeLayerViewController { + + + public override func spaceBetweenMiddleAndBottom() -> CGFloat? { + return PaddingTwo + } + + public override func viewForTop() -> UIView? { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("Header"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForJSON(moleculeJSON, delegate: self) else { + return nil + } + return molecule + } + + public override func viewForMiddle() -> UIView? { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("MoleculeStack") else { + return nil + } + return MVMCoreUIMoleculeStackView(withJSON: moleculeJSON, delegate: self, additionalData: nil) + } + + override public func viewForBottom() -> UIView? { + guard let moleculeJSON = loadObject?.pageJSON?.optionalDictionaryForKey("Footer"), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeForJSON(moleculeJSON, delegate: self) else { + return nil + } + return molecule + } +}