From af0ebf8678d79d3210f01fbaec146dbbcd95de28 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 28 May 2021 09:56:58 -0400 Subject: [PATCH 1/4] add remove behavior --- MVMCoreUI.xcodeproj/project.pbxproj | 8 ++ .../Atomic/Atoms/FormFields/Tags/Tags.swift | 2 +- .../BaseItemPickerEntryField.swift | 2 +- .../ItemDropdownEntryField.swift | 8 +- .../MultiItemDropdownEntryField.swift | 4 +- .../Atoms/Selectors/RadioSwatches.swift | 2 +- .../Atomic/Atoms/Views/LoadImageView.swift | 2 +- MVMCoreUI/Atomic/Atoms/Views/WebView.swift | 2 +- .../Items/AccordionListItemModel.swift | 28 +++++ .../AccordionMoleculeTableViewCell.swift | 13 +- .../Items/DropDownFilterTableViewCell.swift | 17 ++- .../Items/DropDownListItemModel.swift | 26 ++++ .../Molecules/Items/TabsTableViewCell.swift | 7 +- .../Protocols/MoleculeDelegateProtocol.swift | 17 --- .../Protocols/MoleculeListProtocol.swift | 42 +++++++ .../Templates/MoleculeListTemplate.swift | 119 +++++++++--------- .../BaseControllers/ViewController.swift | 8 +- .../Behaviors/AddRemoveMoleculeBehavior.swift | 107 ++++++++++++++++ .../Views/EntryFieldContainer.swift | 2 +- .../OtherHandlers/CoreUIModelMapping.swift | 1 + 20 files changed, 302 insertions(+), 115 deletions(-) create mode 100644 MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift create mode 100644 MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index d48fa228..85e99058 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -386,6 +386,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 */; }; + D270E5672642F77300CDBED2 /* AddRemoveMoleculeBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D270E5662642F77300CDBED2 /* AddRemoveMoleculeBehavior.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 */; }; @@ -493,6 +494,7 @@ D2B18B922361E65A00A9AEDC /* CoreUIObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */; }; D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B18B93236214AD00A9AEDC /* NavigationController.swift */; }; D2B1E3E522F37D6A0065F95C /* ImageHeadlineBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */; }; + D2B9D0E4265EEE9D0084735C /* MoleculeListProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B9D0E3265EEE9D0084735C /* MoleculeListProtocol.swift */; }; D2C5001821F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h in Headers */ = {isa = PBXBuildFile; fileRef = D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2C5001921F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */; }; D2C521A923EDE79E00CA2634 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C521A823EDE79E00CA2634 /* ViewController.swift */; }; @@ -949,6 +951,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 = ""; }; + D270E5662642F77300CDBED2 /* AddRemoveMoleculeBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoveMoleculeBehavior.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 = ""; }; @@ -1059,6 +1062,7 @@ D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUIObject.swift; sourceTree = ""; }; D2B18B93236214AD00A9AEDC /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeadlineBody.swift; sourceTree = ""; }; + D2B9D0E3265EEE9D0084735C /* MoleculeListProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListProtocol.swift; sourceTree = ""; }; D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewControllerMappingObject.h; sourceTree = ""; }; D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIViewControllerMappingObject.m; sourceTree = ""; }; D2C521A823EDE79E00CA2634 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -1313,6 +1317,7 @@ 0A1C30972620F61A00B47F3B /* Protocols */, 27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */, D23A900826125FFB007E14CE /* GetContactBehavior.swift */, + D270E5662642F77300CDBED2 /* AddRemoveMoleculeBehavior.swift */, ); path = Behaviors; sourceTree = ""; @@ -2320,6 +2325,7 @@ 012A88AC238C418100FE3DA1 /* TemplateProtocol.swift */, D28BA7442481652D00B75CB8 /* TabBarProtocol.swift */, D20F3B5D252F9B5D004B3F56 /* NavigationBarRefreshProtocol.swift */, + D2B9D0E3265EEE9D0084735C /* MoleculeListProtocol.swift */, 011B58EE23A2AA850085F53C /* ModelProtocols */, ); path = Protocols; @@ -2929,6 +2935,7 @@ AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */, 52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */, D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */, + D270E5672642F77300CDBED2 /* AddRemoveMoleculeBehavior.swift in Sources */, BB2BF0EC2452A9D5001D0FC2 /* ListDeviceComplexButtonSmallModel.swift in Sources */, 943784F6236B77BB006A1E82 /* WheelAnimationHandler.swift in Sources */, 011D95A1240453D0000E3791 /* RuleEqualsModel.swift in Sources */, @@ -2987,6 +2994,7 @@ BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */, C695A69423C9909000BFB94E /* DoughnutChartModel.swift in Sources */, D2D3957D252FDBCD00047B11 /* ModalSectionListTemplateModel.swift in Sources */, + D2B9D0E4265EEE9D0084735C /* MoleculeListProtocol.swift in Sources */, D29C559625C099630082E7D6 /* VideoDataManager.swift in Sources */, 8D4687E2242E2DE400802879 /* ListFourColumnDataUsageListItemModel.swift in Sources */, D29E28DD23D7404C00ACEA85 /* ContainerHelper.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/Tags/Tags.swift b/MVMCoreUI/Atomic/Atoms/FormFields/Tags/Tags.swift index 4b1fa4f9..be518465 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/Tags/Tags.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/Tags/Tags.swift @@ -26,7 +26,7 @@ open class Tags: View, MFButtonProtocol { DispatchQueue.main.async { self.collectionView.collectionViewLayout.invalidateLayout() self.collectionViewHeight.constant = self.collectionView.contentSize.height - self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + self.delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift index 00b5a072..fa880a50 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/BaseItemPickerEntryField.swift @@ -23,7 +23,7 @@ open class BaseItemPickerEntryField: BaseDropdownEntryField, UIPickerViewDelegat //-------------------------------------------------- /// Closure passed here will run as picker changes items. - public var observeDropdownChange: ((String, String) -> ())? + public var observeDropdownChange: ((String?, String) -> ())? /// Closure passed here will run upon dismissing the selection picker. public var observeDropdownSelection: ((String) -> ())? diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift index b3aeb172..6f69be43 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/ItemDropdownEntryField.swift @@ -56,9 +56,9 @@ open class ItemDropdownEntryField: BaseItemPickerEntryField { if setInitialValueInTextField { let pickerIndex = pickerView.selectedRow(inComponent: 0) - observeDropdownChange?(text ?? "", pickerData[pickerIndex]) - text = pickerData[pickerIndex] itemDropdownEntryFieldModel?.selectedIndex = pickerIndex + observeDropdownChange?(text, pickerData[pickerIndex]) + text = pickerData[pickerIndex] } } @@ -108,8 +108,8 @@ open class ItemDropdownEntryField: BaseItemPickerEntryField { @objc public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { guard !pickerData.isEmpty else { return } - observeDropdownChange?(text ?? "", pickerData[row]) - text = pickerData[row] itemDropdownEntryFieldModel?.selectedIndex = row + observeDropdownChange?(text, pickerData[row]) + text = pickerData[row] } } diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/MultiItemDropdownEntryField.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/MultiItemDropdownEntryField.swift index 8bed3996..13f1cc5b 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/MultiItemDropdownEntryField.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/Dropdown Fields/Item Dropdown/MultiItemDropdownEntryField.swift @@ -61,7 +61,7 @@ open class MultiItemDropdownEntryField: BaseItemPickerEntryField { else { return } // Update observing function and update text UI. - observeDropdownChange?(text ?? "", rowText) + observeDropdownChange?(text, rowText) text = rowText // Set row index value of selected component. @@ -135,7 +135,7 @@ open class MultiItemDropdownEntryField: BaseItemPickerEntryField { guard pickerHasComponent(component) else { return } - let oldText = text ?? "" + let oldText = text dropdownModel?.selectedIndexes[component] = row let newText = dropdownModel?.selectedRowText observeDropdownChange?(oldText, newText ?? "") diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift index 5a5b6836..8587bb2f 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift @@ -96,7 +96,7 @@ open class RadioSwatches: View { height != oldHeight { // Notify delegate of height change, called async to avoid various race conditions caused while happening while laying out initially. DispatchQueue.main.async { - self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + self.delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) } } collectionViewHeight?.constant = CGFloat(height) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift index 8d8ef49e..fa3d290c 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift @@ -331,7 +331,7 @@ self.addConstraints(width: width, height: height, size: image?.size) self.loadingSpinnerHeightConstraint?.constant = 0 if layoutWillChange { - self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + self.delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) } completionBlock(image,data,isFallbackImage) })} diff --git a/MVMCoreUI/Atomic/Atoms/Views/WebView.swift b/MVMCoreUI/Atomic/Atoms/Views/WebView.swift index e6708055..d75c59ab 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/WebView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/WebView.swift @@ -148,7 +148,7 @@ extension WebView : WKUIDelegate { //if failed to get height from javascript, using scrollview.contensize's height self.webViewHeight?.constant = webView.scrollView.contentSize.height } - self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + self.delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) }) }) } diff --git a/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift index a19c3813..b3e723de 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift @@ -17,7 +17,10 @@ class AccordionListItemModel: MoleculeListItemModel { public var molecules: [ListItemModelProtocol & MoleculeModelProtocol] public var hideLineWhenExpanded: Bool = false + public var selected: Bool = false + private var added: Bool = false + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -27,6 +30,7 @@ class AccordionListItemModel: MoleculeListItemModel { case molecules case molecule case hideLineWhenExpanded + case selected } //-------------------------------------------------- @@ -45,6 +49,7 @@ class AccordionListItemModel: MoleculeListItemModel { required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeModels(codingKey: .molecules) + selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) ?? false if let hideLine = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideLineWhenExpanded) { hideLineWhenExpanded = hideLine } @@ -56,6 +61,29 @@ class AccordionListItemModel: MoleculeListItemModel { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(moleculeName, forKey: .moleculeName) try container.encodeModels(molecules, forKey: .molecules) + try container.encode(selected, forKey: .selected) try container.encodeIfPresent(hideLineWhenExpanded, forKey: .hideLineWhenExpanded) } } + +extension AccordionListItemModel: PageBehaviorProtocolRequirer { + public func getRequiredBehaviors() -> [PageBehaviorModelProtocol] { + return [AddRemoveMoleculeBehaviorModel()] + } +} + +extension AccordionListItemModel: AddMolecules { + public func moleculesToAdd() -> ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculePosition, UITableView.RowAnimation?)? { + guard !added, selected else { return nil } + added = true + return (molecules, .below, .automatic) + } +} + +extension AccordionListItemModel: RemoveMolecules { + public func moleculesToRemove() -> ([ListItemModelProtocol & MoleculeModelProtocol], UITableView.RowAnimation?)? { + guard added else { return nil } + added = false + return (molecules, .automatic) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift index 46a1e651..cafc8947 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -43,17 +43,22 @@ accordionButton.setTitle(accordionButton.isSelected ? "-" : "+", for: .normal) guard let model = accordionListItemModel else { return } + model.selected = accordionButton.isSelected if accordionButton.isSelected { - if let indexPath = delegateObject?.moleculeDelegate?.getIndexPath(for: model) { - delegateObject?.moleculeDelegate?.addMolecules(model.molecules, indexPath: indexPath, animation: .automatic) - } + MVMCoreActionHandler.shared()?.handleAction("addMoleculesAction", actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: delegateObject) } else { - delegateObject?.moleculeDelegate?.removeMolecules(model.molecules, animation: .automatic) + MVMCoreActionHandler.shared()?.handleAction("removeMoleculesAction", actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: delegateObject) } if (accordionListItemModel?.hideLineWhenExpanded ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { bottomSeparatorView?.isHidden = accordionButton.isSelected } } + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? AccordionListItemModel else { return } + accordionButton.isSelected = model.selected + } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift index aa581ba1..778fc85f 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift @@ -14,7 +14,6 @@ import UIKit //-------------------------------------------------- let dropDown = ItemDropdownEntryField() var delegateObject: MVMCoreUIDelegateObject? - var previousIndex = NSNotFound //-------------------------------------------------- // MARK: - Lifecycle @@ -28,18 +27,16 @@ import UIKit guard newValue != oldValue, let self = self, - let index = self.dropDown.pickerData.firstIndex(of: newValue), let model = self.listItemModel as? DropDownListItemModel else { return } - - if self.previousIndex != NSNotFound { - self.delegateObject?.moleculeDelegate?.removeMolecules(model.molecules[self.previousIndex], animation: .fade) - } + MVMCoreDispatchUtility.performBlock(inBackground: { + if let oldValue = oldValue, + oldValue.count > 0 { + MVMCoreUIActionHandler.shared()?.synchronouslyHandleAction("removeMoleculesAction", actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) + } - if let indexPath = self.delegateObject?.moleculeDelegate?.getIndexPath(for: model) { - self.delegateObject?.moleculeDelegate?.addMolecules(model.molecules[index], indexPath: indexPath, animation: .fade) - } - self.previousIndex = index + MVMCoreActionHandler.shared()?.handleAction("addMoleculesAction", actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) + }) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/DropDownListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/DropDownListItemModel.swift index 693874e6..7ffd754b 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/DropDownListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/DropDownListItemModel.swift @@ -17,6 +17,8 @@ import Foundation public static var identifier: String = "dropDownListItem" public var molecules: [[ListItemModelProtocol & MoleculeModelProtocol]] public var dropDown: ItemDropdownEntryFieldModel + private var addedMolecules: [ListItemModelProtocol & MoleculeModelProtocol]? + //-------------------------------------------------- // MARK: - Methods @@ -81,3 +83,27 @@ import Foundation try container.encode(dropDown, forKey: .dropDown) } } + +extension DropDownListItemModel: PageBehaviorProtocolRequirer { + public func getRequiredBehaviors() -> [PageBehaviorModelProtocol] { + return [AddRemoveMoleculeBehaviorModel()] + } +} + +extension DropDownListItemModel: AddMolecules { + public func moleculesToAdd() -> ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculePosition, UITableView.RowAnimation?)? { + guard addedMolecules == nil, + let index = dropDown.selectedIndex else { return nil } + let addedMolecules = molecules[index] + self.addedMolecules = addedMolecules + return (addedMolecules, .below, .fade) + } +} + +extension DropDownListItemModel: RemoveMolecules { + public func moleculesToRemove() -> ([ListItemModelProtocol & MoleculeModelProtocol], UITableView.RowAnimation?)? { + guard let addedMolecules = addedMolecules else { return nil } + self.addedMolecules = nil + return (addedMolecules, .fade) + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift index bbad9823..e6989607 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift @@ -52,11 +52,12 @@ import UIKit public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { 46 } } +// TODO: Move to AddRemoveMoleculeBehaviorModel extension TabsTableViewCell: TabsDelegate { public func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool { if let model = tabsListItemModel { let molecules = model.molecules[tabs.selectedIndex] - delegateObject?.moleculeDelegate?.removeMolecules(molecules, animation: indexPath.row < tabs.selectedIndex ? .right : .left) + delegateObject?.moleculeListDelegate?.removeMolecules(molecules, animation: indexPath.row < tabs.selectedIndex ? .right : .left) } previousTabIndex = tabs.selectedIndex return true @@ -66,9 +67,9 @@ extension TabsTableViewCell: TabsDelegate { let index = indexPath.row if let model = tabsListItemModel, index < model.molecules.count, - let cellIndexPath = delegateObject?.moleculeDelegate?.getIndexPath(for: model) { + let cellIndexPath = delegateObject?.moleculeListDelegate?.getIndexPath(for: model) { let molecules = model.molecules[index] - delegateObject?.moleculeDelegate?.addMolecules(molecules, indexPath: cellIndexPath, animation: index < previousTabIndex ? .left : .right) + delegateObject?.moleculeListDelegate?.addMolecules(molecules, indexPath: cellIndexPath, animation: index < previousTabIndex ? .left : .right) } } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift index e61ea662..c0bdc031 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift @@ -15,21 +15,4 @@ public protocol MoleculeDelegateProtocol: AnyObject { /// returns a module for the corresponding module name. func getModuleWithName(_ name: String?) -> [AnyHashable : Any]? func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? - - /// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. Mainly used for list or collections. - func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) //optional - - /// Asks the delegate to add or remove molecules. Mainly used for list or collections. - func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? - func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) - func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) -} - -extension MoleculeDelegateProtocol { - - public func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) {} - - public func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { return nil } - public func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) {} - public func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) {} } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift new file mode 100644 index 00000000..091fa00b --- /dev/null +++ b/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift @@ -0,0 +1,42 @@ +// +// MoleculeListProtocol.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 5/26/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +public protocol MoleculeListProtocol { + + /// Asks the delegate for the index of molecule. + func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? + + /// Asks the delegate to add molecules. + func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation?) + + /// Asks the delegate to remove molecules. + func removeMolecules(at indexPaths: [IndexPath], animation: UITableView.RowAnimation?) + + /// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. Mainly used for list or collections. + func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) +} + +extension MoleculeListProtocol { + /// Convenience function that removes the passed molecule + public func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation?) { + var indexPaths: [IndexPath] = [] + for molecule in molecules { + guard let indexPath = getIndexPath(for: molecule) else { continue } + indexPaths.append(indexPath) + } + if indexPaths.count > 0 { + removeMolecules(at: indexPaths, animation: animation) + } + } +} + +public extension MVMCoreUIDelegateObject { + weak var moleculeListDelegate: (MoleculeListProtocol & NSObjectProtocol)? { + return (moleculeDelegate as? MoleculeListProtocol & NSObjectProtocol) + } +} diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 34f81bc4..568edb8a 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -177,68 +177,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol tableView.beginUpdates() tableView.endUpdates() } - - //-------------------------------------------------- - // MARK: - MoleculeDelegateProtocol - //-------------------------------------------------- - - open override func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { - guard let tableView = tableView else { return } - - let point = molecule.convert(molecule.bounds.origin, to: tableView) - if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { - performTableViewUpdates() - } - } - - open override func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { - guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in - //TODO: check for molecule protocol eqaulity - let json = moleculeInfo.molecule.toJSON() - return json == molecule.toJSON() - }) else { return nil } - - return IndexPath(row: index, section: 0) - } - - open override func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) { - // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. - DispatchQueue.main.async { - var indexPaths: [IndexPath] = [] - - for molecule in molecules { - if let info = self.createMoleculeInfo(with: molecule) { - self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) - let index = indexPath.row + 1 + indexPaths.count - self.moleculesInfo?.insert(info, at: index) - indexPaths.append(IndexPath(row: index, section: 0)) - } - } - - guard indexPaths.count > 0 else { return } - self.tableView?.insertRows(at: indexPaths, with: animation) - self.updateViewConstraints() - self.view.layoutIfNeeded() - } - } - - open override func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) { - var indexPaths: [IndexPath] = [] - //TODO: check for molecule protocol equality - - for molecule in molecules { - if let removeIndex = moleculesInfo?.firstIndex(where: { molecule.toJSON() == $0.molecule.toJSON() }) { - moleculesInfo?.remove(at: removeIndex) - indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) - } - } - - guard indexPaths.count > 0 else { return } - tableView?.deleteRows(at: indexPaths, with: animation) - updateViewConstraints() - view.layoutIfNeeded() - } - + //-------------------------------------------------- // MARK: - Convenience //-------------------------------------------------- @@ -304,3 +243,59 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol return modules } } + +extension MoleculeListTemplate: MoleculeListProtocol { + public func removeMolecules(at indexPaths: [IndexPath], animation: UITableView.RowAnimation?) { + for (index, indexPath) in indexPaths.sorted().enumerated() { + let removeIndex = indexPath.row - index + moleculesInfo?.remove(at: removeIndex) + } + + guard let animation = animation, + indexPaths.count > 0 else { return } + tableView?.deleteRows(at: indexPaths, with: animation) + updateViewConstraints() + view.layoutIfNeeded() + } + + public func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation?) { + // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. + DispatchQueue.main.async { + var indexPaths: [IndexPath] = [] + + for molecule in molecules { + if let info = self.createMoleculeInfo(with: molecule) { + self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + let index = indexPath.row + indexPaths.count + self.moleculesInfo?.insert(info, at: index) + indexPaths.append(IndexPath(row: index, section: 0)) + } + } + + guard let animation = animation, + indexPaths.count > 0 else { return } + self.tableView?.insertRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() + } + } + + open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { + guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in + //TODO: check for molecule protocol eqaulity + let json = moleculeInfo.molecule.toJSON() + return json == molecule.toJSON() + }) else { return nil } + + return IndexPath(row: index, section: 0) + } + + open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { + guard let tableView = tableView else { return } + + let point = molecule.convert(molecule.bounds.origin, to: tableView) + if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { + performTableViewUpdates() + } + } +} diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index cd67632f..049b3463 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -525,13 +525,7 @@ import UIKit return nil } - - // Needed otherwise when subclassed, the extension gets called. - open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } - open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { nil } - open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) { } - open func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) { } - + //-------------------------------------------------- // MARK: - MVMCoreUIDetailViewProtocol //-------------------------------------------------- diff --git a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift new file mode 100644 index 00000000..981bf985 --- /dev/null +++ b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift @@ -0,0 +1,107 @@ +// +// AddRemoveMoleculeBehavior.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 5/5/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +public enum AddMoleculePosition { + case above + case below +} + +public protocol AddMolecules { + /// Get the molecules that need to be added, the position to add them to, and the animation to use + func moleculesToAdd() -> ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculePosition, UITableView.RowAnimation?)? +} + +public protocol RemoveMolecules { + /// Get the molecules that need to be removed, and the animation to use + func moleculesToRemove() -> ([ListItemModelProtocol & MoleculeModelProtocol], UITableView.RowAnimation?)? +} + +public extension AddMolecules { + + /// Gets the molecules that need to be added, recursively checking for additional molecules to add. + func getRecursiveMoleculesToAdd() -> ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculePosition, UITableView.RowAnimation?)? { + guard let moleculesToAdd = moleculesToAdd() else { return nil } + var newMolecules: [ListItemModelProtocol & MoleculeModelProtocol] = [] + for molecule in moleculesToAdd.0 { + if let anotherAddMolecule = molecule as? AddMolecules, + let moreMolecules = anotherAddMolecule.getRecursiveMoleculesToAdd() { + guard moreMolecules.1 == .above else { + newMolecules.append(molecule) + newMolecules.append(contentsOf: moreMolecules.0) + continue + } + newMolecules.append(contentsOf: moreMolecules.0) + } + newMolecules.append(molecule) + } + return (newMolecules, moleculesToAdd.1, moleculesToAdd.2) + } +} + +public extension RemoveMolecules { + /// Gets the molecules that need to be removed, recursively checking for additional molecules to remove. + func getRecursiveMoleculesToRemove() -> ([ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation?)? { + guard let moleculesToRemove = moleculesToRemove() else { return nil } + var newMolecules: [ListItemModelProtocol & MoleculeModelProtocol] = moleculesToRemove.0 + for case let molecule as (ListItemModelProtocol & MoleculeModelProtocol & RemoveMolecules) in moleculesToRemove.0 { + guard let moreMolecules = molecule.getRecursiveMoleculesToRemove() else { continue } + newMolecules.append(contentsOf: moreMolecules.0) + } + return (newMolecules, moleculesToRemove.1) + } +} + +public class AddRemoveMoleculeBehaviorModel: PageBehaviorModelProtocol { + public class var identifier: String { "addRemoveListItemBehavior" } + public var shouldAllowMultipleInstances: Bool { false } + public init() {} +} + +public class AddRemoveMoleculeBehavior: PageCustomActionHandlerBehavior { + + var delegate: MVMCoreUIDelegateObject? + + public required init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { + self.delegate = delegateObject + } + + public func handleAction(type actionType: String?, information: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) -> Bool { + if actionType == "addMoleculesAction" { + guard let list = delegate?.moleculeListDelegate, + let model = additionalData?[KeySourceModel] as? (ListItemModelProtocol & MoleculeModelProtocol & AddMolecules), + let moleculesToAdd = model.getRecursiveMoleculesToAdd(), + let indexPath = list.getAdjustedIndexPath(for: model, position: moleculesToAdd.1) else { return true } + DispatchQueue.main.async { + list.addMolecules(moleculesToAdd.0, indexPath: indexPath, animation: moleculesToAdd.2) + } + return true + } else if actionType == "removeMoleculesAction" { + guard let list = delegate?.moleculeListDelegate, + let model = additionalData?[KeySourceModel] as? (ListItemModelProtocol & MoleculeModelProtocol & RemoveMolecules), + let moleculesToRemove = model.getRecursiveMoleculesToRemove() else { return true } + DispatchQueue.main.async { + list.removeMolecules(moleculesToRemove.0, animation: moleculesToRemove.1) + } + return true + } + return false + } +} + +private extension MoleculeListProtocol { + /// Convenience function to get the index path adjusted for position. + func getAdjustedIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol, position: AddMoleculePosition) -> IndexPath? { + guard let indexPath = getIndexPath(for: molecule) else { return nil } + if position == .below { + return IndexPath(row: indexPath.row + 1, section: indexPath.section) + } + return indexPath + } +} diff --git a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift index 9ccd1d25..52be603f 100644 --- a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift @@ -270,7 +270,7 @@ import UIKit bottomBar?.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) if updateMoleculeLayout || heightChanged { - delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) } setNeedsDisplay() diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 45d1b0c4..88cabc68 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -224,6 +224,7 @@ open class CoreUIModelMapping: ModelMapping { open class func registerBehaviors() { try? ModelRegistry.register(handler: ScreenBrightnessModifierBehavior.self, for: ScreenBrightnessModifierBehaviorModel.self) try? ModelRegistry.register(handler: PageGetContactBehavior.self, for: PageGetContactBehaviorModel.self) + try? ModelRegistry.register(handler: AddRemoveMoleculeBehavior.self, for: AddRemoveMoleculeBehaviorModel.self) } open override class func registerActions() { From 42b8b37c589e793bb7b29d54c45220a0beeecdf2 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 28 May 2021 10:23:17 -0400 Subject: [PATCH 2/4] Add Actions --- .../AccordionMoleculeTableViewCell.swift | 4 +-- .../Items/DropDownFilterTableViewCell.swift | 4 +-- .../Behaviors/AddRemoveMoleculeBehavior.swift | 36 +++++++++++++++++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift index cafc8947..29689142 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -46,9 +46,9 @@ model.selected = accordionButton.isSelected if accordionButton.isSelected { - MVMCoreActionHandler.shared()?.handleAction("addMoleculesAction", actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: delegateObject) + MVMCoreActionHandler.shared()?.handleAction(AddMoleculesActionModel.identifier, actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: delegateObject) } else { - MVMCoreActionHandler.shared()?.handleAction("removeMoleculesAction", actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: delegateObject) + MVMCoreActionHandler.shared()?.handleAction(RemoveMoleculesActionModel.identifier, actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: delegateObject) } if (accordionListItemModel?.hideLineWhenExpanded ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { diff --git a/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift index 778fc85f..5589fccb 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift @@ -32,10 +32,10 @@ import UIKit MVMCoreDispatchUtility.performBlock(inBackground: { if let oldValue = oldValue, oldValue.count > 0 { - MVMCoreUIActionHandler.shared()?.synchronouslyHandleAction("removeMoleculesAction", actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) + MVMCoreUIActionHandler.shared()?.synchronouslyHandleAction(RemoveMoleculesActionModel.identifier, actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) } - MVMCoreActionHandler.shared()?.handleAction("addMoleculesAction", actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) + MVMCoreActionHandler.shared()?.handleAction(AddMoleculesActionModel.identifier, actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) }) } } diff --git a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift index 981bf985..73921dc9 100644 --- a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift +++ b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift @@ -73,7 +73,7 @@ public class AddRemoveMoleculeBehavior: PageCustomActionHandlerBehavior { } public func handleAction(type actionType: String?, information: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) -> Bool { - if actionType == "addMoleculesAction" { + if actionType == AddMoleculesActionModel.identifier { guard let list = delegate?.moleculeListDelegate, let model = additionalData?[KeySourceModel] as? (ListItemModelProtocol & MoleculeModelProtocol & AddMolecules), let moleculesToAdd = model.getRecursiveMoleculesToAdd(), @@ -82,7 +82,7 @@ public class AddRemoveMoleculeBehavior: PageCustomActionHandlerBehavior { list.addMolecules(moleculesToAdd.0, indexPath: indexPath, animation: moleculesToAdd.2) } return true - } else if actionType == "removeMoleculesAction" { + } else if actionType == RemoveMoleculesActionModel.identifier { guard let list = delegate?.moleculeListDelegate, let model = additionalData?[KeySourceModel] as? (ListItemModelProtocol & MoleculeModelProtocol & RemoveMolecules), let moleculesToRemove = model.getRecursiveMoleculesToRemove() else { return true } @@ -105,3 +105,35 @@ private extension MoleculeListProtocol { return indexPath } } + +public class AddMoleculesActionModel: ActionModelProtocol { + public static var identifier: String = "addMoleculesAction" + public var actionType: String = AddMoleculesActionModel.identifier + public var extraParameters: JSONValueDictionary? + public var analyticsData: JSONValueDictionary? + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + self.extraParameters = extraParameters + self.analyticsData = analyticsData + } +} + +public class RemoveMoleculesActionModel: ActionModelProtocol { + public static var identifier: String = "removeMoleculesAction" + public var actionType: String = RemoveMoleculesActionModel.identifier + public var extraParameters: JSONValueDictionary? + public var analyticsData: JSONValueDictionary? + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + + public init(_ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + self.extraParameters = extraParameters + self.analyticsData = analyticsData + } +} From dc12612c6f2f4cdda5cebe429d331532eb9f9f25 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 26 Jul 2021 18:51:22 -0400 Subject: [PATCH 3/4] add and remove clean up, tabs addition --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + .../UITableViewRowAnimation+Extension.swift | 4 +- .../Items/AccordionListItemModel.swift | 10 +- .../AccordionMoleculeTableViewCell.swift | 9 +- .../Items/DropDownFilterTableViewCell.swift | 5 +- .../Items/DropDownListItemModel.swift | 10 +- .../Molecules/Items/TabsListItemModel.swift | 26 ++- .../Molecules/Items/TabsTableViewCell.swift | 13 +- .../Templates/MoleculeListTemplate.swift | 185 +++++------------- .../Behaviors/AddRemoveMoleculeBehavior.swift | 81 +++++--- .../OtherHandlers/CoreUIModelMapping.swift | 2 +- 11 files changed, 163 insertions(+), 186 deletions(-) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 267767b4..6c64b7af 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -513,6 +513,7 @@ D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */; }; D2D90B42240463E100DD6EC9 /* MoleculeHeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D90B41240463E100DD6EC9 /* MoleculeHeaderModel.swift */; }; D2D90B442404789000DD6EC9 /* MoleculeContainerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2D90B432404789000DD6EC9 /* MoleculeContainerProtocol.swift */; }; + D2E0FFF826AF68530085D696 /* UITableViewRowAnimation+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E0FFF726AF68530085D696 /* UITableViewRowAnimation+Extension.swift */; }; D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; }; D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */; }; D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */; }; @@ -1082,6 +1083,7 @@ D2D6CD4122E78FAB00D701B8 /* ThreeLayerTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTemplate.swift; sourceTree = ""; }; D2D90B41240463E100DD6EC9 /* MoleculeHeaderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeHeaderModel.swift; sourceTree = ""; }; D2D90B432404789000DD6EC9 /* MoleculeContainerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeContainerProtocol.swift; sourceTree = ""; }; + D2E0FFF726AF68530085D696 /* UITableViewRowAnimation+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewRowAnimation+Extension.swift"; sourceTree = ""; }; D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreUIDelegateObject.swift; sourceTree = ""; }; D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTableViewController.swift; sourceTree = ""; }; D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListTemplate.swift; sourceTree = ""; }; @@ -1486,6 +1488,7 @@ 0AB764D224460FA400E7FE72 /* UIPickerView+Extension.swift */, D2ED27E6254B0CE600A1C293 /* UIAlertActionStyle+Codable.swift */, D2ED27E7254B0CE600A1C293 /* UIAlertControllerStyle+Extension.swift */, + D2E0FFF726AF68530085D696 /* UITableViewRowAnimation+Extension.swift */, ); path = Extensions; sourceTree = ""; @@ -2942,6 +2945,7 @@ BB2BF0EC2452A9D5001D0FC2 /* ListDeviceComplexButtonSmallModel.swift in Sources */, 943784F6236B77BB006A1E82 /* WheelAnimationHandler.swift in Sources */, 011D95A1240453D0000E3791 /* RuleEqualsModel.swift in Sources */, + D2E0FFF826AF68530085D696 /* UITableViewRowAnimation+Extension.swift in Sources */, 1D6D258826899B0C00DEBB08 /* ImageButtonModel.swift in Sources */, AA07EA912510A442009A2AE3 /* StarModel.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, diff --git a/MVMCoreUI/Atomic/Extensions/UITableViewRowAnimation+Extension.swift b/MVMCoreUI/Atomic/Extensions/UITableViewRowAnimation+Extension.swift index 712ba742..39ac638d 100644 --- a/MVMCoreUI/Atomic/Extensions/UITableViewRowAnimation+Extension.swift +++ b/MVMCoreUI/Atomic/Extensions/UITableViewRowAnimation+Extension.swift @@ -1,9 +1,9 @@ // -// UITableViewRowAnimation.swift +// UITableViewRowAnimation+Extension.swift // MVMCoreUI // // Created by Scott Pfeil on 7/26/21. // Copyright © 2021 Verizon Wireless. All rights reserved. // -import Foundation +extension UITableView.RowAnimation: Codable {} diff --git a/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift index b3e723de..b4f39881 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/AccordionListItemModel.swift @@ -68,22 +68,22 @@ class AccordionListItemModel: MoleculeListItemModel { extension AccordionListItemModel: PageBehaviorProtocolRequirer { public func getRequiredBehaviors() -> [PageBehaviorModelProtocol] { - return [AddRemoveMoleculeBehaviorModel()] + return [AddRemoveMoleculesBehaviorModel()] } } extension AccordionListItemModel: AddMolecules { - public func moleculesToAdd() -> ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculePosition, UITableView.RowAnimation?)? { + public func moleculesToAdd() -> AddMolecules.AddParameters? { guard !added, selected else { return nil } added = true - return (molecules, .below, .automatic) + return (molecules, .below) } } extension AccordionListItemModel: RemoveMolecules { - public func moleculesToRemove() -> ([ListItemModelProtocol & MoleculeModelProtocol], UITableView.RowAnimation?)? { + public func moleculesToRemove() -> [ListItemModelProtocol & MoleculeModelProtocol]? { guard added else { return nil } added = false - return (molecules, .automatic) + return molecules } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift index 29689142..c3d37ca9 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/AccordionMoleculeTableViewCell.swift @@ -46,9 +46,9 @@ model.selected = accordionButton.isSelected if accordionButton.isSelected { - MVMCoreActionHandler.shared()?.handleAction(AddMoleculesActionModel.identifier, actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: delegateObject) + MVMCoreActionHandler.shared()?.asyncHandleAction(with: AddMoleculesActionModel(.automatic), additionalData: [KeySourceModel: model], delegateObject: delegateObject) } else { - MVMCoreActionHandler.shared()?.handleAction(RemoveMoleculesActionModel.identifier, actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: delegateObject) + MVMCoreActionHandler.shared()?.asyncHandleAction(with: RemoveMoleculesActionModel(.automatic), additionalData: [KeySourceModel: model], delegateObject: delegateObject) } if (accordionListItemModel?.hideLineWhenExpanded ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { @@ -60,5 +60,10 @@ super.set(with: model, delegateObject, additionalData) guard let model = model as? AccordionListItemModel else { return } accordionButton.isSelected = model.selected + accordionButton.setTitle(accordionButton.isSelected ? "-" : "+", for: .normal) + + if (accordionListItemModel?.hideLineWhenExpanded ?? false) && (self.bottomSeparatorView?.shouldBeVisible() ?? false) { + bottomSeparatorView?.isHidden = accordionButton.isSelected + } } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift index 5589fccb..b6de93d1 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/DropDownFilterTableViewCell.swift @@ -32,10 +32,9 @@ import UIKit MVMCoreDispatchUtility.performBlock(inBackground: { if let oldValue = oldValue, oldValue.count > 0 { - MVMCoreUIActionHandler.shared()?.synchronouslyHandleAction(RemoveMoleculesActionModel.identifier, actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) + MVMCoreActionHandler.shared()?.syncHandleAction(with: RemoveMoleculesActionModel(.fade), additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) } - - MVMCoreActionHandler.shared()?.handleAction(AddMoleculesActionModel.identifier, actionInformation: nil, additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) + MVMCoreActionHandler.shared()?.syncHandleAction(with: AddMoleculesActionModel(.fade), additionalData: [KeySourceModel: model], delegateObject: self.delegateObject) }) } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/DropDownListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/DropDownListItemModel.swift index 7ffd754b..01b3abfc 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/DropDownListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/DropDownListItemModel.swift @@ -86,24 +86,24 @@ import Foundation extension DropDownListItemModel: PageBehaviorProtocolRequirer { public func getRequiredBehaviors() -> [PageBehaviorModelProtocol] { - return [AddRemoveMoleculeBehaviorModel()] + return [AddRemoveMoleculesBehaviorModel()] } } extension DropDownListItemModel: AddMolecules { - public func moleculesToAdd() -> ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculePosition, UITableView.RowAnimation?)? { + public func moleculesToAdd() -> AddMolecules.AddParameters? { guard addedMolecules == nil, let index = dropDown.selectedIndex else { return nil } let addedMolecules = molecules[index] self.addedMolecules = addedMolecules - return (addedMolecules, .below, .fade) + return (addedMolecules, .below) } } extension DropDownListItemModel: RemoveMolecules { - public func moleculesToRemove() -> ([ListItemModelProtocol & MoleculeModelProtocol], UITableView.RowAnimation?)? { + public func moleculesToRemove() -> [ListItemModelProtocol & MoleculeModelProtocol]? { guard let addedMolecules = addedMolecules else { return nil } self.addedMolecules = nil - return (addedMolecules, .fade) + return addedMolecules } } diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift index 397cf613..becb0106 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsListItemModel.swift @@ -17,7 +17,8 @@ public class TabsListItemModel: ListItemModel, MoleculeModelProtocol { public static var identifier: String = "tabsListItem" var tabs: TabsModel var molecules: [[ListItemModelProtocol & MoleculeModelProtocol]] - + private var addedMolecules: [ListItemModelProtocol & MoleculeModelProtocol]? + //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- @@ -70,3 +71,26 @@ public class TabsListItemModel: ListItemModel, MoleculeModelProtocol { try container.encodeModels2D(molecules, forKey: .molecules) } } + +extension TabsListItemModel: PageBehaviorProtocolRequirer { + public func getRequiredBehaviors() -> [PageBehaviorModelProtocol] { + return [AddRemoveMoleculesBehaviorModel()] + } +} + +extension TabsListItemModel: AddMolecules { + public func moleculesToAdd() -> AddMolecules.AddParameters? { + guard addedMolecules == nil else { return nil } + let addedMolecules = molecules[tabs.selectedIndex] + self.addedMolecules = addedMolecules + return (addedMolecules, .below) + } +} + +extension TabsListItemModel: RemoveMolecules { + public func moleculesToRemove() -> [ListItemModelProtocol & MoleculeModelProtocol]? { + guard let addedMolecules = addedMolecules else { return nil } + self.addedMolecules = nil + return addedMolecules + } +} diff --git a/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift index e6989607..a636b90b 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/TabsTableViewCell.swift @@ -55,9 +55,9 @@ import UIKit // TODO: Move to AddRemoveMoleculeBehaviorModel extension TabsTableViewCell: TabsDelegate { public func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool { + guard indexPath.row != tabs.selectedIndex else { return false } if let model = tabsListItemModel { - let molecules = model.molecules[tabs.selectedIndex] - delegateObject?.moleculeListDelegate?.removeMolecules(molecules, animation: indexPath.row < tabs.selectedIndex ? .right : .left) + MVMCoreActionHandler.shared()?.asyncHandleAction(with: RemoveMoleculesActionModel(indexPath.row < tabs.selectedIndex ? .right : .left), additionalData: [KeySourceModel: model], delegateObject: delegateObject) } previousTabIndex = tabs.selectedIndex return true @@ -65,12 +65,9 @@ extension TabsTableViewCell: TabsDelegate { public func didSelectItem(_ indexPath: IndexPath, tabs: Tabs) { let index = indexPath.row - if let model = tabsListItemModel, - index < model.molecules.count, - let cellIndexPath = delegateObject?.moleculeListDelegate?.getIndexPath(for: model) { - let molecules = model.molecules[index] - delegateObject?.moleculeListDelegate?.addMolecules(molecules, indexPath: cellIndexPath, animation: index < previousTabIndex ? .left : .right) - } + guard let model = tabsListItemModel, + index < model.molecules.count else { return } + MVMCoreActionHandler.shared()?.asyncHandleAction(with: AddMoleculesActionModel(index < previousTabIndex ? .left : .right), additionalData: [KeySourceModel: model], delegateObject: delegateObject) } } diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 554cfda1..2447aedb 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -182,44 +182,6 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol // MARK: - MoleculeDelegateProtocol //-------------------------------------------------- - open override func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { - guard let tableView = tableView else { return } - if let indexPath = tableView.indexPathForRow(at: molecule.center), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { - performTableViewUpdates() - } - } - - open override func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { - guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in - //TODO: check for molecule protocol eqaulity - let json = moleculeInfo.molecule.toJSON() - return json == molecule.toJSON() - }) else { return nil } - - return IndexPath(row: index, section: 0) - } - - open override func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) { - // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. - DispatchQueue.main.async { - var indexPaths: [IndexPath] = [] - - for molecule in molecules { - if let info = self.createMoleculeInfo(with: molecule) { - self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) - let index = indexPath.row + 1 + indexPaths.count - self.moleculesInfo?.insert(info, at: index) - indexPaths.append(IndexPath(row: index, section: 0)) - } - } - - guard indexPaths.count > 0 else { return } - self.tableView?.insertRows(at: indexPaths, with: animation) - self.updateViewConstraints() - self.view.layoutIfNeeded() - } - } - open func newData(for molecule: MoleculeModelProtocol) { //TODO: expand for header, navigation, etc let json = molecule.toJSON() @@ -252,23 +214,6 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol tableView.selectRow(at: selectedIndex, animated: false, scrollPosition: .none) } } - - open override func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) { - var indexPaths: [IndexPath] = [] - //TODO: check for molecule protocol equality - - for molecule in molecules { - if let removeIndex = moleculesInfo?.firstIndex(where: { molecule.toJSON() == $0.molecule.toJSON() }) { - moleculesInfo?.remove(at: removeIndex) - indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) - } - } - - guard indexPaths.count > 0 else { return } - tableView?.deleteRows(at: indexPaths, with: animation) - updateViewConstraints() - view.layoutIfNeeded() - } //-------------------------------------------------- // MARK: - Convenience @@ -337,91 +282,57 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } extension MoleculeListTemplate: MoleculeListProtocol { -open override func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { -guard let tableView = tableView else { return } -if let indexPath = tableView.indexPathForRow(at: molecule.center), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { -performTableViewUpdates() -} -} + open func removeMolecules(at indexPaths: [IndexPath], animation: UITableView.RowAnimation?) { + for (index, indexPath) in indexPaths.sorted().enumerated() { + let removeIndex = indexPath.row - index + moleculesInfo?.remove(at: removeIndex) + } -open override func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { -guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in -//TODO: check for molecule protocol eqaulity -let json = moleculeInfo.molecule.toJSON() -return json == molecule.toJSON() -}) else { return nil } + guard let animation = animation, + indexPaths.count > 0 else { return } + tableView?.deleteRows(at: indexPaths, with: animation) + updateViewConstraints() + view.layoutIfNeeded() + } + + open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation?) { + // This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. + DispatchQueue.main.async { + var indexPaths: [IndexPath] = [] -return IndexPath(row: index, section: 0) -} + for molecule in molecules { + if let info = self.createMoleculeInfo(with: molecule) { + self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) + let index = indexPath.row + indexPaths.count + self.moleculesInfo?.insert(info, at: index) + indexPaths.append(IndexPath(row: index, section: 0)) + } + } -open override func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) { -// This dispatch is needed to fix a race condition that can occur if this function is called during the table setup. -DispatchQueue.main.async { -var indexPaths: [IndexPath] = [] + guard let animation = animation, + indexPaths.count > 0 else { return } + self.tableView?.insertRows(at: indexPaths, with: animation) + self.updateViewConstraints() + self.view.layoutIfNeeded() + } + } + + open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { + guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in + //TODO: check for molecule protocol eqaulity + let json = moleculeInfo.molecule.toJSON() + return json == molecule.toJSON() + }) else { return nil } + + return IndexPath(row: index, section: 0) + } -for molecule in molecules { -if let info = self.createMoleculeInfo(with: molecule) { -self.tableView?.register(info.class, forCellReuseIdentifier: info.identifier) -let index = indexPath.row + 1 + indexPaths.count -self.moleculesInfo?.insert(info, at: index) -indexPaths.append(IndexPath(row: index, section: 0)) -} -} + open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { + guard let tableView = tableView else { return } -guard indexPaths.count > 0 else { return } -self.tableView?.insertRows(at: indexPaths, with: animation) -self.updateViewConstraints() -self.view.layoutIfNeeded() -} -} - -open func newData(for molecule: MoleculeModelProtocol) { -//TODO: expand for header, navigation, etc -let json = molecule.toJSON() -guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in -//TODO: check for molecule protocol eqaulity -if json == moleculeInfo.molecule.toJSON() { -return true -} else if let parent = moleculeInfo.molecule as? ParentMoleculeModelProtocol { -// Get all molecules of the same type for faster check. -let molecules: [MoleculeModelProtocol] = parent.reduceDepthFirstTraverse(options: .childFirst, depth: 0, initialResult: []) { (accumulator, currentMolecule, depth) in -if currentMolecule.moleculeName == molecule.moleculeName { -return accumulator + [currentMolecule] -} -return accumulator -} -for molecule in molecules { -if json == molecule.toJSON() { -return true -} -} -} -return false -}) else { return } - -// Refresh the cell. (reload loses cell selection) -let selectedIndex = tableView.indexPathForSelectedRow -let indexPath = IndexPath(row: index, section: 0) -tableView.reloadRows(at: [indexPath], with: .automatic) -if let selectedIndex = selectedIndex { -tableView.selectRow(at: selectedIndex, animated: false, scrollPosition: .none) -} -} - -open override func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) { -var indexPaths: [IndexPath] = [] -//TODO: check for molecule protocol equality - -for molecule in molecules { -if let removeIndex = moleculesInfo?.firstIndex(where: { molecule.toJSON() == $0.molecule.toJSON() }) { -moleculesInfo?.remove(at: removeIndex) -indexPaths.append(IndexPath(row: removeIndex + indexPaths.count, section: 0)) -} -} - -guard indexPaths.count > 0 else { return } -tableView?.deleteRows(at: indexPaths, with: animation) -updateViewConstraints() -view.layoutIfNeeded() -} + let point = molecule.convert(molecule.bounds.origin, to: tableView) + if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { + performTableViewUpdates() + } + } } diff --git a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift index 73921dc9..ee4d9587 100644 --- a/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift +++ b/MVMCoreUI/Behaviors/AddRemoveMoleculeBehavior.swift @@ -8,25 +8,28 @@ import Foundation -public enum AddMoleculePosition { +public enum AddMoleculesPosition { case above case below } +/// Protocol for adding items to a list public protocol AddMolecules { + typealias AddParameters = ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculesPosition) + /// Get the molecules that need to be added, the position to add them to, and the animation to use - func moleculesToAdd() -> ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculePosition, UITableView.RowAnimation?)? + func moleculesToAdd() -> AddParameters? } +/// Protocol for removing items to a list public protocol RemoveMolecules { /// Get the molecules that need to be removed, and the animation to use - func moleculesToRemove() -> ([ListItemModelProtocol & MoleculeModelProtocol], UITableView.RowAnimation?)? + func moleculesToRemove() -> [ListItemModelProtocol & MoleculeModelProtocol]? } public extension AddMolecules { - - /// Gets the molecules that need to be added, recursively checking for additional molecules to add. - func getRecursiveMoleculesToAdd() -> ([ListItemModelProtocol & MoleculeModelProtocol], AddMoleculePosition, UITableView.RowAnimation?)? { + /// Convenience function that gets the molecules that need to be added, recursively checking for additional molecules to add. + func getRecursiveMoleculesToAdd() -> AddMolecules.AddParameters? { guard let moleculesToAdd = moleculesToAdd() else { return nil } var newMolecules: [ListItemModelProtocol & MoleculeModelProtocol] = [] for molecule in moleculesToAdd.0 { @@ -41,30 +44,30 @@ public extension AddMolecules { } newMolecules.append(molecule) } - return (newMolecules, moleculesToAdd.1, moleculesToAdd.2) + return (newMolecules, moleculesToAdd.1) } } public extension RemoveMolecules { - /// Gets the molecules that need to be removed, recursively checking for additional molecules to remove. - func getRecursiveMoleculesToRemove() -> ([ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation?)? { + /// Convenience function that gets the molecules that need to be removed, recursively checking for additional molecules to remove. + func getRecursiveMoleculesToRemove() -> [ListItemModelProtocol & MoleculeModelProtocol]? { guard let moleculesToRemove = moleculesToRemove() else { return nil } - var newMolecules: [ListItemModelProtocol & MoleculeModelProtocol] = moleculesToRemove.0 - for case let molecule as (ListItemModelProtocol & MoleculeModelProtocol & RemoveMolecules) in moleculesToRemove.0 { + var newMolecules: [ListItemModelProtocol & MoleculeModelProtocol] = moleculesToRemove + for case let molecule as (ListItemModelProtocol & MoleculeModelProtocol & RemoveMolecules) in moleculesToRemove { guard let moreMolecules = molecule.getRecursiveMoleculesToRemove() else { continue } - newMolecules.append(contentsOf: moreMolecules.0) + newMolecules.append(contentsOf: moreMolecules) } - return (newMolecules, moleculesToRemove.1) + return newMolecules } } -public class AddRemoveMoleculeBehaviorModel: PageBehaviorModelProtocol { +public class AddRemoveMoleculesBehaviorModel: PageBehaviorModelProtocol { public class var identifier: String { "addRemoveListItemBehavior" } public var shouldAllowMultipleInstances: Bool { false } public init() {} } -public class AddRemoveMoleculeBehavior: PageCustomActionHandlerBehavior { +public class AddRemoveMoleculesBehavior: PageCustomActionHandlerBehavior, PageMoleculeTransformationBehavior { var delegate: MVMCoreUIDelegateObject? @@ -72,22 +75,34 @@ public class AddRemoveMoleculeBehavior: PageCustomActionHandlerBehavior { self.delegate = delegateObject } + public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject) { + guard let list = delegate?.moleculeListDelegate else { return } + for case let model as (MoleculeModelProtocol & ListItemModelProtocol & AddMolecules) in rootMolecules { + if let moleculesToAdd = model.getRecursiveMoleculesToAdd(), + let indexPath = list.getAdjustedIndexPath(for: model, position: moleculesToAdd.1) { + list.addMolecules(moleculesToAdd.0, indexPath: indexPath, animation: nil) + } + } + } + public func handleAction(type actionType: String?, information: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) -> Bool { if actionType == AddMoleculesActionModel.identifier { guard let list = delegate?.moleculeListDelegate, let model = additionalData?[KeySourceModel] as? (ListItemModelProtocol & MoleculeModelProtocol & AddMolecules), let moleculesToAdd = model.getRecursiveMoleculesToAdd(), - let indexPath = list.getAdjustedIndexPath(for: model, position: moleculesToAdd.1) else { return true } + let indexPath = list.getAdjustedIndexPath(for: model, position: moleculesToAdd.1), + let animation = information?["animation"] as? Int else { return true } DispatchQueue.main.async { - list.addMolecules(moleculesToAdd.0, indexPath: indexPath, animation: moleculesToAdd.2) + list.addMolecules(moleculesToAdd.0, indexPath: indexPath, animation: UITableView.RowAnimation(rawValue: animation)) } return true } else if actionType == RemoveMoleculesActionModel.identifier { guard let list = delegate?.moleculeListDelegate, let model = additionalData?[KeySourceModel] as? (ListItemModelProtocol & MoleculeModelProtocol & RemoveMolecules), - let moleculesToRemove = model.getRecursiveMoleculesToRemove() else { return true } + let moleculesToRemove = model.getRecursiveMoleculesToRemove(), + let animation = information?["animation"] as? Int else { return true } DispatchQueue.main.async { - list.removeMolecules(moleculesToRemove.0, animation: moleculesToRemove.1) + list.removeMolecules(moleculesToRemove, animation: UITableView.RowAnimation(rawValue: animation)) } return true } @@ -97,7 +112,7 @@ public class AddRemoveMoleculeBehavior: PageCustomActionHandlerBehavior { private extension MoleculeListProtocol { /// Convenience function to get the index path adjusted for position. - func getAdjustedIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol, position: AddMoleculePosition) -> IndexPath? { + func getAdjustedIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol, position: AddMoleculesPosition) -> IndexPath? { guard let indexPath = getIndexPath(for: molecule) else { return nil } if position == .below { return IndexPath(row: indexPath.row + 1, section: indexPath.section) @@ -109,6 +124,7 @@ private extension MoleculeListProtocol { public class AddMoleculesActionModel: ActionModelProtocol { public static var identifier: String = "addMoleculesAction" public var actionType: String = AddMoleculesActionModel.identifier + public var animation: UITableView.RowAnimation = .automatic public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? @@ -116,15 +132,26 @@ public class AddMoleculesActionModel: ActionModelProtocol { // MARK: - Initializer //-------------------------------------------------- - public init(_ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + public init(_ animation: UITableView.RowAnimation, extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + self.animation = animation self.extraParameters = extraParameters self.analyticsData = analyticsData } + + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let animation = try typeContainer.decodeIfPresent(UITableView.RowAnimation.self, forKey: .animation) { + self.animation = animation + } + extraParameters = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .extraParameters) + analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) + } } public class RemoveMoleculesActionModel: ActionModelProtocol { public static var identifier: String = "removeMoleculesAction" public var actionType: String = RemoveMoleculesActionModel.identifier + public var animation: UITableView.RowAnimation = .automatic public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? @@ -132,8 +159,18 @@ public class RemoveMoleculesActionModel: ActionModelProtocol { // MARK: - Initializer //-------------------------------------------------- - public init(_ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + public init(_ animation: UITableView.RowAnimation, extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + self.animation = animation self.extraParameters = extraParameters self.analyticsData = analyticsData } + + public required init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let animation = try typeContainer.decodeIfPresent(UITableView.RowAnimation.self, forKey: .animation) { + self.animation = animation + } + extraParameters = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .extraParameters) + analyticsData = try typeContainer.decodeIfPresent(JSONValueDictionary.self, forKey: .analyticsData) + } } diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 0c706be3..78dd2916 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -222,7 +222,7 @@ open class CoreUIModelMapping: ModelMapping { open class func registerBehaviors() { ModelRegistry.register(handler: ScreenBrightnessModifierBehavior.self, for: ScreenBrightnessModifierBehaviorModel.self) ModelRegistry.register(handler: PageGetContactBehavior.self, for: PageGetContactBehaviorModel.self) - ModelRegistry.register(handler: AddRemoveMoleculeBehavior.self, for: AddRemoveMoleculeBehaviorModel.self) + ModelRegistry.register(handler: AddRemoveMoleculesBehavior.self, for: AddRemoveMoleculesBehaviorModel.self) } open override class func registerActions() { From 4108cfb7c65cc6c5de9e5b1d6aa66aaaf33bf522 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Mon, 26 Jul 2021 19:11:17 -0400 Subject: [PATCH 4/4] Move molecule layout update back --- .../Atomic/Atoms/FormFields/Tags/Tags.swift | 2 +- .../Atomic/Atoms/Selectors/RadioSwatches.swift | 2 +- .../Atomic/Atoms/Views/LoadImageView.swift | 2 +- MVMCoreUI/Atomic/Atoms/Views/WebView.swift | 2 +- .../Protocols/MoleculeDelegateProtocol.swift | 8 ++++++++ .../Protocols/MoleculeListProtocol.swift | 3 --- .../Templates/MoleculeListTemplate.swift | 18 +++++++++--------- MVMCoreUI/BaseControllers/ViewController.swift | 5 ++++- .../Containers/Views/EntryFieldContainer.swift | 2 +- 9 files changed, 26 insertions(+), 18 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/Tags/Tags.swift b/MVMCoreUI/Atomic/Atoms/FormFields/Tags/Tags.swift index be518465..4b1fa4f9 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/Tags/Tags.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/Tags/Tags.swift @@ -26,7 +26,7 @@ open class Tags: View, MFButtonProtocol { DispatchQueue.main.async { self.collectionView.collectionViewLayout.invalidateLayout() self.collectionViewHeight.constant = self.collectionView.contentSize.height - self.delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) } } diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift index 8587bb2f..5a5b6836 100644 --- a/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioSwatches.swift @@ -96,7 +96,7 @@ open class RadioSwatches: View { height != oldHeight { // Notify delegate of height change, called async to avoid various race conditions caused while happening while laying out initially. DispatchQueue.main.async { - self.delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) } } collectionViewHeight?.constant = CGFloat(height) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift index fa3d290c..8d8ef49e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift @@ -331,7 +331,7 @@ self.addConstraints(width: width, height: height, size: image?.size) self.loadingSpinnerHeightConstraint?.constant = 0 if layoutWillChange { - self.delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) } completionBlock(image,data,isFallbackImage) })} diff --git a/MVMCoreUI/Atomic/Atoms/Views/WebView.swift b/MVMCoreUI/Atomic/Atoms/Views/WebView.swift index d75c59ab..e6708055 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/WebView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/WebView.swift @@ -148,7 +148,7 @@ extension WebView : WKUIDelegate { //if failed to get height from javascript, using scrollview.contensize's height self.webViewHeight?.constant = webView.scrollView.contentSize.height } - self.delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) }) }) } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift index 25bddd08..1d0a3f61 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeDelegateProtocol.swift @@ -15,4 +15,12 @@ public protocol MoleculeDelegateProtocol: AnyObject { func getModuleWithName(_ name: String?) -> [AnyHashable: Any]? func getModuleWithName(_ moleculeName: String) -> MoleculeModelProtocol? + + /// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. Mainly used for list or collections. + func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) //optional +} + +extension MoleculeDelegateProtocol { + + public func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } } diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift index 091fa00b..dfeb7b11 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeListProtocol.swift @@ -16,9 +16,6 @@ public protocol MoleculeListProtocol { /// Asks the delegate to remove molecules. func removeMolecules(at indexPaths: [IndexPath], animation: UITableView.RowAnimation?) - - /// Notifies the delegate that the molecule layout update. Should be called when the layout may change due to an async method. Mainly used for list or collections. - func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) } extension MoleculeListProtocol { diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 2447aedb..843d807e 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -182,6 +182,15 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol // MARK: - MoleculeDelegateProtocol //-------------------------------------------------- + open override func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { + guard let tableView = tableView else { return } + + let point = molecule.convert(molecule.bounds.origin, to: tableView) + if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { + performTableViewUpdates() + } + } + open func newData(for molecule: MoleculeModelProtocol) { //TODO: expand for header, navigation, etc let json = molecule.toJSON() @@ -326,13 +335,4 @@ extension MoleculeListTemplate: MoleculeListProtocol { return IndexPath(row: index, section: 0) } - - open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { - guard let tableView = tableView else { return } - - let point = molecule.convert(molecule.bounds.origin, to: tableView) - if let indexPath = tableView.indexPathForRow(at: point), tableView.indexPathsForVisibleRows?.contains(indexPath) ?? false { - performTableViewUpdates() - } - } } diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 59a06018..91f98d19 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -504,7 +504,10 @@ import UIKit return nil } - + + // Needed otherwise when subclassed, the extension gets called. + open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) { } + //-------------------------------------------------- // MARK: - MVMCoreUIDetailViewProtocol //-------------------------------------------------- diff --git a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift index 52be603f..9ccd1d25 100644 --- a/MVMCoreUI/Containers/Views/EntryFieldContainer.swift +++ b/MVMCoreUI/Containers/Views/EntryFieldContainer.swift @@ -270,7 +270,7 @@ import UIKit bottomBar?.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size) if updateMoleculeLayout || heightChanged { - delegateObject?.moleculeListDelegate?.moleculeLayoutUpdated(self) + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) } setNeedsDisplay()