diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 3fa3be01..2c760af1 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -196,8 +196,12 @@ AA11A42123F15D7000D7962F /* ListRightVariablePaymentsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA11A42023F15D7000D7962F /* ListRightVariablePaymentsModel.swift */; }; AA1EC59724373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1EC59624373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift */; }; AA1EC59924373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1EC59824373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift */; }; + AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2AD115244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift */; }; + AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */; }; AA56A20F243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */; }; AA56A211243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA56A210243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift */; }; + AA617AB02453010A00910B8F /* ListDeviceComplexLinkSmall.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA617AAF2453010A00910B8F /* ListDeviceComplexLinkSmall.swift */; }; + AA617AB22453012400910B8F /* ListDeviceComplexLinkSmallModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA617AB12453012400910B8F /* ListDeviceComplexLinkSmallModel.swift */; }; AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA69AAF52445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift */; }; AA69AAF82445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA69AAF72445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift */; }; AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */; }; @@ -241,6 +245,12 @@ D202AFE4242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D202AFE3242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift */; }; D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D202AFE5242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift */; }; D2092349244A51D40044AD09 /* RadioSwatchModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092348244A51D40044AD09 /* RadioSwatchModel.swift */; }; + D209234F244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D209234E244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift */; }; + D2092351244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092350244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift */; }; + D2092353244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */; }; + D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */; }; + D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */; }; + D20923592450ECE00044AD09 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20923582450ECE00044AD09 /* TableView.swift */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; D20FB165241A5D75004AFC3A /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; @@ -641,8 +651,12 @@ AA11A42023F15D7000D7962F /* ListRightVariablePaymentsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariablePaymentsModel.swift; sourceTree = ""; }; AA1EC59624373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnSpeedTestDividerModel.swift; sourceTree = ""; }; AA1EC59824373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnSpeedTestDivider.swift; sourceTree = ""; }; + AA2AD115244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkMedium.swift; sourceTree = ""; }; + AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkMediumModel.swift; sourceTree = ""; }; AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDividerModel.swift; sourceTree = ""; }; AA56A210243C5EFC00303286 /* ListTwoColumnSubsectionDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDivider.swift; sourceTree = ""; }; + AA617AAF2453010A00910B8F /* ListDeviceComplexLinkSmall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkSmall.swift; sourceTree = ""; }; + AA617AB12453012400910B8F /* ListDeviceComplexLinkSmallModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkSmallModel.swift; sourceTree = ""; }; AA69AAF52445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxBodyText.swift; sourceTree = ""; }; AA69AAF72445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxBodyTextModel.swift; sourceTree = ""; }; AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchCollectionViewCell.swift; sourceTree = ""; }; @@ -686,6 +700,12 @@ D202AFE3242A5F5E00E5BEDF /* NSTextAlignment+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTextAlignment+Extension.swift"; sourceTree = ""; }; D202AFE5242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewScrollPosition+Extension.swift"; sourceTree = ""; }; D2092348244A51D40044AD09 /* RadioSwatchModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchModel.swift; sourceTree = ""; }; + D209234E244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCenterTemplate.swift; sourceTree = ""; }; + D2092350244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerCenterTemplateModel.swift; sourceTree = ""; }; + D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUIViewControllerMappingObject+Extension.swift"; sourceTree = ""; }; + D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTemplateModelProtocol.swift; sourceTree = ""; }; + D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerModelBase.swift; sourceTree = ""; }; + D20923582450ECE00044AD09 /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; @@ -944,6 +964,7 @@ D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */, 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */, D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */, + D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */, ); path = ModelProtocols; sourceTree = ""; @@ -1181,6 +1202,10 @@ children = ( BB1D17DF244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift */, BB1D17E1244EAA46001D2002 /* ListDeviceComplexButtonMedium.swift */, + AA617AB12453012400910B8F /* ListDeviceComplexLinkSmallModel.swift */, + AA617AAF2453010A00910B8F /* ListDeviceComplexLinkSmall.swift */, + AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */, + AA2AD115244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift */, ); path = Device; sourceTree = ""; @@ -1541,6 +1566,7 @@ isa = PBXGroup; children = ( D22D8392241C27B100D3DF69 /* TemplateModel.swift */, + D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */, 014AA72823C5059B006F3E93 /* StackPageTemplateModel.swift */, D2A5146022121FBF00345BFB /* MoleculeStackTemplate.swift */, 942C378D2412F5B60066E45E /* ModalMoleculeStackTemplate.swift */, @@ -1552,6 +1578,8 @@ 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */, 014AA72A23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift */, D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */, + D2092350244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift */, + D209234E244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift */, D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */, D264FA8B243BCD8E00D98315 /* CollectionTemplateModel.swift */, D264FA8D243BCD9A00D98315 /* CollectionTemplate.swift */, @@ -1804,6 +1832,7 @@ D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */, D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */, D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */, + D2092352244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift */, D296E14622A597490051EBE7 /* MVMCoreUIViewConstrainingProtocol.h */, ); path = OtherHandlers; @@ -1911,6 +1940,7 @@ D2B18B7E2360913400A9AEDC /* Control.swift */, D2B18B802360945C00A9AEDC /* View.swift */, 0AE14F63238315D2005417F8 /* TextField.swift */, + D20923582450ECE00044AD09 /* TableView.swift */, D2755D7A23689C7500485468 /* TableViewCell.swift */, D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */, D264FAA92440F97600D98315 /* CollectionView.swift */, @@ -2131,6 +2161,7 @@ AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, 0116A4E5228B19640094F3ED /* RadioButtonSelectionHelper.swift in Sources */, + D2092353244F7D630044AD09 /* MVMCoreUIViewControllerMappingObject+Extension.swift in Sources */, 017BEB48236230DB0024EF95 /* MoleculeViewProtocol.swift in Sources */, D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */, 94382086243238D100B43AF3 /* WebViewModel.swift in Sources */, @@ -2139,6 +2170,7 @@ D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, D21B7F602437C5BC00051ABF /* MoleculeStackView.swift in Sources */, 0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */, + AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */, D22D1F1F220343560077CEC0 /* MVMCoreUICheckMarkView.m in Sources */, D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */, 01004F3022721C3800991ECC /* RadioButton.swift in Sources */, @@ -2169,11 +2201,13 @@ D2E2A98323D8B32D000B42E6 /* EyebrowHeadlineBodyLinkModel.swift in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */, + D2092351244F7BE80044AD09 /* ThreeLayerCenterTemplateModel.swift in Sources */, D21B7F77243BB70700051ABF /* MoleculeCollectionItemModel.swift in Sources */, AAB9C10A243496DD00151545 /* RadioSwatch.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, 011D9602240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift in Sources */, D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */, + AA617AB22453012400910B8F /* ListDeviceComplexLinkSmallModel.swift in Sources */, D260106323D0C05000764D80 /* StackItemModel.swift in Sources */, D2E2A99823D8D63C000B42E6 /* ActionDetailWithImageModel.swift in Sources */, BBBBC87D24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift in Sources */, @@ -2255,8 +2289,10 @@ 0A7EF85B23D8A52800B2AAD1 /* EntryFieldModel.swift in Sources */, 8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */, 94F217B723E0BF6100A47C06 /* PrimaryButtonView.m in Sources */, + D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */, 0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */, 0A21DB8B235E06EF00C160A2 /* MFDigitTextBox.m in Sources */, + D20923592450ECE00044AD09 /* TableView.swift in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, BB47A586241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift in Sources */, AA69AAF82445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift in Sources */, @@ -2264,6 +2300,7 @@ C6FA7D5423C77A4A00A3614A /* NumberedList.swift in Sources */, 0A7ECC702441001C00C828E8 /* UIToolbar+Extension.swift in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, + D209234F244F77FD0044AD09 /* ThreeLayerCenterTemplate.swift in Sources */, 525019E52406852100EED91C /* ListFourColumnDataUsageDividerModel.swift in Sources */, 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */, 94FB966323D797DA003D482B /* MFTextButton.m in Sources */, @@ -2355,6 +2392,7 @@ 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, D20FB165241A5D75004AFC3A /* NavigationItemModelProtocol.swift in Sources */, + AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */, DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */, 0A21DB89235E06EF00C160A2 /* MFMdnTextField.m in Sources */, D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */, @@ -2367,10 +2405,12 @@ 94C0150A24215643005811A9 /* ActionTopAlertModel.swift in Sources */, 012A88DB238ED45900FE3DA1 /* CarouselModel.swift in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, + D2092355244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift in Sources */, 0AE14F64238315D2005417F8 /* TextField.swift in Sources */, 0AB764D124460F6300E7FE72 /* UIDatePicker+Extension.swift in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, D2C78CD224228BBD00B69FDE /* ActionOpenPanelModel.swift in Sources */, + AA617AB02453010A00910B8F /* ListDeviceComplexLinkSmall.swift in Sources */, C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */, 01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, diff --git a/MVMCoreUI/Actions/ActionCollapseNotificationModel.swift b/MVMCoreUI/Actions/ActionCollapseNotificationModel.swift index 581c8687..4663d539 100644 --- a/MVMCoreUI/Actions/ActionCollapseNotificationModel.swift +++ b/MVMCoreUI/Actions/ActionCollapseNotificationModel.swift @@ -11,7 +11,12 @@ import UIKit @objcMembers public class ActionCollapseNotificationModel: ActionModelProtocol { public static var identifier: String = "collapseNotification" - public var actionType: String + public var actionType: String = ActionCollapseNotificationModel.identifier public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? + + public init(_ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + self.extraParameters = extraParameters + self.analyticsData = analyticsData + } } diff --git a/MVMCoreUI/Actions/ActionOpenPanelModel.swift b/MVMCoreUI/Actions/ActionOpenPanelModel.swift index 9149b32b..eaad4746 100644 --- a/MVMCoreUI/Actions/ActionOpenPanelModel.swift +++ b/MVMCoreUI/Actions/ActionOpenPanelModel.swift @@ -23,7 +23,9 @@ public class ActionOpenPanelModel: ActionModelProtocol { public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? - public init(panel: Panel) { + public init(panel: Panel, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.panel = panel + self.extraParameters = extraParameters + self.analyticsData = analyticsData } } diff --git a/MVMCoreUI/Actions/ActionTopAlertModel.swift b/MVMCoreUI/Actions/ActionTopAlertModel.swift index 82d9fa17..62e496c2 100644 --- a/MVMCoreUI/Actions/ActionTopAlertModel.swift +++ b/MVMCoreUI/Actions/ActionTopAlertModel.swift @@ -16,7 +16,9 @@ import Foundation public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? - public init(pageType: String) { + public init(pageType: String, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.pageType = pageType + self.extraParameters = extraParameters + self.analyticsData = analyticsData } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift index 1a968b7a..2b22fcee 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift @@ -19,13 +19,14 @@ import UIKit widthConstraint?.constant = diameter } } - + public override var isSelected: Bool { didSet { radioModel?.state = isSelected + updateAccessibilityLabel() } } - + public var enabledColor: UIColor = .mvmBlack public var disabledColor: UIColor = .mvmCoolGray3 public var delegateObject: MVMCoreUIDelegateObject? @@ -39,12 +40,12 @@ import UIKit }() lazy public var radioButtonSelectionHelper: RadioButtonSelectionHelper? = { - if let radioGroupName = radioGroupName, - let radioButtonModel = delegateObject?.formHolderDelegate?.formValidator?.radioButtonsModelByGroup[radioGroupName] { - return radioButtonModel - } else { - return nil - } + + guard let radioGroupName = radioGroupName, + let radioButtonModel = delegateObject?.formHolderDelegate?.formValidator?.radioButtonsModelByGroup[radioGroupName] + else { return nil } + + return radioButtonModel }() public override var isEnabled: Bool { @@ -118,6 +119,18 @@ import UIKit return radioModel?.fieldValue } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + /// Adjust accessibility label based on state of RadioButton. + func updateAccessibilityLabel() { + + if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "radio_selected_state" : "radio_not_selected_state") { + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "radio_desc_state") ?? "%@%@", "", state) + } + } + //-------------------------------------------------- // MARK: - MVMViewProtocol //-------------------------------------------------- @@ -136,14 +149,15 @@ import UIKit isAccessibilityElement = true accessibilityTraits = .button accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "radio_action_hint") + updateAccessibilityLabel() } public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject guard let model = model as? RadioButtonModel else { return } - self.delegateObject = delegateObject isSelected = model.state isEnabled = model.enabled RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject) diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index e205c678..7b4de63f 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -123,6 +123,7 @@ import Foundation // Other Container Molecules + MoleculeObjectMapping.shared()?.register(viewClass: MoleculeContainer.self, viewModelClass: MoleculeContainerModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MoleculeHeaderView.self, viewModelClass: MoleculeHeaderModel.self) MoleculeObjectMapping.shared()?.register(viewClass: FooterView.self, viewModelClass: FooterModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Scroller.self, viewModelClass: ScrollerModel.self) @@ -171,9 +172,10 @@ import Foundation // Designed Headers MoleculeObjectMapping.shared()?.register(viewClass: HeadersH2NoButtonsBodyText.self, viewModelClass: HeadersH2NoButtonsBodyTextModel.self) - // Device Items MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexButtonMedium.self, viewModelClass: ListDeviceComplexButtonMediumModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexLinkSmall.self, viewModelClass: ListDeviceComplexLinkSmallModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListDeviceComplexLinkMedium.self, viewModelClass: ListDeviceComplexLinkMediumModel.self) // TODO: Need View try? ModelRegistry.register(TabsModel.self) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/HeadersH2NoButtonsBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/HeadersH2NoButtonsBodyTextModel.swift index eb5502eb..816db00d 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/HeadersH2NoButtonsBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/Headers/HeadersH2NoButtonsBodyTextModel.swift @@ -25,8 +25,8 @@ public class HeadersH2NoButtonsBodyTextModel: HeaderModel, MoleculeModelProtocol public override func setDefaults() { super.setDefaults() - topMarginPadding = PaddingDefaultVerticalSpacing3 - bottomMarginPadding = PaddingDefaultVerticalSpacing3 + topPadding = PaddingDefaultVerticalSpacing3 + bottomPadding = PaddingDefaultVerticalSpacing3 } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift new file mode 100644 index 00000000..f6e18184 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift @@ -0,0 +1,73 @@ +// +// ListDeviceComplexLinkMedium.swift +// MVMCoreUI +// +// Created by Lekshmi S on 21/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers open class ListDeviceComplexLinkMedium: TableViewCell { + + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + public let eyebrow = Label.createLabelRegularMicro(true) + public let headline = Label.createLabelBoldTitleMedium(true) + public let body = Label.createLabelRegularBodySmall(true) + public let body2 = Label.createLabelRegularBodySmall(true) + public let link = Link() + public let rightImage = MFLoadImageView() + let verticalStack: Stack + public let stack: Stack + + //------------------------------------------------------ + // MARK: - Initializers + //------------------------------------------------------ + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + rightImage.addSizeConstraintsForAspectRatio = true + rightImage.heightAnchor.constraint(equalToConstant: 116.0).isActive = true + rightImage.widthAnchor.constraint(equalToConstant: 116.0).isActive = true + verticalStack = Stack.createStack(with: [(view: eyebrow, model: StackItemModel(horizontalAlignment: .leading)), (view: headline, model: StackItemModel(horizontalAlignment: .leading)), (view: body, model: StackItemModel(horizontalAlignment: .leading)), (view: body2, model: StackItemModel(horizontalAlignment: .leading)), (view: link, model: StackItemModel(spacing: 16, horizontalAlignment: .leading))], axis: .vertical, spacing: 0) + + stack = Stack.createStack(with: [(view: verticalStack, model: StackItemModel(horizontalAlignment: .leading)), (view: rightImage, model: StackItemModel(verticalAlignment: .center))], axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //----------------------------------------------------- + // MARK: - View Lifecycle + //----------------------------------------------------- + open override func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + verticalStack.restack() + } + + //------------------------------------------------------ + // MARK: - Molecule + //------------------------------------------------------ + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListDeviceComplexLinkMediumModel else { return } + verticalStack.updateContainedMolecules(with: [model.eyebrow, model.headline, model.body, model.body2, model.link], delegateObject, additionalData) + rightImage.set(with: model.image, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 120 + } + + open override func reset() { + super.reset() + eyebrow.styleRegularMicro(true) + headline.styleBoldTitleMedium(true) + body.styleRegularBodySmall(true) + body2.styleRegularBodySmall(true) + eyebrow.textColor = .mvmCoolGray6 + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMediumModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMediumModel.swift new file mode 100644 index 00000000..c07d7788 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMediumModel.swift @@ -0,0 +1,61 @@ +// +// ListDeviceComplexLinkMediumModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 21/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public class ListDeviceComplexLinkMediumModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listDvcLnkM" + public var eyebrow: LabelModel? + public var headline: LabelModel? + public var body: LabelModel? + public var body2: LabelModel? + public var link: LinkModel + public var image: ImageViewModel + + public init(eyebrow: LabelModel, headline: LabelModel, body: LabelModel, body2: LabelModel, link: LinkModel, image: ImageViewModel) { + self.eyebrow = eyebrow + self.headline = headline + self.body = body + self.body2 = body2 + self.link = link + self.image = image + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case eyebrow + case headline + case body + case body2 + case link + case image + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) + headline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .headline) + body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body) + body2 = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body2) + link = try typeContainer.decode(LinkModel.self, forKey: .link) + image = try typeContainer.decode(ImageViewModel.self, forKey: .image) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(eyebrow, forKey: .eyebrow) + try container.encodeIfPresent(headline, forKey: .headline) + try container.encodeIfPresent(body, forKey: .body) + try container.encodeIfPresent(body2, forKey: .body2) + try container.encode(link, forKey: .link) + try container.encode(image, forKey: .image) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift new file mode 100644 index 00000000..c57b3b18 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift @@ -0,0 +1,73 @@ +// +// ListDeviceComplexLinkSmall.swift +// MVMCoreUI +// +// Created by Lekshmi S on 24/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers open class ListDeviceComplexLinkSmall: TableViewCell { + + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + public let eyebrow = Label.createLabelRegularMicro(true) + public let headline = Label.createLabelBoldTitleMedium(true) + public let body = Label.createLabelRegularBodySmall(true) + public let body2 = Label.createLabelRegularBodySmall(true) + public let link = Link() + public let rightImage = MFLoadImageView() + let verticalStack: Stack + public let stack: Stack + + //------------------------------------------------------ + // MARK: - Initializers + //------------------------------------------------------ + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + rightImage.addSizeConstraintsForAspectRatio = true + rightImage.heightAnchor.constraint(equalToConstant: 71.0).isActive = true + rightImage.widthAnchor.constraint(equalToConstant: 71.0).isActive = true + verticalStack = Stack.createStack(with: [(view: eyebrow, model: StackItemModel(horizontalAlignment: .leading)), (view: headline, model: StackItemModel(horizontalAlignment: .leading)), (view: body, model: StackItemModel(horizontalAlignment: .leading)), (view: body2, model: StackItemModel(horizontalAlignment: .leading)), (view: link, model: StackItemModel(spacing: 16, horizontalAlignment: .leading))], axis: .vertical, spacing: 0) + + stack = Stack.createStack(with: [(view: verticalStack, model: StackItemModel(horizontalAlignment: .leading)), (view: rightImage, model: StackItemModel(verticalAlignment: .center))], axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //----------------------------------------------------- + // MARK: - View Lifecycle + //----------------------------------------------------- + open override func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + verticalStack.restack() + } + + //------------------------------------------------------ + // MARK: - Molecule + //------------------------------------------------------ + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListDeviceComplexLinkSmallModel else { return } + verticalStack.updateContainedMolecules(with: [model.eyebrow, model.headline, model.body, model.body2, model.link], delegateObject, additionalData) + rightImage.set(with: model.image, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 120 + } + + open override func reset() { + super.reset() + eyebrow.styleRegularMicro(true) + headline.styleBoldTitleMedium(true) + body.styleRegularBodySmall(true) + body2.styleRegularBodySmall(true) + eyebrow.textColor = .mvmCoolGray6 + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmallModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmallModel.swift new file mode 100644 index 00000000..a0b3047b --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmallModel.swift @@ -0,0 +1,61 @@ +// +// ListDeviceComplexLinkSmallModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 24/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public class ListDeviceComplexLinkSmallModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listDvcLnkS" + public var eyebrow: LabelModel? + public var headline: LabelModel? + public var body: LabelModel? + public var body2: LabelModel? + public var link: LinkModel + public var image: ImageViewModel + + public init(eyebrow: LabelModel, headline: LabelModel, body: LabelModel, body2: LabelModel, link: LinkModel, image: ImageViewModel) { + self.eyebrow = eyebrow + self.headline = headline + self.body = body + self.body2 = body2 + self.link = link + self.image = image + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case eyebrow + case headline + case body + case body2 + case link + case image + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + eyebrow = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .eyebrow) + headline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .headline) + body = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body) + body2 = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .body2) + link = try typeContainer.decode(LinkModel.self, forKey: .link) + image = try typeContainer.decode(ImageViewModel.self, forKey: .image) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(eyebrow, forKey: .eyebrow) + try container.encodeIfPresent(headline, forKey: .headline) + try container.encodeIfPresent(body, forKey: .body) + try container.encodeIfPresent(body2, forKey: .body2) + try container.encode(link, forKey: .link) + try container.encode(image, forKey: .image) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/FooterModel.swift b/MVMCoreUI/Atomic/Molecules/FooterModel.swift index d61d1194..12e0eca8 100644 --- a/MVMCoreUI/Atomic/Molecules/FooterModel.swift +++ b/MVMCoreUI/Atomic/Molecules/FooterModel.swift @@ -9,47 +9,24 @@ import Foundation -@objcMembers public class FooterModel: MoleculeContainerModel, MoleculeModelProtocol { - public static var identifier: String = "footer" - public var backgroundColor: Color? - - private enum CodingKeys: String, CodingKey { - case moleculeName - case backgroundColor +@objcMembers public class FooterModel: MoleculeContainerModel { + public override class var identifier: String { + return "footer" } /// Defaults to set - func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefaultVerticalSpacing + if topPadding == nil { + topPadding = PaddingDefaultVerticalSpacing } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefaultVerticalSpacing + if bottomPadding == nil { + bottomPadding = PaddingDefaultVerticalSpacing } } - - public override init(with moleculeModel: MoleculeModelProtocol) { - super.init(with: moleculeModel) - setDefaults() - } - - required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - try super.init(from: decoder) - setDefaults() - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - } } diff --git a/MVMCoreUI/Atomic/Molecules/HeaderModel.swift b/MVMCoreUI/Atomic/Molecules/HeaderModel.swift index 6314947c..09fe077f 100644 --- a/MVMCoreUI/Atomic/Molecules/HeaderModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HeaderModel.swift @@ -18,18 +18,18 @@ import Foundation } /// Defaults to set - public func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefaultVerticalSpacing + if topPadding == nil { + topPadding = PaddingDefaultVerticalSpacing } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefaultVerticalSpacing + if bottomPadding == nil { + bottomPadding = PaddingDefaultVerticalSpacing } if line == nil { line = LineModel(type: .heavy) @@ -38,7 +38,6 @@ import Foundation public override init() { super.init() - setDefaults() } required public init(from decoder: Decoder) throws { @@ -46,7 +45,6 @@ import Foundation let typeContainer = try decoder.container(keyedBy: CodingKeys.self) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - setDefaults() } public override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift index 9a00a94d..8fdb4d77 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/ListItemModel.swift @@ -38,7 +38,7 @@ import Foundation //-------------------------------------------------- /// Defaults to set - open func setDefaults() { + open override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } @@ -54,9 +54,12 @@ import Foundation // MARK: - Initializer //-------------------------------------------------- + public override init(horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil, useHorizontalMargins: Bool? = nil, leftPadding: CGFloat? = nil, rightPadding: CGFloat? = nil, useVerticalMargins: Bool? = nil, topPadding: CGFloat? = nil, bottomPadding: CGFloat? = nil) { + super.init(horizontalAlignment: horizontalAlignment, verticalAlignment: verticalAlignment, useHorizontalMargins: useHorizontalMargins, leftPadding: leftPadding, rightPadding: rightPadding, useVerticalMargins: useVerticalMargins, topPadding: topPadding, bottomPadding: bottomPadding) + } + public override init() { super.init() - setDefaults() } //-------------------------------------------------- @@ -71,7 +74,6 @@ import Foundation line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) style = try typeContainer.decodeIfPresent(String.self, forKey: .style) try super.init(from: decoder) - setDefaults() } open override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift index ed9467fd..4f203813 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionItemModel.swift @@ -9,49 +9,36 @@ import Foundation /// A model for a collection item that is a container for any molecule. -@objcMembers public class MoleculeCollectionItemModel: MoleculeContainerModel, CollectionItemModelProtocol, MoleculeModelProtocol { - open class var identifier: String { +@objcMembers public class MoleculeCollectionItemModel: MoleculeContainerModel, CollectionItemModelProtocol { + open override class var identifier: String { return "collectionItem" } - public var backgroundColor: Color? /// Defaults to set - public func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefault + if topPadding == nil { + topPadding = PaddingDefault } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefault + if bottomPadding == nil { + bottomPadding = PaddingDefault } } - private enum CodingKeys: String, CodingKey { - case moleculeName - case backgroundColor - } - public override init(with moleculeModel: MoleculeModelProtocol) { super.init(with: moleculeModel) - setDefaults() } required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) try super.init(from: decoder) - setDefaults() } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift index 42af1a53..10b51bff 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift @@ -54,6 +54,6 @@ open class MoleculeCollectionViewCell: CollectionViewCell { let height = classType.estimatedHeight(with: model.molecule, delegateObject) else { return 100 } - return height + (model.topMarginPadding ?? 0) + (model.bottomMarginPadding ?? 0) + return height + (model.topPadding ?? 0) + (model.bottomPadding ?? 0) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift index 1a6bced6..1f6e4d88 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift @@ -8,16 +8,15 @@ import Foundation -@objcMembers public class MoleculeStackItemModel: MoleculeContainerModel, MoleculeModelProtocol, StackItemModelProtocol { - public static var identifier: String = "stackItem" - public var backgroundColor: Color? +@objcMembers public class MoleculeStackItemModel: MoleculeContainerModel, StackItemModelProtocol { + public override class var identifier: String { + return "stackItem" + } public var spacing: CGFloat? public var percent: Int? public var gone: Bool = false private enum CodingKeys: String, CodingKey { - case moleculeName - case backgroundColor case spacing case percent case gone @@ -34,17 +33,14 @@ import Foundation if let gone = try typeContainer.decodeIfPresent(Bool.self, forKey: .gone) { self.gone = gone } - backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) try super.init(from: decoder) } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(spacing, forKey: .spacing) try container.encodeIfPresent(percent, forKey: .percent) try container.encode(gone, forKey: .gone) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift index 436ed9cd..69fe570d 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/StackItemModel.swift @@ -24,16 +24,17 @@ import Foundation // MARK: - Initializer //-------------------------------------------------- - public convenience init(spacing: CGFloat? = nil, percent: Int? = nil, horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil, gone: Bool? = nil) { - self.init() - - self.horizontalAlignment = horizontalAlignment - self.verticalAlignment = verticalAlignment + public init(spacing: CGFloat? = nil, percent: Int? = nil, horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil, gone: Bool? = nil) { self.spacing = spacing self.percent = percent if let gone = gone { self.gone = gone } + super.init(horizontalAlignment: horizontalAlignment, verticalAlignment: verticalAlignment) + } + + required public init(from decoder: Decoder) throws { + fatalError("init(from:) has not been implemented") } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift index e49b4adb..c83b8933 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift @@ -24,8 +24,8 @@ public class TabsListItemModel: ListItemModel, MoleculeModelProtocol { hideArrow = true action = nil style = nil - topMarginPadding = 8 - bottomMarginPadding = 0 + topPadding = 8 + bottomPadding = 0 } public init(with tabs: TabsModel, molecules: [[ListItemModelProtocol & MoleculeModelProtocol]]) { diff --git a/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift index d7df7cc9..f15a847a 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift @@ -10,17 +10,16 @@ import Foundation public protocol NavigationItemModelProtocol { var title: String? { get set } - var titleView: MoleculeModelProtocol? { get set } var hidden: Bool { get set } var backgroundColor: Color? { get set } - var transparent: Bool { get set } + var translucent: Bool { get set } var tintColor: Color { get set } var line: LineModel? { get set } - var systemBackButton: Bool { get set } - var showLeftPanelButton: Bool? { get set } - var showRightPanelButton: Bool? { get set } - var additionalLeftItems: [NavigationItemButtonModel]? { get set } - var additionalRightItems: [NavigationItemButtonModel]? { get set } + var showLeftPanelButton: Bool { get set } + var showRightPanelButton: Bool { get set } + var backButton: NavigationItemButtonModel? { get set } + var additionalLeftButtons: [NavigationItemButtonModel]? { get set } + var additionalRightButtons: [NavigationItemButtonModel]? { get set } } public class NavigationItemButtonModel: Codable { @@ -52,74 +51,73 @@ public class NavigationItemButtonModel: Codable { public class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtocol { public class var identifier: String { - return "navigationItem" + return "navigationBar" } public var title: String? - public var titleView: MoleculeModelProtocol? public var hidden: Bool public var backgroundColor: Color? - public var transparent: Bool + public var translucent: Bool public var tintColor: Color public var line: LineModel? - public var systemBackButton = false - public var showLeftPanelButton: Bool? - public var showRightPanelButton: Bool? - public var additionalLeftItems: [NavigationItemButtonModel]? - public var additionalRightItems: [NavigationItemButtonModel]? + public var showLeftPanelButton: Bool + public var showRightPanelButton: Bool + public var backButton: NavigationItemButtonModel? + public var additionalLeftButtons: [NavigationItemButtonModel]? + public var additionalRightButtons: [NavigationItemButtonModel]? - init() { + public init() { hidden = false - transparent = false + translucent = false backgroundColor = Color(uiColor: .white) tintColor = Color(uiColor: .black) line = LineModel(type: .standard) + showLeftPanelButton = true + showRightPanelButton = true + backButton = NavigationItemButtonModel(with: "back", action: ActionBackModel()) } private enum CodingKeys: String, CodingKey { case title - case titleView case hidden case backgroundColor - case transparent + case translucent case tintColor case line - case systemBackButton + case backButton case showLeftPanelButton case showRightPanelButton - case additionalLeftItems - case additionalRightItems + case additionalLeftButtons + case additionalRightButtons } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) title = try typeContainer.decodeIfPresent(String.self, forKey: .title) - titleView = try typeContainer.decodeModelIfPresent(codingKey: .titleView) hidden = try typeContainer.decodeIfPresent(Bool.self, forKey: .hidden) ?? false backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) ?? Color(uiColor: .white) - transparent = try typeContainer.decodeIfPresent(Bool.self, forKey: .transparent) ?? false + translucent = try typeContainer.decodeIfPresent(Bool.self, forKey: .translucent) ?? false tintColor = try typeContainer.decodeIfPresent(Color.self, forKey: .tintColor) ?? Color(uiColor: .black) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) - systemBackButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .systemBackButton) ?? false - showLeftPanelButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .showLeftPanelButton) - showRightPanelButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .showRightPanelButton) - additionalLeftItems = try typeContainer.decodeIfPresent([NavigationItemButtonModel].self, forKey: .additionalLeftItems) - additionalRightItems = try typeContainer.decodeIfPresent([NavigationItemButtonModel].self, forKey: .additionalRightItems) + backButton = try typeContainer.decodeIfPresent(NavigationItemButtonModel.self, forKey: .backButton) ?? NavigationItemButtonModel(with: "back", action: ActionBackModel()) + showLeftPanelButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .showLeftPanelButton) ?? true + showRightPanelButton = try typeContainer.decodeIfPresent(Bool.self, forKey: .showRightPanelButton) ?? true + additionalLeftButtons = try typeContainer.decodeIfPresent([NavigationItemButtonModel].self, forKey: .additionalLeftButtons) + additionalRightButtons = try typeContainer.decodeIfPresent([NavigationItemButtonModel].self, forKey: .additionalRightButtons) } open func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(title, forKey: .title) - try container.encodeModelIfPresent(titleView, forKey: .titleView) try container.encode(hidden, forKey: .hidden) try container.encode(backgroundColor, forKey: .backgroundColor) - try container.encode(transparent, forKey: .transparent) + try container.encode(translucent, forKey: .translucent) try container.encode(tintColor, forKey: .tintColor) try container.encodeIfPresent(line, forKey: .line) - try container.encode(systemBackButton, forKey: .systemBackButton) + try container.encodeIfPresent(backButton, forKey: .backButton) try container.encode(showLeftPanelButton, forKey: .showLeftPanelButton) try container.encode(showRightPanelButton, forKey: .showRightPanelButton) - try container.encodeIfPresent(additionalLeftItems, forKey: .additionalLeftItems) - try container.encodeIfPresent(additionalRightItems, forKey: .additionalRightItems) + try container.encodeIfPresent(additionalLeftButtons, forKey: .additionalLeftButtons) + try container.encodeIfPresent(additionalRightButtons, forKey: .additionalRightButtons) } } diff --git a/MVMCoreUI/Atomic/Molecules/ScrollerModel.swift b/MVMCoreUI/Atomic/Molecules/ScrollerModel.swift index 74256e99..697556b5 100644 --- a/MVMCoreUI/Atomic/Molecules/ScrollerModel.swift +++ b/MVMCoreUI/Atomic/Molecules/ScrollerModel.swift @@ -8,8 +8,9 @@ import UIKit -public class ScrollerModel: MoleculeContainerModel, MoleculeModelProtocol { - public static var identifier: String = "scroller" +public class ScrollerModel: MoleculeContainerModel { + public override class var identifier: String { + return "scroller" + } public var moleculeName: String = ScrollerModel.identifier - public var backgroundColor: Color? } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift index 650e4875..f6fd4fcf 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift @@ -20,22 +20,21 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc self.headlineBody = headlineBody self.image = image super.init() - setDefaults() } /// Defaults to set - func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefault + if topPadding == nil { + topPadding = PaddingDefault } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefault + if bottomPadding == nil { + bottomPadding = PaddingDefault } if image.height == nil { image.height = BGImageHeadlineBodyButton.heightConstant @@ -59,7 +58,6 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc image = try typeContainer.decode(ImageViewModel.self, forKey: .image) button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button) try super.init(from: decoder) - setDefaults() } public override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift index f30b89ff..9fdd8612 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift @@ -19,22 +19,21 @@ public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProto self.headlineBody = headlineBody self.image = image super.init() - setDefaults() } /// Defaults to set - func setDefaults() { + public override func setDefaults() { if useHorizontalMargins == nil { useHorizontalMargins = true } if useVerticalMargins == nil { useVerticalMargins = true } - if topMarginPadding == nil { - topMarginPadding = PaddingDefault + if topPadding == nil { + topPadding = PaddingDefault } - if bottomMarginPadding == nil { - bottomMarginPadding = PaddingDefault + if bottomPadding == nil { + bottomPadding = PaddingDefault } if image.height == nil { image.height = HeadLineBodyCaretLinkImage.heightConstant @@ -56,7 +55,6 @@ public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProto image = try typeContainer.decode(ImageViewModel.self, forKey: .image) caretLink = try typeContainer.decodeIfPresent(CaretLinkModel.self, forKey: .caretLink) try super.init(from: decoder) - setDefaults() } public override func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ContainerModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ContainerModelProtocol.swift index bc230731..29e25903 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ContainerModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ContainerModelProtocol.swift @@ -11,10 +11,12 @@ import Foundation public protocol ContainerModelProtocol { var horizontalAlignment: UIStackView.Alignment? { get set } - var verticalAlignment: UIStackView.Alignment? { get set } var useHorizontalMargins: Bool? { get set } - + var leftPadding: CGFloat? { get set } + var rightPadding: CGFloat? { get set } + + var verticalAlignment: UIStackView.Alignment? { get set } var useVerticalMargins: Bool? { get set } - var topMarginPadding: CGFloat? { get set } - var bottomMarginPadding: CGFloat? { get set } + var topPadding: CGFloat? { get set } + var bottomPadding: CGFloat? { get set } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/PageModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/PageModelProtocol.swift index be4e4e65..ec2bead8 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/PageModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/PageModelProtocol.swift @@ -12,5 +12,6 @@ public protocol PageModelProtocol { var pageType: String { get set } /// Temporary: for legacy response var screenHeading: String? { get set } + var backgroundColor: Color? { get set } var navigationItem: (NavigationItemModelProtocol & MoleculeModelProtocol)? { get set } } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/ThreeLayerTemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ThreeLayerTemplateModelProtocol.swift new file mode 100644 index 00000000..3f05a1e5 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/ThreeLayerTemplateModelProtocol.swift @@ -0,0 +1,17 @@ +// +// ThreeLayerTemplateModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol ThreeLayerTemplateModelProtocol: TemplateModelProtocol { + var anchorHeader: Bool { get set } + var header: MoleculeModelProtocol? { get set } + + var anchorFooter: Bool { get set } + var footer: MoleculeModelProtocol? { get set } +} diff --git a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift index 56b1a86c..fc9873bd 100644 --- a/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/TemplateProtocol.swift @@ -22,5 +22,8 @@ public extension TemplateProtocol where Self: ViewController { let templateModel = try decoder.decode(TemplateModel.self, from: data) self.templateModel = templateModel self.pageModel = templateModel as? MVMControllerModelProtocol + if let backgroundColor = templateModel.backgroundColor { + view.backgroundColor = backgroundColor.uiColor + } } } diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift index 1d2f07a6..be4273df 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplate.swift @@ -79,6 +79,8 @@ import Foundation open override func handleNewData() { + topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false + bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false setup() registerCells() super.handleNewData() diff --git a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift index 56c56456..f9d8c0a7 100644 --- a/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/CollectionTemplateModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers public class CollectionTemplateModel: TemplateModel { +@objcMembers public class CollectionTemplateModel: ThreeLayerModelBase { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -16,9 +16,7 @@ import Foundation public override class var identifier: String { return "collection" } - public var header: MoleculeModelProtocol? public var molecules: [CollectionItemModelProtocol & MoleculeModelProtocol]? - public var footer: MoleculeModelProtocol? public var columns: Int? //-------------------------------------------------- @@ -37,8 +35,6 @@ import Foundation private enum CodingKeys: String, CodingKey { case molecules - case header - case footer case columns } @@ -49,8 +45,6 @@ import Foundation required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeModelsIfPresent(codingKey: .molecules) - header = try typeContainer.decodeModelIfPresent(codingKey: .header) - footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) columns = try typeContainer.decodeIfPresent(Int.self, forKey: .columns) try super.init(from: decoder) } @@ -59,8 +53,6 @@ import Foundation try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeModelsIfPresent(molecules, forKey: .molecules) - try container.encodeModelIfPresent(header, forKey: .header) - try container.encodeModelIfPresent(footer, forKey: .footer) try container.encodeIfPresent(columns, forKey: .columns) } } diff --git a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift index d2418cbc..b239bd81 100644 --- a/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ListPageTemplateModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers public class ListPageTemplateModel: TemplateModel { +@objcMembers public class ListPageTemplateModel: ThreeLayerModelBase { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -16,9 +16,7 @@ import Foundation public override class var identifier: String { return "list" } - public var header: MoleculeModelProtocol? public var molecules: [ListItemModelProtocol & MoleculeModelProtocol]? - public var footer: MoleculeModelProtocol? public var line: LineModel? //-------------------------------------------------- @@ -37,8 +35,6 @@ import Foundation private enum CodingKeys: String, CodingKey { case molecules - case header - case footer case line } @@ -49,8 +45,6 @@ import Foundation required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeModelsIfPresent(codingKey: .molecules) - header = try typeContainer.decodeModelIfPresent(codingKey: .header) - footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) line = try typeContainer.decodeIfPresent(LineModel.self, forKey: .line) try super.init(from: decoder) } @@ -59,8 +53,6 @@ import Foundation try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeModelsIfPresent(molecules, forKey: .molecules) - try container.encodeModelIfPresent(header, forKey: .header) - try container.encodeModelIfPresent(footer, forKey: .footer) try container.encode(line, forKey: .line) } } diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index b236e332..e40fbc33 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -81,6 +81,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } open override func handleNewData() { + topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false + bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false setup() registerWithTable() super.handleNewData() diff --git a/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift index b26bbc8a..90096145 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift @@ -11,6 +11,12 @@ import UIKit open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { var observer: NSKeyValueObservation? public var templateModel: StackPageTemplateModel? + + open override func handleNewData() { + topViewOutsideOfScroll = templateModel?.anchorHeader ?? false + bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false + super.handleNewData() + } open override func parsePageJSON() throws { try parseTemplate(json: loadObject?.pageJSON) diff --git a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift index c7b5239a..f21a0421 100644 --- a/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/StackPageTemplateModel.swift @@ -9,14 +9,11 @@ import Foundation -@objcMembers public class StackPageTemplateModel: TemplateModel { +@objcMembers public class StackPageTemplateModel: ThreeLayerModelBase { public override class var identifier: String { return "stack" } - - public var header: MoleculeModelProtocol? public var moleculeStack: StackModel - public var footer: MoleculeModelProtocol? public init(pageType: String, moleculeStack: StackModel) { self.moleculeStack = moleculeStack @@ -24,16 +21,12 @@ import Foundation } private enum CodingKeys: String, CodingKey { - case header - case footer case stack } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) moleculeStack = try typeContainer.decode(StackModel.self, forKey: .stack) - header = try typeContainer.decodeModelIfPresent(codingKey: .header) - footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) try super.init(from: decoder) } @@ -41,7 +34,5 @@ import Foundation try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeStack, forKey: .stack) - try container.encodeModelIfPresent(header, forKey: .header) - try container.encodeModelIfPresent(footer, forKey: .footer) } } diff --git a/MVMCoreUI/Atomic/Templates/TemplateModel.swift b/MVMCoreUI/Atomic/Templates/TemplateModel.swift index dbbbc494..86da1981 100644 --- a/MVMCoreUI/Atomic/Templates/TemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/TemplateModel.swift @@ -17,6 +17,7 @@ import Foundation // Although this is done in the extension, it is needed for the encoding. return Self.identifier } + public var backgroundColor: Color? public var screenHeading: String? public var navigationItem: (NavigationItemModelProtocol & MoleculeModelProtocol)? public var formRules: [FormGroupRule]? @@ -29,6 +30,7 @@ import Foundation case pageType case template case screenHeading + case backgroundColor case formRules case navigationItem } @@ -37,6 +39,7 @@ import Foundation let typeContainer = try decoder.container(keyedBy: CodingKeys.self) pageType = try typeContainer.decode(String.self, forKey: .pageType) screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) formRules = try typeContainer.decodeIfPresent([FormGroupRule].self, forKey: .formRules) navigationItem = try typeContainer.decodeModelIfPresent(codingKey: .navigationItem) } @@ -45,6 +48,7 @@ import Foundation var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(pageType, forKey: .pageType) try container.encode(template, forKey: .template) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(screenHeading, forKey: .screenHeading) try container.encodeIfPresent(formRules, forKey: .formRules) try container.encodeModelIfPresent(navigationItem, forKey: .navigationItem) diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplate.swift new file mode 100644 index 00000000..3d0119a9 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplate.swift @@ -0,0 +1,15 @@ +// +// ThreeLayerCenterTemplate.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class ThreeLayerCenterTemplate: ThreeLayerTemplate { + open override func spaceBetweenTopAndMiddle() -> CGFloat? { + return nil + } +} diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplateModel.swift new file mode 100644 index 00000000..4bea76f2 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerCenterTemplateModel.swift @@ -0,0 +1,15 @@ +// +// ThreeLayerCenterTemplateModel.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class ThreeLayerCenterPageTemplateModel: ThreeLayerPageTemplateModel { + public override class var identifier: String { + return "threeLayerCenter" + } +} diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift new file mode 100644 index 00000000..f0276979 --- /dev/null +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerModelBase.swift @@ -0,0 +1,49 @@ +// +// ThreeLayerModelBase.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class ThreeLayerModelBase: TemplateModel, ThreeLayerTemplateModelProtocol { + public var anchorHeader: Bool = false + public var header: MoleculeModelProtocol? + public var anchorFooter: Bool = false + public var footer: MoleculeModelProtocol? + + public override init(pageType: String) { + super.init(pageType: pageType) + } + + private enum CodingKeys: String, CodingKey { + case anchorHeader + case header + case anchorFooter + case footer + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let anchor = try typeContainer.decodeIfPresent(Bool.self, forKey: .anchorHeader) { + anchorHeader = anchor + } + header = try typeContainer.decodeModelIfPresent(codingKey: .header) + if let anchor = try typeContainer.decodeIfPresent(Bool.self, forKey: .anchorFooter) { + anchorFooter = anchor + } + footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(anchorHeader, forKey: .anchorHeader) + try container.encodeModelIfPresent(header, forKey: .header) + try container.encodeIfPresent(anchorFooter, forKey: .anchorFooter) + try container.encodeModelIfPresent(footer, forKey: .footer) + } +} diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift index 61476707..c4e93f7a 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerPageTemplateModel.swift @@ -8,13 +8,11 @@ import Foundation -@objcMembers public class ThreeLayerPageTemplateModel: TemplateModel { +@objcMembers public class ThreeLayerPageTemplateModel: ThreeLayerModelBase { public override class var identifier: String { return "threeLayer" } - public var header: MoleculeModelProtocol? public var middle: MoleculeModelProtocol? - public var footer: MoleculeModelProtocol? public init(pageType: String, header: MoleculeModelProtocol?, middle: MoleculeModelProtocol?, footer: MoleculeModelProtocol?) { super.init(pageType: pageType) @@ -24,24 +22,18 @@ import Foundation } private enum CodingKeys: String, CodingKey { - case header - case footer case middle } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - header = try typeContainer.decodeModelIfPresent(codingKey: .header) middle = try typeContainer.decodeModelIfPresent(codingKey: .middle) - footer = try typeContainer.decodeModelIfPresent(codingKey: .footer) try super.init(from: decoder) } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encodeModelIfPresent(header, forKey: .header) try container.encodeModelIfPresent(header, forKey: .middle) - try container.encodeModelIfPresent(footer, forKey: .footer) } } diff --git a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift index 96e85fad..4884d5b1 100644 --- a/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/ThreeLayerTemplate.swift @@ -8,23 +8,18 @@ import UIKit -@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController, TemplateProtocol { - public var templateModel: ThreeLayerPageTemplateModel? +@objcMembers open class ThreeLayerTemplate: ThreeLayerViewController, TemplateProtocol { + public var templateModel: TemplateModel? open override func parsePageJSON() throws { try parseTemplate(json: loadObject?.pageJSON) try super.parsePageJSON() } - override open func viewDidLoad() { - super.viewDidLoad() - bottomViewOutsideOfScroll = true - // Do any additional setup after loading the view. - } - open override func handleNewData() { + topViewOutsideOfScroll = templateModel?.anchorHeader ?? false + bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false super.handleNewData() - heightConstraint?.isActive = true } open override func viewForTop() -> UIView? { diff --git a/MVMCoreUI/BaseClasses/CollectionView.swift b/MVMCoreUI/BaseClasses/CollectionView.swift index a1a57376..4b034462 100644 --- a/MVMCoreUI/BaseClasses/CollectionView.swift +++ b/MVMCoreUI/BaseClasses/CollectionView.swift @@ -10,6 +10,11 @@ import Foundation open class CollectionView: UICollectionView, MVMCoreViewProtocol { + /// A block that gets called on tableview frame changes + public var frameChangeAction: (() -> Void)? + + private var previousFrame = CGRect.zero + private var initialSetupPerformed = false private func initialSetup() { @@ -29,6 +34,14 @@ open class CollectionView: UICollectionView, MVMCoreViewProtocol { initialSetup() } + open override func layoutSubviews() { + super.layoutSubviews() + if frame != previousFrame { + frameChangeAction?() + } + previousFrame = frame + } + public func updateView(_ size: CGFloat) { for cell in visibleCells { (cell as? MVMCoreViewProtocol)?.updateView(size) diff --git a/MVMCoreUI/BaseClasses/TableView.swift b/MVMCoreUI/BaseClasses/TableView.swift new file mode 100644 index 00000000..9fb07ac7 --- /dev/null +++ b/MVMCoreUI/BaseClasses/TableView.swift @@ -0,0 +1,25 @@ +// +// TableView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class TableView: UITableView { + + /// A block that gets called on tableview frame changes + public var frameChangeAction: (() -> Void)? + + private var previousFrame = CGRect.zero + + open override func layoutSubviews() { + super.layoutSubviews() + if frame != previousFrame { + frameChangeAction?() + } + previousFrame = frame + } +} diff --git a/MVMCoreUI/BaseClasses/TableViewCell.swift b/MVMCoreUI/BaseClasses/TableViewCell.swift index f27a38d8..30f9df22 100644 --- a/MVMCoreUI/BaseClasses/TableViewCell.swift +++ b/MVMCoreUI/BaseClasses/TableViewCell.swift @@ -51,36 +51,36 @@ import UIKit } open func styleStandard() { - listItemModel?.topMarginPadding = 24 - listItemModel?.bottomMarginPadding = 24 + listItemModel?.topPadding = 24 + listItemModel?.bottomPadding = 24 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.standard) } open func styleTallDivider() { - listItemModel?.topMarginPadding = 48 - listItemModel?.bottomMarginPadding = 16 + listItemModel?.topPadding = 48 + listItemModel?.bottomPadding = 16 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.thin) } open func styleShortDivider() { - listItemModel?.topMarginPadding = 32 - listItemModel?.bottomMarginPadding = 16 + listItemModel?.topPadding = 32 + listItemModel?.bottomPadding = 16 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.thin) } open func styleFooter() { - listItemModel?.topMarginPadding = 24 - listItemModel?.bottomMarginPadding = 0 + listItemModel?.topPadding = 24 + listItemModel?.bottomPadding = 0 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.none) } open func styleNone() { - listItemModel?.topMarginPadding = 0 - listItemModel?.bottomMarginPadding = 0 + listItemModel?.topPadding = 0 + listItemModel?.bottomPadding = 0 topSeparatorView?.setStyle(.none) bottomSeparatorView?.setStyle(.none) } diff --git a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift index d7fb0ed4..c59fd097 100644 --- a/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift +++ b/MVMCoreUI/BaseControllers/ContainerCollectionReusableView.swift @@ -20,11 +20,8 @@ public class ContainerCollectionReusableView: UICollectionReusableView { view.setContentCompressionResistancePriority(.required, for: .vertical) addSubview(view) self.view = view - topConstraint = view.topAnchor.constraint(equalTo: topAnchor) - topConstraint?.isActive = true - bottomConstraint = bottomAnchor.constraint(equalTo: view.bottomAnchor) - bottomConstraint?.isActive = true - rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true - view.leftAnchor.constraint(equalTo: leftAnchor).isActive = true + let constraints = NSLayoutConstraint.constraintPinSubview(toSuperview: view) + topConstraint = constraints?[ConstraintTop] as? NSLayoutConstraint + bottomConstraint = constraints?[ConstraintBot] as? NSLayoutConstraint } } diff --git a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift index 5e7b87b0..85e0f3a7 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift @@ -11,7 +11,9 @@ import Foundation /// A base view controller with a collection view. @objc open class ProgrammaticCollectionViewController: ScrollingViewController { - public var collectionView: UICollectionView? + public var collectionView: CollectionView? + public var topConstraint: NSLayoutConstraint? + public var bottomConstraint: NSLayoutConstraint? open override func loadView() { let view = UIView() @@ -19,7 +21,9 @@ import Foundation let collection = createCollectionView() view.addSubview(collection) - NSLayoutConstraint.constraintPinSubview(toSuperview: collection) + let constraints = NSLayoutConstraint.constraintPinSubview(toSuperview: collection) + topConstraint = constraints?[ConstraintTop] as? NSLayoutConstraint + bottomConstraint = constraints?[ConstraintBot] as? NSLayoutConstraint collectionView = collection scrollView = collectionView @@ -45,7 +49,7 @@ import Foundation } /// Creates the collection view. - open func createCollectionView() -> UICollectionView { + open func createCollectionView() -> CollectionView { let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) collection.dataSource = self collection.delegate = self diff --git a/MVMCoreUI/BaseControllers/ProgrammaticScrollViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticScrollViewController.swift index b6ed6353..19c6f2b7 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticScrollViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticScrollViewController.swift @@ -35,19 +35,20 @@ open class ProgrammaticScrollViewController: ScrollingViewController { view.addSubview(scrollView) // Sets the constraints for the scroll view - let constraints = NSLayoutConstraint.constraintPinSubview(toSuperview: scrollView) - topConstraint = constraints?[ConstraintTop] as? NSLayoutConstraint - bottomConstraint = constraints?[ConstraintBot] as? NSLayoutConstraint + scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true + topConstraint = scrollView.topAnchor.constraint(equalTo: view.topAnchor) + topConstraint?.isActive = true + bottomConstraint = view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor) + bottomConstraint?.isActive = true + // Sets the constraints for the content view let contentView = MVMCoreUICommonViewsUtility.commonView() scrollView.addSubview(contentView) - - // Sets the constraints for the content view - NSLayoutConstraint.constraintPinSubview(toSuperview: contentView) - - // Super will set later. - contentWidthConstraint = contentView.widthAnchor.constraint(equalToConstant: 320.0) - contentWidthConstraint?.isActive = true + contentView.leftAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.leftAnchor).isActive = true + scrollView.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true + contentView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true + scrollView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true self.contentView = contentView self.scrollView = scrollView diff --git a/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift index b27845b3..21f1de1e 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticTableViewController.swift @@ -9,9 +9,9 @@ import Foundation open class ProgrammaticTableViewController: ProgrammaticScrollViewController, UITableViewDelegate, UITableViewDataSource { - @IBOutlet public var tableView: UITableView! + @IBOutlet public var tableView: TableView! - public init(with tableView: UITableView) { + public init(with tableView: TableView) { self.tableView = tableView super.init(with: tableView) } @@ -48,8 +48,8 @@ open class ProgrammaticTableViewController: ProgrammaticScrollViewController, UI } /// This class should create the table view that will be used here. Subclass this for different table styles. - open func createTableView() -> UITableView { - let tableView = UITableView(frame: .zero, style: .grouped) + open func createTableView() -> TableView { + let tableView = TableView(frame: .zero, style: .grouped) tableView.backgroundColor = .clear tableView.separatorStyle = UITableViewCell.SeparatorStyle.none tableView.delegate = self @@ -67,6 +67,11 @@ open class ProgrammaticTableViewController: ProgrammaticScrollViewController, UI tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude } + open func refreshTable() { + tableView.beginUpdates() + tableView.endUpdates() + } + /// For subclassing, returns the number of sections for table. This function calls numberOfSectionsForTableview aftre ensuring the table is setup properly. open func getNumberOfSections() -> Int { return 1 diff --git a/MVMCoreUI/BaseControllers/ScrollingViewController.swift b/MVMCoreUI/BaseControllers/ScrollingViewController.swift index 97e83f98..0eea84cc 100644 --- a/MVMCoreUI/BaseControllers/ScrollingViewController.swift +++ b/MVMCoreUI/BaseControllers/ScrollingViewController.swift @@ -12,7 +12,6 @@ open class ScrollingViewController: ViewController { public var dismissKeyboardTapGesture: UITapGestureRecognizer? @IBOutlet public var scrollView: UIScrollView! public var contentView: UIView? - public var contentWidthConstraint: NSLayoutConstraint? private var keyboardNotificationsAdded = false private var keyboardIsShowing = false @@ -42,13 +41,7 @@ open class ScrollingViewController: ViewController { scrollView.alwaysBounceVertical = false scrollView.delegate = self } - - open override func updateViewConstraints() { - super.updateViewConstraints() - // Sets the width of the content to the width of the screen. - contentWidthConstraint?.constant = view.bounds.width - scrollView.contentInset.left - scrollView.contentInset.right - } - + open override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view.setNeedsUpdateConstraints() diff --git a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift index aa34f2f2..88d8157a 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerCollectionViewController.swift @@ -17,10 +17,18 @@ import Foundation private var footerView: ContainerCollectionReusableView? private let headerID = "header" private let footerID = "footer" + public var bottomViewOutsideOfScrollArea: Bool = false + public var topViewOutsideOfScrollArea: Bool = false + + open override func updateViewConstraints() { + // Update the spacing on constraint update + updateFlexibleSpace() + super.updateViewConstraints() + } /// Updates the padding for flexible space (header or footer) private func updateFlexibleSpace() { - guard let tableView = collectionView else { return } + guard let collectionView = collectionView else { return } let minimumSpace: CGFloat = minimumFillSpace() var currentSpace: CGFloat = 0 @@ -41,33 +49,43 @@ import Foundation } guard fillTop || fillBottom else { return } - - let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) - - // If the bottom view is outside of the scroll, then only the top view constraint is being used, so we have to double it to account for the bottom constraint not being there when we compare to the new value. - var currentSpaceForCompare: CGFloat = currentSpace - if fillTop { - currentSpaceForCompare = currentSpace * 2; - } - - if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 1) { + let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: collectionView, minimumHeight: totalMinimumSpace) + if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpace, 1) { if fillTop && fillBottom { // space both let half = newSpace / 2 headerView?.bottomConstraint?.constant = half footerView?.topConstraint?.constant = half - collectionView?.collectionViewLayout.invalidateLayout() + collectionView.collectionViewLayout.invalidateLayout() } else if fillTop { - // Only top is spaced (half the size if the bottom view is out of the scroll because it needs to be sized as if there are two spacers but there is only one. + // Only top is spaced. headerView?.bottomConstraint?.constant = newSpace - collectionView?.collectionViewLayout.invalidateLayout() + collectionView.collectionViewLayout.invalidateLayout() } else if fillBottom { // Only bottom is spaced. footerView?.topConstraint?.constant = newSpace - collectionView?.collectionViewLayout.invalidateLayout() + collectionView.collectionViewLayout.invalidateLayout() } } } + + func addTopViewOutside() { + guard let collectionView = collectionView, let topView = topView else { return } + view.addSubview(topView) + topView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true + topView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true + view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true + collectionView.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true + } + + func addBottomViewOutside() { + guard let collectionView = collectionView, let bottomView = bottomView else { return } + view.addSubview(bottomView) + bottomView.topAnchor.constraint(equalTo: collectionView.bottomAnchor).isActive = true + bottomView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true + view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true + view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true + } //MARK: - ViewController open override func updateViews() { @@ -89,17 +107,35 @@ import Foundation self.invalidateCollectionLayout() } } - + open override func handleNewData() { super.handleNewData() - createViewForHeader() - createViewForFooter() + topView?.removeFromSuperview() + bottomView?.removeFromSuperview() + topView = viewForTop() + bottomView = viewForBottom() + if topViewOutsideOfScrollArea { + topConstraint?.isActive = false + addTopViewOutside() + } else { + topConstraint?.isActive = true + } + if bottomViewOutsideOfScrollArea { + bottomConstraint?.isActive = false + addBottomViewOutside() + } else { + bottomConstraint?.isActive = true + } reloadCollectionData() } override open func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. + + collectionView?.frameChangeAction = { [weak self] in + self?.invalidateCollectionLayout() + } } //MARK: - Spacing @@ -118,27 +154,6 @@ import Foundation open func minimumFillSpace() -> CGFloat { return 0 } - - //MARK: - Header Footer - /// Creates the top view. - open func createViewForHeader() { - guard let topView = viewForTop() else { - self.topView = nil - self.headerView = nil - return - } - self.topView = topView - } - - /// Creates the footer - open func createViewForFooter() { - guard let bottomView = viewForBottom() else { - self.bottomView = nil - self.footerView = nil - return - } - self.bottomView = bottomView - } //MARK: - Functions to subclass /// Subclass for a top view. @@ -187,18 +202,14 @@ import Foundation } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { - guard let _ = topView, - section == 0 else { return .zero } - + guard section == 0 else { return .zero } // Calculate the height of the header since apple doesn't support autolayout. Width is fixed, height is tall as content. let header = headerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: section)) return header.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { - guard let _ = bottomView, - section == numberOfSections(in: collectionView) - 1 else { return .zero } - + guard section == numberOfSections(in: collectionView) - 1 else { return .zero } // Calculate the height of the footr since apple doesn't support autolayout. Width is fixed, height is tall as content. let footer = footerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionFooter, at: IndexPath(row: 0, section: section)) return footer.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) @@ -207,13 +218,15 @@ import Foundation open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if kind == UICollectionView.elementKindSectionFooter, let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footerID, for: indexPath) as? ContainerCollectionReusableView { - footerView.addAndContain(view: bottomView!) + let bottomView = (bottomViewOutsideOfScrollArea ? nil : self.bottomView) ?? MVMCoreUICommonViewsUtility.getView(with: 0.5) + footerView.addAndContain(view: bottomView) footerView.topConstraint?.constant = spaceAboveBottomView() ?? 0 self.footerView = footerView return footerView } else if kind == UICollectionView.elementKindSectionHeader, let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? ContainerCollectionReusableView { - headerView.addAndContain(view: topView!) + let topView = (topViewOutsideOfScrollArea ? nil : self.topView) ?? MVMCoreUICommonViewsUtility.getView(with: 0.5) + headerView.addAndContain(view: topView) headerView.bottomConstraint?.constant = spaceBelowTopView() ?? 0 self.headerView = headerView return headerView diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 000f2e7f..8e01cf6c 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -15,9 +15,8 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { private var bottomView: UIView? private var headerView: UIView? private var footerView: UIView? - private var safeAreaView: UIView? - var useMargins: Bool = true public var bottomViewOutsideOfScrollArea: Bool = false + public var topViewOutsideOfScrollArea: Bool = false private var topViewBottomConstraint: NSLayoutConstraint? private var bottomViewTopConstraint: NSLayoutConstraint? @@ -33,7 +32,7 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { bottomView.updateView(width) showFooter(width) } - tableView?.reloadData() + tableView.reloadData() } open override func handleNewData() { @@ -47,6 +46,11 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { super.viewDidLoad() // Do any additional setup after loading the view. setNoSectionHeadersFooters() + + // Ensures the footer and headers are the right size + tableView.frameChangeAction = { [weak self] in + self?.view.setNeedsUpdateConstraints() + } } //MARK: - Spacing @@ -67,40 +71,37 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { } open override func updateViewConstraints() { - super.updateViewConstraints() - - guard let tableView = tableView else { return } + guard let tableView = tableView else { + super.updateViewConstraints() + return + } let minimumSpace: CGFloat = minimumFillSpace() var currentSpace: CGFloat = 0 var totalMinimumSpace: CGFloat = 0 var fillTop = false - if spaceBelowTopView() == nil, self.tableView?.tableHeaderView != nil { + if spaceBelowTopView() == nil, tableView.tableHeaderView != nil { fillTop = true currentSpace += topViewBottomConstraint?.constant ?? 0 totalMinimumSpace += minimumSpace } var fillBottom = false - if spaceAboveBottomView() == nil, !bottomViewOutsideOfScrollArea, self.tableView?.tableFooterView != nil { + if spaceAboveBottomView() == nil, tableView.tableFooterView != nil { fillBottom = true currentSpace += bottomViewTopConstraint?.constant ?? 0 totalMinimumSpace += minimumSpace } - guard fillTop || fillBottom else { return } - - let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) - - // If the bottom view is outside of the scroll, then only the top view constraint is being used, so we have to double it to account for the bottom constraint not being there when we compare to the new value. - var currentSpaceForCompare: CGFloat = currentSpace - if fillTop && bottomViewOutsideOfScrollArea { - currentSpaceForCompare = currentSpace * 2; + guard fillTop || fillBottom else { + super.updateViewConstraints() + return } + let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace) let width = view.bounds.width - if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpaceForCompare, 0.1) { + if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpace, 0.1) { if fillTop && fillBottom { // space both let half = newSpace / 2 @@ -109,27 +110,30 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { showHeader(width) showFooter(width) } else if fillTop { - // Only top is spaced (half the size if the bottom view is out of the scroll because it needs to be sized as if there are two spacers but there is only one. - if bottomViewOutsideOfScrollArea { - topViewBottomConstraint?.constant = newSpace / 2 - } else { - topViewBottomConstraint?.constant = newSpace - } + topViewBottomConstraint?.constant = newSpace showHeader(width) } else if fillBottom { // Only bottom is spaced. bottomViewTopConstraint?.constant = newSpace showFooter(width) } + DispatchQueue.main.async { + self.refreshTable() + } } + super.updateViewConstraints() } //MARK: - Header Footer /// Gets the top view and adds it to a spacing view, headerView, and then calls showHeader. open func createViewForTableHeader() { - let topView = viewForTop() + var topView = viewForTop() self.topView = topView + // If top view is outside of scroll area, create a dummy view for the header. Small height is needed to stop apple from adding padding for grouped tables when no header. + if topViewOutsideOfScrollArea { + topView = MVMCoreUICommonViewsUtility.getView(with: 0.5) + } let headerView = MVMCoreUICommonViewsUtility.commonView() headerView.addSubview(topView) topView.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true @@ -143,9 +147,13 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { /// Gets the bottom view and adds it to a spacing view, footerView, and then calls showFooter. open func createViewForTableFooter() { - let bottomView = viewForBottom() + var bottomView = viewForBottom() self.bottomView = bottomView + // If bottom view is outside of scroll area, create a dummy view for the header. Small height is needed to stop apple from adding padding for grouped tables when no header. + if bottomViewOutsideOfScrollArea { + bottomView = MVMCoreUICommonViewsUtility.getView(with: 0.5) + } let footerView = MVMCoreUICommonViewsUtility.commonView() footerView.addSubview(bottomView) bottomViewTopConstraint = bottomView.topAnchor.constraint(equalTo: footerView.topAnchor, constant: spaceAboveBottomView() ?? 0) @@ -161,10 +169,20 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { func showHeader(_ sizingWidth: CGFloat?) { headerView?.removeFromSuperview() tableView?.tableHeaderView = nil - guard let headerView = headerView else { - return - } + guard let headerView = headerView else { return } + if topViewOutsideOfScrollArea, + let topView = topView { + // put top view outside of scrolling area. + topConstraint?.isActive = false + view.addSubview(topView) + topView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true + topView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true + tableView.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true + } else { + topConstraint?.isActive = true + } // This extra view is needed because of the wonkiness of apple's table header. Things breaks if using autolayout. headerView.setNeedsLayout() headerView.layoutIfNeeded() @@ -178,56 +196,55 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { /// Takes the current footerView and adds it to the tableFooterView func showFooter(_ sizingWidth: CGFloat?) { footerView?.removeFromSuperview() - safeAreaView?.removeFromSuperview() - guard let footerView = footerView, let tableView = tableView else { - return + guard let footerView = footerView, + let tableView = tableView else { + self.tableView?.tableFooterView = nil + return } - if bottomViewOutsideOfScrollArea { + if bottomViewOutsideOfScrollArea, + let bottomView = bottomView { // put bottom view outside of scrolling area. bottomConstraint?.isActive = false - view.addSubview(footerView) - footerView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true - footerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true - view.rightAnchor.constraint(equalTo: footerView.rightAnchor).isActive = true - view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: footerView.bottomAnchor).isActive = true - safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: view) - safeAreaView?.backgroundColor = bottomView?.backgroundColor + view.addSubview(bottomView) + bottomView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true + bottomView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + view.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true + view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true } else { bottomConstraint?.isActive = true - var y: CGFloat? - if let tableFooterView = tableView.tableFooterView { - // if footer already exists, use the same y location to avoid strange moving animation - y = tableFooterView.frame.minY - } - - // This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout. - MVMCoreUIUtility.sizeView(toFit: footerView) - let tableFooterView = UIView(frame: CGRect(x: 0, y: y ?? 0, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height)) - tableFooterView.addSubview(footerView) - NSLayoutConstraint.constraintPinSubview(toSuperview: footerView) - tableView.tableFooterView = tableFooterView } + + // if footer already exists, use the same y location to avoid strange moving animation + let y = tableView.tableFooterView?.frame.minY ?? 0.0 + + // This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout. + MVMCoreUIUtility.sizeView(toFit: footerView) + let tableFooterView = UIView(frame: CGRect(x: 0, y: y, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height)) + tableFooterView.addSubview(footerView) + NSLayoutConstraint.constraintPinSubview(toSuperview: footerView) + tableView.tableFooterView = tableFooterView } //MARK: - Functions to subclass /// Subclass for a top view. open func viewForTop() -> UIView { - let view = MVMCoreUICommonViewsUtility.commonView() // Small height is needed to stop apple from adding padding for grouped tables when no header. - view.heightAnchor.constraint(equalToConstant: 1).isActive = true - return view + return MVMCoreUICommonViewsUtility.getView(with: 0.5) } /// Subclass for a bottom view. open func viewForBottom() -> UIView { // Default spacing is standard when no buttons. - let view = MVMCoreUICommonViewsUtility.commonView() - view.heightAnchor.constraint(equalToConstant: PaddingDefaultVerticalSpacing).isActive = true - return view + return MVMCoreUICommonViewsUtility.getView(with: PaddingDefaultVerticalSpacing) } deinit { tableView?.delegate = nil } + + // Ensures the footer and headers are the right size + func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) { + view.setNeedsUpdateConstraints() + } } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift index 9eacd25c..210148cf 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerViewController.swift @@ -18,10 +18,12 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { var bottomView: UIView? var useMargins: Bool = false + // The top view can be put outside of the scrolling area. + var topViewOutsideOfScroll = false + // The bottom view can be put outside of the scrolling area. var bottomViewOutsideOfScroll = false - private var safeAreaView: UIView? var heightConstraint: NSLayoutConstraint? open override func updateViews() { @@ -39,24 +41,11 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { } } - open override func updateViewConstraints() { - super.updateViewConstraints() - guard let scrollView = scrollView else { - return - } - - if scrollView.contentInsetAdjustmentBehavior == UIScrollView.ContentInsetAdjustmentBehavior.automatic { - heightConstraint?.constant = -scrollView.adjustedContentInset.top - scrollView.adjustedContentInset.bottom - } else { - heightConstraint?.constant = -scrollView.contentInset.top - scrollView.contentInset.bottom - } - } - open override func loadView() { super.loadView() // The height is used to keep the bottom view at the bottom. if let contentView = contentView, let scrollView = scrollView { - heightConstraint = contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor, multiplier: 1.0) + heightConstraint = contentView.heightAnchor.constraint(equalTo: scrollView.safeAreaLayoutGuide.heightAnchor, multiplier: 1.0) heightConstraint?.priority = UILayoutPriority.defaultLow } } @@ -68,7 +57,6 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { topView?.removeFromSuperview() middleView?.removeFromSuperview() bottomView?.removeFromSuperview() - safeAreaView?.removeFromSuperview() if let subViews = contentView?.subviews { for view in subViews { view.removeFromSuperview() @@ -76,6 +64,7 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { } // Reset constraints + topConstraint?.isActive = true bottomConstraint?.isActive = true heightConstraint?.isActive = false @@ -111,33 +100,30 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController { //MARK:-Setup extension ThreeLayerViewController { - func setupViewAsTop() -> UIView? { - if let topView = viewForTop() { - self.topView = topView + func setupViewAsTop() -> UIView { + var topView = viewForTop() ?? MVMCoreUICommonViewsUtility.getView(with: 0) + self.topView = topView + + // Adds the top view outside the scroll if directed. + if topViewOutsideOfScroll { + topConstraint?.isActive = false + addViewOutsideOfScrollViewTop(topView) + + // Adds and returns an empty view to use for the internal logic. + topView = MVMCoreUICommonViewsUtility.getView(with: 0) + addViewInsideOfScrollViewTop(topView) } else { - topView = MVMCoreUICommonViewsUtility.commonView() - topView?.heightAnchor.constraint(equalToConstant: 0).isActive = true + topConstraint?.isActive = true + addViewInsideOfScrollViewTop(topView) } - guard let topView = topView, let contentView = contentView else { - return nil - } - contentView.addSubview(topView) - NSLayoutConstraint.pinViewTop(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true - NSLayoutConstraint.pinViewLeft(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true - NSLayoutConstraint.pinViewRight(toSuperview: topView, useMargins: useMargins, constant: 0).isActive = true return topView } - func setupViewAsMiddle() -> UIView? { - if let middleView = viewForMiddle() { - self.middleView = middleView - } else { - middleView = MVMCoreUICommonViewsUtility.commonView() - middleView?.heightAnchor.constraint(equalToConstant: 0).isActive = true - } - guard let middleView = middleView, let contentView = contentView else { - return nil - } + func setupViewAsMiddle() -> UIView { + let middleView = viewForMiddle() ?? MVMCoreUICommonViewsUtility.getView(with: 0) + self.middleView = middleView + + guard let contentView = contentView else { return middleView } contentView.addSubview(middleView) NSLayoutConstraint.pinViewLeft(toSuperview: middleView, useMargins: useMargins, constant: 0).isActive = true NSLayoutConstraint.pinViewRight(toSuperview: middleView, useMargins: useMargins, constant: 0).isActive = true @@ -145,33 +131,30 @@ extension ThreeLayerViewController { return middleView } - func setupViewAsBottom() -> UIView? { - if let bottomView = viewForBottom() { - self.bottomView = bottomView - } else { - bottomView = MVMCoreUICommonViewsUtility.commonView() - bottomView?.heightAnchor.constraint(equalToConstant: 0).isActive = true - } - guard let bottomView = bottomView else { - return nil - } + func setupViewAsBottom() -> UIView { + var bottomView = viewForBottom() ?? MVMCoreUICommonViewsUtility.getView(with: 0) + self.bottomView = bottomView // Adds the bottom view outside the scroll if directed. if bottomViewOutsideOfScroll { - bottomConstraint?.isActive = false; - addViewInsideOfScrollViewBottom(ViewConstrainingView.empty()) + bottomConstraint?.isActive = false addViewOutsideOfScrollViewBottom(bottomView) + + // Adds and returns an empty view to use for the internal logic. + bottomView = MVMCoreUICommonViewsUtility.getView(with: 0) + addViewInsideOfScrollViewBottom(bottomView) } else { - bottomConstraint?.isActive = true; + bottomConstraint?.isActive = true addViewInsideOfScrollViewBottom(bottomView) } return bottomView } func setupLayers() { - guard let contentView = contentView, let topView = setupViewAsTop(), let middleView = setupViewAsMiddle(), let bottomView = setupViewAsBottom() else { - return - } + guard let contentView = contentView else { return } + let topView = setupViewAsTop() + let middleView = setupViewAsMiddle() + let bottomView = setupViewAsBottom() let spaceAbove = spaceBetweenTopAndMiddle() let spaceBelow = spaceBetweenMiddleAndBottom() if let spaceAbove = spaceAbove, let spaceBelow = spaceBelow { @@ -197,7 +180,7 @@ extension ThreeLayerViewController { contentView.addSubview(topSpacer) topSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true contentView.rightAnchor.constraint(equalTo: topSpacer.rightAnchor).isActive = true - topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingTwo).isActive = true + topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true topSpacer.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true middleView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor).isActive = true bottomView.topAnchor.constraint(equalTo: middleView.bottomAnchor, constant: spaceBelow).isActive = true @@ -212,7 +195,7 @@ extension ThreeLayerViewController { bottomSpacer.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true contentView.rightAnchor.constraint(equalTo: bottomSpacer.rightAnchor).isActive = true topSpacer.heightAnchor.constraint(equalTo: bottomSpacer.heightAnchor).isActive = true - topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: PaddingTwo).isActive = true + topSpacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true topSpacer.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true middleView.topAnchor.constraint(equalTo: topSpacer.bottomAnchor).isActive = true bottomSpacer.topAnchor.constraint(equalTo: middleView.bottomAnchor).isActive = true @@ -221,10 +204,25 @@ extension ThreeLayerViewController { } } + func addViewInsideOfScrollViewTop(_ view: UIView) { + guard let contentView = contentView else { return } + contentView.addSubview(view) + NSLayoutConstraint.pinViewTop(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + } + + func addViewOutsideOfScrollViewTop(_ view: UIView) { + guard let scrollView = scrollView, let parentView = self.view else { return } + parentView.addSubview(view) + scrollView.topAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true + view.topAnchor.constraint(equalTo: parentView.safeAreaLayoutGuide.topAnchor).isActive = true + } + func addViewInsideOfScrollViewBottom(_ view: UIView) { - guard let contentView = contentView else { - return - } + guard let contentView = contentView else { return } contentView.addSubview(view); NSLayoutConstraint.pinViewLeft(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true NSLayoutConstraint.pinViewRight(toSuperview: view, useMargins: useMargins, constant: 0).isActive = true @@ -232,16 +230,11 @@ extension ThreeLayerViewController { } func addViewOutsideOfScrollViewBottom(_ view: UIView) { - self.view?.addSubview(view) - if let scrollView = scrollView, let parentView = self.view { - 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 - parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true - if let safeAreaView = MVMCoreUICommonViewsUtility.getAndSetupSafeAreaView(on: parentView) { - safeAreaView.backgroundColor = bottomView?.backgroundColor - self.safeAreaView = safeAreaView - } - } + guard let scrollView = scrollView, let parentView = self.view else { return } + parentView.addSubview(view) + 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 + parentView.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true } } diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index eb869e89..f278236c 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -43,10 +43,10 @@ import UIKit public static func set(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { viewController.navigationItem.title = navigationItemModel.title viewController.navigationItem.accessibilityLabel = navigationItemModel.title - viewController.navigationItem.hidesBackButton = !navigationItemModel.systemBackButton + viewController.navigationItem.hidesBackButton = (navigationItemModel.backButton != nil) navigationController.setNavigationBarHidden(navigationItemModel.hidden, animated: true) - UIColor.setBackgroundColor(navigationItemModel.backgroundColor?.uiColor ?? .white, for: navigationController.navigationBar, isTransparent: navigationItemModel.transparent) + UIColor.setBackgroundColor(navigationItemModel.backgroundColor?.uiColor ?? .white, for: navigationController.navigationBar, isTransparent: navigationItemModel.translucent) let tint = navigationItemModel.tintColor.uiColor navigationController.navigationBar.tintColor = tint @@ -64,8 +64,8 @@ import UIKit if navigationController == MVMCoreUISplitViewController.main()?.navigationController, navigationController.topViewController == viewController { // Update Panels - MVMCoreUISplitViewController.main()?.setLeftPanelIsAccessible(navigationItemModel.showLeftPanelButton ?? false, for: viewController) - MVMCoreUISplitViewController.main()?.setRightPanelIsAccessible(navigationItemModel.showRightPanelButton ?? false, for: viewController) + MVMCoreUISplitViewController.main()?.setLeftPanelIsAccessible(navigationItemModel.showLeftPanelButton , for: viewController) + MVMCoreUISplitViewController.main()?.setRightPanelIsAccessible(navigationItemModel.showRightPanelButton , for: viewController) MVMCoreUISession.sharedGlobal()?.splitViewController?.setNavigationIconColor(tint) } } diff --git a/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift b/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift index 223589fe..4ce97f5c 100644 --- a/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift +++ b/MVMCoreUI/Containers/Views/Container/ContainerHelper.swift @@ -169,8 +169,15 @@ open class ContainerHelper: NSObject { } } + /// Updates the view margins according to model and size. If useHorizontalMargins is true, we will try to use the left and right padding, else we will default to the normal gutters. If useVerticalMargins is true, we will try to use the top and bottom padding. All else, we use zero. open func updateViewMargins(_ view: UIView, model: ContainerModelProtocol?, size: CGFloat) { - MFStyler.setMarginsFor(view, size: size, defaultHorizontal: model?.useHorizontalMargins ?? false, top: (model?.useVerticalMargins ?? false) ? (model?.topMarginPadding ?? 0) : 0, bottom: (model?.useVerticalMargins ?? false) ? (model?.bottomMarginPadding ?? 0) : 0) + let left = (model?.useHorizontalMargins ?? false) ? (model?.leftPadding ?? Padding.Component.horizontalPaddingForSize(size)) : 0.0 + let right = (model?.useHorizontalMargins ?? false) ? (model?.rightPadding ?? Padding.Component.horizontalPaddingForSize(size)) : 0.0 + let top = (model?.useVerticalMargins ?? false) ? (model?.topPadding ?? 0.0) : 0.0 + let bottom = (model?.useVerticalMargins ?? false) ? (model?.bottomPadding ?? 0.0) : 0.0 + MVMCoreDispatchUtility.performBlock(onMainThread: { + MVMCoreUIUtility.setMarginsFor(view, leading: left, top: top, trailing: right, bottom: bottom) + }) } open func set(with model: ContainerModelProtocol, for contained: MVMCoreUIViewConstrainingProtocol?) { diff --git a/MVMCoreUI/Containers/Views/Container/ContainerModel.swift b/MVMCoreUI/Containers/Views/Container/ContainerModel.swift index ec6f9769..ea7d8012 100644 --- a/MVMCoreUI/Containers/Views/Container/ContainerModel.swift +++ b/MVMCoreUI/Containers/Views/Container/ContainerModel.swift @@ -9,41 +9,61 @@ import Foundation open class ContainerModel: ContainerModelProtocol, Codable { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- public var horizontalAlignment: UIStackView.Alignment? - public var verticalAlignment: UIStackView.Alignment? public var useHorizontalMargins: Bool? + public var leftPadding: CGFloat? + public var rightPadding: CGFloat? + public var verticalAlignment: UIStackView.Alignment? public var useVerticalMargins: Bool? - public var topMarginPadding: CGFloat? - public var bottomMarginPadding: CGFloat? + public var topPadding: CGFloat? + public var bottomPadding: CGFloat? //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- private enum CodingKeys: String, CodingKey { - case verticalAlignment case horizontalAlignment case useHorizontalMargins + case leftPadding + case rightPadding + case verticalAlignment case useVerticalMargins - case topMarginPadding - case bottomMarginPadding + case topPadding + case bottomPadding } + //-------------------------------------------------- + // MARK: - Subclassable + //-------------------------------------------------- + + /// Sets the default values. Should be called on init. + public func setDefaults() {} + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- - public init() {} + public init() { + setDefaults() + } - public convenience init(horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil) { - self.init() + public init(horizontalAlignment: UIStackView.Alignment? = nil, verticalAlignment: UIStackView.Alignment? = nil, useHorizontalMargins: Bool? = nil, leftPadding: CGFloat? = nil, rightPadding: CGFloat? = nil, useVerticalMargins: Bool? = nil, topPadding: CGFloat? = nil, bottomPadding: CGFloat? = nil) { self.horizontalAlignment = horizontalAlignment self.verticalAlignment = verticalAlignment + self.useHorizontalMargins = useHorizontalMargins + self.leftPadding = leftPadding + self.rightPadding = rightPadding + self.useVerticalMargins = useVerticalMargins + self.topPadding = topPadding + self.bottomPadding = bottomPadding + setDefaults() } //-------------------------------------------------- @@ -59,9 +79,12 @@ open class ContainerModel: ContainerModelProtocol, Codable { horizontalAlignment = ContainerHelper.getAlignment(for: horizontalAlignmentString) } useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins) + leftPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .leftPadding) + rightPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .rightPadding) useVerticalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useVerticalMargins) - topMarginPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .topMarginPadding) - bottomMarginPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .bottomMarginPadding) + topPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .topPadding) + bottomPadding = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .bottomPadding) + setDefaults() } open func encode(to encoder: Encoder) throws { @@ -69,9 +92,10 @@ open class ContainerModel: ContainerModelProtocol, Codable { try container.encodeIfPresent(ContainerHelper.getAlignmentString(for: verticalAlignment), forKey: .verticalAlignment) try container.encodeIfPresent(ContainerHelper.getAlignmentString(for: horizontalAlignment), forKey: .horizontalAlignment) try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins) + try container.encodeIfPresent(leftPadding, forKey: .leftPadding) + try container.encodeIfPresent(rightPadding, forKey: .rightPadding) try container.encodeIfPresent(useVerticalMargins, forKey: .useVerticalMargins) - // TODO: can add this back once we have type erasures. - //try container.encodeIfPresent(topMarginPadding, forKey: .topMarginPadding) - //try container.encodeIfPresent(bottomMarginPadding, forKey: .bottomMarginPadding) + try container.encodeIfPresent(topPadding, forKey: .topPadding) + try container.encodeIfPresent(bottomPadding, forKey: .bottomPadding) } } diff --git a/MVMCoreUI/Containers/Views/MoleculeContainer.swift b/MVMCoreUI/Containers/Views/MoleculeContainer.swift index 98ecfa28..4e2c6279 100644 --- a/MVMCoreUI/Containers/Views/MoleculeContainer.swift +++ b/MVMCoreUI/Containers/Views/MoleculeContainer.swift @@ -41,9 +41,9 @@ open class MoleculeContainer: Container { guard let containerModel = model as? MoleculeContainerModelProtocol else { return 0 } guard let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(containerModel.molecule), let moleculeHeight = moleculeClass.estimatedHeight(with: containerModel.molecule, delegateObject) else { - return (containerModel.topMarginPadding ?? 0) + (containerModel.bottomMarginPadding ?? 0) + return (containerModel.topPadding ?? 0) + (containerModel.bottomPadding ?? 0) } - return moleculeHeight + (containerModel.topMarginPadding ?? 0) + (containerModel.bottomMarginPadding ?? 0) + return moleculeHeight + (containerModel.topPadding ?? 0) + (containerModel.bottomPadding ?? 0) } public override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { diff --git a/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift b/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift index 01116a48..86693823 100644 --- a/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift +++ b/MVMCoreUI/Containers/Views/MoleculeContainerModel.swift @@ -8,11 +8,17 @@ import Foundation -public class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtocol { +public class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProtocol, MoleculeModelProtocol { + public class var identifier: String { + return "container" + } + public var backgroundColor: Color? public var molecule: MoleculeModelProtocol private enum CodingKeys: String, CodingKey { + case moleculeName case molecule + case backgroundColor } public init(with moleculeModel: MoleculeModelProtocol) { @@ -23,12 +29,15 @@ public class MoleculeContainerModel: ContainerModel, MoleculeContainerModelProto required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecule = try typeContainer.decodeModel(codingKey: .molecule) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) try super.init(from: decoder) } public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) try container.encodeModel(molecule, forKey: .molecule) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) } } diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject+Extension.swift b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject+Extension.swift new file mode 100644 index 00000000..5cb2abdb --- /dev/null +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject+Extension.swift @@ -0,0 +1,29 @@ +// +// MVMCoreUIViewControllerMappingObject+Extension.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/21/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public extension MVMCoreUIViewControllerMappingObject { + func register(template: T.Type) { + add(toTemplateViewControllerMapping: [template.TemplateModel.identifier: MVMCoreViewControllerProgrammaticMappingObject(with: template)!]) + } + + @objc func registerTemplates() { + register(template: MoleculeStackTemplate.self) + add(toTemplateViewControllerMapping: [StackCenteredPageTemplateModel.identifier: MVMCoreViewControllerProgrammaticMappingObject(with: MoleculeStackCenteredTemplate.self)!]) + add(toTemplateViewControllerMapping: ["modalStack": MVMCoreViewControllerProgrammaticMappingObject(with: ModalMoleculeStackTemplate.self)!]) + + register(template: MoleculeListTemplate.self) + add(toTemplateViewControllerMapping: ["modalList": MVMCoreViewControllerProgrammaticMappingObject(with: ModalMoleculeListTemplate.self)!]) + + register(template: ThreeLayerTemplate.self) + register(template: ThreeLayerCenterTemplate.self) + + register(template: CollectionTemplate.self) + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m index 20fb924e..0ad51d4c 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewControllerMappingObject.m @@ -12,23 +12,14 @@ @implementation MVMCoreUIViewControllerMappingObject -- (NSMutableDictionary *)viewControllerMapping { - - // Keeps a mapping of the given page type - static dispatch_once_t onceToken; - static NSMutableDictionary *viewControllerMapping; - dispatch_once(&onceToken, ^{ - viewControllerMapping = [@{ - @"stack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackTemplate class]], - @"centerMoleculeStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeStackCenteredTemplate class]], - @"list" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[MoleculeListTemplate class]], - @"threeLayer" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ThreeLayerTemplate class]], - @"modalStack" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeStackTemplate class]], - @"modalList" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[ModalMoleculeListTemplate class]], - @"collection" : [[MVMCoreViewControllerProgrammaticMappingObject alloc] initWithClass:[CollectionTemplate class]] - } mutableCopy]; - }); - return viewControllerMapping; +- (instancetype)init { + if (self = [super init]) { + if (self.viewControllerMapping == nil) { + self.viewControllerMapping = [NSMutableDictionary dictionary]; + } + [self registerTemplates]; + } + return self; } @end diff --git a/MVMCoreUI/Utility/MFFonts.m b/MVMCoreUI/Utility/MFFonts.m index 533b3a87..99ad05c4 100644 --- a/MVMCoreUI/Utility/MFFonts.m +++ b/MVMCoreUI/Utility/MFFonts.m @@ -84,15 +84,19 @@ NSString * const DS55Rg = @"NHaasGroteskDSStd-55Rg"; + (nonnull UIFont *)mfFont75Bd:(CGFloat)size { - [self loadMVMFonts]; - UIFont *font = [UIFont fontWithName:DS75Bd size:size]; - return font ?: [UIFont boldSystemFontOfSize:size]; + if (size >= 15) { + return [MFFonts mfFontDSBold:size]; + } else { + return [MFFonts mfFontTXBold:size]; + } } + (nonnull UIFont *)mfFont55Rg:(CGFloat)size { - [self loadMVMFonts]; - UIFont *font = [UIFont fontWithName:DS55Rg size:size]; - return font ?: [UIFont systemFontOfSize:size]; + if (size >= 15) { + return [MFFonts mfFontDSRegular:size]; + } else { + return [MFFonts mfFontTXRegular:size]; + } } + (nullable UIFont *)mfFontOcratxt:(CGFloat)size { diff --git a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility+Extension.swift b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility+Extension.swift index 704cf801..84a6534c 100644 --- a/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility+Extension.swift +++ b/MVMCoreUI/Utility/MVMCoreUICommonViewsUtility+Extension.swift @@ -20,4 +20,10 @@ public extension MVMCoreUICommonViewsUtility { return toolbar } + + static func getView(with height: CGFloat) -> UIView { + let view = self.commonView() + view.heightAnchor.constraint(equalToConstant: height).isActive = true + return view + } }