diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 49776618..08b9caf0 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 48; objects = { /* Begin PBXBuildFile section */ @@ -23,6 +23,13 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; }; 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; + 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; + 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; + 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; + 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; + B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; + B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; }; D206997721FB8A0B00CAE0DE /* MVMCoreUINavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D206997821FB8A0B00CAE0DE /* MVMCoreUINavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; @@ -43,6 +50,8 @@ D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; }; D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D274CA332236A78900B01B62 /* StandardFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* StandardFooterView.swift */; }; + D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */; }; + D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */; }; D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */; }; D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */; }; D282AACB2243C61700C46919 /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D282AACA2243C61700C46919 /* ButtonView.swift */; }; @@ -150,7 +159,6 @@ D29DF2CB21E7BFCC003B2FB9 /* MFSizeThreshold.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF14721E68728003B2FB9 /* MFSizeThreshold.m */; }; D29DF2CE21E7C104003B2FB9 /* MFLoadingViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF2CC21E7C104003B2FB9 /* MFLoadingViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF2CD21E7C104003B2FB9 /* MFLoadingViewController.m */; }; - D29DF2D121E7C1C8003B2FB9 /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29DF2D021E7C1C8003B2FB9 /* MVMAnimationFramework.framework */; }; D29DF2E121E9240B003B2FB9 /* MVMCoreUIPanelProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF2E021E9240B003B2FB9 /* MVMCoreUIPanelProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF2EE21ECEADF003B2FB9 /* MFFonts.h in Headers */ = {isa = PBXBuildFile; fileRef = D29DF14D21E693AD003B2FB9 /* MFFonts.h */; settings = {ATTRIBUTES = (Public, ); }; }; D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */ = {isa = PBXBuildFile; fileRef = D29DF14C21E693AD003B2FB9 /* MFFonts.m */; }; @@ -207,6 +215,13 @@ 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; + 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = ""; }; + 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; + 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; + 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; + 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; + B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; + B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUINavigationController.h; sourceTree = ""; }; D206997621FB8A0B00CAE0DE /* MVMCoreUINavigationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUINavigationController.m; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; @@ -227,6 +242,8 @@ D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = ""; }; D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = ""; }; D274CA322236A78900B01B62 /* StandardFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardFooterView.swift; sourceTree = ""; }; + D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewCell.swift; sourceTree = ""; }; + D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EyebrowHeadlineBodyLink.swift; sourceTree = ""; }; D282AAB3223FDDAE00C46919 /* MFLoadImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFLoadImageView.swift; sourceTree = ""; }; D282AAB9224131D100C46919 /* MFTransparentGIFView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MFTransparentGIFView.swift; sourceTree = ""; }; D282AACA2243C61700C46919 /* ButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = ""; }; @@ -338,7 +355,6 @@ D29DF2C321E7BF57003B2FB9 /* MFTabBarInteractor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFTabBarInteractor.m; sourceTree = ""; }; D29DF2CC21E7C104003B2FB9 /* MFLoadingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFLoadingViewController.h; sourceTree = ""; }; D29DF2CD21E7C104003B2FB9 /* MFLoadingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFLoadingViewController.m; sourceTree = ""; }; - D29DF2D021E7C1C8003B2FB9 /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; D29DF2E021E9240B003B2FB9 /* MVMCoreUIPanelProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPanelProtocol.h; sourceTree = ""; }; D29DF31621ECECC0003B2FB9 /* NHaasGroteskDSStd-45Lt.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NHaasGroteskDSStd-45Lt.otf"; sourceTree = ""; }; D29DF31721ECECC0003B2FB9 /* OCRAExtended.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = OCRAExtended.ttf; sourceTree = ""; }; @@ -385,7 +401,7 @@ buildActionMask = 2147483647; files = ( D29DF0E621E4F3C7003B2FB9 /* MVMCore.framework in Frameworks */, - D29DF2D121E7C1C8003B2FB9 /* MVMAnimationFramework.framework in Frameworks */, + 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -418,6 +434,7 @@ children = ( D2A638FC22CA98280052ED1F /* HeadlineBody.swift */, D22479952316AF6D003FCCF9 /* HeadlineBodyTextButton.swift */, + D27CD40F2339057800C1DC07 /* EyebrowHeadlineBodyLink.swift */, ); path = VerticalCombinationViews; sourceTree = ""; @@ -455,6 +472,7 @@ 01509D8E2327EC6F00EF99AA /* MoleculeTableViewCell.swift */, D2A6390422CBCE160052ED1F /* MoleculeCollectionViewCell.swift */, D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, + D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */, ); path = Items; sourceTree = ""; @@ -528,8 +546,8 @@ D29DF0E421E4F3C7003B2FB9 /* Frameworks */ = { isa = PBXGroup; children = ( - D29DF2D021E7C1C8003B2FB9 /* MVMAnimationFramework.framework */, D29DF0E521E4F3C7003B2FB9 /* MVMCore.framework */, + 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */, ); name = Frameworks; sourceTree = ""; @@ -648,6 +666,7 @@ D29DF2A021E7AF4E003B2FB9 /* MVMCoreUIUtility.m */, D29DF2A721E7B2F9003B2FB9 /* MVMCoreUIConstants.h */, D29DF2A821E7B2F9003B2FB9 /* MVMCoreUIConstants.m */, + 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */, ); path = Utility; sourceTree = ""; @@ -687,6 +706,8 @@ D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */, D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */, D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */, + B8200E142280C4CF007245F4 /* ProgressBar.swift */, + 948DB67D2326DCD90011F916 /* MultiProgress.swift */, DBC4391622442196001AB423 /* CaretView.swift */, DBC4391722442197001AB423 /* DashLine.swift */, DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */, @@ -719,6 +740,8 @@ DB891E822253FA8500022516 /* Label.swift */, 0198F7A02256A80A0066C936 /* MFRadioButton.h */, 0198F7A22256A80A0066C936 /* MFRadioButton.m */, + 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, + 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */, ); path = Views; sourceTree = ""; @@ -950,11 +973,12 @@ D29DF0CB21E404D4003B2FB9 = { CreatedOnToolsVersion = 10.1; LastSwiftMigration = 1010; + ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = D29DF0C621E404D4003B2FB9 /* Build configuration list for PBXProject "MVMCoreUI" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -1004,6 +1028,7 @@ D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, + D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */, D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, 01004F3022721C3800991ECC /* RadioButton.swift in Sources */, @@ -1034,6 +1059,7 @@ 01509D952327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, + 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, @@ -1041,6 +1067,7 @@ D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, + 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */, D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, D29DF18121E69E50003B2FB9 /* MFView.m in Sources */, @@ -1066,6 +1093,7 @@ D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, + D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, @@ -1074,6 +1102,7 @@ D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, + 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, @@ -1081,6 +1110,7 @@ D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, + 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, @@ -1169,7 +1199,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1228,12 +1258,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.1; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -1255,12 +1284,8 @@ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks"; INFOPLIST_FILE = MVMCoreUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -1285,12 +1310,8 @@ FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../SharedFrameworks"; INFOPLIST_FILE = MVMCoreUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.vzw.MVMCoreUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/MVMCoreUI/Atoms/TextFields/MFTextField.m b/MVMCoreUI/Atoms/TextFields/MFTextField.m index c52c4972..28cd5343 100644 --- a/MVMCoreUI/Atoms/TextFields/MFTextField.m +++ b/MVMCoreUI/Atoms/TextFields/MFTextField.m @@ -83,11 +83,9 @@ self.enabled = YES; // Disable SmartQuotes - if (@available(iOS 11.0, *)) { - self.textField.smartQuotesType = UITextSmartQuotesTypeNo; - self.textField.smartDashesType = UITextSmartDashesTypeNo; - self.textField.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; - } + self.textField.smartQuotesType = UITextSmartQuotesTypeNo; + self.textField.smartDashesType = UITextSmartDashesTypeNo; + self.textField.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; } } diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift new file mode 100644 index 00000000..6f308651 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -0,0 +1,436 @@ +// +// Checkbox.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import MVMCore + +/* + !!! -- DO NOT REMOVE -- !!! + (Unless Design changes the appearance of the checkmark). + + // Offsets based on the 124x124 example checkmark + let startXOffset: Float = 42.0 / 124.0 ~~ 0.33871 + let startYOffset: Float = 66.0 / 124.0 ~~ 0.53225 + let pivotXOffset: Float = 58.0 / 124.0 ~~ 0.46774 + let pivotYOffset: Float = 80.0 / 124.0 ~~ 0.64516 + let endXOffset: Float = 83.0 / 124.0 ~~ 0.66935 + let endYOffset: Float = 46.0 / 124.0 ~~ 0.37097 + let pivotPercentage: Float = 0.34 + let endPercentage = 1.0 - pivotPercentage + let animationInterval: Float = 0.01 + */ + +/** + This class expects its height and width to be equal. + */ +@objcMembers open class Checkbox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + // Form Validation + var isRequired = false + var fieldKey: String? + var delegateObject: DelegateObject? + + public static let defaultHeightWidth: CGFloat = 18.0 + + /// If true the border of this checkbox will be circular. + public var isRound: Bool = false + + /// Determined if the checkbox's UI should animated when selected. + public var isAnimated: Bool = true + + /// Disables all selection logic when setting the value of isSelected, reducing it to a stored property. + public var updateSelectionOnly: Bool = false + + /// The color of the background when checked. + public var checkedBackgroundColor: UIColor = .white { + didSet { + if isSelected { + backgroundColor = checkedBackgroundColor + } + } + } + + /// The color of the background when unChecked. + public var unCheckedBackgroundColor: UIColor = .white { + didSet { + if !isSelected { + backgroundColor = unCheckedBackgroundColor + } + } + } + + /// Retrieves ideeal radius value to curve square into a circle. + public var cornerRadiusValue: CGFloat { + return bounds.size.height / 2 + } + + /// Action Block called when the switch is selected. + public var actionBlock: ActionBlock? + + /// Manages the appearance of the checkbox. + private var shapeLayer: CAShapeLayer? + + /// Width of the check mark. + public var checkWidth: CGFloat = 2 { + didSet { + if let shapeLayer = shapeLayer { + CATransaction.withDisabledAnimations { + shapeLayer.lineWidth = checkWidth + } + } + } + } + + /// Color of the check mark. + public var checkColor: UIColor = .black { + didSet { + if let shapeLayer = shapeLayer { + CATransaction.withDisabledAnimations { + shapeLayer.strokeColor = checkColor.cgColor + } + } + } + } + + /// Border width of the checkbox + public var borderWidth: CGFloat = 1 { + didSet { + layer.borderWidth = borderWidth + } + } + + /// border color of the Checkbox + public var borderColor: UIColor = .black { + didSet { + layer.borderColor = borderColor.cgColor + } + } + + /** + The represented state of the Checkbox. + + Setting updateSelectionOnly to true bypasses the animation logic inherent with setting this property. + */ + override open var isSelected: Bool { + didSet { + if !updateSelectionOnly { + layoutIfNeeded() + shapeLayer?.removeAllAnimations() + + updateCheckboxUI(isSelected: isSelected, isAnimated: isAnimated) + + if let delegate = delegateObject as? FormValidationProtocol { + delegate.formValidatorModel?()?.enableByValidation() + } + + updateAccessibilityLabel() + } + } + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + override public init(frame: CGRect) { + super.init(frame: frame) + + accessibilityTraits = .button + accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") + updateAccessibilityLabel() + + setupView() + } + + /// There is currently no intention on using xib files. + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + fatalError("xib file is not implemented for Checkbox.") + } + + public convenience init() { + self.init(frame:.zero) + } + + public convenience init(isChecked: Bool) { + self.init(frame: .zero) + updateSelectionOnly = true + isSelected = isChecked + updateSelectionOnly = false + } + + public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) { + self.init(frame: .zero) + updateSelectionOnly = true + isSelected = isChecked + updateSelectionOnly = false + self.checkedBackgroundColor = checkedBackgroundColor + self.unCheckedBackgroundColor = unCheckedBackgroundColor + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + override open func layoutSubviews() { + super.layoutSubviews() + + drawCheck() + layer.cornerRadius = isRound ? cornerRadiusValue : 0 + layer.borderWidth = borderWidth + layer.borderColor = borderColor.cgColor + } + + open func setupView() { + + isUserInteractionEnabled = true + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .white + } + + //-------------------------------------------------- + // MARK: - Actions + //-------------------------------------------------- + + open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { + super.sendAction(action, to: target, for: event) + toggleAndAction() + } + + open override func sendActions(for controlEvents: UIControl.Event) { + super.sendActions(for: controlEvents) + toggleAndAction() + } + + /// This will toggle the state of the Checkbox and execute the actionBlock if provided. + public func toggleAndAction() { + + isSelected.toggle() + actionBlock?() + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + /// Creates the check mark used for the checkbox. + private func drawCheck() { + + if shapeLayer == nil { + + let shapeLayer = CAShapeLayer() + self.shapeLayer = shapeLayer + shapeLayer.frame = bounds + layer.addSublayer(shapeLayer) + shapeLayer.strokeColor = checkColor.cgColor + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.path = checkMarkPath() + shapeLayer.lineJoin = .miter + shapeLayer.lineWidth = checkWidth + + CATransaction.withDisabledAnimations { + shapeLayer.strokeEnd = isSelected ? 1 : 0 + } + } + } + + /// Returns a UIBezierPath detailing the path of a checkmark + func checkMarkPath() -> CGPath { + + let sideLength = max(bounds.size.height, bounds.size.width) + let startPoint = CGPoint(x: 0.33871 * sideLength, y: 0.53225 * sideLength) + let pivotOffSet = CGPoint(x: 0.46774 * sideLength, y: 0.64516 * sideLength) + let endOffset = CGPoint(x: 0.66935 * sideLength , y: 0.37097 * sideLength) + + let bezierPath = UIBezierPath() + bezierPath.move(to: startPoint) + bezierPath.addLine(to: pivotOffSet) + bezierPath.addLine(to: endOffset) + + return bezierPath.cgPath + } + + /// Programmatic means to check/uncheck the box. + /// - parameter selected: state of the check box: true = checked OR false = unchecked. + /// - parameter animated: allows the state of the checkbox to change with or without animation. + public func updateSelection(to selected: Bool, animated: Bool) { + + DispatchQueue.main.async { + + self.updateSelectionOnly = true + self.isSelected = selected + self.updateSelectionOnly = false + self.drawCheck() + self.shapeLayer?.removeAllAnimations() + self.updateCheckboxUI(isSelected: selected, isAnimated: animated) + } + } + + /// updates the visuals of the check mark and background. + /// - parameter isSelection: the check state of the checkbox. + /// - parameter isAnimated: determines of the changes should animate or immediately refelect. + public func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) { + + if isAnimated { + let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") + animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) + animateStrokeEnd.duration = 0.3 + animateStrokeEnd.fillMode = .both + animateStrokeEnd.isRemovedOnCompletion = false + animateStrokeEnd.fromValue = !isSelected ? 1 : 0 + animateStrokeEnd.toValue = isSelected ? 1 : 0 + self.shapeLayer?.add(animateStrokeEnd, forKey: "strokeEnd") + + UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + self.backgroundColor = isSelected ? self.checkedBackgroundColor : self.unCheckedBackgroundColor + }) + } else { + CATransaction.withDisabledAnimations { + self.shapeLayer?.strokeEnd = isSelected ? 1 : 0 + } + + self.backgroundColor = isSelected ? self.checkedBackgroundColor : self.unCheckedBackgroundColor + } + } + + /// Adjust accessibility label based on state of Checkbox. + func updateAccessibilityLabel() { + // Attention: This needs to be addressed with the accessibility team. + if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@", state) + } + } + + //-------------------------------------------------- + // MARK: - UITouch + //-------------------------------------------------- + + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { + + sendActions(for: touchIsAcceptablyOutside(touches.first) ? .touchUpOutside : .touchUpInside) + } + + func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool { + let endLocation = touch?.location(in: self) + let x = endLocation?.x ?? 0.0 + let y = endLocation?.y ?? 0.0 + let faultTolerance: CGFloat = 20.0 + let widthLimit = CGFloat(bounds.size.width + faultTolerance) + let heightLimt = CGFloat(bounds.size.height + faultTolerance) + + return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt + } + + override open func accessibilityActivate() -> Bool { + sendActions(for: .touchUpInside) + return true + } + + //-------------------------------------------------- + // MARK: - Molecular + //-------------------------------------------------- + + open func needsToBeConstrained() -> Bool { + return true + } + + open func reset() { + + shapeLayer?.removeAllAnimations() + shapeLayer?.removeFromSuperlayer() + shapeLayer = nil + backgroundColor = nil + borderColor = .black + borderWidth = 1.0 + checkColor = .black + checkWidth = 2.0 + updateSelectionOnly = true + isSelected = false + updateSelectionOnly = false + } + + open func setAsMolecule() { + setupView() + } + + public func updateView(_ size: CGFloat) { + + layoutIfNeeded() + } + + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.delegateObject = delegateObject + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + + guard let dictionary = json else { return } + + if let fieldKey = dictionary[KeyFieldKey] as? String { + self.fieldKey = fieldKey + } + + if let isRequired = dictionary[KeyRequired] as? Bool { + self.isRequired = isRequired + } + + if let borderColorHex = dictionary["borderColor"] as? String { + layer.borderColor = UIColor.mfGet(forHex: borderColorHex).cgColor + } + + if let borderWidth = dictionary["borderWidth"] as? CGFloat { + layer.borderWidth = borderWidth + } + + if let isChecked = dictionary["isChecked"] as? Bool, isChecked { + updateSelectionOnly = true + isSelected = isChecked + updateSelectionOnly = false + } + + if let checkColorHex = dictionary["checkColor"] as? String { + checkColor = UIColor.mfGet(forHex: checkColorHex) + } + + if let unCheckedBackgroundColorHex = dictionary["unCheckedBackgroundColor"] as? String { + unCheckedBackgroundColor = UIColor.mfGet(forHex: unCheckedBackgroundColorHex) + } + + if let checkedBackgroundColorHex = dictionary["checkedBackgroundColor"] as? String { + checkedBackgroundColor = UIColor.mfGet(forHex: checkedBackgroundColorHex) + } + + if let isAnimated = dictionary["isAnimated"] as? Bool { + self.isAnimated = isAnimated + } + + if let isRound = dictionary["isRound"] as? Bool { + self.isRound = isRound + } + + if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { + actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } + } + } +} + +// MARK:- FormValidationProtocol +extension Checkbox: FormValidationProtocol { + + public func isValidField() -> Bool { + return isRequired ? isSelected : true + } + + public func formFieldName() -> String? { + return fieldKey + } + + public func formFieldValue() -> Any? { + return NSNumber(value: isSelected) + } +} diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift new file mode 100644 index 00000000..f148863b --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -0,0 +1,200 @@ +// +// CheckboxWithLabelView.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + + +@objcMembers open class CheckboxWithLabelView: ViewConstrainingView { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public let checkbox = Checkbox() + public let label = Label.commonLabelB2(true) + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) + + var checkboxPosition: CheckboxPosition = .center + + public enum CheckboxPosition: String { + case center + case top + case bottom + } + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + var checkboxWidthConstraint: NSLayoutConstraint? + var checkboxHeightConstraint: NSLayoutConstraint? + var checkboxTopConstraint: NSLayoutConstraint? + var checkboxBottomConstraint: NSLayoutConstraint? + var checkboxCenterYConstraint: NSLayoutConstraint? + var centerLabelCheckboxConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + + override open func setupView() { + super.setupView() + + guard subviews.isEmpty else { return } + + translatesAutoresizingMaskIntoConstraints = false + + addSubview(checkbox) + addSubview(label) + + label.text = "" + + let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth + checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) + checkboxWidthConstraint?.isActive = true + checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) + checkboxHeightConstraint?.isActive = true + + checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor).isActive = true + + let checboxBottom = checkbox.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + checboxBottom.priority = UILayoutPriority(249) + checboxBottom.isActive = true + + // Allows various positions of checkbox. + checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor) + checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) + checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor) + centerLabelCheckboxConstraint = label.centerYAnchor.constraint(equalTo: checkbox.centerYAnchor) + + label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true + label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true + + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true + let bottomLabelConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor) + bottomLabelConstraint.priority = UILayoutPriority(249) + bottomLabelConstraint.isActive = true + + alignCheckbox(.center) + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("xib file is not implemented for CheckboxWithLabelView") + } + + override public init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + public convenience init() { + self.init(frame: .zero) + } + + public convenience init(position: CheckboxPosition) { + self.init(frame: .zero) + + alignCheckbox(position) + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + /// Aligns Checkbox and Label relative to the desired position of the Checkbox. + private func alignCheckbox(_ position: CheckboxPosition) { + checkboxPosition = position + + switch position { + case .center: + checkboxBottomConstraint?.isActive = false + checkboxTopConstraint?.isActive = false + checkboxCenterYConstraint?.isActive = true + centerLabelCheckboxConstraint?.isActive = true + + case .top: + checkboxBottomConstraint?.isActive = false + checkboxTopConstraint?.isActive = true + checkboxCenterYConstraint?.isActive = false + centerLabelCheckboxConstraint?.isActive = false + + case .bottom: + checkboxBottomConstraint?.isActive = true + checkboxTopConstraint?.isActive = false + checkboxCenterYConstraint?.isActive = false + centerLabelCheckboxConstraint?.isActive = false + } + } +} + +/// MARK: - Molecular +extension CheckboxWithLabelView { + + override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return CGFloat(Checkbox.defaultHeightWidth) + } + + @objc override open func updateView(_ size: CGFloat) { + super.updateView(size) + + label.updateView(size) + + if self.checkbox.responds(to: #selector(self.updateView(_:))) { + if let dimension = sizeObject?.getValueBased(onSize: size) { + checkboxWidthConstraint?.constant = dimension + checkboxHeightConstraint?.constant = dimension + checkbox.updateView(size) + } + } + + layoutIfNeeded() + } + + override open func alignment() -> UIStackView.Alignment { + return .leading + } + + open override func resetConstraints() { + super.resetConstraints() + + checkboxCenterYConstraint?.isActive = false + checkboxBottomConstraint?.isActive = false + checkboxTopConstraint?.isActive = false + centerLabelCheckboxConstraint?.isActive = false + } + + open override func reset() { + super.reset() + + label.text = "" + checkbox.reset() + } + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + + guard let dictionary = json else { return } + + if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) { + alignCheckbox(position) + } + + checkbox.setWithJSON(dictionary.dictionaryForKey("checkbox"), delegateObject: delegateObject, additionalData: additionalData) + label.setWithJSON(dictionary.dictionaryForKey("label"), delegateObject: delegateObject, additionalData: additionalData) + } +} diff --git a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift index bc1c2219..ff2e751d 100644 --- a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift +++ b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift @@ -174,7 +174,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt super.init(frame: .zero) setText(fullText, startTag: startTag, endTag: endTag) - actionBlock = label?.createActionBlockFrom(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) + setActionMap(actionMap, additionalData: additionalData, delegateObject: delegateObject) } //------------------------------------------------------ diff --git a/MVMCoreUI/Atoms/Views/MFTextView.m b/MVMCoreUI/Atoms/Views/MFTextView.m index c120d964..424c8117 100644 --- a/MVMCoreUI/Atoms/Views/MFTextView.m +++ b/MVMCoreUI/Atoms/Views/MFTextView.m @@ -173,11 +173,10 @@ view.placeHolderLabel.textColor = [UIColor mfLightGrayColor]; // Disable SmartQuotes - if (@available(iOS 11.0, *)) { - view.textView.smartQuotesType = UITextSmartQuotesTypeNo; - view.textView.smartDashesType = UITextSmartDashesTypeNo; - view.textView.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; - } + view.textView.smartQuotesType = UITextSmartQuotesTypeNo; + view.textView.smartDashesType = UITextSmartDashesTypeNo; + view.textView.smartInsertDeleteType = UITextSmartInsertDeleteTypeNo; + [view didSetFont:view.textView.font]; view.hideBorder = YES; return view; diff --git a/MVMCoreUI/Atoms/Views/MFView.m b/MVMCoreUI/Atoms/Views/MFView.m index 4e0d71ab..6d939fc8 100644 --- a/MVMCoreUI/Atoms/Views/MFView.m +++ b/MVMCoreUI/Atoms/Views/MFView.m @@ -48,9 +48,7 @@ - (void)setAsMolecule { self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 11.0, *)) { - self.insetsLayoutMarginsFromSafeArea = NO; - } + self.insetsLayoutMarginsFromSafeArea = NO; } - (void)reset { diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h index fc76ed71..f1638f6a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.h @@ -12,7 +12,7 @@ @class Label; @class MFSizeObject; -@interface MVMCoreUICheckBox : UIControl +@interface MVMCoreUICheckBox : UIControl @property (nullable, weak, nonatomic) MVMCoreUICheckMarkView *checkMark; @property (readonly, nonatomic) BOOL isSelected; diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m index f31aace3..50377b1a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m +++ b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m @@ -307,6 +307,8 @@ static const CGFloat CheckBoxHeightWidth = 18.0; self.checkedSquare.layer.cornerRadius = self.isRoundRectCheckMark ? 5.0f : 0; } +// TODO:..................................... + #pragma mark - XIB Helpers - (instancetype)awakeAfterUsingCoder:(NSCoder *)aDecoder { diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h index a21dd55f..32e98f2a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.h @@ -9,10 +9,11 @@ #import #import +#import @import MVMCore.MVMCoreViewProtocol; typedef void(^ValueChangeBlock)(void); -@interface MVMCoreUISwitch : UIControl +@interface MVMCoreUISwitch : UIControl @property (assign, nonatomic, getter=isOn) BOOL on; @property (nullable, strong, nonatomic) UIColor *onTintColor; diff --git a/MVMCoreUI/Atoms/Views/MultiProgress.swift b/MVMCoreUI/Atoms/Views/MultiProgress.swift new file mode 100644 index 00000000..fe902d9f --- /dev/null +++ b/MVMCoreUI/Atoms/Views/MultiProgress.swift @@ -0,0 +1,99 @@ +// +// MultiProgress.swift +// MVMCoreUI +// +// Created by Ryan on 9/9/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class ProgressBarObject { + ///from 0.0 to 1.0. input progress should be [0 100] + var progress: CGFloat = 0.0 + ///default color is cerulean + var color: UIColor = UIColor.mfCerulean() + + init(_ module: [AnyHashable: Any]?) { + progress = (module?.optionalCGFloatForKey("progress") ?? 0.0)/100 + if let colorString = module?.optionalStringForKey("progressColor") { + color = UIColor.mfGet(forHex: colorString) + } + } + + static func getProgressBarObjectList(_ list: [[AnyHashable: Any]]?) -> [ProgressBarObject]? { + guard list?.count ?? 0 > 0 else { + return nil + } + var progressList = [ProgressBarObject]() + for module in list! { + let progressObject = ProgressBarObject(module) + progressList.append(progressObject) + } + return progressList + } +} + +@objcMembers open class MultiProgress: MFView { + ///passing value to progressList creates corresponding progress bars + var progressList: Array? { + didSet { + for subview in subviews { + subview.removeFromSuperview() + } + guard (progressList?.count ?? 0) > 0 else { + return + } + var previous: UIView? + for progressObject in progressList! { + guard progressObject.progress > 0.0 else { + continue + } + let view = MFView(frame: .zero) + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + view.backgroundColor = progressObject.color + view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: progressObject.progress).isActive = true + view.leadingAnchor.constraint(equalTo: previous?.trailingAnchor ?? leadingAnchor).isActive = true + previous = view + NSLayoutConstraint.constraintPinSubview(view, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false) + } + } + } + + var roundedRect: Bool = false { + didSet { + if roundedRect { + layer.cornerRadius = (thicknessConstraint?.constant ?? defaultHeight)/2 + } else { + layer.cornerRadius = 0 + } + } + } + var thicknessConstraint: NSLayoutConstraint? + let defaultHeight: CGFloat = 8 + + override open func setupView() { + super.setupView() + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .mfLightSilver() + clipsToBounds = true + if thicknessConstraint == nil { + thicknessConstraint = heightAnchor.constraint(equalToConstant: defaultHeight) + thicknessConstraint?.isActive = true + } + } + + open override func reset() { + super.reset() + backgroundColor = .mfLightSilver() + progressList = nil + } + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + thicknessConstraint?.constant = json?.optionalCGFloatForKey("thickness") ?? defaultHeight + roundedRect = json?.optionalBoolForKey("roundedRect") ?? false + progressList = ProgressBarObject.getProgressBarObjectList(json?.optionalArrayForKey("progressList") as? [[AnyHashable: Any]]) + } +} diff --git a/MVMCoreUI/Atoms/Views/ProgressBar.swift b/MVMCoreUI/Atoms/Views/ProgressBar.swift index 5416f0a7..a9333966 100644 --- a/MVMCoreUI/Atoms/Views/ProgressBar.swift +++ b/MVMCoreUI/Atoms/Views/ProgressBar.swift @@ -70,7 +70,7 @@ import Foundation thickness = 8 progress = 0 progressTintColor = UIColor.mfCerulean() - trackTintColor = UIColor.mfSilver() + trackTintColor = UIColor.mfLightSilver() } public static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h index a5e359d0..df443afd 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.h +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.h @@ -34,6 +34,9 @@ @property (nonatomic) BOOL updateViewHorizontalDefaults; @property (nonatomic) BOOL updateViewVerticalDefaults; +@property (nonatomic) CGFloat topMarginPadding; +@property (nonatomic) CGFloat bottomMarginPadding; + /// A molecule if we constrain one. @property (weak, nullable, nonatomic) UIView *molecule; diff --git a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m index ba5f1bd0..66a3a83d 100644 --- a/MVMCoreUI/Atoms/Views/ViewConstrainingView.m +++ b/MVMCoreUI/Atoms/Views/ViewConstrainingView.m @@ -304,18 +304,15 @@ [super setupView]; self.translatesAutoresizingMaskIntoConstraints = NO; self.backgroundColor = [UIColor clearColor]; + self.topMarginPadding = PaddingDefaultVerticalSpacing3; + self.bottomMarginPadding = PaddingDefaultVerticalSpacing3; [MVMCoreUIUtility setMarginsForView:self leading:0 top:0 trailing:0 bottom:0]; } - (void)updateView:(CGFloat)size { [super updateView:size]; - if ([self.constrainedView respondsToSelector:@selector(updateView:)]) { - [((id)self.constrainedView) updateView:size]; - } - if (self.molecule != self.constrainedView) { - [self.molecule updateView:size]; - } - [MFStyler setDefaultMarginsForView:self size:size horizontal:self.updateViewHorizontalDefaults vertical:self.updateViewVerticalDefaults]; + [self.molecule updateView:size]; + [MFStyler setMarginsForView:self size:size defaultHorizontal:self.updateViewHorizontalDefaults top:(self.updateViewVerticalDefaults ? self.topMarginPadding : 0) bottom:(self.updateViewVerticalDefaults ? self.bottomMarginPadding : 0)]; UIEdgeInsets margins = [MVMCoreUIUtility getMarginsForView:self]; if (self.updateViewHorizontalDefaults) { [self setLeftPinConstant:margins.left]; @@ -344,6 +341,8 @@ [super reset]; self.updateViewHorizontalDefaults = NO; self.updateViewVerticalDefaults = NO; + self.topMarginPadding = PaddingDefaultVerticalSpacing3; + self.bottomMarginPadding = PaddingDefaultVerticalSpacing3; if ([self.molecule respondsToSelector:@selector(alignment)]) { [self alignHorizontal:[(UIView *)self.molecule alignment]]; } @@ -354,8 +353,10 @@ } - (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData { - [super setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; - + // Only treated as a container if we are constraining a molecule. + if (!self.constrainedView) { + [super setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; + } [self.molecule setWithJSON:json delegateObject:delegateObject additionalData:additionalData]; if (self.shouldSetupMoleculeFromJSON) { NSDictionary *moleculeJSON = [json dict:KeyMolecule]; diff --git a/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m b/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m index 98d2b5ef..1d0d2a3b 100644 --- a/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m +++ b/MVMCoreUI/BaseControllers/MFProgrammaticTableViewController.m @@ -61,12 +61,8 @@ tableView.separatorStyle = UITableViewCellSeparatorStyleNone; tableView.delegate = self; tableView.dataSource = self; - if (@available(iOS 11.0, *)) { - tableView.insetsContentViewsToSafeArea = NO; - } - if ([tableView respondsToSelector:@selector(setCellLayoutMarginsFollowReadableWidth:)]) { - tableView.cellLayoutMarginsFollowReadableWidth = NO; - } + tableView.insetsContentViewsToSafeArea = NO; + tableView.cellLayoutMarginsFollowReadableWidth = NO; return tableView; } diff --git a/MVMCoreUI/BaseControllers/MFScrollingViewController.m b/MVMCoreUI/BaseControllers/MFScrollingViewController.m index 5486b54a..56019cb7 100644 --- a/MVMCoreUI/BaseControllers/MFScrollingViewController.m +++ b/MVMCoreUI/BaseControllers/MFScrollingViewController.m @@ -91,10 +91,7 @@ static NSTimeInterval const HandScrollAnimationTiming = 7.f; - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; - BOOL automaticInset = NO; - if (@available(iOS 11.0, *)) { - automaticInset = self.navigationController && self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic; - } + BOOL automaticInset = self.navigationController && self.scrollView.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic; // Takes into account the navigation bar. if (!automaticInset && (self.edgesForExtendedLayout & UIRectEdgeTop)) { diff --git a/MVMCoreUI/BaseControllers/MFViewController.m b/MVMCoreUI/BaseControllers/MFViewController.m index 256bf050..edbabf80 100644 --- a/MVMCoreUI/BaseControllers/MFViewController.m +++ b/MVMCoreUI/BaseControllers/MFViewController.m @@ -527,6 +527,13 @@ } } +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + if (self.selectedField) { + [self.selectedField resignFirstResponder]; + } +} + - (void)dealloc { [self stopObservingForResponseJSONUpdates]; MVMCoreLog(@"%@ deallocated", [[self class] description]); diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 0293e860..d1bfa0db 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -190,13 +190,9 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true view.rightAnchor.constraint(equalTo: footerView.rightAnchor).isActive = true - if #available(iOS 11.0, *) { - view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true - safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) - safeAreaView?.backgroundColor = bottomView?.backgroundColor - } else { - view.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true - } + view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true + safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) + safeAreaView?.backgroundColor = bottomView?.backgroundColor } else { bottomConstraint?.isActive = true var y: CGFloat? diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index dae861dd..ea6923c5 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -45,7 +45,7 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { return } - if #available(iOS 11.0, *), scrollView.contentInsetAdjustmentBehavior == UIScrollView.ContentInsetAdjustmentBehavior.automatic { + if scrollView.contentInsetAdjustmentBehavior == UIScrollView.ContentInsetAdjustmentBehavior.automatic { heightConstraint?.constant = -scrollView.adjustedContentInset.top - scrollView.adjustedContentInset.bottom } else { heightConstraint?.constant = -scrollView.contentInset.top - scrollView.contentInset.bottom @@ -233,14 +233,10 @@ extension ThreeLayerViewController { view.topAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true - 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 { - NSLayoutConstraint.pinViewBottom(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + if let safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: parentView) { + safeAreaView.backgroundColor = bottomView?.backgroundColor + self.safeAreaView = safeAreaView } } } diff --git a/MVMCoreUI/Containers/TabBarController/MVMCoreUITabBarPageControlViewController.m b/MVMCoreUI/Containers/TabBarController/MVMCoreUITabBarPageControlViewController.m index 1d8481d6..cb48eb54 100644 --- a/MVMCoreUI/Containers/TabBarController/MVMCoreUITabBarPageControlViewController.m +++ b/MVMCoreUI/Containers/TabBarController/MVMCoreUITabBarPageControlViewController.m @@ -283,16 +283,6 @@ // So we will update titles. [self newDataBuildScreen]; - - // Fix for right bar button item with custom view which disappears when user navigates to top tabbar page controller - if (@available(iOS 11.0, *)) { - } else { - NSMutableArray *buttonItems = [[NSMutableArray alloc] init]; - UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:self action:nil]; - [buttonItems addObject:space]; - [buttonItems addObjectsFromArray:self.navigationItem.rightBarButtonItems]; - self.navigationItem.rightBarButtonItems = buttonItems; - } } - (void)viewWillDisappear:(BOOL)animated { diff --git a/MVMCoreUI/Containers/TabBarController/TopTabbar.h b/MVMCoreUI/Containers/TabBarController/TopTabbar.h index e63911e7..d851294b 100644 --- a/MVMCoreUI/Containers/TabBarController/TopTabbar.h +++ b/MVMCoreUI/Containers/TabBarController/TopTabbar.h @@ -30,6 +30,9 @@ @property (nonatomic, readonly) NSInteger selectedIndex; +/// A flag for if there should be padding before the first item. +@property (nonatomic) BOOL paddingBeforeFirstTab; + //method to set the height - (void)pinHeight:(CGFloat)height; diff --git a/MVMCoreUI/Containers/TabBarController/TopTabbar.m b/MVMCoreUI/Containers/TabBarController/TopTabbar.m index c52e9d66..e9d77c77 100644 --- a/MVMCoreUI/Containers/TabBarController/TopTabbar.m +++ b/MVMCoreUI/Containers/TabBarController/TopTabbar.m @@ -75,6 +75,7 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (void)setupView { + self.paddingBeforeFirstTab = YES; self.maxHeight = BAR_HEIGHT; self.selectedIndex = 0; self.backgroundColor = [UIColor whiteColor]; @@ -229,6 +230,9 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + if (!self.paddingBeforeFirstTab && section == 0) { + return UIEdgeInsetsMake(SECTION_TOPPIN, 0,SECTION_BOTPIN, 0); + } return UIEdgeInsetsMake(SECTION_TOPPIN, SECTION_PADDING,SECTION_BOTPIN, 0); } @@ -341,7 +345,7 @@ static NSString * const COLLECTION_CELL_ID = @"cell"; } - (void)selectIndex:(NSInteger)index animated:(BOOL)animated { - if (self.collectionView) { + if (self.collectionView && [self.datasource numberOfTopTabbarItems:self] > 0) { [MVMCoreDispatchUtility performBlockOnMainThread:^{ NSInteger currentIndex = self.selectedIndex; self.selectedIndex = index; diff --git a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m index ca75b778..1c90d9e0 100644 --- a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m +++ b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsTableViewController.m @@ -325,19 +325,13 @@ NSLayoutConstraint *bottomViewTop = [NSLayoutConstraint constraintWithItem:footerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:tableView attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; bottomViewTop.active = YES; - NSLayoutConstraint *bottomViewBot = nil; - if (@available(iOS 11.0, *)) { - bottomViewBot = [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:footerView.bottomAnchor]; - - UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; - safeAreaView.backgroundColor = footerView.backgroundColor; - self.safeAreaView = safeAreaView; - } else { - // Fallback on earlier versions - bottomViewBot = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:footerView attribute:NSLayoutAttributeBottom multiplier:1 constant:0]; - } - bottomViewBot.priority = 900; - bottomViewBot.active = YES; + NSLayoutConstraint *bottomViewBot = [self.view.safeAreaLayoutGuide.bottomAnchor constraintEqualToAnchor:footerView.bottomAnchor]; + bottomViewBot.priority = 900; + bottomViewBot.active = YES; + + UIView *safeAreaView = [MVMCoreUICommonViewsUtility getAndSetupSafeAreaViewOnView:self.view]; + safeAreaView.backgroundColor = footerView.backgroundColor; + self.safeAreaView = safeAreaView; [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[footerView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(footerView)]]; } else { diff --git a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m index 77472e11..0c9d1d5d 100644 --- a/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m +++ b/MVMCoreUI/LegacyControllers/TopLabelsAndBottomButtonsViewController.m @@ -272,13 +272,8 @@ - (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; - } + 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; } @@ -322,17 +317,13 @@ [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:@"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; + [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0@900-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(bottomView)]]; } diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift index f1e295fe..aca450aa 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/ImageHeadlineBody.swift @@ -20,27 +20,30 @@ import UIKit guard subviews.count == 0 else { return } - MVMCoreUIUtility.setMarginsFor(self, leading: 0, top: 0, trailing: 0, bottom: 0) headlineBody.headlineLabel.styleB1(true) headlineBody.spaceBetweenLabelsConstant = 0 - addSubview(headlineBody) - addSubview(imageView) - NSLayoutConstraint.pinViewTop(toSuperview: headlineBody, useMargins: true, constant: 0).isActive = true - NSLayoutConstraint.pinViewRight(toSuperview: headlineBody, useMargins: true, constant: 0).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor).isActive = true - var constraint = NSLayoutConstraint.pinViewBottom(toSuperview: headlineBody, useMargins: true, constant: 0) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + view.addSubview(headlineBody) + view.addSubview(imageView) + + headlineBody.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor).isActive = true + view.bottomAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor).isActive = true + var constraint = view.bottomAnchor.constraint(equalTo: headlineBody.bottomAnchor) constraint.priority = .defaultLow constraint.isActive = true - imageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - NSLayoutConstraint.pinViewLeft(toSuperview: imageView, useMargins: true, constant: 0).isActive = true - imageView.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor).isActive = true - constraint = NSLayoutConstraint.pinViewBottom(toSuperview: imageView, useMargins: true, constant: 0) + imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true + imageView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + imageView.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor).isActive = true + view.bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor).isActive = true + constraint = view.bottomAnchor.constraint(equalTo: imageView.bottomAnchor) constraint.priority = UILayoutPriority(rawValue: 200) constraint.isActive = true - constraint = NSLayoutConstraint.pinViewTop(toSuperview: imageView, useMargins: true, constant: 0) + constraint = imageView.topAnchor.constraint(equalTo: view.topAnchor) constraint.priority = UILayoutPriority(rawValue: 200) constraint.isActive = true diff --git a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift index b188d45b..075d33f1 100644 --- a/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -34,9 +34,9 @@ import UIKit } if accordionButton.isSelected { - delegateObject?.moleculeDelegate?.addMolecules?(molecules, senderIndexPath: indexPath) + delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: .automatic) } else { - delegateObject?.moleculeDelegate?.removeMolecules?(molecules, senderIndexPath: indexPath) + delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: .automatic) } if (json?.boolForKey("hideSeparatorWhenExpanded") ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { diff --git a/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift index 42a5e5cd..685d2e7a 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift @@ -37,11 +37,9 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi } isAccessibilityElement = false contentView.isAccessibilityElement = false - if #available(iOS 11.0, *) { - insetsLayoutMarginsFromSafeArea = false - contentView.insetsLayoutMarginsFromSafeArea = false - contentView.preservesSuperviewLayoutMargins = false - } + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false // Covers the card when peaking. peakingCover.backgroundColor = .white diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 210eac6f..3c20402e 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -87,29 +87,16 @@ import UIKit // MARK: - MFViewProtocol public func updateView(_ size: CGFloat) { MFStyler.setMarginsFor(self, size: size, defaultHorizontal: updateViewHorizontalDefaults, top: topMarginPadding, bottom: bottomMarginPadding) - if #available(iOS 11.0, *) { - if accessoryView != nil { - // Smaller left margin if accessory view. - var margin = directionalLayoutMargins - margin.trailing = 16 - contentView.directionalLayoutMargins = margin - } else { - contentView.directionalLayoutMargins = directionalLayoutMargins - } - topSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) - bottomSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) + if accessoryView != nil { + // Smaller left margin if accessory view. + var margin = directionalLayoutMargins + margin.trailing = 16 + contentView.directionalLayoutMargins = margin } else { - if accessoryView != nil { - // Smaller left margin if accessory view. - var margin = layoutMargins - margin.right = 16 - contentView.layoutMargins = margin - } else { - contentView.layoutMargins = layoutMargins - } - topSeparatorView?.setLeftAndRightPinConstant(layoutMargins.left) - bottomSeparatorView?.setLeftAndRightPinConstant(layoutMargins.left) + contentView.directionalLayoutMargins = directionalLayoutMargins } + topSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) + bottomSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) molecule?.updateView(size) if let _ = accessoryView, let caretView = caretView, let widthObject = caretViewWidthSizeObject, let heightObject = caretViewHeightSizeObject { @@ -121,11 +108,9 @@ import UIKit public func setupView() { selectionStyle = .none - if #available(iOS 11.0, *) { - insetsLayoutMarginsFromSafeArea = false - contentView.insetsLayoutMarginsFromSafeArea = false - contentView.preservesSuperviewLayoutMargins = false - } + insetsLayoutMarginsFromSafeArea = false + contentView.insetsLayoutMarginsFromSafeArea = false + contentView.preservesSuperviewLayoutMargins = false } // MARK: - MVMCoreUIMoleculeViewProtocol @@ -137,7 +122,7 @@ import UIKit if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { updateViewHorizontalDefaults = useHorizontalMargins } - if json?.optionalBoolForKey("useVerticalMargins") ?? false { + if (json?.optionalBoolForKey("useVerticalMargins") ?? true) == false { topMarginPadding = 0 bottomMarginPadding = 0 } diff --git a/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift new file mode 100644 index 00000000..1c509a8f --- /dev/null +++ b/MVMCoreUI/Molecules/Items/TabsTableViewCell.swift @@ -0,0 +1,81 @@ +// +// TabsTableViewCell.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/6/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers public class TabsTableViewCell: MoleculeTableViewCell { + let tabs = TopTabbar(frame: .zero) + var delegateObject: MVMCoreUIDelegateObject? + var previousTabIndex = 0 + + // MARK: - MFViewProtocol + override public func setupView() { + super.setupView() + guard tabs.superview == nil else { + return + } + tabs.paddingBeforeFirstTab = false + topMarginPadding = 8 + bottomMarginPadding = 0 + + tabs.translatesAutoresizingMaskIntoConstraints = false + tabs.delegate = self + tabs.datasource = self + contentView.addSubview(tabs) + + NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: tabs, useMargins: true).values)) + tabs.reloadData() + } + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + tabs.updateView(size) + } + + // MARK: - MoleculeDelegateProtocol + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + tabs.reloadData() + } + + public override func reset() { + super.reset() + topMarginPadding = 8 + bottomMarginPadding = 0 + } +} + +extension TabsTableViewCell: TopTabbarDelegate { + public func shouldSelectItem(at index: Int, topTabbar: TopTabbar) -> Bool { + if let moleculesArrays = json?.arrayForKey(KeyMolecules), let molecules = moleculesArrays[topTabbar.selectedIndex] as? [[AnyHashable: Any]] { + delegateObject?.moleculeDelegate?.removeMolecules?(molecules, sender: self, animation: index < tabs.selectedIndex ? .right : .left) + } + previousTabIndex = tabs.selectedIndex + return true + } + + public func topTabbar(_ topTabbar: TopTabbar, didSelectItemAt index: Int) { + if let moleculesArrays = json?.arrayForKey(KeyMolecules), let molecules = moleculesArrays[index] as? [[AnyHashable: Any]] { + delegateObject?.moleculeDelegate?.addMolecules?(molecules, sender: self, animation: index < previousTabIndex ? .left : .right) + } + } +} + +extension TabsTableViewCell: TopTabbarDataSource { + public func number(ofTopTabbarItems topTabbar: TopTabbar) -> Int { + return json?.optionalDictionaryForKey("tabs")?.optionalArrayForKey("tabs")?.count ?? 0 + } + + public func topTabbar(_ topTabbar: TopTabbar, titleForItemAt index: Int) -> String? { + guard let tabs = json?.optionalDictionaryForKey("tabs")?.arrayForKey("tabs"), let label = tabs[index] as? [AnyHashable: Any], let title = label.optionalStringForKey(KeyText) else { + return "Select" + } + return title + } +} diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift index ffab9920..d373ce87 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodySwitch.swift @@ -25,8 +25,12 @@ import UIKit return } headlineBody.styleListItem() - addSubview(headlineBody) - addSubview(mvmSwitch) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineBody) + view.addSubview(mvmSwitch) NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBody, rightView: mvmSwitch) } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift index 754f508b..0f184544 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/HeadlineBodyTextButtonSwitch.swift @@ -24,9 +24,13 @@ import UIKit guard mvmSwitch.superview == nil else { return } + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + headlineBodyTextButton.headlineBody.styleListItem() - addSubview(headlineBodyTextButton) - addSubview(mvmSwitch) + view.addSubview(headlineBodyTextButton) + view.addSubview(mvmSwitch) NSLayoutConstraint.pinSubviewsCenter(leftView: headlineBodyTextButton, rightView: mvmSwitch) } diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift index 017eee99..d47cf44c 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift @@ -24,8 +24,12 @@ import UIKit guard mvmSwitch.superview == nil else { return } - addSubview(label) - addSubview(mvmSwitch) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(label) + view.addSubview(mvmSwitch) label.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) NSLayoutConstraint.pinSubviewsCenter(leftView: label, rightView: mvmSwitch) } diff --git a/MVMCoreUI/Molecules/Scroller.swift b/MVMCoreUI/Molecules/Scroller.swift index cd2949d8..e4289bc3 100644 --- a/MVMCoreUI/Molecules/Scroller.swift +++ b/MVMCoreUI/Molecules/Scroller.swift @@ -19,7 +19,8 @@ import UIKit } translatesAutoresizingMaskIntoConstraints = false scrollView.translatesAutoresizingMaskIntoConstraints = false - addConstrainedView(scrollView) + addSubview(scrollView) + pinView(toSuperView: scrollView) scrollView.addSubview(contentView) NSLayoutConstraint.constraintPinSubview(toSuperview: contentView) let constraint = contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: 1.0) diff --git a/MVMCoreUI/Molecules/StandardFooterView.swift b/MVMCoreUI/Molecules/StandardFooterView.swift index f00af028..7734883e 100644 --- a/MVMCoreUI/Molecules/StandardFooterView.swift +++ b/MVMCoreUI/Molecules/StandardFooterView.swift @@ -11,6 +11,8 @@ import UIKit open class StandardFooterView: ViewConstrainingView { open override func setupView() { super.setupView() + topMarginPadding = PaddingDefaultVerticalSpacing + bottomMarginPadding = PaddingDefaultVerticalSpacing shouldSetupMoleculeFromJSON = true updateViewVerticalDefaults = true updateViewHorizontalDefaults = true diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/StandardHeaderView.swift index 093a2e31..12819313 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/StandardHeaderView.swift @@ -22,6 +22,8 @@ public class StandardHeaderView: ViewConstrainingView { shouldSetupMoleculeFromJSON = true updateViewVerticalDefaults = true updateViewHorizontalDefaults = true + topMarginPadding = PaddingDefaultVerticalSpacing + bottomMarginPadding = PaddingDefaultVerticalSpacing if separatorView == nil, let separatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot, withHorizontalPadding: 0) { separatorView.setAsHeavy() addSubview(separatorView) @@ -54,6 +56,8 @@ public class StandardHeaderView: ViewConstrainingView { open override func reset() { super.reset() + topMarginPadding = PaddingDefaultVerticalSpacing + bottomMarginPadding = PaddingDefaultVerticalSpacing separatorView?.setAsHeavy() separatorView?.show() } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift new file mode 100644 index 00000000..306cf690 --- /dev/null +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -0,0 +1,70 @@ +// +// EyebrowHeadlineBodyLink.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 9/23/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +@objcMembers open class EyebrowHeadlineBodyLink: ViewConstrainingView { + let stack = MoleculeStackView(frame: .zero) + let eyebrow = Label.commonLabelB3(true) + let headline = Label.commonLabelB1(true) + let body = Label.commonLabelB2(true) + let link = MFTextButton(nil, constrainHeight: false, forWidth: MVMCoreUIUtility.getWidth()) + + // MARK: - MFViewProtocol + open override func setupView() { + super.setupView() + guard stack.superview == nil else { + return + } + stack.spacing = 0 + stack.updateViewHorizontalDefaults = false + addSubview(stack) + pinView(toSuperView: stack) + stack.addStackItem(StackItem(with: eyebrow), lastItem: false) + stack.addStackItem(StackItem(with: headline), lastItem: false) + stack.addStackItem(StackItem(with: body), lastItem: false) + + // To visually take into account the extra padding in the intrinsic content of a button. + let stackItem = StackItem(with: link) + stackItem.spacing = -6 + stack.addStackItem(stackItem, lastItem: true) + } + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + stack.updateView(size) + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + eyebrow.setWithJSON(json?.optionalDictionaryForKey("eyebrow"), delegateObject: delegateObject, additionalData: additionalData) + stack.items[0].gone = !eyebrow.hasText + headline.setWithJSON(json?.optionalDictionaryForKey("headline"), delegateObject: delegateObject, additionalData: additionalData) + stack.items[1].gone = !headline.hasText + body.setWithJSON(json?.optionalDictionaryForKey("body"), delegateObject: delegateObject, additionalData: additionalData) + stack.items[2].gone = !body.hasText + link.setWithJSON(json?.optionalDictionaryForKey("link"), delegateObject: delegateObject, additionalData: additionalData) + stack.items[3].gone = link.titleLabel?.text?.count ?? 0 == 0 + stack.restack() + } + + open override func reset() { + super.reset() + stack.reset() + stack.spacing = 0 + stack.updateViewHorizontalDefaults = false + eyebrow.styleB3(true) + headline.styleB1(true) + body.styleB2(true) + } + + public override static func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 65 + } +} diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift index 1077ac0e..e63eb2ba 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -32,6 +32,8 @@ open class HeadlineBody: ViewConstrainingView { stylePageHeader() case "item": styleListItem() + case "itemHeader": + styleListItemDivider() default: break } } @@ -53,6 +55,12 @@ open class HeadlineBody: ViewConstrainingView { messageLabel.styleB2(true) spaceBetweenLabelsConstant = 0 } + + func styleListItemDivider() { + headlineLabel.styleH3(true) + messageLabel.styleB2(true) + spaceBetweenLabelsConstant = 0 + } // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { @@ -70,34 +78,36 @@ open class HeadlineBody: ViewConstrainingView { translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear clipsToBounds = true - - addSubview(headlineLabel) - addSubview(messageLabel) + + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineLabel) + view.addSubview(messageLabel) headlineLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) messageLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + view.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - topPin = headlineLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0) - topPin?.isActive = true + headlineLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: spaceBetweenLabelsConstant) spaceBetweenLabels?.isActive = true - leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: leftAnchor) + leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: view.leftAnchor) leftConstraintTitle?.isActive = true - rightConstraintTitle = rightAnchor.constraint(equalTo: headlineLabel.rightAnchor) + rightConstraintTitle = view.rightAnchor.constraint(equalTo: headlineLabel.rightAnchor) rightConstraintTitle?.isActive = true - leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: leftAnchor) + leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: view.leftAnchor) leftConstraintMessage?.isActive = true - rightConstraintMessage = rightAnchor.constraint(equalTo: messageLabel.rightAnchor) + rightConstraintMessage = view.rightAnchor.constraint(equalTo: messageLabel.rightAnchor) rightConstraintMessage?.isActive = true - bottomPin = bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0) - bottomPin?.isActive = true + view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0).isActive = true } // MARK: - Constraining @@ -109,16 +119,6 @@ open class HeadlineBody: ViewConstrainingView { } } - open override func setLeftPinConstant(_ constant: CGFloat) { - leftConstraintTitle?.constant = constant - leftConstraintMessage?.constant = constant - } - - open override func setRightPinConstant(_ constant: CGFloat) { - rightConstraintTitle?.constant = constant - rightConstraintMessage?.constant = constant - } - // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift index bb638624..009189cb 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyTextButton.swift @@ -28,23 +28,27 @@ import UIKit guard subviews.count == 0 else { return } - addSubview(headlineBody) - addSubview(textButton) + let view = MVMCoreUICommonViewsUtility.commonView() + addSubview(view) + pinView(toSuperView: view) + + view.addSubview(headlineBody) + view.addSubview(textButton) headlineBody.styleListItem() - headlineBody.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 0).isActive = true - headlineBody.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true - var constraint = layoutMarginsGuide.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) + headlineBody.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true + headlineBody.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + var constraint = view.rightAnchor.constraint(equalTo: headlineBody.rightAnchor) constraint.priority = .defaultHigh constraint.isActive = true spaceBetween = textButton.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant) spaceBetween?.isActive = true - textButton.leftAnchor.constraint(equalTo: layoutMarginsGuide.leftAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(equalTo: textButton.bottomAnchor).isActive = true - layoutMarginsGuide.rightAnchor.constraint(greaterThanOrEqualTo: textButton.rightAnchor).isActive = true - constraint = layoutMarginsGuide.rightAnchor.constraint(equalTo: textButton.rightAnchor) + textButton.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.bottomAnchor.constraint(equalTo: textButton.bottomAnchor).isActive = true + view.rightAnchor.constraint(greaterThanOrEqualTo: textButton.rightAnchor).isActive = true + constraint = view.rightAnchor.constraint(equalTo: textButton.rightAnchor) constraint.priority = .defaultHigh constraint.isActive = true } diff --git a/MVMCoreUI/Organisms/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift index ad395eec..14ad9e70 100644 --- a/MVMCoreUI/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -14,24 +14,30 @@ public class StackItem { var percentage: Int? var verticalAlignment: UIStackView.Alignment? var horizontalAlignment: UIStackView.Alignment? + var gone = false init(with view: UIView) { self.view = view } - init(with view: UIView, json: [AnyHashable: Any]) { + init(with view: UIView, json: [AnyHashable: Any]?) { self.view = view update(with: json) } - func update(with json: [AnyHashable: Any]) { - spacing = json.optionalCGFloatForKey("spacing") - percentage = json["percent"] as? Int - if let alignment = json.optionalStringForKey("verticalAlignment") { + func update(with json: [AnyHashable: Any]?) { + gone = json?.boolForKey("gone") ?? (json == nil) + spacing = json?.optionalCGFloatForKey("spacing") + percentage = json?["percent"] as? Int + if let alignment = json?.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"verticalAlignment"]) { verticalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) + } else { + verticalAlignment = nil } - if let alignment = json.optionalStringForKey("horizontalAlignment") { + if let alignment = json?.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule,"horizontalAlignment"]) { horizontalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) + } else { + horizontalAlignment = nil } } } @@ -80,11 +86,13 @@ public class MoleculeStackView: ViewConstrainingView { /// Restacks the existing items. func restack() { - MVMCoreUIStackableViewController.remove(contentView.subviews) - let stackItems = items - items.removeAll() - for (index, item) in stackItems.enumerated() { - addStackItem(item, lastItem: index == stackItems.count - 1) + setWithStackItems(items) + } + + /// Removes all stack items views from the view. + func removeAllItemViews() { + for item in items { + item.view.removeFromSuperview() } } @@ -140,7 +148,7 @@ public class MoleculeStackView: ViewConstrainingView { open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { let previousJSON = self.json super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - MVMCoreUIStackableViewController.remove(contentView.subviews) + removeAllItemViews() // If the items in the stack are the same, just update previous items instead of re-allocating. var items: [StackItem]? @@ -234,6 +242,10 @@ public class MoleculeStackView: ViewConstrainingView { /// Adds the stack item to the stack. func addStackItem(_ stackItem: StackItem, lastItem: Bool) { + guard !stackItem.gone else { + items.append(stackItem) + return + } let view = stackItem.view contentView.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false @@ -245,10 +257,13 @@ public class MoleculeStackView: ViewConstrainingView { view.alignHorizontal?(horizontalAlignment) view.alignVertical?(verticalAlignment) } + let first = items.first { !$0.gone } == nil if axis == .vertical { - if items.count == 0 { + if first { pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : stackItem.spacing ?? 0) - } else if let previousView = items.last?.view { + } else if let previousView = items.last(where: { stackItem in + return !stackItem.gone + })?.view { _ = NSLayoutConstraint(pinFirstView: previousView, toSecondView: view, withConstant: spacing, directionVertical: true) } pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) @@ -260,10 +275,12 @@ public class MoleculeStackView: ViewConstrainingView { pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) } } else { - if items.count == 0 { + if first { // First horizontal item has no spacing by default unless told otherwise. pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: useStackSpacingBeforeFirstItem ? spacing : stackItem.spacing ?? 0) - } else if let previousView = items.last?.view { + } else if let previousView = items.last(where: { stackItem in + return !stackItem.gone + })?.view { _ = NSLayoutConstraint(pinFirstView: previousView, toSecondView: view, withConstant: spacing, directionVertical: false) } pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) @@ -277,4 +294,20 @@ public class MoleculeStackView: ViewConstrainingView { } items.append(stackItem) } + + func setWithStackItems(_ items: [StackItem]) { + removeAllItemViews() + self.items.removeAll() + var previousPresentItem: StackItem? = nil + for item in items { + if !item.gone { + previousPresentItem = item + } + addStackItem(item, lastItem: false) + } + if let lastView = previousPresentItem?.view { + let attribute: NSLayoutConstraint.Attribute = axis == .vertical ? .bottom : .right + pinView(contentView, toView: lastView, attribute: attribute, relation: .equal, priority: .required, constant: 0) + } + } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index a3656148..27302b20 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -38,9 +38,11 @@ @"caretButton": CaretButton.class, @"textField" : MFTextField.class, @"digitTextField" : MFDigitTextField.class, - @"checkbox" : MVMCoreUICheckBox.class, + @"checkbox" : Checkbox.class, + @"checkboxWithLabelView" : CheckboxWithLabelView.class, @"cornerLabels" : CornerLabels.class, @"progressBar": ProgressBar.class, + @"multiProgressBar": MultiProgress.class, @"checkbox": MVMCoreUICheckBox.class, @"radioButton": RadioButton.class, @"listItem": MoleculeTableViewCell.class, @@ -59,7 +61,9 @@ @"labelSwitch": LabelSwitch.class, @"headlineBodySwitch": HeadlineBodySwitch.class, @"headlineBodyTextButton": HeadlineBodyTextButton.class, - @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class + @"headlineBodyTextButtonSwitch": HeadlineBodyTextButtonSwitch.class, + @"tabsListItem": TabsTableViewCell.class, + @"eyebrowHeadlineBodyLink": EyebrowHeadlineBodyLink.class } mutableCopy]; }); return mapping; diff --git a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h index cfa1504f..57eb63f5 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h +++ b/MVMCoreUI/OtherHandlers/MoleculeDelegateProtocol.h @@ -17,7 +17,7 @@ - (void)moleculeLayoutUpdated:(nonnull UIView *)molecule; /// Asks the delegate to add or remove molecules. -- (void)addMolecules:(nonnull NSArray *)molecules senderIndexPath:(nonnull NSIndexPath *)indexPath; -- (void)removeMolecules:(nonnull NSArray *)molecules senderIndexPath:(nonnull NSIndexPath *)indexPath; +- (void)addMolecules:(nonnull NSArray *)molecules sender:(nonnull UITableViewCell *)sender animation:(UITableViewRowAnimation)animation; +- (void)removeMolecules:(nonnull NSArray *)molecules sender:(nonnull UITableViewCell *)sender animation:(UITableViewRowAnimation)animation; @end diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 7ad011b3..0270cb63 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -117,34 +117,40 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { } } - open override func addMolecules(_ molecules: [[AnyHashable: Any]], senderIndexPath indexPath: IndexPath) { - var indexPaths: [IndexPath] = [] - var moleculeList: [(identifier: String, class: AnyClass, molecule: [AnyHashable: Any])] = [] - for (index, molecule) in molecules.enumerated() { - if let info = getMoleculeInfo(with: molecule) { - moleculeList.append(info) - indexPaths.append(IndexPath(row: indexPath.row + 1 + index, section: 0)) - tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + open override func addMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. + DispatchQueue.main.async { + guard let cell = sender as? MoleculeTableViewCell, let indexPath = self.tableView?.indexPath(for: cell) else { + return } + var indexPaths: [IndexPath] = [] + for molecule in molecules { + if let info = self.getMoleculeInfo(with: molecule) { + self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + let index = indexPath.row + 1 + indexPaths.count + self.moleculesInfo?.insert(info, at: index) + indexPaths.append(IndexPath(row: index, section: 0)) + } + } + self.tableView?.insertRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() } - moleculesInfo?.insert(contentsOf: moleculeList, at: indexPath.row + 1) - tableView?.insertRows(at: indexPaths, with: .automatic) } - open override func removeMolecules(_ molecules: [[AnyHashable: Any]], senderIndexPath indexPath: IndexPath) { - guard let moleculesList = moleculesInfo else { - return - } + open override func removeMolecules(_ molecules: [[AnyHashable : Any]], sender: UITableViewCell, animation: UITableView.RowAnimation) { var indexPaths: [IndexPath] = [] - for (index, moleculeInfo) in moleculesList.enumerated() { - if molecules.contains(where: { (molecule) -> Bool in + for molecule in molecules { + if let removeIndex = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in return NSDictionary(dictionary: molecule).isEqual(to: moleculeInfo.molecule) }) { - indexPaths.append(IndexPath(row: index, section: 0)) - moleculesInfo?.remove(at: index) + moleculesInfo?.remove(at: removeIndex) + indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) } } - tableView?.deleteRows(at: indexPaths, with: .automatic) + self.tableView?.deleteRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() } // MARK: - Convenience diff --git a/MVMCoreUI/Utility/CATransaction+Extension.swift b/MVMCoreUI/Utility/CATransaction+Extension.swift new file mode 100644 index 00000000..66bb33d3 --- /dev/null +++ b/MVMCoreUI/Utility/CATransaction+Extension.swift @@ -0,0 +1,21 @@ +// +// CATransaction+Extension.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/2/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + + +extension CATransaction { + + /// Performs changes without activating animation actions. + static func withDisabledAnimations(_ actionBlock: ActionBlock) { + CATransaction.begin() + CATransaction.setDisableActions(true) + actionBlock() + CATransaction.commit() + } +} diff --git a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m index 94c6ca85..fd5c9b21 100644 --- a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility.m @@ -198,13 +198,8 @@ static const CGFloat VertialShadowOffset = 6; [view.rightAnchor constraintEqualToAnchor:button.rightAnchor constant:PaddingTwo].active = YES; [view.centerYAnchor constraintEqualToAnchor:button.centerYAnchor].active = YES; } else { - if (@available(iOS 11.0, *)) { - [button.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.topAnchor constant:PaddingOne].active = YES; - [view.safeAreaLayoutGuide.trailingAnchor constraintEqualToAnchor:button.trailingAnchor constant:PaddingTwo].active = YES; - } else { - [NSLayoutConstraint constraintPinSubview:button pinTop:YES topConstant:PaddingOne pinBottom:NO bottomConstant:0 pinLeft:NO leftConstant:0 pinRight:YES rightConstant:PaddingTwo]; - } - + [button.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.topAnchor constant:PaddingOne].active = YES; + [view.safeAreaLayoutGuide.trailingAnchor constraintEqualToAnchor:button.trailingAnchor constant:PaddingTwo].active = YES; } } return button; @@ -249,16 +244,12 @@ static const CGFloat VertialShadowOffset = 6; } + (nullable UIView *)getAndSetupSafeAreaViewOnView:(nonnull UIView *)view { - if (@available(iOS 11.0, *)) { - UIView *safeAreaView = [MVMCoreUICommonViewsUtility commonView]; - [view addSubview:safeAreaView]; - [safeAreaView.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.bottomAnchor].active = YES; - [view.bottomAnchor constraintEqualToAnchor:safeAreaView.bottomAnchor].active = YES; - [NSLayoutConstraint constraintPinSubview:safeAreaView pinTop:NO topConstant:0 pinBottom:NO bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0]; - return safeAreaView; - } else { - return nil; - } + UIView *safeAreaView = [MVMCoreUICommonViewsUtility commonView]; + [view addSubview:safeAreaView]; + [safeAreaView.topAnchor constraintEqualToAnchor:view.safeAreaLayoutGuide.bottomAnchor].active = YES; + [view.bottomAnchor constraintEqualToAnchor:safeAreaView.bottomAnchor].active = YES; + [NSLayoutConstraint constraintPinSubview:safeAreaView pinTop:NO topConstant:0 pinBottom:NO bottomConstant:0 pinLeft:YES leftConstant:0 pinRight:YES rightConstant:0]; + return safeAreaView; } #pragma mark - shadows diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m index 4f150783..48a7a6a1 100644 --- a/MVMCoreUI/Utility/MVMCoreUIUtility.m +++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m @@ -58,21 +58,13 @@ } + (UIEdgeInsets)getMarginsForView:(nullable UIView *)view { - if (@available(iOS 11.0, *)) { - return UIEdgeInsetsMake(view.directionalLayoutMargins.top, view.directionalLayoutMargins.leading, view.directionalLayoutMargins.bottom, view.directionalLayoutMargins.trailing); - } else { - return view.layoutMargins; - } + return UIEdgeInsetsMake(view.directionalLayoutMargins.top, view.directionalLayoutMargins.leading, view.directionalLayoutMargins.bottom, view.directionalLayoutMargins.trailing); } #pragma mark - Setters + (void)setMarginsForView:(nullable UIView *)view leading:(CGFloat)leading top:(CGFloat)top trailing:(CGFloat)trailing bottom:(CGFloat)bottom { - if (@available(iOS 11.0, *)) { - view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(top, leading, bottom, trailing); - } else { - view.layoutMargins = UIEdgeInsetsMake(top, leading, bottom, trailing); - } + view.directionalLayoutMargins = NSDirectionalEdgeInsetsMake(top, leading, bottom, trailing); } #pragma mark - Formatting @@ -152,12 +144,9 @@ CGFloat topInset = scrollview.contentInset.top; CGFloat bottomInset = scrollview.contentInset.bottom; - // Updates for ios 11 - if (@available(iOS 11.0, *)) { - if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { - topInset = scrollview.adjustedContentInset.top; - bottomInset = scrollview.adjustedContentInset.bottom; - } + if (scrollview.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentAutomatic) { + topInset = scrollview.adjustedContentInset.top; + bottomInset = scrollview.adjustedContentInset.bottom; } CGFloat remainingSpace = frameHeight - contentSizeHeight - topInset - bottomInset;