From 72f1d48eebe4196e51e15cbf0c185d55101a1135 Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Mon, 11 May 2020 11:52:18 -0400 Subject: [PATCH 01/43] page behavior protocol --- MVMCoreUI.xcodeproj/project.pbxproj | 18 ++++- MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 3 + .../TemplateModelProtocol.swift | 2 +- .../Atomic/Templates/TemplateModel.swift | 6 +- .../MVMControllerModelProtocol.swift | 2 +- .../BaseControllers/ViewController.swift | 17 +++++ MVMCoreUI/Behaviors/PageBehavior.swift | 44 ++++++++++++ .../ScreenBrightnessModifierBehavior.swift | 70 +++++++++++++++++++ 8 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 MVMCoreUI/Behaviors/PageBehavior.swift create mode 100644 MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 3915b055..6bae9611 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -108,6 +108,8 @@ 0AE98BB523FF18D2004C5109 /* Arrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB423FF18D2004C5109 /* Arrow.swift */; }; 0AE98BB723FF18E9004C5109 /* ArrowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE98BB623FF18E9004C5109 /* ArrowModel.swift */; }; 279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279B1568242BBC2F00921D6C /* ActionModelAdapter.swift */; }; + 27F973532466074500CAB5C5 /* PageBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F973522466074500CAB5C5 /* PageBehavior.swift */; }; + 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */; }; 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */; }; 31BE15CC23D8924D00452370 /* CheckboxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15CA23D8924C00452370 /* CheckboxModel.swift */; }; 522679C123FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */; }; @@ -519,6 +521,8 @@ 0AE98BB423FF18D2004C5109 /* Arrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arrow.swift; sourceTree = ""; }; 0AE98BB623FF18E9004C5109 /* ArrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowModel.swift; sourceTree = ""; }; 279B1568242BBC2F00921D6C /* ActionModelAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionModelAdapter.swift; sourceTree = ""; }; + 27F973522466074500CAB5C5 /* PageBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehavior.swift; sourceTree = ""; }; + 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenBrightnessModifierBehavior.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 = ""; }; 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinks.swift; sourceTree = ""; }; @@ -948,6 +952,15 @@ path = Adapters; sourceTree = ""; }; + 27F973512466071600CAB5C5 /* Behaviors */ = { + isa = PBXGroup; + children = ( + 27F973522466074500CAB5C5 /* PageBehavior.swift */, + 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */, + ); + path = Behaviors; + sourceTree = ""; + }; 5206F150241144A900658DC5 /* Headers */ = { isa = PBXGroup; children = ( @@ -1383,6 +1396,7 @@ D29DF0CE21E404D4003B2FB9 /* MVMCoreUI */ = { isa = PBXGroup; children = ( + 27F973512466071600CAB5C5 /* Behaviors */, D2C78CD324252F4E00B69FDE /* Atomic */, 012A88EF23985E0100FE3DA1 /* CustomPrimitives */, D2B18B7D236090D500A9AEDC /* BaseClasses */, @@ -2103,7 +2117,7 @@ 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */, 011B58F023A2AA980085F53C /* ListItemModelProtocol.swift in Sources */, D22479962316AF6E003FCCF9 /* HeadlineBodyLink.swift in Sources */, - 8DE5BECD2456F7A200772E76 /* ListTwoColumnDropdownSelectorsModel.swift in Sources */, + 8DE5BECD2456F7A200772E76 /* ListTwoColumnDropdownSelectorsModel.swift in Sources */, 0A41BA7F23453A6400D4C0BC /* TextEntryField.swift in Sources */, BB55B51D244482C1002001AD /* ListRightVariablePriceChangeBodyText.swift in Sources */, 017BEB382360C6AC0024EF95 /* RadioButtonLabel.swift in Sources */, @@ -2128,6 +2142,7 @@ AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, + 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */, D2A5146B2214905000345BFB /* ThreeLayerViewController.swift in Sources */, 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */, 8D24041523E7FC0B009E23BE /* ListLeftVariableIconWithRightCaretModel.swift in Sources */, @@ -2220,6 +2235,7 @@ D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, + 27F973532466074500CAB5C5 /* PageBehavior.swift in Sources */, 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */, D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */, D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */, diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index a9e86d50..a77b50f4 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -198,6 +198,9 @@ import Foundation try? ModelRegistry.register(ActionTopAlertModel.self) try? ModelRegistry.register(ActionCollapseNotificationModel.self) try? ModelRegistry.register(ActionOpenPanelModel.self) + + // Behaviors + try? ModelRegistry.register(ScreenBrightnessModifierBehavior.self) } /// Convenience function to get required modules for a give model diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift index 3ed37d6b..f34c4ebd 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift @@ -9,7 +9,7 @@ import Foundation -public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol { +public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, PageBehaviorsTemplateProtocol { var template: String { get } } diff --git a/MVMCoreUI/Atomic/Templates/TemplateModel.swift b/MVMCoreUI/Atomic/Templates/TemplateModel.swift index 86da1981..864c2bf8 100644 --- a/MVMCoreUI/Atomic/Templates/TemplateModel.swift +++ b/MVMCoreUI/Atomic/Templates/TemplateModel.swift @@ -9,6 +9,7 @@ import Foundation @objcMembers public class TemplateModel: MVMControllerModelProtocol { + public class var identifier: String { return "" } @@ -21,7 +22,8 @@ import Foundation public var screenHeading: String? public var navigationItem: (NavigationItemModelProtocol & MoleculeModelProtocol)? public var formRules: [FormGroupRule]? - + public var behaviors: [PageBehaviorProtocol]? + public init(pageType: String) { self.pageType = pageType } @@ -32,6 +34,7 @@ import Foundation case screenHeading case backgroundColor case formRules + case behaviors case navigationItem } @@ -41,6 +44,7 @@ import Foundation screenHeading = try typeContainer.decodeIfPresent(String.self, forKey: .screenHeading) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) formRules = try typeContainer.decodeIfPresent([FormGroupRule].self, forKey: .formRules) + behaviors = try typeContainer.decodeModelsIfPresent(codingKey: .behaviors) navigationItem = try typeContainer.decodeModelIfPresent(codingKey: .navigationItem) } diff --git a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift index 579a612e..4ec8ed2c 100644 --- a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift +++ b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift @@ -8,6 +8,6 @@ import Foundation -public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol { +public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorsTemplateProtocol { } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 75cb897c..28aacc6e 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -306,6 +306,18 @@ import UIKit MVMCoreUISession.sharedGlobal()?.currentPageType = pageType MVMCoreUILoggingHandler.shared()?.defaultLogPageState(forController: self) } + + executeBehaviors { (behavior: PageVisibilityBehavior) in + behavior.onPageShown() + } + } + + open override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + executeBehaviors { (behavior: PageVisibilityBehavior) in + behavior.onPageHidden() + } } deinit { @@ -420,4 +432,9 @@ import UIKit selectedField = nil } } + + // MARK: - Behavior Execution + func executeBehaviors(_ behaviorBlock:(_ behavior:T)->Void) { + pageModel?.behaviors?.compactMap({ $0 as? T }).forEach({ behaviorBlock($0) }) + } } diff --git a/MVMCoreUI/Behaviors/PageBehavior.swift b/MVMCoreUI/Behaviors/PageBehavior.swift new file mode 100644 index 00000000..d8fd99a3 --- /dev/null +++ b/MVMCoreUI/Behaviors/PageBehavior.swift @@ -0,0 +1,44 @@ +// +// PageBehaviors.swift +// MVMCoreUI +// +// Created by Kyle on 5/8/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol PageBehaviorProtocol: ModelProtocol { + + // The type of rule + var behaviorName: String { get } + +} + +public extension PageBehaviorProtocol { + + var behaviorName: String { + get { return Self.identifier } + } + + static var categoryCodingKey: String { + return "behaviorName" + } + + static var categoryName: String { + return "\(PageBehaviorProtocol.self)" + } +} + +public protocol PageVisibilityBehavior: PageBehaviorProtocol { + + func onPageShown() + func onPageHidden() + +} + +public protocol PageBehaviorsTemplateProtocol { + + var behaviors: [PageBehaviorProtocol]? { get } + +} diff --git a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift new file mode 100644 index 00000000..422222b6 --- /dev/null +++ b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift @@ -0,0 +1,70 @@ +// +// ScreenBrightnessModifierBehavior.swift +// MVMCoreUI +// +// Created by Kyle on 5/9/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { + + static var identifier = "screenBrightnessModifier" + + @Clamping(range: 0...1) var screenBrightness: CGFloat + + var originalScreenBrightness: CGFloat? + + //MARK:- PageVisibilityBehavior + + func onPageShown() { + changeScreenBrightness() + } + + func onPageHidden() { + restoreScreenBrightness() + } + + //MARK:- Behavior + + func changeScreenBrightness() { + if originalScreenBrightness == nil { + originalScreenBrightness = UIScreen.main.brightness + UIScreen.main.brightness = screenBrightness + NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.willResignActiveNotification, object: nil) + } + } + + func restoreScreenBrightness() { + if let originalScreenBrightness = originalScreenBrightness { + UIScreen.main.brightness = originalScreenBrightness + self.originalScreenBrightness = nil + NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) + } + } + + @objc func didEnterBackground() { + restoreScreenBrightness() + NotificationCenter.default.addObserver(self, selector: #selector(didEnterForeground), name: UIApplication.didBecomeActiveNotification, object: nil) + } + + @objc func didEnterForeground() { + NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) + changeScreenBrightness() + } + + //MARK:- Codable + + enum CodingKeys: String, CodingKey { + case screenBrightness + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + screenBrightness = try typeContainer.decode(CGFloat.self, forKey: .screenBrightness) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(screenBrightness, forKey: .screenBrightness) + } +} From a19003f240194a90358ee11ed6952ff4fd86c269 Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Mon, 11 May 2020 12:04:06 -0400 Subject: [PATCH 02/43] privatize coding keys --- MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift index 422222b6..b6712354 100644 --- a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift +++ b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift @@ -54,7 +54,7 @@ class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { //MARK:- Codable - enum CodingKeys: String, CodingKey { + private enum CodingKeys: String, CodingKey { case screenBrightness } From 3cbd9b53a8646c38720573fd0a893652a9c60b99 Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Tue, 12 May 2020 18:56:10 +0530 Subject: [PATCH 03/43] 18972(iOS - List - ProgressBar - Thin) story initial commit. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 ++ MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 1 + .../List/ListProgressBarThin.swift | 89 +++++++++++++++++++ .../List/ListProgressBarThinModel.swift | 69 ++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift create mode 100644 MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index ec7e68b0..35697500 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -196,6 +196,8 @@ AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */; }; AAA74A172410C04600080241 /* HeadersH2NoButtonsBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */; }; AAA74A192410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */; }; + AAB7EDEF246ADA1600E54929 /* ListProgressBarThinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB7EDEE246ADA1600E54929 /* ListProgressBarThinModel.swift */; }; + AAB7EDF1246ADA2A00E54929 /* ListProgressBarThin.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB7EDF0246ADA2A00E54929 /* ListProgressBarThin.swift */; }; AAB9C10824346F4B00151545 /* RadioSwatches.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB9C10724346F4B00151545 /* RadioSwatches.swift */; }; AAB9C10A243496DD00151545 /* RadioSwatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB9C109243496DD00151545 /* RadioSwatch.swift */; }; AAC6F167243332E400F295C1 /* RadioSwatchesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */; }; @@ -607,6 +609,8 @@ AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchCollectionViewCell.swift; sourceTree = ""; }; AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2NoButtonsBodyText.swift; sourceTree = ""; }; AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2NoButtonsBodyTextModel.swift; sourceTree = ""; }; + AAB7EDEE246ADA1600E54929 /* ListProgressBarThinModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListProgressBarThinModel.swift; sourceTree = ""; }; + AAB7EDF0246ADA2A00E54929 /* ListProgressBarThin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListProgressBarThin.swift; sourceTree = ""; }; AAB9C10724346F4B00151545 /* RadioSwatches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatches.swift; sourceTree = ""; }; AAB9C109243496DD00151545 /* RadioSwatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatch.swift; sourceTree = ""; }; AAC6F166243332E400F295C1 /* RadioSwatchesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchesModel.swift; sourceTree = ""; }; @@ -1240,6 +1244,8 @@ D20492F12434CB5F00A5EED6 /* FourColumn */, AA4FC2A323F4F69600E251DB /* RightVariable */, D22B38EB23F4E0AE00490EF6 /* LeftVariable */, + AAB7EDEE246ADA1600E54929 /* ListProgressBarThinModel.swift */, + AAB7EDF0246ADA2A00E54929 /* ListProgressBarThin.swift */, ); path = List; sourceTree = ""; @@ -2180,6 +2186,7 @@ C695A68123C9830D00BFB94E /* NumberedListModel.swift in Sources */, 01EB3684236097C0006832FA /* MoleculeModelProtocol.swift in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, + AAB7EDF1246ADA2A00E54929 /* ListProgressBarThin.swift in Sources */, 8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */, D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, @@ -2264,6 +2271,7 @@ EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */, 0105618D224BBE7700E1557D /* FormValidator.swift in Sources */, 01509D912327ECE600EF99AA /* CornerLabels.swift in Sources */, + AAB7EDEF246ADA1600E54929 /* ListProgressBarThinModel.swift in Sources */, D21B7F75243BAC8900051ABF /* CarouselItem.swift in Sources */, C695A69823C990C200BFB94E /* DoughnutChartView.swift in Sources */, 8D3BA9BD2433787000D341BA /* ListThreeColumnInternationalDataDividerModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index a9e86d50..cb108207 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -162,6 +162,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnInternationalData.self, viewModelClass: ListThreeColumnInternationalDataModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListThreeColumnDataUsage.self, viewModelClass: ListThreeColumnDataUsageModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListFourColumnDataUsageListItem.self, viewModelClass: ListFourColumnDataUsageListItemModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListProgressBarThin.self, viewModelClass: ListProgressBarThinModel.self) // Designed Section Dividers MoleculeObjectMapping.shared()?.register(viewClass: ListFourColumnDataUsageDivider.self, viewModelClass: ListFourColumnDataUsageDividerModel.self) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift new file mode 100644 index 00000000..69e26ea2 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift @@ -0,0 +1,89 @@ +// +// ListProgressBarThin.swift +// MVMCoreUI +// +// Created by Lekshmi S on 12/05/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers open class ListProgressBarThin: TableViewCell { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + let progressBar = ProgressBar() + let leftHeadline = Label.commonLabelB1(true) + let leftBody = Label.commonLabelB2(true) + let leftBody2 = Label.commonLabelB2(true) + let bar = Line() + let rightLabel = Label.commonLabelB2(true) + public var horizontalStack: Stack + public var verticalStack: Stack + public var stack: Stack + + //------------------------------------------------------ + // MARK: - Initializers + //------------------------------------------------------ + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + verticalStack = Stack.createStack(with: [(view: leftHeadline, model: StackItemModel(horizontalAlignment: .leading)), + (view: leftBody, model: StackItemModel(horizontalAlignment: .leading)), + (view: leftBody2, model: StackItemModel(horizontalAlignment: .leading))], + axis: .vertical, spacing: 0) + horizontalStack = Stack.createStack(with: [(view: verticalStack, model: StackItemModel(horizontalAlignment: .leading, verticalAlignment: .leading)), + (view: bar, model: StackItemModel(horizontalAlignment: .fill)), (view: rightLabel, model: StackItemModel(spacing: 5, horizontalAlignment: .fill))], + axis: .horizontal) + stack = Stack.createStack(with: [(view: horizontalStack, model: StackItemModel(horizontalAlignment: .fill)), (view: progressBar, model: StackItemModel(spacing: 20, horizontalAlignment: .fill))], axis: .vertical) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func alignAccessoryToHero() -> CGPoint? { + let heroCenter = super.alignAccessoryToHero() + return heroCenter + } + + //------------------------------------------------------- + // MARK: - View Lifecycle + //------------------------------------------------------- + open override func setupView() { + super.setupView() + bar.widthAnchor.constraint(equalToConstant: 20).isActive = true + rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) + rightLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .horizontal) + rightLabel.numberOfLines = 1 + addMolecule(stack) + stack.restack() + horizontalStack.restack() + verticalStack.restack() + } + + //------------------------------------------------------ + // MARK: - Molecule + //------------------------------------------------------ + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListProgressBarThinModel else { return } + verticalStack.updateContainedMolecules(with: [model.leftHeadline, + model.leftBody, + model.leftBody2],delegateObject, additionalData) + progressBar.set(with: model.progressBar, delegateObject, additionalData) + bar.set(with: model.bar, delegateObject, additionalData) + rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 120 + } + + open override func reset() { + super.reset() + leftHeadline.styleB1(true) + leftBody.styleB2(true) + leftBody2.styleB2(true) + rightLabel.styleB2(true) + bar.setStyle(.medium) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift new file mode 100644 index 00000000..2e0b7cdb --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift @@ -0,0 +1,69 @@ +// +// ListProgressBarThinModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 12/05/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { + public static var identifier = "listPrgBarThin" + public var progressBar: ProgressBarModel + public var leftHeadline: LabelModel? + public var leftBody: LabelModel? + public var leftBody2: LabelModel? + public var bar: LineModel + public var rightLabel: LabelModel + + public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel, leftBody2: LabelModel, bar: LineModel, rightLabel: LabelModel) { + self.progressBar = progressBar + self.leftHeadline = leftHeadline + self.leftBody = leftBody + self.leftBody2 = leftBody2 + self.bar = bar + self.rightLabel = rightLabel + super.init() + } + + override public func setDefaults() { + super.setDefaults() + bar.type = .medium + if bar.backgroundColor == nil { + bar.backgroundColor = Color(uiColor: .gray) + } + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case progressBar + case leftHeadline + case leftBody + case leftBody2 + case line + case rightLabel + } + + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + progressBar = try typeContainer.decode(ProgressBarModel.self, forKey:.progressBar) + leftHeadline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftHeadline) + leftBody = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftBody) + leftBody2 = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftBody2) + bar = try typeContainer.decode(LineModel.self, forKey: .line) + rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(progressBar, forKey: .progressBar) + try container.encodeIfPresent(leftHeadline, forKey: .leftHeadline) + try container.encodeIfPresent(leftBody, forKey: .leftBody) + try container.encodeIfPresent(leftBody2, forKey: .leftBody2) + try container.encode(bar, forKey: .line) + try container.encode(rightLabel, forKey: .rightLabel) + } +} From b4f8e4281759dedb60e62f2bc62c44128baf491d Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Tue, 12 May 2020 16:06:22 -0400 Subject: [PATCH 04/43] code review items --- .../ScreenBrightnessModifierBehavior.swift | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift index b6712354..03464841 100644 --- a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift +++ b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift @@ -6,9 +6,9 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { +public class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { - static var identifier = "screenBrightnessModifier" + public static var identifier = "screenBrightnessModifier" @Clamping(range: 0...1) var screenBrightness: CGFloat @@ -16,38 +16,36 @@ class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { //MARK:- PageVisibilityBehavior - func onPageShown() { + public func onPageShown() { changeScreenBrightness() } - func onPageHidden() { + public func onPageHidden() { restoreScreenBrightness() } //MARK:- Behavior func changeScreenBrightness() { - if originalScreenBrightness == nil { - originalScreenBrightness = UIScreen.main.brightness - UIScreen.main.brightness = screenBrightness - NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.willResignActiveNotification, object: nil) - } + if originalScreenBrightness != nil { return } + originalScreenBrightness = UIScreen.main.brightness + UIScreen.main.brightness = screenBrightness + NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil) } func restoreScreenBrightness() { - if let originalScreenBrightness = originalScreenBrightness { - UIScreen.main.brightness = originalScreenBrightness - self.originalScreenBrightness = nil - NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) - } + guard let originalScreenBrightness = originalScreenBrightness else { return } + UIScreen.main.brightness = originalScreenBrightness + self.originalScreenBrightness = nil + NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) } - @objc func didEnterBackground() { + @objc func willResignActive() { restoreScreenBrightness() - NotificationCenter.default.addObserver(self, selector: #selector(didEnterForeground), name: UIApplication.didBecomeActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) } - @objc func didEnterForeground() { + @objc func didBecomeActive() { NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) changeScreenBrightness() } From 233a0ae71b1b3883a6aff494a345bd5ba00b8a52 Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Tue, 12 May 2020 16:07:03 -0400 Subject: [PATCH 05/43] code review --- .../Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift index f34c4ebd..3ed37d6b 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift @@ -9,7 +9,7 @@ import Foundation -public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol, PageBehaviorsTemplateProtocol { +public protocol TemplateModelProtocol: PageModelProtocol, ModelProtocol { var template: String { get } } From a47bb11bb6a7344fd1ac9291f8f079aee1b955ed Mon Sep 17 00:00:00 2001 From: "Xinlei(Ryan) Pan" Date: Tue, 12 May 2020 16:59:19 -0400 Subject: [PATCH 06/43] remove NHaasGroteskDSStd font --- MVMCoreUI.xcodeproj/project.pbxproj | 8 -------- .../Fonts/NHaasGroteskDSStd-55Rg.otf | Bin 75048 -> 0 bytes .../Fonts/NHaasGroteskDSStd-75Bd.otf | Bin 64188 -> 0 bytes MVMCoreUI/Utility/MFFonts.h | 3 --- MVMCoreUI/Utility/MFFonts.m | 7 ------- 5 files changed, 18 deletions(-) delete mode 100644 MVMCoreUI/SupportingFiles/Fonts/NHaasGroteskDSStd-55Rg.otf delete mode 100644 MVMCoreUI/SupportingFiles/Fonts/NHaasGroteskDSStd-75Bd.otf diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 1b66b097..ef7059f0 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -146,8 +146,6 @@ 8DE5BECF2456F7B100772E76 /* ListTwoColumnDropdownSelectors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DE5BECE2456F7B100772E76 /* ListTwoColumnDropdownSelectors.swift */; }; 8DEFA95C243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DEFA95B243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift */; }; 8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DEFA95D243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift */; }; - 942C372E241149170066E45E /* NHaasGroteskDSStd-75Bd.otf in Resources */ = {isa = PBXBuildFile; fileRef = 942C372C241149170066E45E /* NHaasGroteskDSStd-75Bd.otf */; }; - 942C372F241149170066E45E /* NHaasGroteskDSStd-55Rg.otf in Resources */ = {isa = PBXBuildFile; fileRef = 942C372D241149170066E45E /* NHaasGroteskDSStd-55Rg.otf */; }; 942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */; }; 942C378E2412F5B60066E45E /* ModalMoleculeStackTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 942C378D2412F5B60066E45E /* ModalMoleculeStackTemplate.swift */; }; 9432A79F23DB47BA00719041 /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */; }; @@ -560,8 +558,6 @@ 8DEFA95B243DAC20000D27E5 /* ListThreeColumnDataUsageDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageDividerModel.swift; sourceTree = ""; }; 8DEFA95D243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListThreeColumnDataUsageDivider.swift; sourceTree = ""; }; 9402C34F23A2CEA3004B974C /* LeftRightLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftRightLabelModel.swift; sourceTree = ""; }; - 942C372C241149170066E45E /* NHaasGroteskDSStd-75Bd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NHaasGroteskDSStd-75Bd.otf"; sourceTree = ""; }; - 942C372D241149170066E45E /* NHaasGroteskDSStd-55Rg.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NHaasGroteskDSStd-55Rg.otf"; sourceTree = ""; }; 942C378B2412F4FA0066E45E /* ModalMoleculeListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalMoleculeListTemplate.swift; sourceTree = ""; }; 942C378D2412F5B60066E45E /* ModalMoleculeStackTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalMoleculeStackTemplate.swift; sourceTree = ""; }; 9432A79E23DB47BA00719041 /* EntryFieldContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; @@ -1714,8 +1710,6 @@ 94CA227924058533002D6750 /* VerizonNHGeDS-Regular.otf */, 94CA227824058533002D6750 /* VerizonNHGeTX-Bold.otf */, 94CA227B24058533002D6750 /* VerizonNHGeTX-Regular.otf */, - 942C372D241149170066E45E /* NHaasGroteskDSStd-55Rg.otf */, - 942C372C241149170066E45E /* NHaasGroteskDSStd-75Bd.otf */, D29DF31721ECECC0003B2FB9 /* OCRAExtended.ttf */, ); path = Fonts; @@ -1902,8 +1896,6 @@ D29DF32C21EE8736003B2FB9 /* Localizable.strings in Resources */, 94CA227D24058534002D6750 /* VerizonNHGeDS-Regular.otf in Resources */, D29DF32E21EE8C3D003B2FB9 /* Media.xcassets in Resources */, - 942C372E241149170066E45E /* NHaasGroteskDSStd-75Bd.otf in Resources */, - 942C372F241149170066E45E /* NHaasGroteskDSStd-55Rg.otf in Resources */, 94CA227E24058534002D6750 /* VerizonNHGeDS-Bold.otf in Resources */, D287651A245B338E00CB882D /* VerizonNHGeTX-Regular.otf in Resources */, D29DF31B21ECECC0003B2FB9 /* OCRAExtended.ttf in Resources */, diff --git a/MVMCoreUI/SupportingFiles/Fonts/NHaasGroteskDSStd-55Rg.otf b/MVMCoreUI/SupportingFiles/Fonts/NHaasGroteskDSStd-55Rg.otf deleted file mode 100644 index f56ed7aaf89f16bad53d72034e0002639d48364e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75048 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

_D2qW`Ep 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

3x1o+N6#i zy<>+(wvI_YIMEC1T#y$Ar@L`3i~@F(hNz!u7R_HiKiPBBs-Zo44H_7u_l4mV!@%Lo zh8Pp#y<4e{?cII!Xvgnbg?H#29-i9eD69wemaxrE>N`}>(;iCK5-@^SiRSVei#SIW zCM_{s`}+Ho`yLMNhe^R7 z8o~-!%h866J!^KaM}w*###p^7%=i6r=G48fyenzqrwka}-Lq@_vb9t2FJRUWEr0fO z*t9r#)i#eyjdxXo5)jBD-4*N<{AsSBPtqRW^3|biAz;4W=x*V#+?N>B(W4Jp?$h7d zyJ6RQn1O@=EYU@eGn)4`$x}1gktkZPwv?%fpIK!(4kk_Sc`4+qTtly?PBG^O($% zUn{caG&$K=L(nfxIfTg(Y+EdIUaNm_sPFpji@j%P^%>s{Na?r~{ayV_0i8c*GYoBs zt|V=G!~Pu$)~)kimArV%c@Oh9QnlB!wc-7yxAcymy_-eA@fYH3`8=(ZE zJNKupyR+HO-4?WQH>C@<0i4GI*>9rP1!Jd%ShMVe^Q|y(CH-jLPuA6((2pwsP1i+K zVg@(~^S52FNbn|Rkf7hVYDG%Q%E5i~EWIPrx2E(*SbEx&bx_a`H1aU9xqRpg(;V?J zENosj{&IC&cUYDjIDGjKzg*o8*ssP#g5Am}H=C<> zf(78^biHi|IjE8_S^GNzJ~H4ZR;20MM7 z)%r-I1CJf0;uP2#n=3e=^IraiJ|h@2c+>Lr*(kT^FN9Jj4zdW=3I%6-Bc` z!FtP1i4K_i=^*cv(0?a5+&5xCUsaE3yp7@)$jnD?=-yZVh`%MeC~v_?8O)UFMa2uU zafS`^j|J!1VvIgcxTu;W=&7wfd6kzNYM9^DW6eh)ZpV&I08*cYp)+;_fbIV$(RyvX zE}VEm=X9UI%o{D5f!C^O^chC13fPM?VaY@<7FF&vw>$#o1zsy)3P)c{ss(vGIFwAN zJ=cF@gc(4or{o1|Z(@Zs*lUw&8T3x6Fg=3lJ4890s=qf)J7vO{sfnIL6X!0~M;jfM z!xA0(EU*}>A0m#v#gn+}ofm%JdRWAlB^`~c{BO}7(+_PqS z%^I8cURX?PVsu9|{ZCABhrX)13MQe>r9 zViA0tgs$fJ01tiok3J?vpJ@uF%T&|`n98N`vr%E@TS7>ap*@S;GoA}CyX)nXq;bvz zOyiWpP2&{Zl9UQwp(qqs;@a=#u3MMAoSj*6*VV`?rUH4W@@gQ}is7hit zvCC}>$*Vc#*qNMZ>{P|rvE1=p&s!SyPnp!EjBBIzl^TEDXY$uCs)R>Wd32#_r*2JK zepz|I(GgL`si$w3J>FZ$sdoLFL0{+0*^;tr$b#(kjk?!sP_d7GPP4}4W;}^#H2t#n zp+|Z1xD654&((QR;qc%)GdG@^Wn3KFL^t2-&dsO2?#CZ(cyw8pn)e?ZbY0awA;x9I z<_ZcW*l5+*wgnJsj%hC%5mKX_OCbuRWT3+q_3=- z>_HKuGtcL$6!=ZVh3;J|BHZLkiAoR% zdy`m!zM5K@2CP#Bs-fN{R)Dtj0H5|t!n#K@VBKhSV=h!A9iYS zqVehCiFY3sD1P*g$F>!U6>G*-upe(2TTK|}P(CHnt?RA~^&eAiW`F(ljy4`4YaY)z z7+5`JaMeLU-!y+%Ep^eQrgQUkF&~cDm_?Qm3BZpVUMV z*mQit$=9v!J-(-zd^>r;vQGBjJXn43(71`my4=6Jxpc;<)s1`HIOW;?z^3ZDD}lZ% z0;X4*w)pXWZP734*0$5`>RF`t{;*4R6CbZQpQhO9az6Z&>#D>KuZ9J8j2|_{ZCUxy zm1kO|kE)eZHfT@tdEDy0!uXD_I<I$b(svwp1&j1GIOIP6?#!0;L&ckY!wUE%ze6(9*y%Qs5Lw&L zU~(oB>zwV|;`2H9B)Cr~J^{A_2l?7K$_9_rQgz^WcaZtQz? zCSc}{kHo`$IhOL z<-b?zNwBr7lCM|z-=#mvuWc1zwcfL5a^X}vWorm}KtEgNw)_Gk6pLD;0 z1=?0PmryxizSAP7y?3H3ZTR`eqtj-TJvP1Qwfe2js~HNJvt|O&3@SFw^Zp)M*Tb`sAc=535^&}}eX^TsZ9g~mjvuKQ)rm-1+Q)3YC$Qnau z(m=WuJZGv+Vcx1BQ=qA8QspH7Nv4nK4VhPLStYbK(;~#F#u9&~LU;{y8dD+Q%w%_y zo0Y$n&h}6tV6?yreif<&*w#NO66onheO^6^I9j)e-_&7?$}KoFZWDjCNRx9Z(<1ua z)sGYYo&Y7x~1)er2Rf!8sd6+i0+2YH--Ef+qTG8hH{SvdM2TtDc%P$MLKhC#a z(5(2y^`)8(-|}78+AqqcXQo~6bbDX9iNU+R-F5wb+uVaYPmKQUy#2Cg3;!s0sYXjr z&%jdR7mZBgRIhROw8(MKmL>0RuUPngyMkpVZt~6S@D9eN&NgEFXRT;8Qxy;d5?V1d zwky7T65!Kfa5sD##HU3(zLga}>}`qlsXZ7UOTouchOrj4x~Yo4U%qJ4Uz1fd$L{__ z3@l1O@9di2q{a^(I0#=@Vc#3^t6{1xqv#*rGrliAND}2UcpyHFGRTKGWMELGN<)VZ zeYcFldC006@%7bn#XlPsy^cK*HQD?#5WU5}lpSlWvCH@C{8FzNs zP^e*C8@G{BX`Ln&xRSkT?SS#KrcX6bQB5^yQY%a-I{)E?r7fCB7kv-hc)q<#4X>|` z*-5XRD@GogI`6>NSZYw$UZtA${wZs0*N9=O?zt6mGL%ZW z-?Z|Wat(ay?!6OLJZjpx=BeH%0#Ap}z8|0C9yPf4-iFcV1BVsZU|M&xe3!6g*EYED z>&IUU{MuCAd4V!$WJvh#&G#yKj=#0ev1PDg$F*{Aitb+9VO2r>`Za}GI8?6KbZvTC zrxKTE?0?a3WSRYZ$Jc(7Bd^Su*eha+d(xpQ&mNUqRWx&U&iJ3!{i{ntrx9JoziF%Q zSi3;?@Ge(Fi$0abe;j`kl)A>6fp}WZB4CR?ZDN& zdRFQc6NTQZw@GX1469te`stF#q)jtt^*MGvvG?V$!sSP}S2UGyEoRX|vG^F(+dMX8 zAPXZ%)QUmzxSCw7`J%UC=}IY;e^W{N5m0Xxx=|){qmq0lF?I*Hzt@rK-@R)9Q-IVV zM)%0qJ9OGF=@E6V3(siI)0loJ!;4}&vy66|8$_?>cBQX-Kjm)A)+8g5H-PZs}+U% zr#>_mx?ZO8?!Yr6lxI^9*&?gJ0OCKP`l?sO=c}scsXqTeG+TaF)%V#E__OguT8DPa zo_kK?U3M0_s|vaKW81C0>xFzZ!CrqM;QDpP^G_5J-Y`(OuC97RmeM<6%(A(A7jqkm zjk>yN;>zyzPQ{lx`?AT&!2>Q729_*cP|19~*+S#fN1y|qj|yi+B_Kl_?El{;U)B-h-<`Su#= zwct2m#*5NLdd;aca`%bf?>_rw{(j?EvE|o&d3In%^5Is`nq4hRz|{)DE|nas}JWR4n?}%GvdrJlt0(xz;m(r9*bQ`IodWXZ+l4 z`OnJ_kNPftX-tz**a|*PWAJ&wfpJlTd|JU7 zicjnLw85t|d$9h$&k%nrT;)4(|8&Uwa6(eu`rl9gF|=#gR=4X_T4#JW+N;D)kLcnh z#vGhdz3T4dzJ-4_r*?>_vnOr$fyt&G4SVnRYfwM#>8NMXkEiTB)_X>jvDMk`?LeQ2Vl5L zU5*Oxsq@fQq1Km7t1~D*GL*NQQ?w1%X68?uQ)(hV&^cC`23wP3z)#Kfrw#bOu%sBqF{n7O8lZr+@` zhcJ8>C%QR6XU|J95{0_bE+W$~1aWc0$uP zV~n$Qp15DH*ZyzzE?!(Qw1fFy-wMa3KYp6sBy-EE?3!mp|9;80M#r3vt=Qsp-*&m) z&AY2^c#N{&dhYn6cDXmJIJ)t<*J@>AT3&ga=weG{+#4^A|57*o!B`HJ&xeQIuWDCmF9ugCEd zM=nRX4h^jFbKMy~j$b!>NNdI86*(&>{L(4y*$;>2OmOMZ>tyk?Ll?@ox^%}dFL?zi zuyOo|{E}<@#__QTiYDgi^;srkKQIMsQ?DJ-X|+yWqXPALf3!U6FYk@ouOOSl_4BK4 zs$MyuO6AD+>aX#s#lC}E)$40+6z&;1al9seT&FZ!bs<+l!QUF(Qr*~nv8Xy}m)~B` zTPZu$Vf~jlsDHk3pnhiHq901_?XS*qso}bG#+}l0bC0)acBl086Qx@B8XG!Lzc#Da z*WGWe8sF$z+sQE}_(wAy$B%qEKH8j-(`w_q8hK=Ia&dnn~EUv>P9{e55gBt5Tr0pZ2i!q^_{8H#Ql~(yh~7#OEmQ=pO1G>7MCwbg%6c zb~-x;I~Qy*^1+8GE89idePg%GF2nA3Aw=jWEY)lD;ha6^4Eng{W^r!0*_=C9kn_mR z;XFBk?~S`}ZWiA!H=FOz732rx=J4@|%X9AdjJ+G@gm})m8CVxla|Wb$MS6l!7~$@@ zw*e=c^UTfU0&@>>5r`MbsreYd=$)Iv_klkau=@fw!H+|D|J>WiBb$#$8aqIj06mjI z4MYwR_#c@o0fGbwnUs<!lri)S$4hrr(*_fy@A&YYaGy(Ah#3bA@8E&rl?+!dX@Fdtp;g{jdICe+)fZQ|-l-r!% zl20}uWHSivgc(bj4XiQ|JJS*y)Ud=R$`fXC`F-$|^B@ef^Dy*)%nG2uZBXD6I9dP| zQc!ntY6D`r0uwjDFN`pE(8WqoFHq2%a6lSglr9+05Ino14x{mmL)rnTp#-FT%BV`T zut%sP@RE5{t}CKPVL&2Wi6(N%a`Nz{dW*xm{JMJpE|+WkwxyIrhf9_k&M%$Jk1EC$ zpc!~df~@4AMr2AtYEcU0hd0^aHb^VDEgpFlVrfV|7ID&%K#dUmk3cw5Gd?N=+Gn8NTtI&{QXIF?Gz+pJ zAXScqw%L%AOr*?2%1fXK_zXEDfuaQf0epfG8A2)`xFF9Qq)tbkIjET&izMe@u~f}! zMCM-yq$JJ{#FOMb7%3tF^C|oefSV4?2&)v-ayl?_0X78z?>4Z3cF4o#Hn7P6HYgL~ z>yh#S>Ikq=hN5`-;tqm8gq0x;_=Cf9Q;~vb?1b3P2r&RpSsUhnCvJo4m%wqv?GJ!q z3WE>~?E-B9E%5-;7(>kDpBT8KqlEE|#qp}OD7n@kfiAe}*HU#vE0}Lh= z@GF52;X#}y;h!rY%eqTqn*27X@&LHs24zU2)7uA(G9LI(ZG?vLvH>)Q7GiuHi&FGw zTp?>ilB}FOxkX;6XW}7Q9;mF42~bR8<#b`v&vYPS%Js1Vx!$&D0Mgu9OlD$;`@!)f zH*y`UfR?_EI>@qU=`0432YuAw8zkDi2w~`In83NTWQeS%Af9_v)l@!1x8$fFO<8HcSIZIg!alAewW>eN7TB42obsXe^CNAg zELosXAF4~hqTWz``=BGXs zuma`s+s}`*nU>hHw0yYz{5RW1)D`u0!0EuHFEm;|ltPvwmpp1a6NlxspGCo=R;qoV zeFQ{m)8u+%vI9T(LY5ukkG%Ht1MQ}zgxQ>;YCQG4t9b<}xOQAq^!jswd-o0^*roN^d80@^C*!`zJAOk5ec4}i%f$e9bc z9=*HV+rWz6(3AN-AA-Q^5Lz*TasU>TYYu$bmMatftlW*j=>cd+trVjsQ4jy=rv}%4 zmLIUeMW8{S2f*?J^#rY`27ZJMjRq;MO)Ox>!YKTo{N<+LN=5CUze^YT2?FSL$a1CO z%`VUkVaOGA4O(Ub%S#BSI>m=QiN>GhhuT3N$3ZWa1L%VvI4qrEkpXT>2Sz!dn#^Oj zbJI}!99+b-rurr4Htd}6bK~s$VgksF3ZHy5`9z-!3h@K!=isK=Wk&Kn*Aqm zqcZ2WhSWxq)+|IJsZjryFk}2pJq~#+f+EN@ zWpQm(A2$5T~oD zRmxq1UhN#Y=Ogo)r8eKvV(4=pxAZkwICO=YEB!f0fzgH~XZSw3-L$Pii^O~mi=peKVw@&dXtf4XfyQx5w?m(AX>eDf0!mGC33hfA~Gpag}XZqd7> zI%eV4F{cGHmWGv*eEC1Q#^v(ohkm+`<@jNakXLs|Hsx7JE6}(9ao|CQ7sJ~^Gn62I z?EX%CE0_;FKRxCL690Qaw)OvMDN!?T^*Q1!6h|F(v4;9Kb_= zGk8SJWa<`$Vl1L1(!mR|^)D0%@4W zll}MyI8;9NhBPy+fMTtgLalK={Ym(+oaMHXT10{@Q%J^wR;aPMNtn`G8EZ>C(!hDS zq8!wUvJlez1c}9`bT;klr(uV7d<}S%))2Agi}r0MP*Q{!#Em&7q@|JxNby#W>CbSI-=Tiipa*q`U=eP)Qvf9m zA@a2XZ)T!~NrMvnrO4qpt`vk{LjQ~EidNX=A7Qo&scBab#?(}QSYuN|(+bpAlgHP8 zjT4~A5J{_H_XDK>9q37G#Fh(Ta{tY?uR(hyAiV^^ww5R@ODAg_($uolGw!$K_9=*c zdK|g$NK!24{1-gK>XT%H`j@23|04b;B9f=OY~mM09@H)ol^88qjxxng|H?-Y@MGH5QUfx7 z*tk|0R^CCLtge`jsb+a=+Y(t5;AzzyP!Yqb_ww+8%(5~9XMuV?3?sE%o0ihQ3;56e=fedbVV>a6e=--!X{;$} zS0(#>G5|~j%~l|I~k_+Rb0PBx(5{#mu&BvfrW&Y+j)DX*S5B#o%Xa{U6uttSu*f#@c&G zENSS!rO#}TAT5Pmj|>xQUojhXL?ID5*!lTbW8~>G1Td}fj>cf^G}}vw{uH$V zfBGYRgA`0Qt$A6WfX{l`?*kyJS+yqKT4j^%MU-PR*`z+{JJ{L6lG`}YkNULOHHEm~ z6wpHMv*qn`0e-}5a@%PG0NR@HU^^j^hT3+yRkn%qPkLo6m`j?P)tT&mC>N%`sDCE+ z2>z-5nFXG0jsHsBF$iKYEd|X&d?RN;yIVh$S-zYy*hF>Wsf}zNMCN|X2wPKv0(pLG z*njRxoNna`hArFsOd8ml*zm+h(*0F1Q9|x>lKcQqxQqjtOS87n ze~4okXAcY$FggQ?9I^v9`GbGZ0^O%(3RE!r5-OC7PR;W;&+LQ~XfD7(4-A5VSqLtFE|lvC zOuFG}#`VV490s6cxfZ}8j{6c=BycUck+@m`pM_jF=(7y(leiS%xSc!3jR%d+ar3#` z+(T|D_Y1CM?h*HxTg~O*TFWbV4Y!WbZ8Kk&59hYbUt9Rb$y+)-es!ijI3 zieoU17L%r3aC&ObyW@2ugNr~mI0~Gp=LpmP_8alk822D1Q!OkA+APv=LTxD z1Qo);Ev-?5ZMe3KYh(-xSP`*2zreFT^S{4S#?=z1f#q2kxaVJGQHrvbi%LOP8QieD zY`I_n2c?iN>J>SGNAs^xlp@q}Q4Q0@u`)D5Ip}JFJKS=$LCM;{Uje0+e`S$203{{* zw6%nN>`@!F(;U3g%;L)N8skwP^6;TwlMe+VtgZ#qkdM6o=Y##<0~rKqeD^91TGRqH z>agp*B$j|LII8dbUrBg0xc!hrFz6Nn z90E~8b-{f#!RO2jJmL~3^LUa((@%bqIVAZ<|LG;fycUBL+t!25WEJZDs4zH^MciW4s5y%&nf-a7g!5Uf|Z+$G6C*FDDEyrTv0oY`n zq%fn8TtW){@UH-Bhjh5C$w@EK&lz=C2=y!f-sYv^me6+}UeIIm_3oSh+<#jdTKy`} z!U@!HVAscRF@lqAf1%*{29W<6&^?Wr#;nDa#6>(Ra9#Lx&eqnEKWL60Y#ouNr25Zq zD+?ia7tpw{O&qd-qy`V$f&UADR~7X!owMgSchPP43=xU{7uXb5nZsi|fnn zX72u|_^#c#qs)B}8`G;RuVAjO-{8LSyaRJx;^KSs<2{(`GY~NOlFThXaG=SL_h)V( zTz@{8xpmAzut;FzIy1lQIxs)EvX9y%qP+&? z)S;y8d_AMjU%ls<_OQCt`{t=<3)jiUEzJC~OMJ_-R+R^($uGNZ%rCnn%RG~4o^dw0 zMWBuPaYMM#+$3%mw*YPb8hku&H+PUbiJr+-?hf}e_XO?yAH0Tl;9b!>DG94mb@)ao z6+TnJ7v_Wb4*W=dDWA$`@GljG6lE246&)0V6w?$*ik*tniW`b7#cQR#(nCoWA%KBW zXL&kVo?ez`dCRk=<=M>g>}Gi;Se~;j&kdI6Nz3yG#$UYh7D|bHRAH89eao}1W=EUTBG(*S5((gw~^oFJ>ya5di9VGT>Uv- z-5>Bd#6T|UW{4O5fvXvdA*lV|yU;$U%M$<#1lS-_JH2<=v-ftY=kHvw%Av~2PpwMl z>{OTExzbr$9c+TxUa+~LO0h{fkJG6Zzje)H>9nfRHdg{tCD^3u#A#JMY_3i$jYicd z??U~k>e{3LKdAikE-E>6hRVZop~Y3ntu3Rc@(;YT{NSg!D&-S){Q{o*B_FQxGTz)^ z*JTz@sXU1bwT_#9GQ2IEQn?e?78asVu2e2XjHNa)W^fAS9OVr5o>z{<+mSYJ`{92- z%eyG}yWqwXA!G>y9H4B8yTe-{A%NII+32mX^1!1euBvZ^x}ppv+2vy!t^gI34wg&z z)|-c%M)5N5dh*u0)0|e3VRK!4>+J?kr$~M4+WFS|X`G#6@jKVNw_-$d0zSC;?iHUG zhgWpscm>rx(Z>_?;T8V4K^=uGtsd|-gDx!ueN~_9fKikR`qT-u^o8zHa#_3+AIzT4 z+$i>R;lE(d!hosb>sa2&HuT0=VuaYlsBPm9wfgxD+#j$Slgxvb{4yR^_xNvMFU5`< zj!)?f;ssuhkBk<8W-N#|`_b<8;YPrh%6Vh#Hjr=2M_?5Bfzcb^j*sNqLvyxaO~GL9L>HO6?0p3Qg#@(bgYa2xX~xEQ7K zQ+O@%pTcP|7ORJkpElsb_=bEFgiXNRjBf#V97<3F@0;1iz#CAVPsB~ua>(+I%&9yN z`RCW*j*?eQ;z9S|No!)ebI4l%Co~>Iu^SEc%ah#qgh~l!c4q3-RvkEm>0JVUb84 zZ+r7a@lSzK(U*97L&s}bOI;A~-XG!DLa(SJ*V!_bk43o>F*3fx{mRC~{{jRabwh4t zmNSjiIBo@6RSHvZQ@C|_lC8*4_*dXlwW~0mUXAzb@Ye3{%~SxQ68!_hgUU@a7IL{q zabr;KV8;E_CnH+WZt;Rh;RtFeSegR-ON7#I31aKGO8k1Rj3R<7tcc?ZDSL1(ioNJf z+~vyft-0#B9r?YSx1uV>ACEa7zB%W^p1U}E#Z0a+zku^l)Z<(gZ8--;EN4)(=L#r# zbMA_woTH*E=d6f9FC`o0#Lee$Z*HyvGc<~W$oB}IgK$UU zz7JT3I6vNn^9QT~z{yWhh4WL^!Sf{U$A~`z&tLJR^wYS4iowWp3s+t-J@*ggCXi$Y zr&KJ2CYi!{@Uyre9ysIa%U|R){FdA|phLLAh;)s(QVJigkfI506IY1u$mta|;Sa*N zE1T}z3kWa8{l=A6_C#ESD?(ZPQb^;%kKs!3W=_XQ4Weps3*ix4B(8wt(MXcTHp`P9lD{s!XAFmUg3kRQP0IdC!+fY zZsm^%r@Ekeu(@HeD~sv^&kMLOD$anm)_S12u)V8-KJ=gJ!uF=Rm*xYy&JhkF?AaNJ#R?^LXVO#B2M zCLD(W7Gw^+k{;X*==K|sA>8~X@WLkc^ieeBe9_|h@IL}9jw_ElfG?8U zSTP+izDB#c53)#b`$ER*g2t53d9I40yM-SRm+Bv~;h?y}xx#9er0@l7S?;GYRT8wUBHci^o&-uq9*1CN7_ zMDsnksSc?c1UCQ?0|8BVFbA^>t@!ZIHDI0>0AK=3e zhLK#5o&kR{d2a(cSn8Ezis@3T&Ln+9vJSatPslLIp|ws)w&{kRH9-Fw6yv~i$+)AS z3l>AR)&urB&IrBg3cXxVF%i!J(1YWFZ*|T|F&wxgat4&AuaxDmNG;6@@X`f7klRtjhj-7AGk8F8pMD*Pg*~z&ubDv~Ofv z7xVPMAF~TsTXF>C8*mNGB`dp*fP4vV;oKkK5@bg}CQF>2fawSf3&3s3-olP1{E@)N z5tcX~qIaM}?oP07Nv=IGcS4S2N0V$xV#Wj5Isrbp?kF|-sw^hq?8K}lI&swhbb__k zCvX#xyA!Nt-arlcBAixA-@v6@TmYF|n$>c_ds;`u8{Dpd@4{-$1?NC0jxP%j28J%M zp-JnE-B~!<;WaQk4S;5b-yJkGu=obx1HJ<$WOI@*F#r=-TSk7UZ`|bC1EwphL048f zSN6sgaGt>D!Qv1OuFOuaD(1f~$o(E6l#45JxrHOTu*wLRAh-d-8*n>8C=3Ws;L`3g%yP02qIO}f3B|gZ#0vF?JxMa`O9bEJTF4d_!_=jv*leJ%W)GzF3 zf`8~OV&SG*c4s)dqee2|C)>a7nCm2HzMvs2pfZ05^LGbFxidS!?yz_I0)9v}+ys== zomq(WV71_ZGQI$nsqQ?OGOzVDbj8Y#*3v1glakq$o>86z~8AU^ko9 zhzD|{_{4!8pav|Kf*KFNPw270bp$m$89hA#^9B6Dpo}L=>&a;7$x?YT8XB40c>}9A z=tsLFM;}(3K7jB7es^$!55vco#qmY>8*qs$^7TbM&}=sGk}t4O!wp1yUszyGgByYR z@rCp~!F)PNTyaK+;s_Jq4`ktFHMlr1%!C^PXvLv_%y7vnX>rCUm@x;;(wK8}f@=?$ zr2*$ST&kVYsGTQp1DQVvX-lJ~bZ|p}e`)A}Ot=xKm(qZF32q#VL$-xWLnpkzYMTxe z^Jn&j{TY0Jz)wX;QRL_kJaga%L7(^oPx6N_Hv%>8513}SalpzS^{&JmJ#lpa=Kz=i zfJ1(=!yCY?4+j9k5Aa7K-vE^EF7VbNBoL)j!X>^9M2RRQ5V;4UECt|(u$V2GOJxaU zWeG%C$ijF6I5rSvc>mUl&D>2;1B}-k>I+r5VATQ0?whg#PcD5Kyk=|FV^~yZwN}7g%!SFVA2W{`xRV8 z_K72yh2jX*S3&q4!66Z-FL$_P<28cSQ3Re&xDx<5lBJ4dd>F|jC6dW?B;udM+Ysau z2|B=XFq7g)CLP^S#y4CyVD$pMK39~n2PlvOm%^h!rMqy+hH(_iMScS?j6zHbAzQ{# zj8;*gQYQSKaHA3b1zZD49St}yz&#{=af~u?2q7ERq>J=aiK)zjR2ekVDc-*(N4@IK1oEYrG`tGBm$F6xJ0W&U_yS< zB#9`|6S#r7%iu Date: Tue, 12 May 2020 17:45:17 -0400 Subject: [PATCH 07/43] code review --- MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift index 03464841..b6ea9f6d 100644 --- a/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift +++ b/MVMCoreUI/Behaviors/ScreenBrightnessModifierBehavior.swift @@ -27,7 +27,7 @@ public class ScreenBrightnessModifierBehavior: PageVisibilityBehavior { //MARK:- Behavior func changeScreenBrightness() { - if originalScreenBrightness != nil { return } + guard originalScreenBrightness == nil else { return } originalScreenBrightness = UIScreen.main.brightness UIScreen.main.brightness = screenBrightness NotificationCenter.default.addObserver(self, selector: #selector(willResignActive), name: UIApplication.willResignActiveNotification, object: nil) From 3fce526c90c556d72a6c8e391894e1ac899d7d8b Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Wed, 13 May 2020 15:52:50 +0530 Subject: [PATCH 08/43] Code changes for aligning rightlabel with leftheadline. --- .../List/ListProgressBarThin.swift | 30 +++++++++++++++---- .../List/ListProgressBarThinModel.swift | 3 ++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift index 69e26ea2..73561ec1 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift @@ -17,6 +17,8 @@ import Foundation let leftBody2 = Label.commonLabelB2(true) let bar = Line() let rightLabel = Label.commonLabelB2(true) + private let barStackItem: StackItem + private let rightLabelStackItem: StackItem public var horizontalStack: Stack public var verticalStack: Stack public var stack: Stack @@ -25,14 +27,25 @@ import Foundation // MARK: - Initializers //------------------------------------------------------ public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - verticalStack = Stack.createStack(with: [(view: leftHeadline, model: StackItemModel(horizontalAlignment: .leading)), - (view: leftBody, model: StackItemModel(horizontalAlignment: .leading)), - (view: leftBody2, model: StackItemModel(horizontalAlignment: .leading))], + //vertical stack with leftHeadline, leftBody, leftBody2 + let verticalStackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .leading, verticalAlignment: .leading), StackItemModel(horizontalAlignment: .leading), StackItemModel(horizontalAlignment: .leading)], axis: .vertical, spacing: 0) - horizontalStack = Stack.createStack(with: [(view: verticalStack, model: StackItemModel(horizontalAlignment: .leading, verticalAlignment: .leading)), - (view: bar, model: StackItemModel(horizontalAlignment: .fill)), (view: rightLabel, model: StackItemModel(spacing: 5, horizontalAlignment: .fill))], + let verticalStackItems = [StackItem(andContain: leftHeadline), StackItem(andContain: leftBody), StackItem(andContain: leftBody2)] + verticalStack = Stack(with: verticalStackModel, stackItems: verticalStackItems) + + //horizontal stack with leftHeadline, leftBody, leftBody2, bar, rightLabel + let horizontalStackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .leading), StackItemModel(horizontalAlignment: .fill), StackItemModel(spacing: 5, horizontalAlignment: .fill)], axis: .horizontal) - stack = Stack.createStack(with: [(view: horizontalStack, model: StackItemModel(horizontalAlignment: .fill)), (view: progressBar, model: StackItemModel(spacing: 20, horizontalAlignment: .fill))], axis: .vertical) + barStackItem = StackItem(andContain: bar) + rightLabelStackItem = StackItem(andContain: rightLabel) + let horizontalStackItems = [StackItem(andContain: verticalStack), barStackItem, rightLabelStackItem] + horizontalStack = Stack(with: horizontalStackModel, stackItems: horizontalStackItems) + + //stack with all components + let stackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .fill), + StackItemModel(spacing: 20, horizontalAlignment: .fill)], axis: .vertical) + let stackItems = [StackItem(andContain: horizontalStack), StackItem(andContain: progressBar)] + stack = Stack(with: stackModel, stackItems: stackItems) super.init(style: style, reuseIdentifier: reuseIdentifier) } @@ -42,6 +55,11 @@ import Foundation open override func alignAccessoryToHero() -> CGPoint? { let heroCenter = super.alignAccessoryToHero() + if let heroCenter = heroCenter { + let convertedPoint = horizontalStack.convert(heroCenter, from: self) + barStackItem.containerHelper.alignCenterVerticalConstraint?.constant = convertedPoint.y - horizontalStack.bounds.midY + rightLabelStackItem.containerHelper.alignCenterVerticalConstraint?.constant = convertedPoint.y - horizontalStack.bounds.midY + } return heroCenter } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift index 2e0b7cdb..7bed74b1 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift @@ -32,6 +32,9 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { if bar.backgroundColor == nil { bar.backgroundColor = Color(uiColor: .gray) } + if let leftHeadline = leftHeadline { + leftHeadline.hero = 0 + } } private enum CodingKeys: String, CodingKey { From e217970a0417c992676c342256c913094bfe0408 Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Wed, 13 May 2020 19:41:17 +0530 Subject: [PATCH 09/43] Updated code as per confluence changes. --- .../List/ListProgressBarThin.swift | 21 ++++++------------- .../List/ListProgressBarThinModel.swift | 7 +------ 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift index 73561ec1..901e5b2d 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift @@ -14,7 +14,6 @@ import Foundation let progressBar = ProgressBar() let leftHeadline = Label.commonLabelB1(true) let leftBody = Label.commonLabelB2(true) - let leftBody2 = Label.commonLabelB2(true) let bar = Line() let rightLabel = Label.commonLabelB2(true) private let barStackItem: StackItem @@ -27,25 +26,19 @@ import Foundation // MARK: - Initializers //------------------------------------------------------ public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - //vertical stack with leftHeadline, leftBody, leftBody2 - let verticalStackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .leading, verticalAlignment: .leading), StackItemModel(horizontalAlignment: .leading), StackItemModel(horizontalAlignment: .leading)], - axis: .vertical, spacing: 0) - let verticalStackItems = [StackItem(andContain: leftHeadline), StackItem(andContain: leftBody), StackItem(andContain: leftBody2)] - verticalStack = Stack(with: verticalStackModel, stackItems: verticalStackItems) + //vertical stack with leftHeadline, leftBody + verticalStack = Stack.createStack(with: [leftHeadline, leftBody], axis: .vertical, spacing: 2) - //horizontal stack with leftHeadline, leftBody, leftBody2, bar, rightLabel + //horizontal stack with leftHeadline, leftBody, bar, rightLabel let horizontalStackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .leading), StackItemModel(horizontalAlignment: .fill), StackItemModel(spacing: 5, horizontalAlignment: .fill)], axis: .horizontal) barStackItem = StackItem(andContain: bar) rightLabelStackItem = StackItem(andContain: rightLabel) let horizontalStackItems = [StackItem(andContain: verticalStack), barStackItem, rightLabelStackItem] horizontalStack = Stack(with: horizontalStackModel, stackItems: horizontalStackItems) - + //stack with all components - let stackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .fill), - StackItemModel(spacing: 20, horizontalAlignment: .fill)], axis: .vertical) - let stackItems = [StackItem(andContain: horizontalStack), StackItem(andContain: progressBar)] - stack = Stack(with: stackModel, stackItems: stackItems) + stack = Stack.createStack(with: [horizontalStack, progressBar], axis: .vertical, spacing: PaddingDefaultVerticalSpacing3) super.init(style: style, reuseIdentifier: reuseIdentifier) } @@ -85,8 +78,7 @@ import Foundation super.set(with: model, delegateObject, additionalData) guard let model = model as? ListProgressBarThinModel else { return } verticalStack.updateContainedMolecules(with: [model.leftHeadline, - model.leftBody, - model.leftBody2],delegateObject, additionalData) + model.leftBody], delegateObject, additionalData) progressBar.set(with: model.progressBar, delegateObject, additionalData) bar.set(with: model.bar, delegateObject, additionalData) rightLabel.set(with: model.rightLabel, delegateObject, additionalData) @@ -100,7 +92,6 @@ import Foundation super.reset() leftHeadline.styleB1(true) leftBody.styleB2(true) - leftBody2.styleB2(true) rightLabel.styleB2(true) bar.setStyle(.medium) } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift index 7bed74b1..5c9c507b 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift @@ -12,15 +12,13 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { public var progressBar: ProgressBarModel public var leftHeadline: LabelModel? public var leftBody: LabelModel? - public var leftBody2: LabelModel? public var bar: LineModel public var rightLabel: LabelModel - public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel, leftBody2: LabelModel, bar: LineModel, rightLabel: LabelModel) { + public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel, bar: LineModel, rightLabel: LabelModel) { self.progressBar = progressBar self.leftHeadline = leftHeadline self.leftBody = leftBody - self.leftBody2 = leftBody2 self.bar = bar self.rightLabel = rightLabel super.init() @@ -42,7 +40,6 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { case progressBar case leftHeadline case leftBody - case leftBody2 case line case rightLabel } @@ -52,7 +49,6 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { progressBar = try typeContainer.decode(ProgressBarModel.self, forKey:.progressBar) leftHeadline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftHeadline) leftBody = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftBody) - leftBody2 = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftBody2) bar = try typeContainer.decode(LineModel.self, forKey: .line) rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) try super.init(from: decoder) @@ -65,7 +61,6 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { try container.encode(progressBar, forKey: .progressBar) try container.encodeIfPresent(leftHeadline, forKey: .leftHeadline) try container.encodeIfPresent(leftBody, forKey: .leftBody) - try container.encodeIfPresent(leftBody2, forKey: .leftBody2) try container.encode(bar, forKey: .line) try container.encode(rightLabel, forKey: .rightLabel) } From 1e3c52c15fbe4fbabf6baf56347c0392bfc96a48 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 13 May 2020 16:03:08 -0400 Subject: [PATCH 10/43] to match legacy for now and fix defect) --- MVMCoreUI/Containers/NavigationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index f278236c..05e5c406 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -58,7 +58,7 @@ import UIKit if navigationController == MVMCoreUISession.sharedGlobal()?.navigationController, navigationController.topViewController == viewController { // Update line. - MVMCoreUISession.sharedGlobal()?.navigationController?.separatorView?.setStyle(navigationItemModel.line?.type ?? .standard) + MVMCoreUISession.sharedGlobal()?.navigationController?.separatorView?.isHidden = navigationItemModel.line?.type ?? .standard == .none } if navigationController == MVMCoreUISplitViewController.main()?.navigationController, From 77a99e99c9cff8f4eab6b3700c7e1d048bd1d7bb Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Thu, 14 May 2020 15:43:19 +0530 Subject: [PATCH 11/43] Code changes: made headline as required key and body as optional. --- .../List/ListProgressBarThinModel.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift index 5c9c507b..44d11479 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift @@ -10,12 +10,12 @@ import Foundation public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { public static var identifier = "listPrgBarThin" public var progressBar: ProgressBarModel - public var leftHeadline: LabelModel? + public var leftHeadline: LabelModel public var leftBody: LabelModel? public var bar: LineModel public var rightLabel: LabelModel - public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel, bar: LineModel, rightLabel: LabelModel) { + public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel? = nil, bar: LineModel, rightLabel: LabelModel) { self.progressBar = progressBar self.leftHeadline = leftHeadline self.leftBody = leftBody @@ -30,9 +30,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { if bar.backgroundColor == nil { bar.backgroundColor = Color(uiColor: .gray) } - if let leftHeadline = leftHeadline { - leftHeadline.hero = 0 - } + leftHeadline.hero = 0 } private enum CodingKeys: String, CodingKey { @@ -47,7 +45,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { public required init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) progressBar = try typeContainer.decode(ProgressBarModel.self, forKey:.progressBar) - leftHeadline = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftHeadline) + leftHeadline = try typeContainer.decode(LabelModel.self, forKey: .leftHeadline) leftBody = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftBody) bar = try typeContainer.decode(LineModel.self, forKey: .line) rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) @@ -59,7 +57,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encode(progressBar, forKey: .progressBar) - try container.encodeIfPresent(leftHeadline, forKey: .leftHeadline) + try container.encode(leftHeadline, forKey: .leftHeadline) try container.encodeIfPresent(leftBody, forKey: .leftBody) try container.encode(bar, forKey: .line) try container.encode(rightLabel, forKey: .rightLabel) From b49579d05965983403a2baf96e4436dc70f791ae Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 14 May 2020 10:52:17 -0400 Subject: [PATCH 12/43] line fix, small clean --- .../List/ListProgressBarThin.swift | 31 ++++++++++--------- .../List/ListProgressBarThinModel.swift | 18 +++++------ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift index 901e5b2d..072be6bb 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThin.swift @@ -11,15 +11,15 @@ import Foundation //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- - let progressBar = ProgressBar() - let leftHeadline = Label.commonLabelB1(true) - let leftBody = Label.commonLabelB2(true) - let bar = Line() - let rightLabel = Label.commonLabelB2(true) + public let progressBar = ProgressBar() + public let leftHeadline = Label.commonLabelB1(true) + public let leftBody = Label.commonLabelB2(true) + public let rightBar = Line() + public let rightLabel = Label.commonLabelB2(true) private let barStackItem: StackItem private let rightLabelStackItem: StackItem + public var labelStack: Stack public var horizontalStack: Stack - public var verticalStack: Stack public var stack: Stack //------------------------------------------------------ @@ -27,14 +27,14 @@ import Foundation //------------------------------------------------------ public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { //vertical stack with leftHeadline, leftBody - verticalStack = Stack.createStack(with: [leftHeadline, leftBody], axis: .vertical, spacing: 2) + labelStack = Stack.createStack(with: [leftHeadline, leftBody], axis: .vertical, spacing: 2) //horizontal stack with leftHeadline, leftBody, bar, rightLabel + barStackItem = StackItem(andContain: rightBar) + rightLabelStackItem = StackItem(andContain: rightLabel) + let horizontalStackItems = [StackItem(andContain: labelStack), barStackItem, rightLabelStackItem] let horizontalStackModel = StackModel(molecules: [StackItemModel(horizontalAlignment: .leading), StackItemModel(horizontalAlignment: .fill), StackItemModel(spacing: 5, horizontalAlignment: .fill)], axis: .horizontal) - barStackItem = StackItem(andContain: bar) - rightLabelStackItem = StackItem(andContain: rightLabel) - let horizontalStackItems = [StackItem(andContain: verticalStack), barStackItem, rightLabelStackItem] horizontalStack = Stack(with: horizontalStackModel, stackItems: horizontalStackItems) //stack with all components @@ -47,6 +47,7 @@ import Foundation } open override func alignAccessoryToHero() -> CGPoint? { + // Ensures that the right items are centered with the arrow. let heroCenter = super.alignAccessoryToHero() if let heroCenter = heroCenter { let convertedPoint = horizontalStack.convert(heroCenter, from: self) @@ -61,14 +62,14 @@ import Foundation //------------------------------------------------------- open override func setupView() { super.setupView() - bar.widthAnchor.constraint(equalToConstant: 20).isActive = true + rightBar.widthAnchor.constraint(equalToConstant: 20).isActive = true rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) rightLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .horizontal) rightLabel.numberOfLines = 1 addMolecule(stack) stack.restack() horizontalStack.restack() - verticalStack.restack() + labelStack.restack() } //------------------------------------------------------ @@ -77,10 +78,10 @@ import Foundation open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? ListProgressBarThinModel else { return } - verticalStack.updateContainedMolecules(with: [model.leftHeadline, + labelStack.updateContainedMolecules(with: [model.leftHeadline, model.leftBody], delegateObject, additionalData) progressBar.set(with: model.progressBar, delegateObject, additionalData) - bar.set(with: model.bar, delegateObject, additionalData) + rightBar.set(with: model.rightBar, delegateObject, additionalData) rightLabel.set(with: model.rightLabel, delegateObject, additionalData) } @@ -93,6 +94,6 @@ import Foundation leftHeadline.styleB1(true) leftBody.styleB2(true) rightLabel.styleB2(true) - bar.setStyle(.medium) + rightBar.setStyle(.medium) } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift index 44d11479..acd8e013 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/ListProgressBarThinModel.swift @@ -12,23 +12,23 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { public var progressBar: ProgressBarModel public var leftHeadline: LabelModel public var leftBody: LabelModel? - public var bar: LineModel + public var rightBar: LineModel public var rightLabel: LabelModel - public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel? = nil, bar: LineModel, rightLabel: LabelModel) { + public init(progressBar: ProgressBarModel, leftHeadline: LabelModel, leftBody: LabelModel? = nil, rightBar: LineModel, rightLabel: LabelModel) { self.progressBar = progressBar self.leftHeadline = leftHeadline self.leftBody = leftBody - self.bar = bar + self.rightBar = rightBar self.rightLabel = rightLabel super.init() } override public func setDefaults() { super.setDefaults() - bar.type = .medium - if bar.backgroundColor == nil { - bar.backgroundColor = Color(uiColor: .gray) + rightBar.type = .medium + if rightBar.backgroundColor == nil { + rightBar.backgroundColor = Color(uiColor: .gray) } leftHeadline.hero = 0 } @@ -38,7 +38,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { case progressBar case leftHeadline case leftBody - case line + case rightBar case rightLabel } @@ -47,7 +47,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { progressBar = try typeContainer.decode(ProgressBarModel.self, forKey:.progressBar) leftHeadline = try typeContainer.decode(LabelModel.self, forKey: .leftHeadline) leftBody = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .leftBody) - bar = try typeContainer.decode(LineModel.self, forKey: .line) + rightBar = try typeContainer.decode(LineModel.self, forKey: .rightBar) rightLabel = try typeContainer.decode(LabelModel.self, forKey: .rightLabel) try super.init(from: decoder) } @@ -59,7 +59,7 @@ public class ListProgressBarThinModel: ListItemModel, MoleculeModelProtocol { try container.encode(progressBar, forKey: .progressBar) try container.encode(leftHeadline, forKey: .leftHeadline) try container.encodeIfPresent(leftBody, forKey: .leftBody) - try container.encode(bar, forKey: .line) + try container.encode(rightBar, forKey: .rightBar) try container.encode(rightLabel, forKey: .rightLabel) } } From 8ea3404294830d89ed691d809986638aa3d62a67 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 14 May 2020 11:24:07 -0400 Subject: [PATCH 13/43] load image update control for delegate --- .../Atomic/Atoms/Views/LoadImageView.swift | 18 +++++++++--------- .../Device/ListDeviceComplexButtonMedium.swift | 1 + .../Device/ListDeviceComplexButtonSmall.swift | 1 + .../Device/ListDeviceComplexLinkMedium.swift | 1 + .../Device/ListDeviceComplexLinkSmall.swift | 1 + 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift index a8d0ba9a..e3c9123e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift @@ -12,6 +12,7 @@ import UIKit public let loadingSpinner = MFLoadingSpinner(frame: .zero) public let imageView = MFTransparentGIFView(frame: .zero) public var addSizeConstraintsForAspectRatio = false + public var shouldNotifyDelegateOnUpdate = true var centerX: NSLayoutConstraint? var centerY: NSLayoutConstraint? var widthConstraint: NSLayoutConstraint? @@ -264,16 +265,15 @@ import UIKit } let finishedLoadingBlock: MVMCoreGetImageBlock = {[weak self] (image, data, isFallbackImage) in MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in - guard let loadingImageName = self?.currentImageName, loadingImageName == imageName else { - return - } - self?.isFallbackImage = isFallbackImage - self?.loadingSpinner.pause() - let layoutWillChange = self?.layoutWillChange(width: self?.currentImageWidth, height: self?.currentImageHeight, size: image?.size) ?? false - self?.addConstraints(width: width, height: height, size: image?.size) - self?.loadingSpinnerHeightConstraint?.constant = 0 + guard let self = self, + let loadingImageName = self.currentImageName, loadingImageName == imageName else { return } + self.isFallbackImage = isFallbackImage + self.loadingSpinner.pause() + let layoutWillChange = self.shouldNotifyDelegateOnUpdate ? self.layoutWillChange(width: self.currentImageWidth, height: self.currentImageHeight, size: image?.size) : false + self.addConstraints(width: width, height: height, size: image?.size) + self.loadingSpinnerHeightConstraint?.constant = 0 if layoutWillChange { - self?.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self!) + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) } completionBlock(image,data,isFallbackImage) })} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMedium.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMedium.swift index c39bce65..e6e226cb 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMedium.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonMedium.swift @@ -43,6 +43,7 @@ import Foundation // MARK: - MFViewProtocol open override func setupView() { super.setupView() + rightImageView.shouldNotifyDelegateOnUpdate = false addMolecule(stack) stack.restack() verticalStack.restack() diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmall.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmall.swift index 77a189c2..e8436620 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmall.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmall.swift @@ -43,6 +43,7 @@ import Foundation // MARK: - MFViewProtocol open override func setupView() { super.setupView() + rightImageView.shouldNotifyDelegateOnUpdate = false addMolecule(stack) stack.restack() verticalStack.restack() diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift index f53da09c..573335c0 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkMedium.swift @@ -46,6 +46,7 @@ import Foundation //----------------------------------------------------- open override func setupView() { super.setupView() + rightImage.shouldNotifyDelegateOnUpdate = false addMolecule(stack) stack.restack() verticalStack.restack() diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift index 5ba8ac97..49173067 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexLinkSmall.swift @@ -46,6 +46,7 @@ import Foundation //----------------------------------------------------- open override func setupView() { super.setupView() + rightImage.shouldNotifyDelegateOnUpdate = false addMolecule(stack) stack.restack() verticalStack.restack() From ac86d925744f00116021e0c511227781cf1fb8ba Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 09:03:57 -0400 Subject: [PATCH 14/43] No Validate on Emoty --- .../Atomic/Atoms/TextFields/TextEntryField.swift | 2 ++ .../Atoms/TextFields/TextViewEntryField.swift | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift index 9d7c8927..41254355 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift @@ -259,6 +259,7 @@ import UIKit observingTextFieldDelegate?.isInvalid?(textfield: self) } } + /// Executes on UITextField.textDidBeginEditingNotification @objc func startEditing() { isSelected = true @@ -278,6 +279,7 @@ import UIKit // Don't show error till user starts typing. guard text?.count ?? 0 != 0 else { + showError = false return } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift index 546b5c30..35e25075 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift @@ -71,7 +71,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele /// The text of this textView. open override var text: String? { - get { return textView.text } + get { return textViewEntryFieldModel?.text } set { textView.text = newValue textViewEntryFieldModel?.text = newValue @@ -207,8 +207,19 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele /// Executes on UITextView.textDidEndEditingNotification @objc func endInputing() { - resignFirstResponder() isSelected = false + resignFirstResponder() + + // Don't show error till user starts typing. + guard text?.count ?? 0 != 0 else { + showError = false + return + } + + if let isValid = (model as? TextEntryFieldModel)?.isValid { + self.isValid = isValid + } + showError = !isValid } From 868836b96312a52720aabd7ea3ee0d0df314b705 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 09:10:16 -0400 Subject: [PATCH 15/43] changes to prevent validation on no text. --- MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift | 3 ++- MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift index 41254355..5b131fe6 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift @@ -283,9 +283,10 @@ import UIKit return } - if let isValid = (model as? TextEntryFieldModel)?.isValid { + if let isValid = textEntryFieldModel?.isValid { self.isValid = isValid } + shouldShowError(!isValid) } diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift index 35e25075..9cbf2c57 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift @@ -216,7 +216,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele return } - if let isValid = (model as? TextEntryFieldModel)?.isValid { + if let isValid = textViewEntryFieldModel?.isValid { self.isValid = isValid } From fb7760a329f05d56455d0346dc85210f7ae2ba7c Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 10:55:46 -0400 Subject: [PATCH 16/43] moving validator to parent --- .../Atomic/Atoms/TextFields/EntryField.swift | 29 +++++++++++++++++++ .../Atoms/TextFields/TextEntryField.swift | 22 ++++---------- .../Atoms/TextFields/TextViewEntryField.swift | 22 ++++---------- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift index e54cb80f..3a5084c6 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift @@ -49,6 +49,9 @@ import UIKit public var isValid: Bool = false + /// Validate on each entry in the textField. Default: true + public var validateEachCharacter: Bool = true + //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- @@ -229,6 +232,32 @@ import UIKit entryFieldContainer.updateView(size) } + //-------------------------------------------------- + // MARK: - Validation + //-------------------------------------------------- + + /// Executes on .textDidBeginEditingNotification + @objc func startEditing() { + isSelected = true + } + + /// Executes on .textDidChangeNotification (each character entry) + @objc func valueChanged() { + guard validateEachCharacter else { return } + } + + /// Executes on .textDidEndEditingNotification + @objc func endInputing() { + isSelected = false + resignFirstResponder() + + // Don't show error till user starts typing. + guard text?.count ?? 0 != 0 else { + showError = false + return + } + } + //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift index 5b131fe6..969fdfd4 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift @@ -51,9 +51,6 @@ import UIKit private var observingForChange: Bool = false - /// Validate on each entry in the textField. Default: true - public var validateEachCharacter: Bool = true - /// Validate when user resigns editing. Default: true public var validateWhenDoneEditing: Bool = true @@ -261,27 +258,20 @@ import UIKit } /// Executes on UITextField.textDidBeginEditingNotification - @objc func startEditing() { - isSelected = true + @objc override func startEditing() { + super.startEditing() textField.becomeFirstResponder() } /// Executes on UITextField.textDidChangeNotification (each character entry) - @objc func valueChanged() { - guard validateEachCharacter else { return } - isSelected = true + @objc override func valueChanged() { + super.valueChanged() validateTextField() } /// Executes on UITextField.textDidEndEditingNotification - @objc func endInputing() { - resignFirstResponder() - - // Don't show error till user starts typing. - guard text?.count ?? 0 != 0 else { - showError = false - return - } + @objc override func endInputing() { + super.endInputing() if let isValid = textEntryFieldModel?.isValid { self.isValid = isValid diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift index 9cbf2c57..4331701c 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift @@ -24,9 +24,6 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele // MARK: - Properties //-------------------------------------------------- - /// Validate on each entry in the textView. Default: true - public var validateEachCharacter: Bool = true - private var observingForChange: Bool = false //-------------------------------------------------- @@ -194,27 +191,20 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele } /// Executes on UITextView.textDidBeginEditingNotification - @objc func startEditing() { - isSelected = true + @objc override func startEditing() { + super.startEditing() _ = textView.becomeFirstResponder() } /// Executes on UITextView.textDidChangeNotification (each character entry) - @objc func valueChanged() { - guard validateEachCharacter else { return } + @objc override func valueChanged() { + super.valueChanged() validateTextView() } /// Executes on UITextView.textDidEndEditingNotification - @objc func endInputing() { - isSelected = false - resignFirstResponder() - - // Don't show error till user starts typing. - guard text?.count ?? 0 != 0 else { - showError = false - return - } + @objc override func endInputing() { + super.endInputing() if let isValid = textViewEntryFieldModel?.isValid { self.isValid = isValid From ad4dfcf74819e888f98cde848a273c1a011a5cdb Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 12:20:39 -0400 Subject: [PATCH 17/43] further changes to orbservation --- MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift | 6 ------ MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift | 6 ++++++ MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift | 6 ++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift index 3a5084c6..499fe5a8 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift @@ -250,12 +250,6 @@ import UIKit @objc func endInputing() { isSelected = false resignFirstResponder() - - // Don't show error till user starts typing. - guard text?.count ?? 0 != 0 else { - showError = false - return - } } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift index 969fdfd4..63ba9e71 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift @@ -272,6 +272,12 @@ import UIKit /// Executes on UITextField.textDidEndEditingNotification @objc override func endInputing() { super.endInputing() + + // Don't show error till user starts typing. + guard text?.count ?? 0 != 0 else { + showError = false + return + } if let isValid = textEntryFieldModel?.isValid { self.isValid = isValid diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift index 4331701c..cb8e2cae 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift @@ -206,6 +206,12 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele @objc override func endInputing() { super.endInputing() + // Don't show error till user starts typing. + guard text?.count ?? 0 != 0 else { + showError = false + return + } + if let isValid = textViewEntryFieldModel?.isValid { self.isValid = isValid } From 6a9957d34827516d0cda4277989d546c1720ca4a Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 13:56:57 -0400 Subject: [PATCH 18/43] moved code to parent --- MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift | 4 ++-- MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift | 7 +++++++ MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift | 8 ++++---- .../Atomic/Atoms/TextFields/TextViewEntryField.swift | 8 +++----- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift index 628c7cde..18f61b2d 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/DigitEntryField.swift @@ -322,7 +322,7 @@ import UIKit @objc override open func resignFirstResponder() -> Bool { if validateWhenDoneEditing { - validateTextField() + validateText() } selectedDigitBox?.isSelected = false @@ -440,7 +440,7 @@ extension DigitEntryField { selectedDigitBox = nil if !switchFieldsAutomatically && validateWhenDoneEditing { - validateTextField() + validateText() } proprietorTextDelegate?.textFieldDidEndEditing?(textField) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift index 499fe5a8..41fec6bb 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift @@ -236,6 +236,13 @@ import UIKit // MARK: - Validation //-------------------------------------------------- + /// Validates the text of the entry field. + @objc public func validateText() { + if let isValid = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) { + self.isValid = isValid + } + } + /// Executes on .textDidBeginEditingNotification @objc func startEditing() { isSelected = true diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift index 63ba9e71..e849fc2f 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift @@ -223,7 +223,7 @@ import UIKit @discardableResult @objc override open func resignFirstResponder() -> Bool { if validateWhenDoneEditing { - validateTextField() + validateText() } textField.resignFirstResponder() isSelected = false @@ -231,9 +231,9 @@ import UIKit } /// Validates the text of the entry field. - @objc public func validateTextField() { + @objc public override func validateText() { text = textField.text - _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + super.validateText() } @objc public func updateValidation(_ isValid: Bool) { @@ -266,7 +266,7 @@ import UIKit /// Executes on UITextField.textDidChangeNotification (each character entry) @objc override func valueChanged() { super.valueChanged() - validateTextField() + validateText() } /// Executes on UITextField.textDidEndEditingNotification diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift index cb8e2cae..e08f3843 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift @@ -183,11 +183,9 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele //-------------------------------------------------- /// Validates the text of the entry field. - @objc public func validateTextView() { + @objc public override func validateText() { text = textView.text - if let isValid = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) { - self.isValid = isValid - } + super.validateText() } /// Executes on UITextView.textDidBeginEditingNotification @@ -199,7 +197,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele /// Executes on UITextView.textDidChangeNotification (each character entry) @objc override func valueChanged() { super.valueChanged() - validateTextView() + validateText() } /// Executes on UITextView.textDidEndEditingNotification From 57342800fe6d60399f98173e60476998532d3b43 Mon Sep 17 00:00:00 2001 From: "Khan, Arshad" Date: Fri, 15 May 2020 23:30:49 +0530 Subject: [PATCH 19/43] image width and height fix 1. making addSizeConstraintsForAspectRatio default true 2. removing 900 priority constraints --- MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift index e3c9123e..baa63ee8 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift @@ -11,7 +11,7 @@ import UIKit @objcMembers open class LoadImageView: View { public let loadingSpinner = MFLoadingSpinner(frame: .zero) public let imageView = MFTransparentGIFView(frame: .zero) - public var addSizeConstraintsForAspectRatio = false + public var addSizeConstraintsForAspectRatio = true public var shouldNotifyDelegateOnUpdate = true var centerX: NSLayoutConstraint? var centerY: NSLayoutConstraint? @@ -173,7 +173,6 @@ import UIKit } else { heightConstraint?.isActive = false heightConstraint = imageView.heightAnchor.constraint(equalToConstant: height) - heightConstraint?.priority = UILayoutPriority(rawValue: 900) } heightConstraint?.isActive = true } @@ -183,7 +182,6 @@ import UIKit widthConstraint.constant = width } else { widthConstraint = imageView.widthAnchor.constraint(equalToConstant: width) - widthConstraint?.priority = UILayoutPriority(rawValue: 900) } widthConstraint?.isActive = true } From 7e3c9fdcdbbbdcd43cc85809d36185d8bbb6b0f1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 15:20:03 -0400 Subject: [PATCH 20/43] new rule equalsignorecase --- MVMCoreUI.xcodeproj/project.pbxproj | 4 ++ MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 1 + .../Rules/RuleEqualsIgnoreCaseModel.swift | 55 +++++++++++++++++++ .../Rules/Rules/RuleRequiredModel.swift | 1 + 4 files changed, 61 insertions(+) create mode 100644 MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index d7afb970..20472d78 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -92,6 +92,7 @@ 0A7EF86323D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86223D8AFA000B2AAD1 /* BaseDropdownEntryFieldModel.swift */; }; 0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */; }; 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */; }; + 0A849EFE246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */; }; 0A9D091D2433796500D2E6C0 /* BarsCarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09172433796500D2E6C0 /* BarsCarouselIndicatorModel.swift */; }; 0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09182433796500D2E6C0 /* NumericCarouselIndicatorModel.swift */; }; 0A9D091F2433796500D2E6C0 /* NumericIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09192433796500D2E6C0 /* NumericIndicatorView.swift */; }; @@ -505,6 +506,7 @@ 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDropdownEntryFieldModel.swift; sourceTree = ""; }; 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryFieldModel.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; + 0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleEqualsIgnoreCaseModel.swift; sourceTree = ""; }; 0A9D09172433796500D2E6C0 /* BarsCarouselIndicatorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarsCarouselIndicatorModel.swift; sourceTree = ""; }; 0A9D09182433796500D2E6C0 /* NumericCarouselIndicatorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumericCarouselIndicatorModel.swift; sourceTree = ""; }; 0A9D09192433796500D2E6C0 /* NumericIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumericIndicatorView.swift; sourceTree = ""; }; @@ -873,6 +875,7 @@ 011D95A0240453D0000E3791 /* RuleEqualsModel.swift */, 011D95A2240453F8000E3791 /* RuleRegexModel.swift */, 0A69F610241BDEA700F7231B /* RuleAnyRequiredModel.swift */, + 0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */, ); name = Rules; path = Rules/Rules; @@ -2038,6 +2041,7 @@ AA11A42123F15D7000D7962F /* ListRightVariablePaymentsModel.swift in Sources */, 011D9626240EBB16000E3791 /* RadioButtonLabelModel.swift in Sources */, 8DDD6C1D244D90B8006A2232 /* ListThreeColumnDataUsage.swift in Sources */, + 0A849EFE246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift in Sources */, D28764FB245A33A500CB882D /* TwoLinkViewModel.swift in Sources */, AAA74A192410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift in Sources */, D282AABA224131D100C46919 /* MFTransparentGIFView.swift in Sources */, diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index 76c17c6a..0a0768f8 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -194,6 +194,7 @@ import Foundation try? ModelRegistry.register(RuleAnyValueChangedModel.self) try? ModelRegistry.register(RuleAllValueChangedModel.self) try? ModelRegistry.register(RuleEqualsModel.self) + try? ModelRegistry.register(RuleEqualsIgnoreCaseModel.self) try? ModelRegistry.register(RuleRegexModel.self) // Actions diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift new file mode 100644 index 00000000..c988cfce --- /dev/null +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -0,0 +1,55 @@ +// +// RuleEqualsIgnoreCaseModel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 5/15/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +public class RuleEqualsIgnoreCaseModel: RulesProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static var identifier: String = "equalsIgnoreCase" + public var type: String = RuleEqualsModel.identifier + public var fields: [String] + public var errorMessage: [String: String]? + + //-------------------------------------------------- + // MARK: - Validation + //-------------------------------------------------- + + public func isValid(_ formField: FormFieldProtocol) -> Bool { + return false + } + + public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { + var valid = true + var compareValue: AnyHashable? + + for formKey in fields { + guard let formField = fieldMolecules[formKey] else { continue } + + if compareValue == nil { + compareValue = formField.formFieldValue() + continue + } + + let a = compareValue.caseInsensitiveCompare(formField.formFieldValue()) + + if compareValue != formField.formFieldValue() { + valid = false + (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) + break + } else { + (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) + } + } + + return valid + } +} diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift index c9e7d9f7..77cdf254 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleRequiredModel.swift @@ -24,6 +24,7 @@ public class RuleRequiredModel: RulesProtocol { //-------------------------------------------------- public func isValid(_ formField: FormFieldProtocol) -> Bool { + guard let value = formField.formFieldValue() else { return false } var valid = true From 461fd5e2cab9a58cb7c7fe31e9b3c513a573b169 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 15:37:40 -0400 Subject: [PATCH 21/43] new rule logic. --- .../Rules/Rules/RuleEqualsIgnoreCaseModel.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index c988cfce..15bcb3f0 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -28,23 +28,21 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { } public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { - var valid = true - var compareValue: AnyHashable? + var valid = false + var compareValue: String? for formKey in fields { guard let formField = fieldMolecules[formKey] else { continue } if compareValue == nil { - compareValue = formField.formFieldValue() + compareValue = formField.formFieldValue() as? String continue } - - let a = compareValue.caseInsensitiveCompare(formField.formFieldValue()) - if compareValue != formField.formFieldValue() { + if let compareValue = compareValue, let fieldValue = formField.formFieldValue() as? String, + compareValue.caseInsensitiveCompare(fieldValue) == .orderedSame { valid = false (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) - break } else { (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) } From 6d3805c9581c238739e0990794c3628eadddaa3f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 15:39:35 -0400 Subject: [PATCH 22/43] valid true --- .../FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index 15bcb3f0..224d2334 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -41,7 +41,7 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { if let compareValue = compareValue, let fieldValue = formField.formFieldValue() as? String, compareValue.caseInsensitiveCompare(fieldValue) == .orderedSame { - valid = false + valid = true (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) } else { (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) From c144b07a836e591daa78eb934bb34aa588819278 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 16:14:16 -0400 Subject: [PATCH 23/43] revisions for review --- .../Rules/Rules/RuleEqualsIgnoreCaseModel.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index 224d2334..090812b3 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -29,23 +29,22 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { public func validate(_ fieldMolecules: [String: FormFieldProtocol]) -> Bool { var valid = false - var compareValue: String? + var compareText: String? for formKey in fields { guard let formField = fieldMolecules[formKey] else { continue } - if compareValue == nil { - compareValue = formField.formFieldValue() as? String + guard let compareString = compareText else { + compareText = formField.formFieldValue() as? String continue } - if let compareValue = compareValue, let fieldValue = formField.formFieldValue() as? String, - compareValue.caseInsensitiveCompare(fieldValue) == .orderedSame { + if let fieldValue = formField.formFieldValue() as? String, + compareString.caseInsensitiveCompare(fieldValue) == .orderedSame { valid = true - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) - } else { - (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) } + + (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) } return valid From 926659c12919291c1ff4883b846a54d4194f8496 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 16:15:43 -0400 Subject: [PATCH 24/43] dramatic change --- .../Rules/Rules/RuleEqualsIgnoreCaseModel.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index 090812b3..9f361dfd 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -34,13 +34,14 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { for formKey in fields { guard let formField = fieldMolecules[formKey] else { continue } - guard let compareString = compareText else { - compareText = formField.formFieldValue() as? String - continue + guard let compareString = compareText, + let fieldValue = formField.formFieldValue() as? String + else { + compareText = formField.formFieldValue() as? String + continue } - if let fieldValue = formField.formFieldValue() as? String, - compareString.caseInsensitiveCompare(fieldValue) == .orderedSame { + if compareString.caseInsensitiveCompare(fieldValue) == .orderedSame { valid = true } From ac5bf372eb1e349e133666f3bbd61e293c86c926 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 16:16:14 -0400 Subject: [PATCH 25/43] undo --- .../Rules/Rules/RuleEqualsIgnoreCaseModel.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index 9f361dfd..090812b3 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -34,14 +34,13 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { for formKey in fields { guard let formField = fieldMolecules[formKey] else { continue } - guard let compareString = compareText, - let fieldValue = formField.formFieldValue() as? String - else { - compareText = formField.formFieldValue() as? String - continue + guard let compareString = compareText else { + compareText = formField.formFieldValue() as? String + continue } - if compareString.caseInsensitiveCompare(fieldValue) == .orderedSame { + if let fieldValue = formField.formFieldValue() as? String, + compareString.caseInsensitiveCompare(fieldValue) == .orderedSame { valid = true } From 90d5093318cd0d812a269f34cc6d36213ae6fc09 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 15 May 2020 16:25:37 -0400 Subject: [PATCH 26/43] name change --- .../FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index 090812b3..0bdd7ca3 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -15,7 +15,7 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { //-------------------------------------------------- public static var identifier: String = "equalsIgnoreCase" - public var type: String = RuleEqualsModel.identifier + public var type: String = RuleEqualsIgnoreCaseModel.identifier public var fields: [String] public var errorMessage: [String: String]? From f8025eb67add0e09757f0c4c1a7248fce594d946 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 18 May 2020 12:27:11 -0400 Subject: [PATCH 27/43] more room for validation --- .../Atomic/Atoms/TextFields/EntryField.swift | 27 ++++++++++++ .../Atoms/TextFields/EntryFieldModel.swift | 7 ++-- .../Atoms/TextFields/TextEntryField.swift | 41 +++++-------------- .../Atoms/TextFields/TextViewEntryField.swift | 4 ++ .../Rules/RuleEqualsIgnoreCaseModel.swift | 5 +++ 5 files changed, 50 insertions(+), 34 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift index 41fec6bb..65b13861 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/EntryField.swift @@ -259,6 +259,21 @@ import UIKit resignFirstResponder() } + @objc public func updateValidation(_ isValid: Bool) { + let previousValidity = self.isValid + self.isValid = isValid + + if previousValidity && !isValid { + shouldShowError(true) + } else if (!previousValidity && isValid) { + shouldShowError(false) + } + } + + func shouldShowError(_ showError: Bool) { + self.showError = showError + } + //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- @@ -285,6 +300,18 @@ import UIKit entryFieldContainer.set(with: model, delegateObject, additionalData) + model.updateUI = { [weak self] in + MVMCoreDispatchUtility.performBlock(onMainThread: { + guard let self = self else { return } + + if self.isSelected { + self.updateValidation(model.isValid ?? true) + } else if model.isValid ?? true && self.showError { + self.showError = false + } + }) + } + title = model.title feedback = model.feedback isEnabled = model.enabled diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/EntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/TextFields/EntryFieldModel.swift index 1d6e9221..dd2a3c62 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/EntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/EntryFieldModel.swift @@ -71,12 +71,13 @@ import Foundation } public func setValidity(_ valid: Bool, rule: RulesProtocol) { - if let fieldKey = fieldKey, - let ruleErrorMessage = rule.errorMessage?[fieldKey] { + + if let fieldKey = fieldKey, let ruleErrorMessage = rule.errorMessage?[fieldKey] { self.errorMessage = ruleErrorMessage } + self.isValid = valid - + updateUI?() } //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift index e849fc2f..2daee169 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextEntryField.swift @@ -236,27 +236,6 @@ import UIKit super.validateText() } - @objc public func updateValidation(_ isValid: Bool) { - let previousValidity = self.isValid - self.isValid = isValid - - if previousValidity && !isValid { - shouldShowError(true) - } else if (!previousValidity && isValid) { - shouldShowError(false) - } - } - - func shouldShowError(_ showError: Bool) { - self.showError = showError - if showError { - observingTextFieldDelegate?.isValid?(textfield: self) - entryFieldContainer.originalUI() - } else { - observingTextFieldDelegate?.isInvalid?(textfield: self) - } - } - /// Executes on UITextField.textDidBeginEditingNotification @objc override func startEditing() { super.startEditing() @@ -310,6 +289,16 @@ import UIKit } } + override func shouldShowError(_ showError: Bool) { + super.shouldShowError(showError) + + if showError { + observingTextFieldDelegate?.isValid?(textfield: self) + } else { + observingTextFieldDelegate?.isInvalid?(textfield: self) + } + } + //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- @@ -319,16 +308,6 @@ import UIKit guard let model = model as? TextEntryFieldModel else { return } - model.updateUI = { [weak self] in - MVMCoreDispatchUtility.performBlock(onMainThread: { - guard let self = self else { return } - - if self.isSelected { - self.updateValidation(model.isValid ?? true) - } - }) - } - self.delegateObject = delegateObject FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) text = model.text diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift index e08f3843..0556245d 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift @@ -217,6 +217,10 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele showError = !isValid } + override func shouldShowError(_ showError: Bool) { + super.shouldShowError(showError) + } + //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- diff --git a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift index 0bdd7ca3..5e014cff 100644 --- a/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift +++ b/MVMCoreUI/FormUIHelpers/Rules/Rules/RuleEqualsIgnoreCaseModel.swift @@ -42,6 +42,11 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol { if let fieldValue = formField.formFieldValue() as? String, compareString.caseInsensitiveCompare(fieldValue) == .orderedSame { valid = true + for formKey in fields { + guard let formField = fieldMolecules[formKey] else { continue } + (formField as? FormRuleWatcherFieldProtocol)?.setValidity(true, rule: self) + } + break } (formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self) From d476b9ce14a77354bfa174a2ee4c0074346601f2 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 18 May 2020 14:55:29 -0400 Subject: [PATCH 28/43] Navigation fixing --- MVMCoreUI.xcodeproj/project.pbxproj | 28 ++++++++++-- MVMCoreUI/Atomic/Atoms/Views/Line.swift | 4 ++ .../NavigationItemButtonModel.swift | 35 +++++++++++++++ .../NavigationItemModel.swift} | 43 +------------------ .../NavigationItemModelProtocol.swift | 21 +++++++++ .../PanelNavigationItemModelProtocol.swift | 14 ++++++ .../MVMControllerModelProtocol.swift | 1 + .../Containers/NavigationController.swift | 10 ++--- .../MVMCoreUISplitViewController.h | 4 +- .../MVMCoreUISplitViewController.m | 13 ++++-- 10 files changed, 116 insertions(+), 57 deletions(-) create mode 100644 MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift rename MVMCoreUI/Atomic/Molecules/{NavigationItemModelProtocol.swift => NavigationBar/NavigationItemModel.swift} (70%) create mode 100644 MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift create mode 100644 MVMCoreUI/Atomic/Protocols/ModelProtocols/PanelNavigationItemModelProtocol.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index ec7e68b0..59816653 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -245,7 +245,7 @@ D2092357244FA1EF0044AD09 /* ThreeLayerModelBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */; }; D20923592450ECE00044AD09 /* TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20923582450ECE00044AD09 /* TableView.swift */; }; D20A9A5E2243D3E300ADE781 /* TwoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */; }; - D20FB165241A5D75004AFC3A /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */; }; + D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FB164241A5D75004AFC3A /* NavigationItemModel.swift */; }; D213347723843825008E41B3 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = D213347623843825008E41B3 /* Line.swift */; }; D21B7F602437C5BC00051ABF /* MoleculeStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F5E2437C5BC00051ABF /* MoleculeStackView.swift */; }; D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */; }; @@ -264,6 +264,9 @@ D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */; }; D236E5B7242007C500C38625 /* MVMControllerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */; }; D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; }; + D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */; }; + D2509ED32472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED22472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift */; }; + D2509ED62472EE2F001BFB9D /* NavigationItemButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED52472EE2F001BFB9D /* NavigationItemButtonModel.swift */; }; D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.swift */; }; D253BB9C245874F8002DE544 /* BGImageMolecule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253BB9B245874F8002DE544 /* BGImageMolecule.swift */; }; D253BB9E2458751F002DE544 /* BGImageMoleculeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253BB9D2458751F002DE544 /* BGImageMoleculeModel.swift */; }; @@ -656,7 +659,7 @@ D2092356244FA1EF0044AD09 /* ThreeLayerModelBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerModelBase.swift; sourceTree = ""; }; D20923582450ECE00044AD09 /* TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableView.swift; sourceTree = ""; }; D20A9A5D2243D3E300ADE781 /* TwoButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwoButtonView.swift; sourceTree = ""; }; - D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; + D20FB164241A5D75004AFC3A /* NavigationItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModel.swift; sourceTree = ""; }; D213347623843825008E41B3 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; D21B7F5E2437C5BC00051ABF /* MoleculeStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeStackView.swift; sourceTree = ""; }; D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewCell.swift; sourceTree = ""; }; @@ -675,6 +678,9 @@ D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescriptionModel.swift; sourceTree = ""; }; D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMControllerModelProtocol.swift; sourceTree = ""; }; D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; + D2509ED22472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelNavigationItemModelProtocol.swift; sourceTree = ""; }; + D2509ED52472EE2F001BFB9D /* NavigationItemButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemButtonModel.swift; sourceTree = ""; }; D253BB9B245874F8002DE544 /* BGImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMolecule.swift; sourceTree = ""; }; D253BB9D2458751F002DE544 /* BGImageMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMoleculeModel.swift; sourceTree = ""; }; D256E9922412880000360572 /* Header.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = ""; }; @@ -852,6 +858,8 @@ 011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */, D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */, D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */, + D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */, + D2509ED22472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift */, ); path = ModelProtocols; sourceTree = ""; @@ -1313,6 +1321,15 @@ path = TwoColumn; sourceTree = ""; }; + D2509ED42472EE0B001BFB9D /* NavigationBar */ = { + isa = PBXGroup; + children = ( + D2509ED52472EE2F001BFB9D /* NavigationItemButtonModel.swift */, + D20FB164241A5D75004AFC3A /* NavigationItemModel.swift */, + ); + path = NavigationBar; + sourceTree = ""; + }; D253BB9A24587023002DE544 /* OtherContainers */ = { isa = PBXGroup; children = ( @@ -1453,6 +1470,7 @@ D29DF10E21E67A77003B2FB9 /* Molecules */ = { isa = PBXGroup; children = ( + D2509ED42472EE0B001BFB9D /* NavigationBar */, D253BB9A24587023002DE544 /* OtherContainers */, D22B38E923F4E07800490EF6 /* DesignedComponents */, D22479912316A9EF003FCCF9 /* Items */, @@ -1466,7 +1484,6 @@ 012A88EB238F084D00FE3DA1 /* FooterModel.swift */, D274CA322236A78900B01B62 /* FooterView.swift */, D260105723CF9CC500764D80 /* Doughnut */, - D20FB164241A5D75004AFC3A /* NavigationItemModelProtocol.swift */, ); path = Molecules; sourceTree = ""; @@ -1920,6 +1937,7 @@ 8D3BA9BF2433789900D341BA /* ListThreeColumnInternationalDataDivider.swift in Sources */, 94C661DA23CCF4FB00D9FE5B /* UIColor+Extension.swift in Sources */, D28A838123CCB0D800DFE4FC /* AccordionListItemModel.swift in Sources */, + D2509ED62472EE2F001BFB9D /* NavigationItemButtonModel.swift in Sources */, DBC4391822442197001AB423 /* CaretView.swift in Sources */, C07065C42395677300FBF997 /* Link.swift in Sources */, 0A69F611241BDEA700F7231B /* RuleAnyRequiredModel.swift in Sources */, @@ -2119,8 +2137,10 @@ 014AA72423C501E2006F3E93 /* MoleculeContainerModel.swift in Sources */, D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */, 011B58F223A2AE2C0085F53C /* DropDownListItemModel.swift in Sources */, + D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */, 8D448E5524050A46006211BB /* ListOneColumnFullWidthTextAllTextAndLinksModel.swift in Sources */, 94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */, + D2509ED32472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift in Sources */, 944589212385D6E900DE9FD4 /* DashLineModel.swift in Sources */, D2E2A99623D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift in Sources */, C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, @@ -2160,7 +2180,7 @@ D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, - D20FB165241A5D75004AFC3A /* NavigationItemModelProtocol.swift in Sources */, + D20FB165241A5D75004AFC3A /* NavigationItemModel.swift in Sources */, AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */, DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */, D224798A2314445E003FCCF9 /* LabelToggle.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Line.swift b/MVMCoreUI/Atomic/Atoms/Views/Line.swift index 62550503..d34aee06 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Line.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Line.swift @@ -55,6 +55,10 @@ import UIKit public convenience init(pinTo view: UIView, edge: UIRectEdge, useMargin: Bool) { self.init(frame: .zero) + addLine(to: view, edge: edge, useMargin: useMargin) + } + + open func addLine(to view: UIView, edge: UIRectEdge, useMargin: Bool) { view.addSubview(self) NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: self, useMargins: useMargin, pinTop: edge != .bottom, pinBottom: edge != .top, pinLeft: edge != .right, pinRight: edge != .left).values)) } diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift new file mode 100644 index 00000000..9110bfe9 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift @@ -0,0 +1,35 @@ +// +// NavigationItemButtonModel.swift +// +// +// Created by Scott Pfeil on 5/18/20. +// + +import Foundation + +public class NavigationItemButtonModel: Codable { + var imageName: String + var action: ActionModelProtocol + + public init(with imageName: String, action: ActionModelProtocol) { + self.imageName = imageName + self.action = action + } + + private enum CodingKeys: String, CodingKey { + case imageName + case action + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + imageName = try typeContainer.decode(String.self, forKey: .imageName) + action = try typeContainer.decodeModel(codingKey: .action) + } + + open func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(imageName, forKey: .imageName) + try container.encodeModel(action, forKey: .action) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift similarity index 70% rename from MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift rename to MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift index f15a847a..50ae62d6 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift @@ -8,48 +8,7 @@ import Foundation -public protocol NavigationItemModelProtocol { - var title: String? { get set } - var hidden: Bool { get set } - var backgroundColor: Color? { get set } - var translucent: Bool { get set } - var tintColor: Color { get set } - var line: LineModel? { get set } - var showLeftPanelButton: Bool { get set } - var showRightPanelButton: Bool { get set } - var backButton: NavigationItemButtonModel? { get set } - var additionalLeftButtons: [NavigationItemButtonModel]? { get set } - var additionalRightButtons: [NavigationItemButtonModel]? { get set } -} - -public class NavigationItemButtonModel: Codable { - var imageName: String - var action: ActionModelProtocol - - public init(with imageName: String, action: ActionModelProtocol) { - self.imageName = imageName - self.action = action - } - - private enum CodingKeys: String, CodingKey { - case imageName - case action - } - - required public init(from decoder: Decoder) throws { - let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - imageName = try typeContainer.decode(String.self, forKey: .imageName) - action = try typeContainer.decodeModel(codingKey: .action) - } - - open func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(imageName, forKey: .imageName) - try container.encodeModel(action, forKey: .action) - } -} - -public class NavigationItemModel: NavigationItemModelProtocol, MoleculeModelProtocol { +public class NavigationItemModel: NavigationItemModelProtocol, PanelNavigationItemModelProtocol, MoleculeModelProtocol { public class var identifier: String { return "navigationBar" } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift new file mode 100644 index 00000000..634539d4 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/NavigationItemModelProtocol.swift @@ -0,0 +1,21 @@ +// +// NavigationItemModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 5/18/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol NavigationItemModelProtocol { + var title: String? { get set } + var hidden: Bool { get set } + var backgroundColor: Color? { get set } + var translucent: Bool { get set } + var tintColor: Color { get set } + var line: LineModel? { get set } + var backButton: NavigationItemButtonModel? { get set } + var additionalLeftButtons: [NavigationItemButtonModel]? { get set } + var additionalRightButtons: [NavigationItemButtonModel]? { get set } +} diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/PanelNavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/PanelNavigationItemModelProtocol.swift new file mode 100644 index 00000000..8f599be4 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/PanelNavigationItemModelProtocol.swift @@ -0,0 +1,14 @@ +// +// PanelNavigationItemModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 5/18/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol PanelNavigationItemModelProtocol { + var showLeftPanelButton: Bool { get set } + var showRightPanelButton: Bool { get set } +} diff --git a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift index 579a612e..d6851d78 100644 --- a/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift +++ b/MVMCoreUI/BaseControllers/MVMControllerModelProtocol.swift @@ -5,6 +5,7 @@ // Created by Scott Pfeil on 3/16/20. // Copyright © 2020 Verizon Wireless. All rights reserved. // +// A convenience aggregate for common MVM templates. import Foundation diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index f278236c..3497075f 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -40,6 +40,7 @@ import UIKit return navigationController } + /// Convenience function for setting navigation bar with model. public static func set(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { viewController.navigationItem.title = navigationItemModel.title viewController.navigationItem.accessibilityLabel = navigationItemModel.title @@ -54,13 +55,12 @@ import UIKit // Have the navigation title match the tint color navigationController.navigationBar.titleTextAttributes?.updateValue(tint, forKey: .foregroundColor) - // Update icons if main navigation controller. - if navigationController == MVMCoreUISession.sharedGlobal()?.navigationController, - navigationController.topViewController == viewController { - // Update line. - MVMCoreUISession.sharedGlobal()?.navigationController?.separatorView?.setStyle(navigationItemModel.line?.type ?? .standard) + // Update line. + if let navigationController = navigationController as? NavigationController { + navigationController.separatorView?.setStyle(navigationItemModel.line?.type ?? .standard) } + // Update icons if main navigation controller. if navigationController == MVMCoreUISplitViewController.main()?.navigationController, navigationController.topViewController == viewController { // Update Panels diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h index 0c252a08..e70a8b8b 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.h @@ -124,8 +124,8 @@ typedef NS_ENUM(NSInteger, MFNumberOfDrawers) { - (nullable UIImage *)imageForBackButton; // Can overide to provide other global buttons to be on the navigation bar. -- (nullable NSArray *)additionalLeftButtons; -- (nullable NSArray *)additionalRightButtons; +- (nullable NSArray *)additionalLeftButtonsForViewController:(nonnull UIViewController *)viewController; +- (nullable NSArray *)additionalRightButtonsForViewController:(nonnull UIViewController *)viewController; // The width of the panel when it is permanently extended. Default 320. - (CGFloat)leftPanelExtendedWidth; diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m index e93ad613..f5c07d04 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m @@ -106,11 +106,16 @@ CGFloat const PanelAnimationDuration = 0.2; return (width > 2000 ? MFTwoDrawer : (width > 1000 ? MFOneDrawer : MFNoDrawer)); } -- (nullable NSArray *)additionalLeftButtons { +- (nullable NSArray *)additionalLeftButtonsForViewController:(nonnull UIViewController *)viewController { + if (![viewController conformsToProtocol:@protocol(MVMCoreViewControllerProtocol)]) { + return nil; + } + UIViewController *controller = (UIViewController *)viewController; + controller.page return nil; } -- (nullable NSArray *)additionalRightButtons { +- (nullable NSArray *)additionalRightButtonsForViewController:(nonnull UIViewController *)viewController { return nil; } @@ -239,7 +244,7 @@ CGFloat const PanelAnimationDuration = 0.2; if ((accessible && !extended) && self.leftPanelButton) { [leftBarButtonItems addObject:self.leftPanelButton]; } - NSArray *extraButtons = [self additionalLeftButtons]; + NSArray *extraButtons = [self additionalLeftButtonsForViewController:(nonnull UIViewController *)viewController]; if (extraButtons) { [leftBarButtonItems addObjectsFromArray:extraButtons]; } @@ -410,7 +415,7 @@ CGFloat const PanelAnimationDuration = 0.2; if ((accessible && !extended) && self.rightPanelButton) { [navigationItems addObject:self.rightPanelButton]; } - NSArray *extraButtons = [self additionalRightButtons]; + NSArray *extraButtons = [self additionalRightButtonsForViewController:(nonnull UIViewController *)viewController]; if (extraButtons) { [navigationItems addObjectsFromArray:extraButtons]; } From d4fd6d8415433176f48f5fedbc6afa051b6fce77 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 18 May 2020 15:32:56 -0400 Subject: [PATCH 29/43] removed unneeded func --- MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift index 0556245d..e08f3843 100644 --- a/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/TextFields/TextViewEntryField.swift @@ -217,10 +217,6 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele showError = !isValid } - override func shouldShowError(_ showError: Bool) { - super.shouldShowError(showError) - } - //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- From cde8218e43db95b2b2d9b52fe54927eea91f1f80 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 18 May 2020 21:10:50 -0400 Subject: [PATCH 30/43] navigation item button fixes --- MVMCoreUI.xcodeproj/project.pbxproj | 12 ++-- .../NavigationBar/NavigationItemModel.swift | 6 +- .../PanelNavigationItemModelProtocol.swift | 14 ++++ MVMCoreUI/BaseClasses/BarButtonItem.swift | 71 ++++++++++++++++++- .../BaseControllers/ViewController.swift | 20 ++++-- .../Containers/NavigationController.swift | 9 ++- .../MVMCoreUISplitViewController.m | 31 +++++--- 7 files changed, 139 insertions(+), 24 deletions(-) create mode 100644 MVMCoreUI/Atomic/Protocols/ModelProtocols/PanelNavigationItemModelProtocol.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 7cd0c44e..0d6a0f45 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -269,9 +269,9 @@ D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */; }; D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */; }; D236E5B7242007C500C38625 /* MVMControllerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */; }; + D23EA7E82473654300D60C34 /* PanelNavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7E72473654300D60C34 /* PanelNavigationItemModelProtocol.swift */; }; D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; }; D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */; }; - D2509ED32472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED22472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift */; }; D2509ED62472EE2F001BFB9D /* NavigationItemButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2509ED52472EE2F001BFB9D /* NavigationItemButtonModel.swift */; }; D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.swift */; }; D253BB9C245874F8002DE544 /* BGImageMolecule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253BB9B245874F8002DE544 /* BGImageMolecule.swift */; }; @@ -296,6 +296,7 @@ D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */; }; + D272F5F92473163100BD1A8F /* BarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D272F5F82473163100BD1A8F /* BarButtonItem.swift */; }; D274CA332236A78900B01B62 /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D274CA322236A78900B01B62 /* FooterView.swift */; }; D2755D7B23689C7500485468 /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2755D7A23689C7500485468 /* TableViewCell.swift */; }; D27CD40E2322EEAF00C1DC07 /* TabsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */; }; @@ -689,9 +690,9 @@ D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescription.swift; sourceTree = ""; }; D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescriptionModel.swift; sourceTree = ""; }; D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMControllerModelProtocol.swift; sourceTree = ""; }; + D23EA7E72473654300D60C34 /* PanelNavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelNavigationItemModelProtocol.swift; sourceTree = ""; }; D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationItemModelProtocol.swift; sourceTree = ""; }; - D2509ED22472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanelNavigationItemModelProtocol.swift; sourceTree = ""; }; D2509ED52472EE2F001BFB9D /* NavigationItemButtonModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemButtonModel.swift; sourceTree = ""; }; D253BB9B245874F8002DE544 /* BGImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMolecule.swift; sourceTree = ""; }; D253BB9D2458751F002DE544 /* BGImageMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMoleculeModel.swift; sourceTree = ""; }; @@ -715,6 +716,7 @@ D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxCollectionViewCell.swift; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; + D272F5F82473163100BD1A8F /* BarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonItem.swift; sourceTree = ""; }; D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; D2755D7A23689C7500485468 /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; }; D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsTableViewCell.swift; sourceTree = ""; }; @@ -871,7 +873,7 @@ D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */, D2092354244FA0FD0044AD09 /* ThreeLayerTemplateModelProtocol.swift */, D2509ED02472ED9B001BFB9D /* NavigationItemModelProtocol.swift */, - D2509ED22472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift */, + D23EA7E72473654300D60C34 /* PanelNavigationItemModelProtocol.swift */, ); path = ModelProtocols; sourceTree = ""; @@ -1803,6 +1805,7 @@ D264FAA92440F97600D98315 /* CollectionView.swift */, 0A5D59C323AD488600EFD9E9 /* Protocols */, 0A7918F423F5E7EA00772FF4 /* ImageView.swift */, + D272F5F82473163100BD1A8F /* BarButtonItem.swift */, ); path = BaseClasses; sourceTree = ""; @@ -1988,6 +1991,7 @@ D21B7F602437C5BC00051ABF /* MoleculeStackView.swift in Sources */, 0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */, AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */, + D272F5F92473163100BD1A8F /* BarButtonItem.swift in Sources */, 0A9D09202433796500D2E6C0 /* BarsIndicatorView.swift in Sources */, D2E2A99423D8CCBC000B42E6 /* HeadlineBodyLinkModel.swift in Sources */, 01004F3022721C3800991ECC /* RadioButton.swift in Sources */, @@ -2104,6 +2108,7 @@ 01509D952327ED1900EF99AA /* HeadlineBodyLinkToggle.swift in Sources */, 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, + D23EA7E82473654300D60C34 /* PanelNavigationItemModelProtocol.swift in Sources */, 94F6516D2437954100631BF9 /* Tabs.swift in Sources */, 5248BFEC23F12E350059236A /* ListThreeColumnPlanDataDivider.swift in Sources */, 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */, @@ -2166,7 +2171,6 @@ D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */, 8D448E5524050A46006211BB /* ListOneColumnFullWidthTextAllTextAndLinksModel.swift in Sources */, 94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */, - D2509ED32472EDC8001BFB9D /* PanelNavigationItemModelProtocol.swift in Sources */, 944589212385D6E900DE9FD4 /* DashLineModel.swift in Sources */, D2E2A99623D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift in Sources */, C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift index 50ae62d6..38dee0d2 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemModel.swift @@ -19,12 +19,14 @@ public class NavigationItemModel: NavigationItemModelProtocol, PanelNavigationIt public var translucent: Bool public var tintColor: Color public var line: LineModel? - public var showLeftPanelButton: Bool - public var showRightPanelButton: Bool public var backButton: NavigationItemButtonModel? public var additionalLeftButtons: [NavigationItemButtonModel]? public var additionalRightButtons: [NavigationItemButtonModel]? + // Legacy, will remove once menu is gone. + public var showLeftPanelButton: Bool + public var showRightPanelButton: Bool + public init() { hidden = false translucent = false diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/PanelNavigationItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/PanelNavigationItemModelProtocol.swift new file mode 100644 index 00000000..8f599be4 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/PanelNavigationItemModelProtocol.swift @@ -0,0 +1,14 @@ +// +// PanelNavigationItemModelProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 5/18/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol PanelNavigationItemModelProtocol { + var showLeftPanelButton: Bool { get set } + var showRightPanelButton: Bool { get set } +} diff --git a/MVMCoreUI/BaseClasses/BarButtonItem.swift b/MVMCoreUI/BaseClasses/BarButtonItem.swift index d750c79a..7ee850bc 100644 --- a/MVMCoreUI/BaseClasses/BarButtonItem.swift +++ b/MVMCoreUI/BaseClasses/BarButtonItem.swift @@ -6,4 +6,73 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation +public typealias BarButtonAction = (BarButtonItem) -> () + +@objcMembers open class BarButtonItem: UIBarButtonItem, MFButtonProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + private var buttonAction: BarButtonAction? + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + open weak var buttonDelegate: ButtonDelegateProtocol? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + /// Creates the item with the passed in action. + public static func create(with image: UIImage, actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { + let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock)) + button.set(with: actionModel, delegateObject: delegateObject, additionalData: additionalData) + return button + } + + /// Creates the item with the passed in action map. + public static func create(with image: UIImage, actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { + let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock)) + button.set(with: actionMap, delegateObject: delegateObject, additionalData: additionalData) + return button + } + + /// Creates the item with the passed in action. + public static func create(with image: UIImage, action: @escaping BarButtonAction) -> Self { + let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock)) + button.buttonAction = action + return button + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + @objc func callActionBlock(_ sender: BarButtonItem) { + buttonAction?(self) + } + + open func set(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + buttonDelegate = delegateObject?.buttonDelegate + buttonAction = { [weak self] sender in + guard let self = self else { return } + if let data = try? actionModel.encode(using: JSONEncoder()), + let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any], + delegateObject?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + + open func set(with actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + buttonDelegate = delegateObject?.buttonDelegate + buttonAction = { [weak self] sender in + guard let self = self, + delegateObject?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true else { return } + MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) + } + } +} + diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index c44a8d47..25a25e80 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -158,18 +158,26 @@ import UIKit needsUpdateUI = true view.setNeedsLayout() } - - /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, - open func handleNewData() { - // TODO: remove legacy. Temporary, convert legacy to navigation model. - var navigationModel = pageModel?.navigationItem ?? NavigationItemModel() + + /// Creates a legacy navigation model. + open func createDefaultLegacyNavigationModel() -> NavigationItemModel { + let navigationModel = NavigationItemModel() navigationModel.title = pageModel?.screenHeading navigationModel.showLeftPanelButton = isMasterInitiallyAccessible() navigationModel.showRightPanelButton = isSupportInitiallyAccessible() if /*(self as? MVMCoreUITabBarPageControlViewController) != nil ||*/ manager != nil || loadObject?.requestParameters?.tabWasPressed ?? false == true { navigationModel.line = LineModel(type: .none) } - pageModel?.navigationItem = navigationModel + return navigationModel + } + + /// Processes any new data. Called after the page is loaded the first time and on response updates for this page, + open func handleNewData() { + // TODO: remove legacy. Temporary, convert legacy to navigation model. + if pageModel?.navigationItem == nil { + pageModel?.navigationItem = createDefaultLegacyNavigationModel() + } + if formValidator == nil { let rules = pageModel?.formRules formValidator = FormValidator(rules) diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index 4150da12..5402cae1 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -63,10 +63,13 @@ import UIKit // Update icons if main navigation controller. if navigationController == MVMCoreUISplitViewController.main()?.navigationController, navigationController.topViewController == viewController { - // Update Panels - MVMCoreUISplitViewController.main()?.setLeftPanelIsAccessible(navigationItemModel.showLeftPanelButton , for: viewController) - MVMCoreUISplitViewController.main()?.setRightPanelIsAccessible(navigationItemModel.showRightPanelButton , for: viewController) MVMCoreUISession.sharedGlobal()?.splitViewController?.setNavigationIconColor(tint) + + // Update Panels + if let model = navigationItemModel as? PanelNavigationItemModelProtocol { + MVMCoreUISplitViewController.main()?.setLeftPanelIsAccessible(model.showLeftPanelButton, for: viewController) + MVMCoreUISplitViewController.main()?.setRightPanelIsAccessible(model.showRightPanelButton, for: viewController) + } } } } diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m index f5c07d04..dfc2fda3 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m @@ -12,6 +12,7 @@ @import MVMCore.MVMCoreViewManagerProtocol; @import MVMCore.MVMCoreObject; @import MVMCore.MVMCoreActionUtility; +@import MVMCore.NSDictionary_MFConvenience; #import "MVMCoreUIUtility.h" #import "UIColor+MFConvenience.h" #import "NSLayoutConstraint+MFConvenience.h" @@ -106,17 +107,31 @@ CGFloat const PanelAnimationDuration = 0.2; return (width > 2000 ? MFTwoDrawer : (width > 1000 ? MFOneDrawer : MFNoDrawer)); } -- (nullable NSArray *)additionalLeftButtonsForViewController:(nonnull UIViewController *)viewController { - if (![viewController conformsToProtocol:@protocol(MVMCoreViewControllerProtocol)]) { - return nil; +- (nullable NSArray *)createNavigationItemsFrom:(nonnull NSArray *)JSONlist delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject { + if (JSONlist.count == 0) { return nil; } + NSMutableArray *items = [NSMutableArray arrayWithCapacity:JSONlist.count]; + for (NSDictionary *itemData in JSONlist) { + UIImage *image = [UIImage imageNamed:[itemData string:@"imageName"] inBundle:[[MVMCoreCache sharedCache] bundleToUseForImages] compatibleWithTraitCollection:nil]; + BarButtonItem *item = [BarButtonItem createWith:image actionMap:[itemData dict:@"action"] delegateObject:delegateObject additionalData:nil]; + [items addObject:item]; } + return items; +} + +- (nullable NSArray *)additionalLeftButtonsForViewController:(nonnull UIViewController *)viewController { + if (![viewController conformsToProtocol:@protocol(MVMCoreViewControllerProtocol)]) { return nil; } UIViewController *controller = (UIViewController *)viewController; - controller.page - return nil; + NSArray *items = [controller.loadObject.pageJSON arrayForChainOfKeysOrIndexes:@[@"navigationItem",@"additionalLeftButtons"]]; + DelegateObject *delegate = [controller delegateObject]; + return [self createNavigationItemsFrom:items delegateObject:[delegate isKindOfClass:[MVMCoreUIDelegateObject class]] ? (MVMCoreUIDelegateObject *)delegate : nil]; } - (nullable NSArray *)additionalRightButtonsForViewController:(nonnull UIViewController *)viewController { - return nil; + if (![viewController conformsToProtocol:@protocol(MVMCoreViewControllerProtocol)]) { return nil; } + UIViewController *controller = (UIViewController *)viewController; + NSArray *items = [controller.loadObject.pageJSON arrayForChainOfKeysOrIndexes:@[@"navigationItem",@"additionalRightButtons"]]; + DelegateObject *delegate = [controller delegateObject]; + return [self createNavigationItemsFrom:items delegateObject:[delegate isKindOfClass:[MVMCoreUIDelegateObject class]] ? (MVMCoreUIDelegateObject *)delegate : nil]; } - (CGFloat)leftPanelExtendedWidth { @@ -244,7 +259,7 @@ CGFloat const PanelAnimationDuration = 0.2; if ((accessible && !extended) && self.leftPanelButton) { [leftBarButtonItems addObject:self.leftPanelButton]; } - NSArray *extraButtons = [self additionalLeftButtonsForViewController:(nonnull UIViewController *)viewController]; + NSArray *extraButtons = [self additionalLeftButtonsForViewController:viewController]; if (extraButtons) { [leftBarButtonItems addObjectsFromArray:extraButtons]; } @@ -415,7 +430,7 @@ CGFloat const PanelAnimationDuration = 0.2; if ((accessible && !extended) && self.rightPanelButton) { [navigationItems addObject:self.rightPanelButton]; } - NSArray *extraButtons = [self additionalRightButtonsForViewController:(nonnull UIViewController *)viewController]; + NSArray *extraButtons = [self additionalRightButtonsForViewController:viewController]; if (extraButtons) { [navigationItems addObjectsFromArray:extraButtons]; } From fc8f189b77a7ae394c050f3af59f05fa195fe9ab Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 18 May 2020 21:54:21 -0400 Subject: [PATCH 31/43] fix funny defect --- MVMCoreUI/BaseControllers/ViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 25a25e80..782deae2 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -175,7 +175,8 @@ import UIKit open func handleNewData() { // TODO: remove legacy. Temporary, convert legacy to navigation model. if pageModel?.navigationItem == nil { - pageModel?.navigationItem = createDefaultLegacyNavigationModel() + let navigationItem = createDefaultLegacyNavigationModel() + pageModel?.navigationItem = navigationItem } if formValidator == nil { From cbe1c043867987ba1e7d2ea90c04e26a9646c893 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 19 May 2020 10:42:08 -0400 Subject: [PATCH 32/43] fix to action block MF call super --- MVMCoreUI/BaseClasses/BarButtonItem.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/BaseClasses/BarButtonItem.swift b/MVMCoreUI/BaseClasses/BarButtonItem.swift index 7ee850bc..9c09016b 100644 --- a/MVMCoreUI/BaseClasses/BarButtonItem.swift +++ b/MVMCoreUI/BaseClasses/BarButtonItem.swift @@ -27,21 +27,21 @@ public typealias BarButtonAction = (BarButtonItem) -> () /// Creates the item with the passed in action. public static func create(with image: UIImage, actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { - let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock)) + let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock(_:))) button.set(with: actionModel, delegateObject: delegateObject, additionalData: additionalData) return button } /// Creates the item with the passed in action map. public static func create(with image: UIImage, actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { - let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock)) + let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock(_:))) button.set(with: actionMap, delegateObject: delegateObject, additionalData: additionalData) return button } /// Creates the item with the passed in action. public static func create(with image: UIImage, action: @escaping BarButtonAction) -> Self { - let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock)) + let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock(_:))) button.buttonAction = action return button } From bb506717114c48c242f352ba4d9ba18e587a2f87 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 19 May 2020 12:48:56 -0400 Subject: [PATCH 33/43] target issue fix --- MVMCoreUI/BaseClasses/BarButtonItem.swift | 38 +++++++++++++---------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/MVMCoreUI/BaseClasses/BarButtonItem.swift b/MVMCoreUI/BaseClasses/BarButtonItem.swift index 9c09016b..51142a5a 100644 --- a/MVMCoreUI/BaseClasses/BarButtonItem.swift +++ b/MVMCoreUI/BaseClasses/BarButtonItem.swift @@ -8,41 +8,51 @@ public typealias BarButtonAction = (BarButtonItem) -> () -@objcMembers open class BarButtonItem: UIBarButtonItem, MFButtonProtocol { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- +@objc fileprivate class ActionDelegate: NSObject { + fileprivate var buttonAction: BarButtonAction? + @objc fileprivate func callActionBlock(_ sender: BarButtonItem) { + buttonAction?(sender) + } +} - private var buttonAction: BarButtonAction? +@objcMembers open class BarButtonItem: UIBarButtonItem, MFButtonProtocol { //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- open weak var buttonDelegate: ButtonDelegateProtocol? - + private var actionObject: ActionDelegate? + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- + public static func create(with image: UIImage) -> Self { + let actionObject = ActionDelegate() + let button = self.init(image: image, style: .plain, target: actionObject, action: #selector(actionObject.callActionBlock(_:))) + button.actionObject = actionObject + return button + } + /// Creates the item with the passed in action. public static func create(with image: UIImage, actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { - let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock(_:))) + let button = create(with: image) button.set(with: actionModel, delegateObject: delegateObject, additionalData: additionalData) return button } /// Creates the item with the passed in action map. public static func create(with image: UIImage, actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { - let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock(_:))) + let button = create(with: image) button.set(with: actionMap, delegateObject: delegateObject, additionalData: additionalData) return button } /// Creates the item with the passed in action. public static func create(with image: UIImage, action: @escaping BarButtonAction) -> Self { - let button = self.init(image: image, style: .plain, target: self, action: #selector(callActionBlock(_:))) - button.buttonAction = action + let button = create(with: image) + button.actionObject?.buttonAction = action return button } @@ -50,13 +60,9 @@ public typealias BarButtonAction = (BarButtonItem) -> () // MARK: - Methods //-------------------------------------------------- - @objc func callActionBlock(_ sender: BarButtonItem) { - buttonAction?(self) - } - open func set(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { buttonDelegate = delegateObject?.buttonDelegate - buttonAction = { [weak self] sender in + actionObject?.buttonAction = { [weak self] sender in guard let self = self else { return } if let data = try? actionModel.encode(using: JSONEncoder()), let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any], @@ -68,7 +74,7 @@ public typealias BarButtonAction = (BarButtonItem) -> () open func set(with actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { buttonDelegate = delegateObject?.buttonDelegate - buttonAction = { [weak self] sender in + actionObject?.buttonAction = { [weak self] sender in guard let self = self, delegateObject?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true else { return } MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) From d203285c738a3f82fdf22429926f38128382650e Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 19 May 2020 19:05:55 -0400 Subject: [PATCH 34/43] Cleanup navigation button creation. --- .../NavigationItemButtonModel.swift | 6 +++ MVMCoreUI/BaseClasses/BarButtonItem.swift | 8 +-- .../Containers/NavigationController.swift | 54 ++++++++++++++----- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift index 9110bfe9..7a81cb5b 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift @@ -32,4 +32,10 @@ public class NavigationItemButtonModel: Codable { try container.encode(imageName, forKey: .imageName) try container.encodeModel(action, forKey: .action) } + + /// Convenience function that creates a BarButtonItem for the model. + public func createNavigationItem(delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> BarButtonItem { + let image = UIImage(named: imageName, in: MVMCoreCache.shared()?.bundleToUseForImages(), compatibleWith: nil) + return BarButtonItem.create(with: image, actionModel: action, delegateObject: delegateObject, additionalData: additionalData) + } } diff --git a/MVMCoreUI/BaseClasses/BarButtonItem.swift b/MVMCoreUI/BaseClasses/BarButtonItem.swift index 51142a5a..a2a2e9e6 100644 --- a/MVMCoreUI/BaseClasses/BarButtonItem.swift +++ b/MVMCoreUI/BaseClasses/BarButtonItem.swift @@ -28,7 +28,7 @@ public typealias BarButtonAction = (BarButtonItem) -> () // MARK: - Initializers //-------------------------------------------------- - public static func create(with image: UIImage) -> Self { + public static func create(with image: UIImage?) -> Self { let actionObject = ActionDelegate() let button = self.init(image: image, style: .plain, target: actionObject, action: #selector(actionObject.callActionBlock(_:))) button.actionObject = actionObject @@ -36,21 +36,21 @@ public typealias BarButtonAction = (BarButtonItem) -> () } /// Creates the item with the passed in action. - public static func create(with image: UIImage, actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { + public static func create(with image: UIImage?, actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { let button = create(with: image) button.set(with: actionModel, delegateObject: delegateObject, additionalData: additionalData) return button } /// Creates the item with the passed in action map. - public static func create(with image: UIImage, actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { + public static func create(with image: UIImage?, actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) -> Self { let button = create(with: image) button.set(with: actionMap, delegateObject: delegateObject, additionalData: additionalData) return button } /// Creates the item with the passed in action. - public static func create(with image: UIImage, action: @escaping BarButtonAction) -> Self { + public static func create(with image: UIImage?, action: @escaping BarButtonAction) -> Self { let button = create(with: image) button.actionObject?.buttonAction = action return button diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index 5402cae1..67506221 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -11,35 +11,60 @@ import UIKit @objcMembers open class NavigationController: UINavigationController { public var separatorView: Line? + /// Getter for the main navigation controller public static func navigationController() -> Self? { return MVMCoreActionUtility.initializerClassCheck(MVMCoreUISession.sharedGlobal()?.navigationController, classToVerify: self) as? Self } - public static func style(_ navigationBar: UINavigationBar) { + /// Provides MVM styling to the navigation bar. Returns a reference to the line. + public static func style(_ navigationBar: UINavigationBar) -> Line { UIColor.mfSetBackgroundColor(forNavigationBar: .white, navigationBar: navigationBar, transparent: false) navigationBar.shadowImage = UIImage() navigationBar.isOpaque = true navigationBar.tintColor = .black navigationBar.titleTextAttributes = [NSAttributedString.Key.font: MFStyler.fontBoldBodySmall(false)]; + return Line(pinTo: navigationBar, edge: .bottom, useMargin: false) } + /// Sets up the application with a navigation controller public static func setupNavigationController() -> Self? { let navigationController = self.init() - style(navigationController.navigationBar) - navigationController.separatorView = Line(pinTo: navigationController.navigationBar, edge: .bottom, useMargin: false) - navigationController.separatorView?.setStyle(.standard) + navigationController.separatorView = style(navigationController.navigationBar) MVMCoreUISession.sharedGlobal()?.navigationController = navigationController MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = navigationController MVMCoreNavigationHandler.shared()?.navigationController = navigationController return navigationController } + /// Sets up the application with a navigation controller as the main container. public static func setupNavigationControllerAsMainController() -> Self? { guard let navigationController = setupNavigationController() else { return nil } MVMCoreUISession.sharedGlobal()?.setup(asStandardLoadViewDelegate: navigationController) return navigationController } + /// Convenience function for setting the navigation buttons. + public static func setNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { + let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject + var items: [UIBarButtonItem] = [] + if let backButtonModel = navigationItemModel.backButton, + navigationController.viewControllers.count > 1 { + items.append(backButtonModel.createNavigationItem(delegateObject: delegate, additionalData: nil)) + } + if let itemModels = navigationItemModel.additionalLeftButtons { + for item in itemModels { + items.append(item.createNavigationItem(delegateObject: delegate, additionalData: nil)) + } + viewController.navigationItem.leftBarButtonItems = items + } + if let itemModels = navigationItemModel.additionalRightButtons { + for item in itemModels { + items.append(item.createNavigationItem(delegateObject: delegate, additionalData: nil)) + } + viewController.navigationItem.rightBarButtonItems = items + } + } + /// Convenience function for setting navigation bar with model. public static func set(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { viewController.navigationItem.title = navigationItemModel.title @@ -60,16 +85,21 @@ import UIKit navigationController.separatorView?.isHidden = navigationItemModel.line?.type ?? .standard == .none } + // Let legacy splitview controller handle buttons for now. + guard navigationController == MVMCoreUISplitViewController.main()?.splitViewController?.navigationController, + navigationController.topViewController == viewController else { + // Not the main split view controller, add buttons. + setNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) + return + } + // Update icons if main navigation controller. - if navigationController == MVMCoreUISplitViewController.main()?.navigationController, - navigationController.topViewController == viewController { - MVMCoreUISession.sharedGlobal()?.splitViewController?.setNavigationIconColor(tint) + MVMCoreUISession.sharedGlobal()?.splitViewController?.setNavigationIconColor(tint) - // Update Panels - if let model = navigationItemModel as? PanelNavigationItemModelProtocol { - MVMCoreUISplitViewController.main()?.setLeftPanelIsAccessible(model.showLeftPanelButton, for: viewController) - MVMCoreUISplitViewController.main()?.setRightPanelIsAccessible(model.showRightPanelButton, for: viewController) - } + // Update Panels + if let model = navigationItemModel as? PanelNavigationItemModelProtocol { + MVMCoreUISplitViewController.main()?.setLeftPanelIsAccessible(model.showLeftPanelButton, for: viewController) + MVMCoreUISplitViewController.main()?.setRightPanelIsAccessible(model.showRightPanelButton, for: viewController) } } } From 5a55312c9dea70fd3ceccac0924c3a59e9c70245 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Tue, 19 May 2020 19:57:02 -0400 Subject: [PATCH 35/43] typo fix --- MVMCoreUI/Containers/NavigationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index 67506221..170ce5b3 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -86,7 +86,7 @@ import UIKit } // Let legacy splitview controller handle buttons for now. - guard navigationController == MVMCoreUISplitViewController.main()?.splitViewController?.navigationController, + guard navigationController == MVMCoreUISplitViewController.main()?.navigationController, navigationController.topViewController == viewController else { // Not the main split view controller, add buttons. setNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) From 4453b24d4d7fdefd0e669644fe4500683df02608 Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Wed, 20 May 2020 16:00:44 +0530 Subject: [PATCH 36/43] 21041(iOS - List - Left Variable - Icon - All Text & Links) initial commit. Created Model and Molecule class. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 +++ MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 1 + .../ListLeftVariableIconAllTextLinks.swift | 54 +++++++++++++++++++ ...istLeftVariableIconAllTextLinksModel.swift | 49 +++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift create mode 100644 MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 08865ee9..ba954d23 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -198,6 +198,8 @@ AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA69AAF52445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift */; }; AA69AAF82445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA69AAF72445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift */; }; AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */; }; + AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA99724F2475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift */; }; + AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA997251247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift */; }; AAA74A172410C04600080241 /* HeadersH2NoButtonsBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */; }; AAA74A192410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */; }; AAB7EDEF246ADA1600E54929 /* ListProgressBarThinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB7EDEE246ADA1600E54929 /* ListProgressBarThinModel.swift */; }; @@ -615,6 +617,8 @@ AA69AAF52445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxBodyText.swift; sourceTree = ""; }; AA69AAF72445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxBodyTextModel.swift; sourceTree = ""; }; AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchCollectionViewCell.swift; sourceTree = ""; }; + AA99724F2475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableIconAllTextLinksModel.swift; sourceTree = ""; }; + AA997251247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableIconAllTextLinks.swift; sourceTree = ""; }; AAA74A162410C04600080241 /* HeadersH2NoButtonsBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2NoButtonsBodyText.swift; sourceTree = ""; }; AAA74A182410C05800080241 /* HeadersH2NoButtonsBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2NoButtonsBodyTextModel.swift; sourceTree = ""; }; AAB7EDEE246ADA1600E54929 /* ListProgressBarThinModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListProgressBarThinModel.swift; sourceTree = ""; }; @@ -1283,6 +1287,8 @@ 8D24041023E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift */, 0A6682A32434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift */, 0A6682A12434DB4F00AD3CA1 /* ListLeftVariableRadioButtonBodyText.swift */, + AA99724F2475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift */, + AA997251247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift */, ); path = LeftVariable; sourceTree = ""; @@ -2151,6 +2157,7 @@ D2E2A99623D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift in Sources */, C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, 011D958524042432000E3791 /* RulesProtocol.swift in Sources */, + AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */, D29DF27A21E7A533003B2FB9 /* MVMCoreUISession.m in Sources */, @@ -2244,6 +2251,7 @@ 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */, 013F801923FB4A8E00AD8013 /* UIContentMode+Extension.swift in Sources */, 525239C22407BD1000454969 /* ListTwoColumnPriceDetails.swift in Sources */, + AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index dbff52fe..e46cfed0 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -146,6 +146,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableRadioButtonAndPaymentMethod.self, viewModelClass: ListLeftVariableRadioButtonAndPaymentMethodModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableRadioButtonBodyText.self, viewModelClass: ListLeftVariableRadioButtonBodyTextModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableCheckboxBodyText.self, viewModelClass: ListLeftVariableCheckboxBodyTextModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableIconAllTextLinks.self, viewModelClass: ListLeftVariableIconAllTextLinksModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRVWheel.self, viewModelClass: ListRVWheelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariablePayments.self, viewModelClass: ListRightVariablePaymentsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariableTotalData.self, viewModelClass: ListRightVariableTotalDataModel.self) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift new file mode 100644 index 00000000..f53134a1 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift @@ -0,0 +1,54 @@ +// +// ListLeftVariableIconAllTextLinks.swift +// MVMCoreUI +// +// Created by Lekshmi S on 20/05/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers open class ListLeftVariableIconAllTextLinks: TableViewCell { + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + public let leftImage = LoadImageView() + public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(frame: .zero) + public var stack: Stack + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + stack = Stack.createStack(with: [(view: leftImage, model: StackItemModel(horizontalAlignment: .fill)), (view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .leading))], axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + override open func setupView() { + super.setupView() + leftImage.addSizeConstraintsForAspectRatio = true + leftImage.imageView.contentMode = .scaleAspectFit + addMolecule(stack) + stack.restack() + } + + //------------------------------------------------------ + // MARK: - Molecule + //------------------------------------------------------ + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableIconAllTextLinksModel else { return } + leftImage.set(with: model.image, delegateObject, additionalData) + eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 140 + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift new file mode 100644 index 00000000..8e35984a --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift @@ -0,0 +1,49 @@ +// +// ListLeftVariableIconAllTextLinksModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 20/05/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listLVImgAll" + public var image: ImageViewModel + public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel + + override public func setDefaults() { + super.setDefaults() + if image.width == nil, image.height == nil { + image.width = 30 + image.height = 30 + } + } + + public init(image: ImageViewModel, eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel) { + self.image = image + self.eyebrowHeadlineBodyLink = eyebrowHeadlineBodyLink + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case image + case eyebrowHeadlineBodyLink + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + image = try typeContainer.decode(ImageViewModel.self, forKey: .image) + eyebrowHeadlineBodyLink = try typeContainer.decode(EyebrowHeadlineBodyLinkModel.self, forKey: .eyebrowHeadlineBodyLink) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(image, forKey: .image) + try container.encode(eyebrowHeadlineBodyLink, forKey: .eyebrowHeadlineBodyLink) + } +} From 5bda356dc9cb8dcf48d3171c31ee141c186eeafd Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 20 May 2020 11:00:02 -0400 Subject: [PATCH 37/43] Back button fix --- .../NavigationItemButtonModel.swift | 2 +- .../Containers/NavigationController.swift | 6 +-- .../MVMCoreUISplitViewController.m | 46 ++++++++++++++----- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift index 7a81cb5b..26be6952 100644 --- a/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/NavigationBar/NavigationItemButtonModel.swift @@ -34,7 +34,7 @@ public class NavigationItemButtonModel: Codable { } /// Convenience function that creates a BarButtonItem for the model. - public func createNavigationItem(delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> BarButtonItem { + public func createNavigationItemButton(delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> BarButtonItem { let image = UIImage(named: imageName, in: MVMCoreCache.shared()?.bundleToUseForImages(), compatibleWith: nil) return BarButtonItem.create(with: image, actionModel: action, delegateObject: delegateObject, additionalData: additionalData) } diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index 170ce5b3..d62dec40 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -49,17 +49,17 @@ import UIKit var items: [UIBarButtonItem] = [] if let backButtonModel = navigationItemModel.backButton, navigationController.viewControllers.count > 1 { - items.append(backButtonModel.createNavigationItem(delegateObject: delegate, additionalData: nil)) + items.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } if let itemModels = navigationItemModel.additionalLeftButtons { for item in itemModels { - items.append(item.createNavigationItem(delegateObject: delegate, additionalData: nil)) + items.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } viewController.navigationItem.leftBarButtonItems = items } if let itemModels = navigationItemModel.additionalRightButtons { for item in itemModels { - items.append(item.createNavigationItem(delegateObject: delegate, additionalData: nil)) + items.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } viewController.navigationItem.rightBarButtonItems = items } diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m index dfc2fda3..d9814a1f 100644 --- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m +++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m @@ -107,15 +107,13 @@ CGFloat const PanelAnimationDuration = 0.2; return (width > 2000 ? MFTwoDrawer : (width > 1000 ? MFOneDrawer : MFNoDrawer)); } -- (nullable NSArray *)createNavigationItemsFrom:(nonnull NSArray *)JSONlist delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject { - if (JSONlist.count == 0) { return nil; } - NSMutableArray *items = [NSMutableArray arrayWithCapacity:JSONlist.count]; - for (NSDictionary *itemData in JSONlist) { - UIImage *image = [UIImage imageNamed:[itemData string:@"imageName"] inBundle:[[MVMCoreCache sharedCache] bundleToUseForImages] compatibleWithTraitCollection:nil]; - BarButtonItem *item = [BarButtonItem createWith:image actionMap:[itemData dict:@"action"] delegateObject:delegateObject additionalData:nil]; - [items addObject:item]; - } - return items; +- (nullable UIBarButtonItem *)getBackButtonForViewController:(nonnull UIViewController *)viewController { + if (![viewController conformsToProtocol:@protocol(MVMCoreViewControllerProtocol)]) { return self.backButton; } + UIViewController *controller = (UIViewController *)viewController; + NSDictionary *item = [controller.loadObject.pageJSON dictWithChainOfKeysOrIndexes:@[@"navigationItem",@"backButton"]]; + if (!item) { return self.backButton; } + DelegateObject *delegate = [controller delegateObject]; + return [self createNavigationItemButtonFrom:item delegateObject:[delegate isKindOfClass:[MVMCoreUIDelegateObject class]] ? (MVMCoreUIDelegateObject *)delegate : nil]; } - (nullable NSArray *)additionalLeftButtonsForViewController:(nonnull UIViewController *)viewController { @@ -123,7 +121,7 @@ CGFloat const PanelAnimationDuration = 0.2; UIViewController *controller = (UIViewController *)viewController; NSArray *items = [controller.loadObject.pageJSON arrayForChainOfKeysOrIndexes:@[@"navigationItem",@"additionalLeftButtons"]]; DelegateObject *delegate = [controller delegateObject]; - return [self createNavigationItemsFrom:items delegateObject:[delegate isKindOfClass:[MVMCoreUIDelegateObject class]] ? (MVMCoreUIDelegateObject *)delegate : nil]; + return [self createNavigationItemButtonsFrom:items delegateObject:[delegate isKindOfClass:[MVMCoreUIDelegateObject class]] ? (MVMCoreUIDelegateObject *)delegate : nil]; } - (nullable NSArray *)additionalRightButtonsForViewController:(nonnull UIViewController *)viewController { @@ -131,7 +129,7 @@ CGFloat const PanelAnimationDuration = 0.2; UIViewController *controller = (UIViewController *)viewController; NSArray *items = [controller.loadObject.pageJSON arrayForChainOfKeysOrIndexes:@[@"navigationItem",@"additionalRightButtons"]]; DelegateObject *delegate = [controller delegateObject]; - return [self createNavigationItemsFrom:items delegateObject:[delegate isKindOfClass:[MVMCoreUIDelegateObject class]] ? (MVMCoreUIDelegateObject *)delegate : nil]; + return [self createNavigationItemButtonsFrom:items delegateObject:[delegate isKindOfClass:[MVMCoreUIDelegateObject class]] ? (MVMCoreUIDelegateObject *)delegate : nil]; } - (CGFloat)leftPanelExtendedWidth { @@ -254,7 +252,13 @@ CGFloat const PanelAnimationDuration = 0.2; - (void)setLeftNavigationItemForViewController:(UIViewController * _Nonnull)viewController accessible:(BOOL)accessible extended:(BOOL)extended { NSMutableArray *leftBarButtonItems = [NSMutableArray array]; if ([viewController.navigationController.viewControllers count] > 1) { - [leftBarButtonItems addObject:self.backButton]; + UIBarButtonItem *button = [self getBackButtonForViewController:viewController]; + if (button) { + viewController.navigationItem.hidesBackButton = YES; + [leftBarButtonItems addObject:button]; + } else { + viewController.navigationItem.hidesBackButton = NO; + } } if ((accessible && !extended) && self.leftPanelButton) { [leftBarButtonItems addObject:self.leftPanelButton]; @@ -621,6 +625,24 @@ CGFloat const PanelAnimationDuration = 0.2; #pragma mark - Other Panel Functions +/// Convenience function, creates a BarButtonItem from button json +- (nonnull UIBarButtonItem *)createNavigationItemButtonFrom:(nonnull NSDictionary *)JSON delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject { + UIImage *image = [UIImage imageNamed:[JSON string:@"imageName"] inBundle:[[MVMCoreCache sharedCache] bundleToUseForImages] compatibleWithTraitCollection:nil]; + BarButtonItem *item = [BarButtonItem createWith:image actionMap:[JSON dict:@"action"] delegateObject:delegateObject additionalData:nil]; + return item; +} + +/// Convenience function, creates a list of BarButtonItems list of json +- (nullable NSArray *)createNavigationItemButtonsFrom:(nullable NSArray *)JSONlist delegateObject:(nullable MVMCoreUIDelegateObject *)delegateObject { + if (JSONlist.count == 0) { return nil; } + NSMutableArray *items = [NSMutableArray arrayWithCapacity:JSONlist.count]; + for (NSDictionary *itemData in JSONlist) { + UIBarButtonItem *item = [self createNavigationItemButtonFrom:itemData delegateObject:delegateObject]; + [items addObject:item]; + } + return items; +} + - (void)forceHideBothDrawers { [self hideBothDrawersShouldForceHide:YES]; } From 93e84cf3e417d6ada6a686f0aa348d7d2fdeae65 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 20 May 2020 17:25:49 -0400 Subject: [PATCH 38/43] first change --- MVMCoreUI/Atomic/Organisms/Carousel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index 4cf65f1b..30d4c21e 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -173,7 +173,7 @@ open class Carousel: View { numberOfPages = newMolecules.count molecules = newMolecules - if carouselModel?.loop ?? false && newMolecules.count > 2 { + if carouselModel?.loop ?? false && newMolecules.count > 1 { // Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell). loop = true From d40f41e7bb9671fcdbeb5596634a2f105539e286 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 20 May 2020 19:38:37 -0400 Subject: [PATCH 39/43] cleaning --- MVMCoreUI.xcodeproj/project.pbxproj | 4 ++++ .../Atomic/Molecules/Items/CarouselItem.swift | 2 +- MVMCoreUI/Atomic/Organisms/Carousel.swift | 18 ++++++++---------- MVMCoreUI/Atomic/Organisms/CarouselModel.swift | 8 ++++---- .../CarouselItemModelProtocol.swift | 4 +--- .../ModelProtocols/CarouselItemProtocol.swift | 15 +++++++++++++++ 6 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemProtocol.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index d7afb970..7b35fc55 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -266,6 +266,7 @@ D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */; }; D236E5B5241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */; }; D236E5B7242007C500C38625 /* MVMControllerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */; }; + D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */; }; D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; }; D253BB8A24574CC5002DE544 /* StackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D260106423D0CEA700764D80 /* StackModel.swift */; }; D253BB9C245874F8002DE544 /* BGImageMolecule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D253BB9B245874F8002DE544 /* BGImageMolecule.swift */; }; @@ -680,6 +681,7 @@ D236E5B2241FEB1000C38625 /* ListTwoColumnPriceDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescription.swift; sourceTree = ""; }; D236E5B3241FEB1000C38625 /* ListTwoColumnPriceDescriptionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTwoColumnPriceDescriptionModel.swift; sourceTree = ""; }; D236E5B6242007C500C38625 /* MVMControllerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMControllerModelProtocol.swift; sourceTree = ""; }; + D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselItemProtocol.swift; sourceTree = ""; }; D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; D253BB9B245874F8002DE544 /* BGImageMolecule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMolecule.swift; sourceTree = ""; }; D253BB9D2458751F002DE544 /* BGImageMoleculeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageMoleculeModel.swift; sourceTree = ""; }; @@ -850,6 +852,7 @@ children = ( D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */, 014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */, + D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */, 012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */, 012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */, 01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */, @@ -2226,6 +2229,7 @@ 013F801923FB4A8E00AD8013 /* UIContentMode+Extension.swift in Sources */, 525239C22407BD1000454969 /* ListTwoColumnPriceDetails.swift in Sources */, D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */, + D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */, D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */, D29DF11821E6805F003B2FB9 /* NSLayoutConstraint+MFConvenience.m in Sources */, 94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift index 3089596a..a37fe016 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/CarouselItem.swift @@ -8,7 +8,7 @@ import Foundation -open class CarouselItem: MoleculeCollectionViewCell { +open class CarouselItem: MoleculeCollectionViewCell, CarouselItemProtocol { open var allowsPeaking = false var peakingLeftArrow = UIImageView(image: MVMCoreUIUtility.imageNamed("peakingRightArrow")?.withRenderingMode(.alwaysTemplate)) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index 30d4c21e..9fa72b5d 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -36,7 +36,7 @@ open class Carousel: View { open var numberOfPages = 0 /// The models for the molecules. - var molecules: [MoleculeModelProtocol]? + var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]? /// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%. public var itemAlignment = UICollectionView.ScrollPosition.left @@ -176,11 +176,9 @@ open class Carousel: View { if carouselModel?.loop ?? false && newMolecules.count > 1 { // Sets up the row data with buffer cells on each side (for illusion of endless scroll... also has one more buffer cell on each side in case we can peek that cell). loop = true - - molecules?.insert(newMolecules.last!, at: 0) - molecules?.insert(newMolecules[(newMolecules.count - 2)], at: 0) - molecules?.append(newMolecules.first!) - molecules?.append(newMolecules[1]) + + molecules?.insert(contentsOf: newMolecules.suffix(2), at: 0) + molecules?.append(contentsOf: newMolecules.prefix(2)) } pageIndex = 0 @@ -259,15 +257,15 @@ open class Carousel: View { let visibleItemsPaths = collectionView.indexPathsForVisibleItems.sorted { $0.row < $1.row } if let firstItem = visibleItemsPaths.first, firstItem.row != currentIndex { - (collectionView.cellForItem(at: firstItem) as? CarouselItem)?.setPeaking(true, animated: true) + (collectionView.cellForItem(at: firstItem) as? CarouselItemProtocol)?.setPeaking(true, animated: true) } if let lastItem = visibleItemsPaths.last, lastItem.row != currentIndex { - (collectionView.cellForItem(at: lastItem) as? CarouselItem)?.setPeaking(true, animated: true) + (collectionView.cellForItem(at: lastItem) as? CarouselItemProtocol)?.setPeaking(true, animated: true) } } else { // Hide peaking. for item in collectionView.visibleCells { - (item as? CarouselItem)?.setPeaking(false, animated: true) + (item as? CarouselItemProtocol)?.setPeaking(false, animated: true) } } } @@ -301,7 +299,7 @@ extension Carousel: UICollectionViewDelegateFlowLayout { } open func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - (cell as? CarouselItem)?.setPeaking(false, animated: false) + (cell as? CarouselItemProtocol)?.setPeaking(false, animated: false) } } diff --git a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift index 857901e8..5ff753aa 100644 --- a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift +++ b/MVMCoreUI/Atomic/Organisms/CarouselModel.swift @@ -19,7 +19,7 @@ import UIKit } public var backgroundColor: Color? - public var molecules: [CarouselItemModel] + public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol] public var index: Int = 0 public var spacing: Float? public var border: Bool? @@ -29,7 +29,7 @@ import UIKit public var itemAlignment: UICollectionView.ScrollPosition? public var pagingMolecule: (CarouselPagingModelProtocol & MoleculeModelProtocol)? - public init(molecules: [CarouselItemModel]) { + public init(molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]) { self.molecules = molecules } @@ -57,7 +57,7 @@ import UIKit required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - molecules = try typeContainer.decode([CarouselItemModel].self, forKey: .molecules) + molecules = try typeContainer.decodeModels(codingKey: .molecules) index = try typeContainer.decodeIfPresent(Int.self, forKey: .index) ?? 0 backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) spacing = try typeContainer.decodeIfPresent(Float.self, forKey: .spacing) @@ -73,7 +73,7 @@ import UIKit var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encode(molecules, forKey: .molecules) + try container.encodeModels(molecules, forKey: .molecules) try container.encode(spacing, forKey: .spacing) try container.encode(border, forKey: .border) try container.encode(loop, forKey: .loop) diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift index 093c9337..198cf80d 100644 --- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemModelProtocol.swift @@ -9,7 +9,5 @@ import Foundation -public protocol CarouselItemModelProtocol: ContainerModelProtocol, MoleculeModelProtocol { - var peakingUI: Bool? { get } - var peakingArrowColor: Color? { get } +public protocol CarouselItemModelProtocol: ContainerModelProtocol { } diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemProtocol.swift new file mode 100644 index 00000000..4f49e5f7 --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/CarouselItemProtocol.swift @@ -0,0 +1,15 @@ +// +// CarouselItemProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 5/20/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public protocol CarouselItemProtocol: UICollectionViewCell { + + /// Notifies the cell if it is peaking or not. + func setPeaking(_ peaking: Bool, animated: Bool) +} From 79127889dd7e8fecaefbee688069cc54e8166d2d Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 20 May 2020 20:28:13 -0400 Subject: [PATCH 40/43] name change --- MVMCoreUI/BaseClasses/BarButtonItem.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/BaseClasses/BarButtonItem.swift b/MVMCoreUI/BaseClasses/BarButtonItem.swift index a2a2e9e6..ac26c040 100644 --- a/MVMCoreUI/BaseClasses/BarButtonItem.swift +++ b/MVMCoreUI/BaseClasses/BarButtonItem.swift @@ -22,7 +22,7 @@ public typealias BarButtonAction = (BarButtonItem) -> () //-------------------------------------------------- open weak var buttonDelegate: ButtonDelegateProtocol? - private var actionObject: ActionDelegate? + private var actionDelegate: ActionDelegate? //-------------------------------------------------- // MARK: - Initializers @@ -31,7 +31,7 @@ public typealias BarButtonAction = (BarButtonItem) -> () public static func create(with image: UIImage?) -> Self { let actionObject = ActionDelegate() let button = self.init(image: image, style: .plain, target: actionObject, action: #selector(actionObject.callActionBlock(_:))) - button.actionObject = actionObject + button.actionDelegate = actionObject return button } @@ -52,7 +52,7 @@ public typealias BarButtonAction = (BarButtonItem) -> () /// Creates the item with the passed in action. public static func create(with image: UIImage?, action: @escaping BarButtonAction) -> Self { let button = create(with: image) - button.actionObject?.buttonAction = action + button.actionDelegate?.buttonAction = action return button } @@ -62,11 +62,10 @@ public typealias BarButtonAction = (BarButtonItem) -> () open func set(with actionModel: ActionModelProtocol, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { buttonDelegate = delegateObject?.buttonDelegate - actionObject?.buttonAction = { [weak self] sender in - guard let self = self else { return } + actionDelegate?.buttonAction = { sender in if let data = try? actionModel.encode(using: JSONEncoder()), let actionMap = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init()) as? [AnyHashable: Any], - delegateObject?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { + delegateObject?.buttonDelegate?.button?(sender, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } @@ -74,9 +73,8 @@ public typealias BarButtonAction = (BarButtonItem) -> () open func set(with actionMap: [AnyHashable : Any], delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { buttonDelegate = delegateObject?.buttonDelegate - actionObject?.buttonAction = { [weak self] sender in - guard let self = self, - delegateObject?.buttonDelegate?.button?(self, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true else { return } + actionDelegate?.buttonAction = { sender in + guard delegateObject?.buttonDelegate?.button?(sender, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true else { return } MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } From 518672f4f37433061ea3ccbde7fbcb414842b6ec Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Wed, 20 May 2020 20:40:22 -0400 Subject: [PATCH 41/43] addressing review comments --- MVMCoreUI/Containers/NavigationController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Containers/NavigationController.swift b/MVMCoreUI/Containers/NavigationController.swift index d62dec40..3f7846a0 100644 --- a/MVMCoreUI/Containers/NavigationController.swift +++ b/MVMCoreUI/Containers/NavigationController.swift @@ -51,14 +51,14 @@ import UIKit navigationController.viewControllers.count > 1 { items.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } - if let itemModels = navigationItemModel.additionalLeftButtons { - for item in itemModels { + if let leftItemModels = navigationItemModel.additionalLeftButtons { + for item in leftItemModels { items.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } viewController.navigationItem.leftBarButtonItems = items } - if let itemModels = navigationItemModel.additionalRightButtons { - for item in itemModels { + if let rightItemModels = navigationItemModel.additionalRightButtons { + for item in rightItemModels { items.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } viewController.navigationItem.rightBarButtonItems = items From 5f1fa847db4f70f91915863cdf1cbe164de24dfc Mon Sep 17 00:00:00 2001 From: Kyle Matthew Hedden Date: Thu, 21 May 2020 15:09:04 -0400 Subject: [PATCH 42/43] squash feature/list_leftvariable_numberedlist_alltextlinks --- MVMCoreUI.xcodeproj/project.pbxproj | 8 +++ MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 1 + ...tVariableNumberedListAllTextAndLinks.swift | 60 +++++++++++++++++++ ...ableNumberedListAllTextAndLinksModel.swift | 42 +++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift create mode 100644 MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinksModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index b8fbede1..292e4157 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -114,6 +114,8 @@ 27F9736A246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */; }; 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */; }; 31BE15CC23D8924D00452370 /* CheckboxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BE15CA23D8924C00452370 /* CheckboxModel.swift */; }; + 32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F8804524765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift */; }; + 32F8804824765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F8804724765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift */; }; 522679C123FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */; }; 522679C223FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */; }; 52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52267A0623FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift */; }; @@ -537,6 +539,8 @@ 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenBrightnessModifierBehavior.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 = ""; }; + 32F8804524765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableNumberedListAllTextAndLinksModel.swift; sourceTree = ""; }; + 32F8804724765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableNumberedListAllTextAndLinks.swift; sourceTree = ""; }; 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinks.swift; sourceTree = ""; }; 522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinksModel.swift; sourceTree = ""; }; 52267A0623FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextAllTextAndLinks.swift; sourceTree = ""; }; @@ -1299,6 +1303,8 @@ 0A6682A12434DB4F00AD3CA1 /* ListLeftVariableRadioButtonBodyText.swift */, AA99724F2475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift */, AA997251247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift */, + 32F8804524765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift */, + 32F8804724765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift */, ); path = LeftVariable; sourceTree = ""; @@ -1971,6 +1977,7 @@ 94C661DA23CCF4FB00D9FE5B /* UIColor+Extension.swift in Sources */, D28A838123CCB0D800DFE4FC /* AccordionListItemModel.swift in Sources */, D2509ED62472EE2F001BFB9D /* NavigationItemButtonModel.swift in Sources */, + 32F8804824765C8400C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinks.swift in Sources */, DBC4391822442197001AB423 /* CaretView.swift in Sources */, C07065C42395677300FBF997 /* Link.swift in Sources */, 0A69F611241BDEA700F7231B /* RuleAnyRequiredModel.swift in Sources */, @@ -2180,6 +2187,7 @@ 944589212385D6E900DE9FD4 /* DashLineModel.swift in Sources */, D2E2A99623D8CF85000B42E6 /* HeadlineBodyLinkToggleModel.swift in Sources */, C6FA7D5323C77A4A00A3614A /* StringAndMoleculeStack.swift in Sources */, + 32F8804624765C6E00C2ACB3 /* ListLeftVariableNumberedListAllTextAndLinksModel.swift in Sources */, 011D958524042432000E3791 /* RulesProtocol.swift in Sources */, AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */, AA69AAF62445BF5700AF3D3B /* ListLeftVariableCheckboxBodyText.swift in Sources */, diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index e46cfed0..18b3fee7 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -147,6 +147,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableRadioButtonBodyText.self, viewModelClass: ListLeftVariableRadioButtonBodyTextModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableCheckboxBodyText.self, viewModelClass: ListLeftVariableCheckboxBodyTextModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableIconAllTextLinks.self, viewModelClass: ListLeftVariableIconAllTextLinksModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: ListLeftVariableNumberedListAllTextAndLinks.self, viewModelClass: ListLeftVariableNumberedListAllTextAndLinksModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRVWheel.self, viewModelClass: ListRVWheelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariablePayments.self, viewModelClass: ListRightVariablePaymentsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: ListRightVariableTotalData.self, viewModelClass: ListRightVariableTotalDataModel.self) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift new file mode 100644 index 00000000..e7a35cc6 --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift @@ -0,0 +1,60 @@ +// +// ListLeftVariableNumberedListAllTextAndLinks.swift +// MVMCoreUI +// +// Created by Subhankar Acharya on 21/05/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class ListLeftVariableNumberedListAllTextAndLinks: TableViewCell { + //----------------------------------------------------- + // MARK: - Outlets + //----------------------------------------------------- + public let leftLabel = Label.createLabelTitle2XLarge(true) + public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(frame: .zero) + public var stack: Stack + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + stack = Stack.createStack(with: [(view: leftLabel, model: StackItemModel(horizontalAlignment: .fill)), + (view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .leading))], + axis: .horizontal) + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + override open func setupView() { + super.setupView() + addMolecule(stack) + stack.restack() + } + + //------------------------------------------------------ + // MARK: - Molecule + //------------------------------------------------------ + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableNumberedListAllTextAndLinksModel else { return } + leftLabel.text = String(model.number) + eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + } + + open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + return 140 + } + + open override func reset() { + super.reset() + leftLabel.styleTitle2XLarge(true) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinksModel.swift new file mode 100644 index 00000000..78a85a8e --- /dev/null +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinksModel.swift @@ -0,0 +1,42 @@ +// +// ListLeftVariableNumberedListAllTextAndLinksModel.swift +// MVMCoreUI +// +// Created by Subhankar Acharya on 21/05/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class ListLeftVariableNumberedListAllTextAndLinksModel: ListItemModel, MoleculeModelProtocol { + public static var identifier: String = "listLVNAll" + public var number: Int + public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel + + public init(number: Int, eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel) { + self.number = number + self.eyebrowHeadlineBodyLink = eyebrowHeadlineBodyLink + super.init() + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case number + case eyebrowHeadlineBodyLink + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + number = try typeContainer.decode(Int.self, forKey: .number) + eyebrowHeadlineBodyLink = try typeContainer.decode(EyebrowHeadlineBodyLinkModel.self, forKey: .eyebrowHeadlineBodyLink) + try super.init(from: decoder) + } + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(number, forKey: .number) + try container.encode(eyebrowHeadlineBodyLink, forKey: .eyebrowHeadlineBodyLink) + } +} From 648da2bd3f7e43e6e1fa0a63378455a5d277366a Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 21 May 2020 15:29:24 -0400 Subject: [PATCH 43/43] Bug fix in label --- MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 087e893c..598f3f4e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -677,6 +677,9 @@ public typealias ActionBlock = () -> () @objc public func updateView(_ size: CGFloat) { scaleSize = size as NSNumber + // This fixes a defect for when there are multiple labels stacked in a list item. Sometime some labels will not fill their available space. + preferredMaxLayoutWidth = size + if let originalAttributedString = originalAttributedString { let attributedString = NSMutableAttributedString(attributedString: originalAttributedString) attributedString.removeAttribute(.font, range: NSRange(location: 0, length: attributedString.length))