diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 1ca8be9b..f97e81aa 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -95,8 +95,6 @@ 0AE14F64238315D2005417F8 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE14F63238315D2005417F8 /* TextField.swift */; }; 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */; }; 31BE15CC23D8924D00452370 /* CheckboxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15CA23D8924C00452370 /* CheckboxModel.swift */; }; - 52DD6C6B23E97B040011ECB2 /* ThreeColumnPlanDataDividerListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD6C6A23E97B040011ECB2 /* ThreeColumnPlanDataDividerListModel.swift */; }; - 52DD6C6D23E97B5E0011ECB2 /* ThreeColumnPlanDataDividerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD6C6C23E97B5E0011ECB2 /* ThreeColumnPlanDataDividerList.swift */; }; 9432A79F23DB47BA00719041 /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */; }; 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; @@ -108,6 +106,10 @@ 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; 946EE1BA237B66D80036751F /* MoleculeModelHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1B9237B66D80036751F /* MoleculeModelHelper.swift */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; + 94AF4A3E23E9D13900676048 /* MFCaretButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 94AF4A3C23E9D13900676048 /* MFCaretButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 94AF4A3F23E9D13900676048 /* MFCaretButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AF4A3D23E9D13900676048 /* MFCaretButton.m */; }; + 94AF4A4223E9D19E00676048 /* MFCaretView.h in Headers */ = {isa = PBXBuildFile; fileRef = 94AF4A4023E9D19E00676048 /* MFCaretView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 94AF4A4323E9D19E00676048 /* MFCaretView.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AF4A4123E9D19E00676048 /* MFCaretView.m */; }; 94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9832386F3F80006CF46 /* LabelAttributeModel.swift */; }; 94C2D9A123872BCC0006CF46 /* LabelAttributeUnderlineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9A023872BCC0006CF46 /* LabelAttributeUnderlineModel.swift */; }; 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C2D9A223872C110006CF46 /* LabelAttributeStrikeThroughModel.swift */; }; @@ -325,7 +327,7 @@ DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; DBC4391822442197001AB423 /* CaretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391622442196001AB423 /* CaretView.swift */; }; DBC4391922442197001AB423 /* DashLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391722442197001AB423 /* DashLine.swift */; }; - DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391A224421A0001AB423 /* CaretButton.swift */; }; + DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391A224421A0001AB423 /* CaretLink.swift */; }; DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391C2245232D001AB423 /* LabelWithInternalButton.swift */; }; DBEFFA04225A829700230692 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB891E822253FA8500022516 /* Label.swift */; }; /* End PBXBuildFile section */ @@ -410,8 +412,6 @@ 0AE14F63238315D2005417F8 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxLabelModel.swift; sourceTree = ""; }; 31BE15CA23D8924C00452370 /* CheckboxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxModel.swift; sourceTree = ""; }; - 52DD6C6A23E97B040011ECB2 /* ThreeColumnPlanDataDividerListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeColumnPlanDataDividerListModel.swift; sourceTree = ""; }; - 52DD6C6C23E97B5E0011ECB2 /* ThreeColumnPlanDataDividerList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeColumnPlanDataDividerList.swift; sourceTree = ""; }; 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftRightLabelModel.swift; sourceTree = ""; }; 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; @@ -424,6 +424,10 @@ 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; 946EE1B9237B66D80036751F /* MoleculeModelHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeModelHelper.swift; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; + 94AF4A3C23E9D13900676048 /* MFCaretButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MFCaretButton.h; sourceTree = ""; }; + 94AF4A3D23E9D13900676048 /* MFCaretButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MFCaretButton.m; sourceTree = ""; }; + 94AF4A4023E9D19E00676048 /* MFCaretView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MFCaretView.h; sourceTree = ""; }; + 94AF4A4123E9D19E00676048 /* MFCaretView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MFCaretView.m; sourceTree = ""; }; 94C2D9832386F3F80006CF46 /* LabelAttributeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeModel.swift; sourceTree = ""; }; 94C2D9A023872BCC0006CF46 /* LabelAttributeUnderlineModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeUnderlineModel.swift; sourceTree = ""; }; 94C2D9A223872C110006CF46 /* LabelAttributeStrikeThroughModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeStrikeThroughModel.swift; sourceTree = ""; }; @@ -654,7 +658,7 @@ DB891E822253FA8500022516 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; DBC4391622442196001AB423 /* CaretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretView.swift; sourceTree = ""; }; DBC4391722442197001AB423 /* DashLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashLine.swift; sourceTree = ""; }; - DBC4391A224421A0001AB423 /* CaretButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretButton.swift; sourceTree = ""; }; + DBC4391A224421A0001AB423 /* CaretLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretLink.swift; sourceTree = ""; }; DBC4391C2245232D001AB423 /* LabelWithInternalButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelWithInternalButton.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -831,7 +835,13 @@ D2A5145C2211D22A00345BFB /* MVMCoreUIMoleculeViewProtocol.h */, D29770C721F7C4AE00B2F0D0 /* TopLabelsView.h */, D29770C621F7C4AE00B2F0D0 /* TopLabelsView.m */, + 94F217B423E0BF6100A47C06 /* PrimaryButtonView.h */, + 94F217B523E0BF6100A47C06 /* PrimaryButtonView.m */, D282AACA2243C61700C46919 /* ButtonView.swift */, + 94AF4A3C23E9D13900676048 /* MFCaretButton.h */, + 94AF4A3D23E9D13900676048 /* MFCaretButton.m */, + 94AF4A4023E9D19E00676048 /* MFCaretView.h */, + 94AF4A4123E9D19E00676048 /* MFCaretView.m */, ); path = Views; sourceTree = ""; @@ -875,10 +885,6 @@ D28A838E23CCDEDE00DFE4FC /* TwoButtonViewModel.swift */, D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */, D28A837E23CCA96400DFE4FC /* TabsModel.swift */, - 94F217B423E0BF6100A47C06 /* PrimaryButtonView.h */, - 94F217B523E0BF6100A47C06 /* PrimaryButtonView.m */, - 52DD6C6A23E97B040011ECB2 /* ThreeColumnPlanDataDividerListModel.swift */, - 52DD6C6C23E97B5E0011ECB2 /* ThreeColumnPlanDataDividerList.swift */, ); path = HorizontalCombinationViews; sourceTree = ""; @@ -1162,7 +1168,7 @@ isa = PBXGroup; children = ( 01F2A03123A4498200D954D8 /* CaretLinkModel.swift */, - DBC4391A224421A0001AB423 /* CaretButton.swift */, + DBC4391A224421A0001AB423 /* CaretLink.swift */, D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */, D2E2A99E23E07F8A000B42E6 /* PillButton.swift */, D28A838823CCCFCB00DFE4FC /* LinkModel.swift */, @@ -1414,6 +1420,7 @@ D29DF27521E79E81003B2FB9 /* MVMCoreUILoggingHandler.h in Headers */, D29DF28B21E7AC2B003B2FB9 /* ViewConstrainingView.h in Headers */, D29DF2B321E7B76D003B2FB9 /* MFLoadingSpinner.h in Headers */, + 94AF4A3E23E9D13900676048 /* MFCaretButton.h in Headers */, 0A21DB8A235E06EF00C160A2 /* MFDigitTextBox.h in Headers */, D29DF32521ED0DA2003B2FB9 /* TextButtonView.h in Headers */, 0A21DB8C235E06EF00C160A2 /* MFDigitTextField.h in Headers */, @@ -1432,6 +1439,7 @@ D29DF2CA21E7BFC8003B2FB9 /* MFSizeThreshold.h in Headers */, D29DF28021E7AA51003B2FB9 /* MVMCoreUIDetailViewProtocol.h in Headers */, D29DF2BD21E7BEA4003B2FB9 /* MVMCoreUITabBarPageControlViewController.h in Headers */, + 94AF4A4223E9D19E00676048 /* MFCaretView.h in Headers */, D29DF2EE21ECEADF003B2FB9 /* MFFonts.h in Headers */, D29DF12D21E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.h in Headers */, D29770F321F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsTableViewController.h in Headers */, @@ -1642,7 +1650,6 @@ 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */, 94FB966323D797DA003D482B /* MFTextButton.m in Sources */, D260105323CEA61600764D80 /* ToggleModel.swift in Sources */, - 52DD6C6B23E97B040011ECB2 /* ThreeColumnPlanDataDividerListModel.swift in Sources */, 014AA72523C501E2006F3E93 /* ContainerModel.swift in Sources */, 0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, @@ -1678,6 +1685,7 @@ 944589212385D6E900DE9FD4 /* DashLineModel.swift in Sources */, D2E2A99623D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift in Sources */, C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, + 94AF4A3F23E9D13900676048 /* MFCaretButton.m in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */, @@ -1721,13 +1729,13 @@ D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */, 0A21DB83235DFBC500C160A2 /* MdnEntryField.swift in Sources */, D28A837D23CCA86A00DFE4FC /* TabsListItemModel.swift in Sources */, - 52DD6C6D23E97B5E0011ECB2 /* ThreeColumnPlanDataDividerList.swift in Sources */, 012A88C6238DA34000FE3DA1 /* ModuleMoleculeModel.swift in Sources */, 94C2D9A123872BCC0006CF46 /* LabelAttributeUnderlineModel.swift in Sources */, D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */, D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */, 0A21DB94235E24ED00C160A2 /* DigitEntryField.swift in Sources */, 0A21DB8D235E06EF00C160A2 /* MFDigitTextField.m in Sources */, + 94AF4A4323E9D19E00676048 /* MFCaretView.m in Sources */, 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, @@ -1742,7 +1750,7 @@ 012A889C23889E8400FE3DA1 /* TemplateModelProtocol.swift in Sources */, D29770FC21F7C77400B2F0D0 /* MVMCoreUITextFieldView.m in Sources */, C003506123AA94CD00B6AC29 /* Button.swift in Sources */, - DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, + DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */, 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift index 9eb284d7..cfe8bb2f 100644 --- a/MVMCoreUI/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atoms/Buttons/ButtonModel.swift @@ -41,6 +41,12 @@ public class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol { self.action = action } + init(secondaryButtonWith title: String, action: ActionModelProtocol) { + self.title = title + self.action = action + style = .secondary + } + private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretLink.swift similarity index 74% rename from MVMCoreUI/Atoms/Buttons/CaretButton.swift rename to MVMCoreUI/Atoms/Buttons/CaretLink.swift index 3fcf453a..ca665928 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretLink.swift @@ -1,5 +1,5 @@ // -// CaretButton.swift +// CaretLink.swift // MVMCoreUI // // Created by Kolli, Praneeth on 1/5/18. @@ -8,7 +8,7 @@ // -open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, ModelMoleculeViewProtocol { +open class CaretLink: Button, MVMCoreUIViewConstrainingProtocol { //------------------------------------------------------ // MARK: - Constants @@ -49,7 +49,9 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI didSet { changeCaretColor() } } - public func updateView(_ size: CGFloat) { } + public override func updateView(_ size: CGFloat) { + titleLabel?.font = MFStyler.fontB1() + } //------------------------------------------------------ // MARK: - Methods @@ -61,10 +63,18 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI setTitleColor(disabledColor, for: .disabled) if let rightCaretView = rightView as? CaretView { + rightCaretView.enabledColor = enabledColor + rightCaretView.disabledColor = disabledColor rightCaretView.isEnabled = isEnabled } } + private func createCaretView() -> CaretView { + let caret = CaretView() + caret.lineWidth = 1.5 + return caret + } + private func addCaretImageView() { rightView?.removeFromSuperview() @@ -75,7 +85,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI let edgeInsets: UIEdgeInsets = contentEdgeInsets contentEdgeInsets = UIEdgeInsets(top: edgeInsets.top, left: edgeInsets.left, bottom: edgeInsets.bottom, right: 4 + width) - let caretView: UIView = rightView ?? CaretView() + let caretView: UIView = rightView ?? createCaretView() rightView = caretView rightView?.translatesAutoresizingMaskIntoConstraints = false addSubview(caretView) @@ -115,33 +125,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI setTitleColor(disabledColor, for: .disabled) } - @objc public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - setWithActionMap(json, delegateObject: delegateObject, additionalData: additionalData) - - guard let dictionary = json else { return } - - if let title = dictionary.optionalStringForKey(KeyTitle) { - setTitle(title, for: .normal) - } - - if let disableButtonAsAny = dictionary[KeyDisableButton], let isDisabled = disableButtonAsAny as? Bool { - isEnabled = !isDisabled - } - - if let backgroundColorHex = dictionary[KeyBackgroundColor] as? String { - backgroundColor = UIColor.mfGet(forHex: backgroundColorHex) - } - - if let enabledColorHex = dictionary["enabledColor"] as? String { - enabledColor = UIColor.mfGet(forHex: enabledColorHex) - } - - if let disabledColorHex = dictionary["disabledColor"] as? String { - disabledColor = UIColor.mfGet(forHex: disabledColorHex) - } - } - - public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let caretLinkModel = model as? CaretLinkModel else { return } if let color = caretLinkModel.backgroundColor { backgroundColor = color.uiColor @@ -163,7 +147,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI return .leading } - public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 10 + open override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 10.5 } } diff --git a/MVMCoreUI/Atoms/Buttons/PillButton.swift b/MVMCoreUI/Atoms/Buttons/PillButton.swift index 0d0a3090..d3d09cee 100644 --- a/MVMCoreUI/Atoms/Buttons/PillButton.swift +++ b/MVMCoreUI/Atoms/Buttons/PillButton.swift @@ -97,9 +97,9 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol, FormValidation public static func getHeight(for buttonSize: ButtonSize?, size: CGFloat) -> CGFloat { switch buttonSize { case .tiny: - return MFSizeObject(standardSize: ButtonHeight.tiny.rawValue, standardiPadPortraitSize: 34, iPadProLandscapeSize: 38)?.getValueBased(onSize: size) ?? 20 + return MFSizeObject(standardSize: ButtonHeight.tiny.rawValue, standardiPadPortraitSize: 34, iPadProLandscapeSize: 38)?.getValueBased(onSize: size) ?? ButtonHeight.tiny.rawValue default: - return MFSizeObject(standardSize: ButtonHeight.standard.rawValue, standardiPadPortraitSize: 46, iPadProLandscapeSize: 50)?.getValueBased(onSize: size) ?? 42 + return MFSizeObject(standardSize: ButtonHeight.standard.rawValue, standardiPadPortraitSize: 46, iPadProLandscapeSize: 50)?.getValueBased(onSize: size) ?? ButtonHeight.standard.rawValue } } @@ -108,7 +108,7 @@ open class PillButton: Button, MVMCoreUIViewConstrainingProtocol, FormValidation case .tiny: return MFSizeObject(standardSize: 49.0, standardiPadPortraitSize: 90.0, iPadProLandscapeSize: 135.0)?.getValueBased(onSize: size) ?? 49.0 default: - return MFSizeObject(standardSize: 102.0, standardiPadPortraitSize: 136.0, iPadProLandscapeSize: 153.0)?.getValueBased(onSize: size) ?? 102.0 + return 151.0 } } diff --git a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift index 3290c744..70b46866 100644 --- a/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/BaseDropdownEntryField.swift @@ -21,7 +21,7 @@ import UIKit let caret = CaretView() caret.direction = .down caret.lineWidth = 1.5 - caret.isUserInteractionEnabled = true + caret.isUserInteractionEnabled = false caret.heightAnchor.constraint(equalToConstant: 9).isActive = true caret.widthAnchor.constraint(equalToConstant: 16).isActive = true return caret diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift index d1728b46..48728ce9 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryField.swift @@ -26,6 +26,10 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { /// Closure passed here will run upon dismissing the selection picker. public var observeDropdownSelection: ((String)->())? + public var itemDropdownEntryFieldModel: ItemDropdownEntryFieldModel? { + return model as? ItemDropdownEntryFieldModel + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -100,7 +104,7 @@ open class ItemDropdownEntryField: BaseDropdownEntryField { setPickerDelegates(delegate: self) if let pickerView = pickerView { - self.pickerView(pickerView, didSelectRow: 0, inComponent: 0) + self.pickerView(pickerView, didSelectRow: model.selectedIndex, inComponent: 0) } } } @@ -127,6 +131,7 @@ extension ItemDropdownEntryField: UIPickerViewDelegate, UIPickerViewDataSource { observeDropdownChange?(text ?? "", pickerData[row]) text = pickerData[row] + itemDropdownEntryFieldModel?.selectedIndex = row } } diff --git a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryFieldModel.swift b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryFieldModel.swift index 03c1cf40..56612b7b 100644 --- a/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryFieldModel.swift +++ b/MVMCoreUI/Atoms/TextFields/ItemDropdownEntryFieldModel.swift @@ -16,6 +16,7 @@ } public var options: [String] = [] + public var selectedIndex: Int = 0 //-------------------------------------------------- // MARK: - Keys @@ -24,6 +25,7 @@ private enum CodingKeys: String, CodingKey { case moleculeName case options + case selectedIndex } //-------------------------------------------------- @@ -34,6 +36,7 @@ try super.init(from: decoder) let typeContainer = try decoder.container(keyedBy: CodingKeys.self) options = try typeContainer.decode([String].self, forKey: .options) + selectedIndex = try typeContainer.decodeIfPresent(Int.self, forKey: .selectedIndex) ?? 0 } public override func encode(to encoder: Encoder) throws { @@ -41,5 +44,6 @@ var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encode(options, forKey: .options) + try container.encode(options, forKey: .selectedIndex) } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 66f3ecd4..67721f27 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -9,7 +9,7 @@ import UIKit -@objc public protocol ObservingTextFieldDelegate: NSObjectProtocol { +@objc public protocol ObservingTextFieldDelegate { /// Called when the entered text becomes valid based on the validation block @objc optional func isValid(textfield: TextEntryField?) /// Called when the entered text becomes invalid based on the validation block @@ -25,7 +25,7 @@ import UIKit //-------------------------------------------------- open private(set) var textField: TextField = { - let textField = TextField(frame: .zero) + let textField = TextField() textField.isAccessibilityElement = true textField.setContentCompressionResistancePriority(.required, for: .vertical) textField.font = MFStyler.fontForTextField() @@ -58,7 +58,7 @@ import UIKit get { return super.isEnabled } set (enabled) { super.isEnabled = enabled - + DispatchQueue.main.async { [weak self] in guard let self = self else { return } @@ -85,7 +85,10 @@ import UIKit /// The text of this TextField. open override var text: String? { get { return textField.text } - set { textField.text = newValue } + set { + textField.text = newValue + (model as? TextEntryFieldModel)?.text = newValue + } } /// Placeholder access for the TextField. @@ -99,7 +102,7 @@ import UIKit //-------------------------------------------------- public var validationBlock: ((_ value: String?) -> Bool)? - + //-------------------------------------------------- // MARK: - Delegate Properties //-------------------------------------------------- @@ -169,6 +172,9 @@ import UIKit textFieldTrailingConstraint = container.trailingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 16) textFieldTrailingConstraint?.isActive = true + textField.addTarget(self, action: #selector(startEditing), for: .editingDidBegin) + textField.addTarget(self, action: #selector(dismissFieldInput(_:)), for: .editingDidEnd) + let tap = UITapGestureRecognizer(target: self, action: #selector(startEditing)) entryFieldContainer.addGestureRecognizer(tap) @@ -225,7 +231,7 @@ import UIKit /// Validates the text of the entry field. @discardableResult @objc public func validateTextField() -> Bool { - + isValid = validationBlock?(text) ?? true if isValid { @@ -277,7 +283,7 @@ import UIKit guard let model = model as? TextEntryFieldModel else { return } FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) - + textColor.enabled = model.enabledTextColor?.uiColor textColor.disabled = model.disabledTextColor?.uiColor text = model.text diff --git a/MVMCoreUI/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atoms/Views/Label/Label.swift index 208676df..576f66b9 100644 --- a/MVMCoreUI/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label/Label.swift @@ -220,17 +220,27 @@ public typealias ActionBlock = () -> () } public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + clauses = [] - guard let labelModel = model as? LabelModel else { return } + + guard let labelModel = model as? LabelModel else { + text = "" + return + } + attributedText = nil + originalAttributedString = nil text = labelModel.text Label.setLabel(self, withHTML: labelModel.html) + let alignment = LabelAlignment(rawValue: labelModel.textAlignment ?? "") switch alignment { case .center: textAlignment = .center + case .right: textAlignment = .right + default: textAlignment = .left } @@ -239,9 +249,11 @@ public typealias ActionBlock = () -> () if let backgroundColor = labelModel.backgroundColor { self.backgroundColor = backgroundColor.uiColor } + if let accessibilityText = labelModel.accessibilityText { accessibilityLabel = accessibilityText } + if let fontStyle = labelModel.fontStyle { MFStyler.styleLabel(self, withStyle: fontStyle) MFStyler.styleLabel(self, withStyle: fontStyle, genericScaling: false) @@ -264,19 +276,23 @@ public typealias ActionBlock = () -> () if let attributes = labelModel.attributes, let labelText = text { let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font.withSize(standardFontSize), NSAttributedString.Key.foregroundColor: textColor as UIColor]) + for attribute in attributes { let range = NSRange(location: attribute.location, length: attribute.length) switch attribute { case _ as LabelAttributeUnderlineModel: attributedString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range) + case _ as LabelAttributeStrikeThroughModel: attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range) attributedString.addAttribute(.baselineOffset, value: 0, range: range) + case let colorAtt as LabelAttributeColorModel: if let colorHex = colorAtt.textColor, !colorHex.isEmpty { attributedString.removeAttribute(.foregroundColor, range: range) attributedString.addAttribute(.foregroundColor, value: UIColor.mfGet(forHex: colorHex), range: range) } + case let imageAtt as LabelAttributeImageModel: var fontSize = font.pointSize if let attributeSize = imageAtt.size { @@ -293,6 +309,7 @@ public typealias ActionBlock = () -> () let mutableString = NSMutableAttributedString() mutableString.append(NSAttributedString(attachment: imageAttachment)) attributedString.insert(mutableString, at: imageAtt.location) + case let fontAtt as LabelAttributeFontModel: if let fontStyle = fontAtt.style { let styles = MFStyler.styleGetAttributedString("0", withStyle: fontStyle) @@ -320,6 +337,7 @@ public typealias ActionBlock = () -> () } } addActionAttributes(range: range, string: attributedString) + default: continue } @@ -381,8 +399,9 @@ public typealias ActionBlock = () -> () } if let attributes = json?.optionalArrayForKey("attributes"), let labelText = label.text { - let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: mvmLabel?.font.withSize(mvmLabel!.standardFontSize) ?? label.font as UIFont, - NSAttributedString.Key.foregroundColor: label.textColor as UIColor]) + let attributedString = NSMutableAttributedString(string: labelText, + attributes: [NSAttributedString.Key.font: mvmLabel?.font.withSize(mvmLabel!.standardFontSize) ?? label.font as UIFont, + NSAttributedString.Key.foregroundColor: label.textColor as UIColor]) for case let attribute as [String: Any] in attributes { guard let attributeType = attribute.optionalStringForKey(KeyType), let location = attribute["location"] as? Int, diff --git a/MVMCoreUI/Atoms/Views/Label/LabelModel.swift b/MVMCoreUI/Atoms/Views/Label/LabelModel.swift index b0ad442f..731b23da 100644 --- a/MVMCoreUI/Atoms/Views/Label/LabelModel.swift +++ b/MVMCoreUI/Atoms/Views/Label/LabelModel.swift @@ -48,19 +48,19 @@ import Foundation required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - self.moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName) - self.text = try typeContainer.decode(String.self, forKey: .text) - self.accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) - self.textColor = try typeContainer.decodeIfPresent(String.self, forKey: .textColor) - self.backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - self.fontStyle = try typeContainer.decodeIfPresent(String.self, forKey: .fontStyle) - self.fontName = try typeContainer.decodeIfPresent(String.self, forKey: .fontName) - self.fontSize = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .fontSize) - self.textAlignment = try typeContainer.decodeIfPresent(String.self, forKey: .textAlignment) - self.attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes, typeCodingKey: AttributeTypeKey.type) as? [LabelAttributeModel] - self.html = try typeContainer.decodeIfPresent(String.self, forKey: .html) - self.hero = try typeContainer.decodeIfPresent(Int.self, forKey: .hero) - self.makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable) + moleculeName = try typeContainer.decodeIfPresent(String.self, forKey: .moleculeName) + text = try typeContainer.decode(String.self, forKey: .text) + accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) + textColor = try typeContainer.decodeIfPresent(String.self, forKey: .textColor) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + fontStyle = try typeContainer.decodeIfPresent(String.self, forKey: .fontStyle) + fontName = try typeContainer.decodeIfPresent(String.self, forKey: .fontName) + fontSize = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .fontSize) + textAlignment = try typeContainer.decodeIfPresent(String.self, forKey: .textAlignment) + attributes = try typeContainer.decodeModelsIfPresent(codingKey: .attributes, typeCodingKey: AttributeTypeKey.type) as? [LabelAttributeModel] + html = try typeContainer.decodeIfPresent(String.self, forKey: .html) + hero = try typeContainer.decodeIfPresent(Int.self, forKey: .hero) + makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable) } public func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Atoms/Views/MultiProgress.swift b/MVMCoreUI/Atoms/Views/MultiProgress.swift index cee425cc..cd05ed0b 100644 --- a/MVMCoreUI/Atoms/Views/MultiProgress.swift +++ b/MVMCoreUI/Atoms/Views/MultiProgress.swift @@ -31,7 +31,7 @@ import UIKit view.translatesAutoresizingMaskIntoConstraints = false addSubview(view) view.backgroundColor = progressObject.progressColor.uiColor - view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: progressObject.progress).isActive = true + view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: progressObject.progress/100.0).isActive = true view.leadingAnchor.constraint(equalTo: previous?.trailingAnchor ?? leadingAnchor).isActive = true previous = view NSLayoutConstraint.constraintPinSubview(view, pinTop: true, pinBottom: true, pinLeft: false, pinRight: false) diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 5045d5a4..3176df92 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -68,9 +68,8 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { open override func updateViewConstraints() { super.updateViewConstraints() - guard let tableView = tableView else { - return - } + + guard let tableView = tableView else { return } let minimumSpace: CGFloat = minimumFillSpace() var currentSpace: CGFloat = 0 @@ -90,9 +89,8 @@ open class ThreeLayerTableViewController: MFProgrammaticTableViewController { totalMinimumSpace += minimumSpace } - guard fillTop || fillBottom else { - return - } + 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. diff --git a/MVMCoreUI/Legacy/Views/MFCaretButton.h b/MVMCoreUI/Legacy/Views/MFCaretButton.h new file mode 100644 index 00000000..c6426e35 --- /dev/null +++ b/MVMCoreUI/Legacy/Views/MFCaretButton.h @@ -0,0 +1,26 @@ +// +// MFCaretButton.h +// MobileFirstFramework +// +// Created by Kolli, Praneeth on 1/5/18. +// Copyright © 2018 Verizon Wireless. All rights reserved. +// + +#import +#import + +@interface MFCaretButton : MFCustomButton + +@property (nullable, nonatomic, strong) UIView *rightView; +@property (nullable, nonatomic, strong) NSNumber *rightViewHeight; +@property (nullable, nonatomic, strong) NSNumber *rightViewWidth; + +-(void)updateCaretSpacing:(CGFloat)spacing; + +/* The fill color of the Caret and titleLabel. The default is blackColor */ +-(void)setEnableCaretColor:(nullable UIColor *)enableCaretColor; + +/* The fill color of the Caret and titleLabel. The default is mfSilver */ +-(void)setDisabledCaretColor:(nullable UIColor *)disabledCaretColor; + +@end diff --git a/MVMCoreUI/Legacy/Views/MFCaretButton.m b/MVMCoreUI/Legacy/Views/MFCaretButton.m new file mode 100644 index 00000000..bd8f058c --- /dev/null +++ b/MVMCoreUI/Legacy/Views/MFCaretButton.m @@ -0,0 +1,113 @@ +// +// MFCaretButton.m +// MobileFirstFramework +// +// Created by Kolli, Praneeth on 1/5/18. +// Copyright © 2018 Verizon Wireless. All rights reserved. +// + +#import "MFCaretButton.h" +#import "MFCaretView.h" +#import "UIColor+MFConvenience.h" + +@interface MFCaretButton () + +@property (nonatomic, strong) UIColor *enableColor; +@property (nonatomic, strong) UIColor *disabledColor; +@property (nonatomic, strong) NSLayoutConstraint *caretSpacingConstraint; + +@end + +@implementation MFCaretButton + +CGFloat const CaretViewHeight = 10.8f; +CGFloat const CaretViewWidth = 6.6f; + + +- (void)layoutSubviews { + [self addCaretImageView]; + [super layoutSubviews]; +} + +- (void)setEnabled:(BOOL)enabled { + [super setEnabled:enabled]; + [self changeCaretColor]; +} +- (void)changeCaretColor { + [self setTitleColor:self.enableColor forState:UIControlStateNormal]; + [self setTitleColor:self.disabledColor forState:UIControlStateDisabled]; + if ([self.rightView isKindOfClass:[MFCaretView class]]) { + MFCaretView *caretView = (MFCaretView *)self.rightView; + if (self.enabled) { + [caretView setLineColor:self.enableColor]; + } else { + [caretView setLineColor:self.disabledColor]; + } + } +} + +- (void)addCaretImageView { + [self.rightView removeFromSuperview]; + UIEdgeInsets edgeInsets = self.contentEdgeInsets; + CGFloat rightInset = self.rightViewWidth?self.rightViewWidth.floatValue:CaretViewWidth; + UIEdgeInsets newInsets = UIEdgeInsetsMake(edgeInsets.top, edgeInsets.left, edgeInsets.bottom, 4 + rightInset); + self.contentEdgeInsets = newInsets; + UIView *caretViewIs = self.rightView; + if (!self.rightView) { + caretViewIs = [[MFCaretView alloc]init]; + self.rightView = caretViewIs; + } + self.rightView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:self.rightView]; + + CGFloat width = self.rightViewWidth?self.rightViewWidth.floatValue:CaretViewWidth; + NSLayoutConstraint *caretViewWidthConstraint = [NSLayoutConstraint constraintWithItem:caretViewIs attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:width]; + caretViewWidthConstraint.active = YES; + + CGFloat height = self.rightViewHeight?self.rightViewHeight.floatValue:CaretViewHeight; + NSLayoutConstraint *caretViewHeightConstraint = [NSLayoutConstraint constraintWithItem:caretViewIs attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:height]; + caretViewHeightConstraint.active = YES; + + NSLayoutConstraint *caretLabelSpacing = [NSLayoutConstraint constraintWithItem:caretViewIs attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.titleLabel attribute:NSLayoutAttributeRight multiplier:1.0 constant:4.0]; + caretLabelSpacing.active = YES; + self.caretSpacingConstraint = caretLabelSpacing; + + NSLayoutConstraint *caretCenterY = [NSLayoutConstraint constraintWithItem:caretViewIs attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]; + caretCenterY.active = YES; + + NSLayoutConstraint *caretLabelRightPin = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:caretViewIs attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]; + caretLabelRightPin.active = YES; + self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; + //set correct color after layout + [self changeCaretColor]; +} +- (void)updateCaretSpacing:(CGFloat)spacing { + if (self.caretSpacingConstraint != nil) { + self.caretSpacingConstraint.constant = spacing; + } +} +- (void)setEnableCaretColor:(UIColor *)enableCaretColor { + self.enableColor = enableCaretColor; + [self changeCaretColor]; +} +- (void)setDisabledCaretColor:(UIColor *)disabledCaretColor { + self.disabledColor = disabledCaretColor; + [self changeCaretColor]; +} +@synthesize enableColor = _enableColor; +- (UIColor *)enableColor { + if (!_enableColor) { + _enableColor = [UIColor blackColor]; + } + return _enableColor; +} + +@synthesize disabledColor = _disabledColor; +- (UIColor *)disabledColor { + if (!_disabledColor) { + _disabledColor = [UIColor mfSilver]; + } + return _disabledColor; +} + +@end diff --git a/MVMCoreUI/Legacy/Views/MFCaretView.h b/MVMCoreUI/Legacy/Views/MFCaretView.h new file mode 100644 index 00000000..e2053f9a --- /dev/null +++ b/MVMCoreUI/Legacy/Views/MFCaretView.h @@ -0,0 +1,16 @@ +// +// MFCaretView.h +// MobileFirstFramework +// +// Created by Kolli, Praneeth on 1/5/18. +// Copyright © 2018 Verizon Wireless. All rights reserved. +// + +#import + +@interface MFCaretView : UIView +@property (strong, nonatomic, readonly) UIColor *strokeColor; +- (instancetype)initWithLineWidth:(CGFloat)lineWidth; +- (void)setLineColor:(UIColor *)color; +@end + diff --git a/MVMCoreUI/Legacy/Views/MFCaretView.m b/MVMCoreUI/Legacy/Views/MFCaretView.m new file mode 100644 index 00000000..182f7a74 --- /dev/null +++ b/MVMCoreUI/Legacy/Views/MFCaretView.m @@ -0,0 +1,93 @@ +// +// MFCaretView.m +// MobileFirstFramework +// +// Created by Kolli, Praneeth on 1/5/18. +// Copyright © 2018 Verizon Wireless. All rights reserved. +// + +#import "MFCaretView.h" +#import "UIColor+MFConvenience.h" +@interface MFCaretView () +@property (strong, nonatomic, readwrite) UIColor *strokeColor; +@property (nonatomic) NSNumber *lineWidth; + +@end + +@implementation MFCaretView + +- (instancetype)init { + self = [super init]; + if (self) { + // Initialization code + self.opaque = NO; + self.backgroundColor = [UIColor clearColor]; + self.strokeColor = [UIColor blackColor]; + } + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // Initialization code + self.opaque = NO; + self.backgroundColor = [UIColor clearColor]; + self.strokeColor = [UIColor blackColor]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + // Initialization code + self.opaque = NO; + self.backgroundColor = [UIColor clearColor]; + self.strokeColor = [UIColor blackColor]; + } + return self; +} + +- (instancetype)initWithLineWidth:(CGFloat)lineWidth { + if (self = [super init]) { + self.opaque = NO; + self.backgroundColor = [UIColor clearColor]; + self.lineWidth = @(lineWidth); + self.strokeColor = [UIColor blackColor]; + } + return self; +} + +- (void)drawRect:(CGRect)rect { + // Drawing Caret + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextClearRect(context, rect); + + CGFloat lineWidth; + if (self.lineWidth) { + lineWidth = self.lineWidth.floatValue; + } else { + lineWidth = self.frame.size.width/2.6; + } + + UIBezierPath *path = [[UIBezierPath alloc] init]; + [path moveToPoint:CGPointMake(lineWidth/2.0, 0.0)]; + [path addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height/2.0)]; + [path addLineToPoint:CGPointMake(lineWidth/2.0, self.frame.size.height)]; + [path addLineToPoint:CGPointMake(0.0, self.frame.size.height-lineWidth/2.0)]; + [path addLineToPoint:CGPointMake(self.frame.size.width-lineWidth, self.frame.size.height/2.0)]; + [path addLineToPoint:CGPointMake(0.0, lineWidth/2.0)]; + [path addLineToPoint:CGPointMake(lineWidth/2.0, 0.0)]; + [self.strokeColor setFill]; + [path fill]; + [path closePath]; + [self setBackgroundColor:[UIColor clearColor]]; +} + +- (void)setLineColor:(UIColor *)color { + self.strokeColor = color; + [self setNeedsDisplay]; +} + +@end diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/PrimaryButtonView.h b/MVMCoreUI/Legacy/Views/PrimaryButtonView.h similarity index 100% rename from MVMCoreUI/Molecules/HorizontalCombinationViews/PrimaryButtonView.h rename to MVMCoreUI/Legacy/Views/PrimaryButtonView.h diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/PrimaryButtonView.m b/MVMCoreUI/Legacy/Views/PrimaryButtonView.m similarity index 100% rename from MVMCoreUI/Molecules/HorizontalCombinationViews/PrimaryButtonView.m rename to MVMCoreUI/Legacy/Views/PrimaryButtonView.m diff --git a/MVMCoreUI/MVMCoreUI.h b/MVMCoreUI/MVMCoreUI.h index 9150e015..1b2183aa 100644 --- a/MVMCoreUI/MVMCoreUI.h +++ b/MVMCoreUI/MVMCoreUI.h @@ -81,6 +81,7 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import #import #import +#import #pragma mark Buttons #import @@ -89,6 +90,7 @@ FOUNDATION_EXPORT const unsigned char MVMCoreUIVersionString[]; #import #import #import +#import #pragma mark TextFields #import diff --git a/MVMCoreUI/Models/ModelProtocols/ListItemModelProtocol.swift b/MVMCoreUI/Models/ModelProtocols/ListItemModelProtocol.swift index 9d424a2b..c6be0488 100644 --- a/MVMCoreUI/Models/ModelProtocols/ListItemModelProtocol.swift +++ b/MVMCoreUI/Models/ModelProtocols/ListItemModelProtocol.swift @@ -17,19 +17,14 @@ public protocol ListItemModelProtocol: ContainerModelProtocol, MoleculeModelProt // Not a strict requirement. extension ListItemModelProtocol { + public var action: ActionModelProtocol? { - get { - return nil - } - set { - } + get { return nil } + set { } } public var style: String? { - get { - return nil - } - set { - } + get { return nil } + set { } } } diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift index 5c80bd1a..be6228f8 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift @@ -8,12 +8,12 @@ import UIKit -@objcMembers open class TwoButtonView: ViewConstrainingView { - open var primaryButton: PrimaryButton? = PrimaryButton.button() - open var secondaryButton: PrimaryButton? = PrimaryButton.button() - open var viewForButtons: UIView? - public var heightConstraint: NSLayoutConstraint? - +@objcMembers open class TwoButtonView: View, MVMCoreUIViewConstrainingProtocol { + open var primaryButton: PillButton = PillButton() + open var secondaryButton: PillButton = PillButton() + private var stack = UIStackView() + private var equalWidthConstraint: NSLayoutConstraint? + public init() { super.init(frame: .zero) } @@ -26,289 +26,100 @@ import UIKit super.init(frame: frame) } - public func setDefaultCustom() { - primaryButton?.setAsStandardCustom() - secondaryButton?.setAsSecondaryCustom() + public func setDefault() { + primaryButton.stylePrimary() + secondaryButton.styleSecondary() } // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { super.updateView(size) - MVMCoreDispatchUtility.performBlock(onMainThread: { - self.primaryButton?.updateView(size) - self.secondaryButton?.updateView(size) - }) + self.primaryButton.updateView(size) + self.secondaryButton.updateView(size) } open override func setupView() { super.setupView() - setupWithTwoButtons() - secondaryButton?.bordered = true + + stack.translatesAutoresizingMaskIntoConstraints = false + addSubview(stack) + stack.addArrangedSubview(secondaryButton) + stack.addArrangedSubview(primaryButton) + NSLayoutConstraint.constraintPinSubview(toSuperview: stack) + stack.axis = .horizontal + stack.spacing = 10 + equalWidthConstraint = secondaryButton.widthAnchor.constraint(equalTo: primaryButton.widthAnchor, multiplier: 1) + equalWidthConstraint?.isActive = true + } + + // MARK: - Stack Manipulation + public func showPrimaryButton() { + if !stack.arrangedSubviews.contains(primaryButton) { + stack.addArrangedSubview(primaryButton) + primaryButton.isHidden = false + } + if secondaryButton.superview != nil { + equalWidthConstraint?.isActive = true + } + } + + public func showSecondaryButton() { + if !stack.arrangedSubviews.contains(secondaryButton) { + stack.insertArrangedSubview(secondaryButton, at: 0) + secondaryButton.isHidden = false + } + if primaryButton.superview != nil { + equalWidthConstraint?.isActive = true + } + } + + public func hidePrimaryButton() { + if primaryButton.superview != nil { + stack.removeArrangedSubview(primaryButton) + primaryButton.isHidden = true + } + equalWidthConstraint?.isActive = false + } + + public func hideSecondaryButton() { + if secondaryButton.superview != nil { + stack.removeArrangedSubview(secondaryButton) + secondaryButton.isHidden = true + } + equalWidthConstraint?.isActive = false } // MARK: - MVMCoreUIMoleculeViewProtocol - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - let primaryButtonMap = json?.optionalDictionaryForKey("primaryButton") - let secondaryButtonMap = json?.optionalDictionaryForKey("secondaryButton") - set(primaryButtonJSON: primaryButtonMap, secondaryButtonJSON: secondaryButtonMap, delegateObject: delegateObject, additionalData: additionalData) - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - } - open override func reset() { super.reset() - primaryButton?.setAsStandardCustom() - secondaryButton?.setAsSecondaryCustom() + setDefault() } - - // MARK: - Constraining - func createPrimaryButton() { - if primaryButton == nil { - primaryButton = PrimaryButton.button() - } - } - - func createSecondaryButton() { - if secondaryButton == nil { - secondaryButton = PrimaryButton.button() - secondaryButton?.bordered = true - } - } - - func removeButtons() { - viewForButtons?.removeFromSuperview() - primaryButton?.removeFromSuperview() - secondaryButton?.removeFromSuperview() - viewForButtons = nil - secondaryButton = nil - primaryButton = nil - } - - open func setupConstraintsForViewWithButtons() { - guard let viewForButtons = viewForButtons, - let primaryButton = primaryButton, - let secondaryButton = secondaryButton - else { return } - - viewForButtons.addSubview(primaryButton) - viewForButtons.addSubview(secondaryButton) - secondaryButton.widthAnchor.constraint(equalTo: primaryButton.widthAnchor, multiplier: 1).isActive = true - NSLayoutConstraint.constraintPinSubview(secondaryButton, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) - NSLayoutConstraint.constraintPinSubview(primaryButton, pinTop: true, pinBottom: true, pinLeft: false, pinRight: true) - let constraint = primaryButton.leadingAnchor.constraint(equalTo: secondaryButton.trailingAnchor, constant: 10) - constraint.priority = UILayoutPriority(900) - constraint.isActive = true - } - - func setupWithTwoButtons() { - guard viewForButtons == nil else { - return - } - let viewForButtons = MVMCoreUICommonViewsUtility.commonView() - addSubview(viewForButtons) - self.viewForButtons = viewForButtons - - pinView(toSuperView: viewForButtons) - alignCenterHorizontal() - createPrimaryButton() - createSecondaryButton() - setupConstraintsForViewWithButtons() - } - - open func setupWithPrimaryButton() { - guard primaryButton == nil && secondaryButton == nil else { - return - } - createPrimaryButton() - if let primaryButton = primaryButton { - addSubview(primaryButton) - pinView(toSuperView: primaryButton) - alignCenterHorizontal() - } - } - - open func setupWithSecondaryButton() { - guard secondaryButton == nil && primaryButton == nil else { - return - } - createSecondaryButton() - if let secondaryButton = secondaryButton { - addSubview(secondaryButton) - pinView(toSuperView: secondaryButton) - alignCenterHorizontal() - } - } - - /// Legacy - func setupUI(withPrimaryButtonMap primaryButtonMap: [AnyHashable: Any]?, secondaryButtonMap: [AnyHashable: Any]?) { - setupUI(primaryButtonShowing: primaryButtonMap != nil, secondaryButtonShowing: secondaryButtonMap != nil) - } - - func setupUI(primaryButtonShowing: Bool, secondaryButtonShowing: Bool) { - if primaryButtonShowing, secondaryButtonShowing { - heightConstraint?.isActive = false - if primaryButton == nil || secondaryButton == nil { - removeButtons() - setupWithTwoButtons() - } - } else if primaryButtonShowing { - heightConstraint?.isActive = false - if primaryButton == nil || secondaryButton != nil { - removeButtons() - setupWithPrimaryButton() - } - } else if secondaryButtonShowing { - heightConstraint?.isActive = false - if secondaryButton == nil || primaryButton != nil { - removeButtons() - setupWithSecondaryButton() - } - } else { - removeButtons() - if heightConstraint == nil { - heightConstraint = heightAnchor.constraint(equalToConstant: 0) - heightConstraint?.isActive = true - } - } - } - - open func set(primaryButtonJSON: [AnyHashable: Any]?, secondaryButtonJSON: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - setupUI(withPrimaryButtonMap: primaryButtonJSON, secondaryButtonMap: secondaryButtonJSON) - setDefaultCustom() - primaryButton?.setWithJSON(primaryButtonJSON, delegateObject: delegateObject, additionalData: additionalData) - secondaryButton?.setWithJSON(secondaryButtonJSON, delegateObject: delegateObject, additionalData: additionalData) - } - - // MARK: - Legacy - public convenience init(primaryButtonMap: [AnyHashable: Any]?, secondaryButtonMap: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) { - self.init() - setup(primaryButtonMap: primaryButtonMap, secondaryButtonMap: secondaryButtonMap, delegateObject: delegateObject, additionalData: additionalData) - } - - public convenience init(buttonSmall small: Bool, buttonMap: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: AnyHashable]?) { - self.init() - setup(withButtonMap: buttonMap, delegateObject: delegateObject, additionalData: additionalData) - primaryButton?.setAsSmall(small) - secondaryButton?.setAsSmall(small) - } - - public convenience init(buttonSmall small: Bool, enabled: Bool) { - self.init() - removeButtons() - setupWithPrimaryButton() - primaryButton?.setAsSmall(small) - primaryButton?.isEnabled = enabled - } - - open func setup(primaryButtonMap: [AnyHashable: Any]?, secondaryButtonMap: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) { - setupUI(withPrimaryButtonMap: primaryButtonMap, secondaryButtonMap: secondaryButtonMap) - if primaryButtonMap != nil, secondaryButtonMap != nil { - primaryButton?.setWithActionMap(primaryButtonMap, delegateObject: delegateObject, additionalData: additionalData) - secondaryButton?.setWithActionMap(secondaryButtonMap, delegateObject: delegateObject, additionalData: additionalData) - } else if primaryButtonMap != nil { - primaryButton?.setWithActionMap(primaryButtonMap, delegateObject: delegateObject, additionalData: additionalData) - primaryButton?.bordered = false - } else if secondaryButtonMap != nil { - secondaryButton?.setWithActionMap(secondaryButtonMap, delegateObject: delegateObject, additionalData: additionalData) - secondaryButton?.bordered = true - } - } - - open func setup(withButtonMap buttonMap: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) { - let secondaryButtonMap = buttonMap?.optionalDictionaryForKey(KeySecondaryButton) - let primaryButtonMap = buttonMap?.optionalDictionaryForKey(KeyPrimaryButton) - setup(primaryButtonMap: primaryButtonMap, secondaryButtonMap: secondaryButtonMap, delegateObject: delegateObject, additionalData: additionalData) - } - - public func hideLeftButton() { - guard let secondaryButton = secondaryButton, !secondaryButton.isHidden else { - return - } - secondaryButton.isHidden = true - if let primaryButton = primaryButton { - primaryButton.removeFromSuperview() - viewForButtons?.addSubview(primaryButton) - NSLayoutConstraint.constraintPinSubview(toSuperview: primaryButton) - } - } - - public func hideRightButton() { - guard let primaryButton = primaryButton, !primaryButton.isHidden else { - return - } - primaryButton.isHidden = true - if let secondaryButton = secondaryButton { - secondaryButton.removeFromSuperview() - viewForButtons?.addSubview(secondaryButton) - NSLayoutConstraint.constraintPinSubview(toSuperview: secondaryButton) - } - } - - public func showBothButtons() { - primaryButton?.isHidden = false - secondaryButton?.isHidden = false - if let primaryButton = primaryButton, let secondaryButton = secondaryButton { - primaryButton.removeFromSuperview() - secondaryButton.removeFromSuperview() - setupConstraintsForViewWithButtons() - } - } - - public func hideBothButtons() { - primaryButton?.isHidden = true - secondaryButton?.isHidden = true - } - - override open func horizontalAlignment() -> UIStackView.Alignment { + + // MARK: - MVMCoreUIViewConstrainingProtocol + open func horizontalAlignment() -> UIStackView.Alignment { return .center } -} - -// MARK: - Deprecate -extension TwoButtonView { - @available(*, deprecated) - open func setup(primaryButtonMap: [AnyHashable: Any]?, secondaryButtonMap: [AnyHashable: Any]?, actionDelegate: NSObjectProtocol?, additionalData: [AnyHashable: Any]?, buttonDelegate: Any?) { - setupUI(withPrimaryButtonMap: primaryButtonMap, secondaryButtonMap: secondaryButtonMap) - if primaryButtonMap != nil, secondaryButtonMap != nil { - primaryButton?.setWithActionMap(primaryButtonMap, actionDelegate: actionDelegate as? MVMCoreActionDelegateProtocol & NSObjectProtocol, additionalData: additionalData, buttonDelegate: buttonDelegate as? ButtonDelegateProtocol) - secondaryButton?.setWithActionMap(secondaryButtonMap, actionDelegate: actionDelegate as? MVMCoreActionDelegateProtocol & NSObjectProtocol, additionalData: additionalData, buttonDelegate: buttonDelegate as? ButtonDelegateProtocol) - } else if primaryButtonMap != nil { - primaryButton?.setWithActionMap(primaryButtonMap, actionDelegate: actionDelegate as? MVMCoreActionDelegateProtocol & NSObjectProtocol, additionalData: additionalData, buttonDelegate: buttonDelegate as? ButtonDelegateProtocol) - primaryButton?.bordered = false - } else if secondaryButtonMap != nil { - secondaryButton?.setWithActionMap(secondaryButtonMap, actionDelegate: actionDelegate as? MVMCoreActionDelegateProtocol & NSObjectProtocol, additionalData: additionalData, buttonDelegate: buttonDelegate as? ButtonDelegateProtocol) - secondaryButton?.bordered = true + + // MARK: - ModelMoleculeViewProtocol + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + guard let model = molecule as? TwoButtonViewModel else { return 0 } + return PillButton.estimatedHeight(forRow: model.primaryButton ?? model.secondaryButton, delegateObject: delegateObject) + } + + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? TwoButtonViewModel else { return } + if let secondaryModel = model.secondaryButton { + showSecondaryButton() + secondaryButton.setWithModel(secondaryModel, delegateObject, additionalData) + } else { + hideSecondaryButton() + } + if let primaryModel = model.primaryButton { + showPrimaryButton() + primaryButton.setWithModel(primaryModel, delegateObject, additionalData) + } else { + hidePrimaryButton() } } - - @available(*, deprecated) - open func setup(withButtonMap buttonMap: [AnyHashable: Any]?, actionDelegate: NSObjectProtocol?, additionalData: [AnyHashable: Any]?, buttonDelegate: Any?) { - let secondaryButtonMap = buttonMap?.optionalDictionaryForKey(KeySecondaryButton) - let primaryButtonMap = buttonMap?.optionalDictionaryForKey(KeyPrimaryButton) - setup(primaryButtonMap: primaryButtonMap, secondaryButtonMap: secondaryButtonMap, actionDelegate: actionDelegate, additionalData: additionalData, buttonDelegate: buttonDelegate) - } - - @available(*, deprecated) - public convenience init(buttonSmall small: Bool, buttonMap: [AnyHashable: Any]?, actionDelegate: NSObjectProtocol?, additionalData: [AnyHashable: AnyHashable]?, buttonDelegate: Any?) { - self.init() - setup(withButtonMap: buttonMap, actionDelegate: actionDelegate, additionalData: additionalData, buttonDelegate: buttonDelegate) - primaryButton?.setAsSmall(small) - secondaryButton?.setAsSmall(small) - } - - @available(*, deprecated) - public convenience init(primaryButtonMap: [AnyHashable: Any]?, secondaryButtonMap: [AnyHashable: Any]?, actionDelegate: NSObjectProtocol?, additionalData: [AnyHashable: Any]?, buttonDelegate: Any?) { - self.init() - setup(primaryButtonMap: primaryButtonMap, secondaryButtonMap: secondaryButtonMap, actionDelegate: actionDelegate, additionalData: additionalData, buttonDelegate: buttonDelegate) - } -} - -extension TwoButtonView: ModelMoleculeViewProtocol { - public func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let model = model as? TwoButtonViewModel else { return } - setupUI(primaryButtonShowing: model.primaryButton != nil, secondaryButtonShowing: model.secondaryButton != nil) - setDefaultCustom() - primaryButton?.setWithModel(model.primaryButton, delegateObject, additionalData) - secondaryButton?.setWithModel(model.secondaryButton, delegateObject, additionalData) - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - } } diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift index a7abde9f..5938692b 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonViewModel.swift @@ -8,9 +8,35 @@ import UIKit -public struct TwoButtonViewModel: MoleculeModelProtocol { +public class TwoButtonViewModel: MoleculeModelProtocol { public static var identifier: String = "twoButtonView" public var backgroundColor: Color? public var primaryButton: ButtonModel? public var secondaryButton: ButtonModel? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case primaryButton + case secondaryButton + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + primaryButton = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .primaryButton) + secondaryButton = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .secondaryButton) + // Default value + if secondaryButton?.style == nil { + secondaryButton?.style = .secondary + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(primaryButton, forKey: .primaryButton) + try container.encodeIfPresent(secondaryButton, forKey: .secondaryButton) + } } diff --git a/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift b/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift index 8c813d24..081da875 100644 --- a/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/DropDownFilterTableViewCell.swift @@ -26,6 +26,7 @@ import UIKit super.setupView() guard dropDown.superview == nil else { return } + addMolecule(dropDown) dropDown.observeDropdownChange = { [weak self] oldValue, newValue in diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index 56ff1ca9..9f27a918 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -8,14 +8,19 @@ import UIKit + @objcMembers open class MoleculeTableViewCell: TableViewCell { // MARK: - MVMCoreUIMoleculeViewProtocol + public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.setWithModel(model, delegateObject, additionalData) + guard let model = model as? ListItemModel else { return } + if molecule != nil { (molecule as? ModelMoleculeViewProtocol)?.setWithModel(model.molecule, delegateObject, additionalData) + } else if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(model.molecule, delegateObject, false) { addMolecule(moleculeView) } @@ -24,28 +29,28 @@ import UIKit } public override class func nameForReuse(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { - guard let moleculeModel = (model as? ListItemModel)?.molecule else { - return "\(self)<>" - } + guard let moleculeModel = (model as? ListItemModel)?.molecule else { return "\(self)<>" } + let className = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(moleculeModel) as? ModelMoleculeViewProtocol.Type let moleculeName = className?.nameForReuse(moleculeModel, delegateObject) ?? moleculeModel.moleculeName ?? "" + return "\(self)<\(moleculeName)>" } public class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), - let theClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON) else { - return nil - } + let theClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON) + else { return nil } + return theClass.requiredModules?(moleculeJSON, delegateObject: delegateObject, error: error) } public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { guard let moleculeModel = (molecule as? MoleculeContainerModel)?.molecule, let classType = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(moleculeModel) as? ModelMoleculeViewProtocol.Type, - let height = classType.estimatedHeight(forRow: moleculeModel, delegateObject: delegateObject) else { - return 80 - } + let height = classType.estimatedHeight(forRow: moleculeModel, delegateObject: delegateObject) + else { return 80 } + return max(2 * PaddingDefaultVerticalSpacing3, height) } } diff --git a/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift b/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift index 680e7089..2de9deca 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/CornerLabels.swift @@ -171,10 +171,8 @@ import UIKit guard let model = model as? CornerLabelsModel else { return } if middleView != nil { (middleView as? ModelMoleculeViewProtocol)?.setWithModel(model, delegateObject, additionalData) - } else { - if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(model.molecule, delegateObject) { - addMiddleView(molecule) - } + } else if let moleculeModel = model.molecule, let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(moleculeModel, delegateObject) { + addMiddleView(molecule) } topLeftLabel.setWithModel(model.topLeftLabel, delegateObject, additionalData) diff --git a/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift b/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift index ce3796bf..f4b67216 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/CornerLabelsModel.swift @@ -15,9 +15,9 @@ public class CornerLabelsModel: MoleculeModelProtocol { public var topRightLabel: LabelModel? public var bottomLeftLabel: LabelModel? public var bottomRightLabel: LabelModel? - public var molecule: MoleculeModelProtocol + public var molecule: MoleculeModelProtocol? - init(with molecule: MoleculeModelProtocol) { + init(with molecule: MoleculeModelProtocol?) { self.molecule = molecule } @@ -33,7 +33,7 @@ public class CornerLabelsModel: MoleculeModelProtocol { required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - molecule = try typeContainer.decodeMolecule(codingKey: .molecule) + molecule = try typeContainer.decodeMoleculeIfPresent(codingKey: .molecule) topLeftLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .topLeftLabel) topRightLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .topRightLabel) bottomLeftLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .bottomLeftLabel) diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index fc19749a..2b3567d7 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -9,9 +9,8 @@ import Foundation @objcMembers public class HeadLineBodyCaretLinkImage: Container { let headlineBody = HeadlineBody(frame: .zero) - let caretButton = CaretButton(frame: .zero) + let caretButton = CaretLink(frame: .zero) let backgroundImageView = MFLoadImageView(pinnedEdges: .all) - var spaceBetweenConstant: CGFloat = 104.0 let maxWidth: CGFloat = 350.0 static let heightConstant: CGFloat = 320.0 var heightConstraint: NSLayoutConstraint? @@ -44,17 +43,17 @@ import Foundation headlineBody.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true headlineBody.topAnchor.constraint(equalTo: container.topAnchor, constant: 0).isActive = true - let headLineBodyWidth = headlineBody.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.85) - headLineBodyWidth.priority = .defaultHigh + headlineBody.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.67).isActive = true + let headLineBodyWidth = headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth) + headLineBodyWidth.priority = UILayoutPriority(250) headLineBodyWidth.isActive = true - headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true //Caret view caretButton.translatesAutoresizingMaskIntoConstraints = false caretButton.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true container.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor, constant: 0).isActive = true - caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: spaceBetweenConstant).isActive = true + caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: PaddingTwo).isActive = true //Background image view backgroundImageView.translatesAutoresizingMaskIntoConstraints = false @@ -67,13 +66,6 @@ import Foundation } // MARK: - MVMCoreUIMoleculeViewProtocol - open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - backgroundImageView.setWithJSON(json?.optionalDictionaryForKey("image"), delegateObject: delegateObject, additionalData: additionalData) - headlineBody.setWithJSON(json?.optionalDictionaryForKey("headlineBody"), delegateObject: delegateObject, additionalData: additionalData) - caretButton.setWithJSON(json?.optionalDictionaryForKey("caretLink"), delegateObject: delegateObject, additionalData: additionalData) - } - open override func reset() { super.reset() headlineBody.reset() diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift index 3a270ad5..b94adef0 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -24,9 +24,8 @@ open class HeadlineBody: View { // MARK: - Styling func style(with styleString: String?) { - guard let styleString = styleString else { - return - } + guard let styleString = styleString else { return } + switch styleString { case "header": stylePageHeader() @@ -72,10 +71,9 @@ open class HeadlineBody: View { open override func setupView() { super.setupView() - guard subviews.count == 0 else { - return - } - translatesAutoresizingMaskIntoConstraints = false + + guard subviews.isEmpty else { return } + backgroundColor = .clear clipsToBounds = true @@ -86,9 +84,9 @@ open class HeadlineBody: View { view.addSubview(headlineLabel) view.addSubview(messageLabel) - headlineLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - messageLabel.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) - view.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) + headlineLabel.setContentHuggingPriority(.required, for: .vertical) + messageLabel.setContentHuggingPriority(.required, for: .vertical) + view.setContentHuggingPriority(.required, for: .vertical) headlineLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true @@ -110,7 +108,10 @@ open class HeadlineBody: View { view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0).isActive = true } + //-------------------------------------------------- // MARK: - Constraining + //-------------------------------------------------- + public func setSpacing() { if headlineLabel.hasText && messageLabel.hasText { spaceBetweenLabels?.constant = spaceBetweenLabelsConstant @@ -119,25 +120,29 @@ open class HeadlineBody: View { } } - // MARK:- ModelMoleculeViewProtocol + //-------------------------------------------------- + // MARK: - ModelMoleculeViewProtocol + //-------------------------------------------------- + public override class func estimatedHeight(forRow molecule: MoleculeModelProtocol?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 58 } public override func setWithModel(_ model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.setWithModel(model, delegateObject, additionalData) - guard let headlineBodyModel = model as? HeadlineBodyModel else { - return - } + + guard let headlineBodyModel = model as? HeadlineBodyModel else { return } style(with: headlineBodyModel.style) - + headlineLabel.setWithModel(headlineBodyModel.headline, delegateObject, additionalData) messageLabel.setWithModel(headlineBodyModel.body, delegateObject, additionalData) } + //-------------------------------------------------- // MARK: - MVMCoreUIMoleculeViewProtocol + //-------------------------------------------------- + open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) @@ -159,4 +164,3 @@ open class HeadlineBody: View { return 58 } } - diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift index 62199654..1fbe98e7 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/HeadlineBodyModel.swift @@ -20,4 +20,3 @@ import Foundation self.headline = headline } } - diff --git a/MVMCoreUI/Organisms/Stack.swift b/MVMCoreUI/Organisms/Stack.swift index 2ab77421..4e7a3114 100644 --- a/MVMCoreUI/Organisms/Stack.swift +++ b/MVMCoreUI/Organisms/Stack.swift @@ -33,8 +33,9 @@ open class Stack: Container where T: StackModelProtocol { }) // Adds the views + let totalSpace = getTotalSpace() for (index, view) in stackItems.enumerated() { - addView(view, stackModel.molecules[index], percentModifier: getPercentModifier(), lastItem: lastItemIndex == index) + addView(view, stackModel.molecules[index], totalSpacing: totalSpace, lastItem: lastItemIndex == index) } } @@ -173,14 +174,12 @@ open class Stack: Container where T: StackModelProtocol { // MARK: - Adding to stack /// Gets the percent modifier. This value is used to help properly calculate percent for stack items when spacing is involved. - private func getPercentModifier() -> CGFloat { + private func getTotalSpace() -> CGFloat { guard let stackModel = stackModel else { return 0.0 } var totalSpace: CGFloat = 0.0 - var totalViews = 0 var firstMoleculeFound = false for stackItemModel in stackModel.molecules { guard !stackItemModel.gone else { continue } - totalViews += 1 let spacing = stackItemModel.spacing ?? stackModel.spacing if firstMoleculeFound { totalSpace += spacing @@ -189,11 +188,11 @@ open class Stack: Container where T: StackModelProtocol { totalSpace += (stackModel.useStackSpacingBeforeFirstItem ? spacing : stackItemModel.spacing ?? 0) } } - return (totalViews > 0 ? -(totalSpace / CGFloat(totalViews)) : 0) + return totalSpace } /// Adds the stack item view - private func addView(_ view: UIView,_ model: StackItemModelProtocol, percentModifier: CGFloat, lastItem: Bool) { + private func addView(_ view: UIView,_ model: StackItemModelProtocol, totalSpacing: CGFloat, lastItem: Bool) { guard let stackModel = self.stackModel else { return } guard !model.gone else { // Gone views do not show @@ -223,7 +222,9 @@ open class Stack: Container where T: StackModelProtocol { pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) pinView(contentView, toView: view, attribute: .trailing, relation: .equal, priority: .required, constant: 0) if let percent = model.percent { - view.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: CGFloat(percent)/100.0, constant: percentModifier).isActive = true + let multiplier = CGFloat(percent)/100.0 + let constant = multiplier * totalSpacing + view.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: multiplier, constant: -constant).isActive = true } if lastItem { pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) @@ -240,7 +241,9 @@ open class Stack: Container where T: StackModelProtocol { pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0) if let percent = model.percent { - view.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: CGFloat(percent)/100.0, constant: percentModifier).isActive = true + let multiplier = CGFloat(percent)/100.0 + let constant = multiplier * totalSpacing + view.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: multiplier, constant: -constant).isActive = true } if lastItem { pinView(contentView, toView: view, attribute: .right, relation: .equal, priority: .required, constant: 0) diff --git a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift index cbfacbb7..b029b352 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift +++ b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift @@ -30,7 +30,7 @@ import Foundation MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: PillButton.self, viewModelClass: ButtonModel.self) MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: TwoButtonView.self, viewModelClass: TwoButtonViewModel.self) MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: Link.self, viewModelClass: LinkModel.self) - MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CaretButton.self, viewModelClass: CaretLinkModel.self) + MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: CaretLink.self, viewModelClass: CaretLinkModel.self) // Entry Field MVMCoreUIMoleculeMappingObject.shared()?.register(viewClass: TextEntryField.self, viewModelClass: TextEntryFieldModel.self) diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 75ef7d2f..a088d34a 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -9,52 +9,67 @@ import UIKit open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol { + //-------------------------------------------------- + // MARK: - Stored Properties + //-------------------------------------------------- public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: ListItemModelProtocol)]? + var observer: NSKeyValueObservation? public var templateModel: ListPageTemplateModel? + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + @objc public override func parsePageJSON() throws { try parseTemplateJSON() } open override var loadObject: MVMCoreLoadObject? { didSet { - if loadObject != oldValue { - updateRequiredModules() - observer?.invalidate() - if let newObject = loadObject { - observer = newObject.observe(\MVMCoreLoadObject.pageJSON, options: [.old, .new]) { [weak self] (object, change) in - self?.updateRequiredModules() - } + guard loadObject != oldValue else { return } + + updateRequiredModules() + observer?.invalidate() + if let newObject = loadObject { + observer = newObject.observe(\MVMCoreLoadObject.pageJSON, options: [.old, .new]) { [weak self] object, change in + self?.updateRequiredModules() } } } } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + open override func viewForTop() -> UIView { guard let headerModel = templateModel?.header, - let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(headerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { - return super.viewForTop() - } + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(headerModel, delegateObject() as? MVMCoreUIDelegateObject, false) + else { return super.viewForTop() } // Temporary, Default the horizontal padding if var container = templateModel?.header as? ContainerModelProtocol, container.useHorizontalMargins == nil { container.useHorizontalMargins = true } + return molecule } override open func viewForBottom() -> UIView { guard let footerModel = templateModel?.footer, - let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(footerModel, delegateObject() as? MVMCoreUIDelegateObject, false) else { - return super.viewForBottom() - } + let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(footerModel, delegateObject() as? MVMCoreUIDelegateObject, false) + else { return super.viewForBottom() } + return molecule } open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer) -> Bool { + guard super.shouldFinishProcessingLoad(loadObject, error: error) else { return false } + // This template requires atleast one of the three layers. if templateModel?.header == nil, templateModel?.molecules?.count ?? 0 == 0, @@ -72,7 +87,10 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol registerWithTable() } - // MARK: - table + //-------------------------------------------------- + // MARK: - Table View + //-------------------------------------------------- + open override func registerWithTable() { super.registerWithTable() guard let moleculesInfo = moleculesInfo else { return } @@ -84,9 +102,9 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol open override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { guard let moleculeInfo = moleculesInfo?[indexPath.row], - let estimatedHeight = (moleculeInfo.class as? ModelMoleculeViewProtocol.Type)?.estimatedHeight(forRow: moleculeInfo.molecule, delegateObject: delegateObject() as? MVMCoreUIDelegateObject) else { - return 0 - } + let estimatedHeight = (moleculeInfo.class as? ModelMoleculeViewProtocol.Type)?.estimatedHeight(forRow: moleculeInfo.molecule, delegateObject: delegateObject() as? MVMCoreUIDelegateObject) + else { return 0 } + return estimatedHeight } @@ -95,18 +113,22 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let moleculeInfo = moleculesInfo?[indexPath.row], - let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.identifier) else { - return UITableViewCell() - } + let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.identifier) + else { return UITableViewCell() } + let delegate = delegateObject() as? MVMCoreUIDelegateObject let moleculeCell = cell as? MVMCoreUIMoleculeViewProtocol moleculeCell?.reset?() + if let protocolCell = cell as? MoleculeListCellProtocol { protocolCell.setLines(with: templateModel?.line, delegateObject: delegate, additionalData: nil, indexPath: indexPath) } + (moleculeCell as? ModelMoleculeViewProtocol)?.setWithModel(moleculeInfo.molecule, delegate, nil) - moleculeCell?.updateView(tableView.bounds.width) + moleculeCell?.updateView(tableView.bounds.width) + return cell } @@ -118,16 +140,18 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let cell = tableView.cellForRow(at: indexPath) as? MoleculeListCellProtocol { cell.didSelectCell(at: indexPath, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, additionalData: nil) } } - // MARK: - cache handling + //-------------------------------------------------- + // MARK: - Cache Handling + //-------------------------------------------------- + open override func pageTypesToListenFor() -> [Any]? { - guard let pageType = self.pageType else { - return super.pageTypesToListenFor() - } + guard let pageType = self.pageType else { return super.pageTypesToListenFor() } return [pageType] } @@ -135,8 +159,12 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol return loadObject?.requestParameters?.modules } + //-------------------------------------------------- // MARK: - MoleculeDelegateProtocol + //-------------------------------------------------- + open override func moleculeLayoutUpdated(_ molecule: UIView & MVMCoreUIMoleculeViewProtocol) { + if let tableView = tableView { let point = molecule.convert(molecule.bounds.origin, to: tableView) if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { @@ -159,6 +187,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol DispatchQueue.main.async { guard let indexPath = self.tableView?.indexPath(for: sender) else { return } var indexPaths: [IndexPath] = [] + for molecule in tmpMolecules { if let info = self.getMoleculeInfo(with: molecule) { self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) @@ -167,6 +196,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol indexPaths.append(IndexPath(row: index, section: 0)) } } + self.tableView?.insertRows(at: indexPaths, with: animation) self.updateViewConstraints() self.view.layoutIfNeeded() @@ -184,25 +214,29 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } var indexPaths: [IndexPath] = [] + //TODO: cehck for molecule protocola eqality + for molecule in tmpMolecules { - if let removeIndex = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in - return molecule.toJSONString() == moleculeInfo.molecule.toJSONString() - }) { + if let removeIndex = moleculesInfo?.firstIndex(where: { molecule.toJSON() == $0.molecule.toJSON() }) { + moleculesInfo?.remove(at: removeIndex) indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) } } - self.tableView?.deleteRows(at: indexPaths, with: animation) - self.updateViewConstraints() - self.view.layoutIfNeeded() + + tableView?.deleteRows(at: indexPaths, with: animation) + updateViewConstraints() + view.layoutIfNeeded() } public func addMolecules(_ molecules: [ListItemModelProtocol], sender: UITableViewCell, animation: UITableView.RowAnimation) { + // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. DispatchQueue.main.async { guard let indexPath = self.tableView?.indexPath(for: sender) else { return } var indexPaths: [IndexPath] = [] + for molecule in molecules { if let info = self.getMoleculeInfo(with: molecule) { self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) @@ -211,6 +245,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol indexPaths.append(IndexPath(row: index, section: 0)) } } + self.tableView?.insertRows(at: indexPaths, with: animation) self.updateViewConstraints() self.view.layoutIfNeeded() @@ -218,35 +253,42 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } public func removeMolecules(_ molecules: [ListItemModelProtocol], sender: UITableViewCell, animation: UITableView.RowAnimation) { + var indexPaths: [IndexPath] = [] //TODO: cehck for molecule protocola eqality + for molecule in molecules { - if let removeIndex = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in - return molecule.toJSONString() == moleculeInfo.molecule.toJSONString() - }) { + if let removeIndex = moleculesInfo?.firstIndex(where: { molecule.toJSON() == $0.molecule.toJSON() }) { moleculesInfo?.remove(at: removeIndex) indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) } } - self.tableView?.deleteRows(at: indexPaths, with: animation) - self.updateViewConstraints() - self.view.layoutIfNeeded() + + tableView?.deleteRows(at: indexPaths, with: animation) + updateViewConstraints() + view.layoutIfNeeded() } + //-------------------------------------------------- // MARK: - Convenience + //-------------------------------------------------- + /// Returns the (identifier, class) of the molecule for the given map. func getMoleculeInfo(with listItem: ListItemModelProtocol?) -> (identifier: String, class: AnyClass, molecule: ListItemModelProtocol)? { + guard let listItem = listItem, let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(listItem), - let moleculeName = (moleculeClass as? ModelMoleculeViewProtocol.Type)?.nameForReuse(listItem, delegateObject() as? MVMCoreUIDelegateObject) ?? listItem.moleculeName else { - return nil - } + let moleculeName = (moleculeClass as? ModelMoleculeViewProtocol.Type)?.nameForReuse(listItem, delegateObject() as? MVMCoreUIDelegateObject) ?? listItem.moleculeName + else { return nil } + return (moleculeName, moleculeClass, listItem) } /// Sets up the molecule list and ensures no errors loading all content. func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: ListItemModelProtocol)]? { + var moleculeList: [(identifier: String, class: AnyClass, molecule: ListItemModelProtocol)] = [] + if let molecules = templateModel?.molecules { for molecule in molecules { if let info = getMoleculeInfo(with: molecule) { @@ -254,6 +296,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } } } + return moleculeList.count > 0 ? moleculeList : nil } @@ -271,15 +314,19 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol /// Gets modules required by the loadObject.pageJSON. open func requiredModules() -> [Any]? { + let modules: NSMutableArray = [] let delegate = delegateObject() as? MVMCoreUIDelegateObject + MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate, moduleList: modules, errorList: nil) MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate, moduleList: modules, errorList: nil) + if let molecules = loadObject?.pageJSON?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] { for molecule in molecules { MVMCoreUIMoleculeMappingObject.addRequiredModules(forJSON: molecule, delegateObject: delegate, moduleList: modules, errorList: nil) } } + return modules as? [Any] } } diff --git a/MVMCoreUI/Templates/StackPageTemplateModel.swift b/MVMCoreUI/Templates/StackPageTemplateModel.swift index b9a54125..10a2cf6f 100644 --- a/MVMCoreUI/Templates/StackPageTemplateModel.swift +++ b/MVMCoreUI/Templates/StackPageTemplateModel.swift @@ -30,14 +30,14 @@ import Foundation case screenHeading case header case footer - case moleculeStack + case stack case isAtomicTabs } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) pageType = try typeContainer.decode(String.self, forKey: .pageType) - moleculeStack = try typeContainer.decode(MoleculeStackModel.self, forKey: .moleculeStack) + moleculeStack = try typeContainer.decode(MoleculeStackModel.self, forKey: .stack) screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) isAtomicTabs = try typeContainer.decodeIfPresent(Bool.self, forKey: .isAtomicTabs) header = try typeContainer.decodeMoleculeIfPresent(codingKey: .header) @@ -47,7 +47,7 @@ import Foundation public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(pageType, forKey: .pageType) - try container.encode(moleculeStack, forKey: .moleculeStack) + try container.encode(moleculeStack, forKey: .stack) try container.encodeIfPresent(screenHeading, forKey: .screenHeading) try container.encodeIfPresent(isAtomicTabs, forKey: .isAtomicTabs) try container.encodeModelIfPresent(header, forKey: .header) diff --git a/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift b/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift index fa7548f3..689a7c62 100644 --- a/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift +++ b/MVMCoreUI/Templates/ThreeLayerPageTemplateModel.swift @@ -41,7 +41,7 @@ import Foundation screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) isAtomicTabs = try typeContainer.decodeIfPresent(Bool.self, forKey: .isAtomicTabs) header = try typeContainer.decodeMoleculeIfPresent(codingKey: .header) - header = try typeContainer.decodeMoleculeIfPresent(codingKey: .middle) + middle = try typeContainer.decodeMoleculeIfPresent(codingKey: .middle) footer = try typeContainer.decodeMoleculeIfPresent(codingKey: .footer) }