add remove behavior

This commit is contained in:
Pfeil, Scott Robert 2021-05-28 09:56:58 -04:00
parent 06cf014b1f
commit af0ebf8678
20 changed files with 302 additions and 115 deletions

View File

@ -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 = "<group>"; };
D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = "<group>"; };
D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = "<group>"; };
D270E5662642F77300CDBED2 /* AddRemoveMoleculeBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoveMoleculeBehavior.swift; sourceTree = "<group>"; };
D272F5F82473163100BD1A8F /* BarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonItem.swift; sourceTree = "<group>"; };
D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = "<group>"; };
D2755D7A23689C7500485468 /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = "<group>"; };
@ -1059,6 +1062,7 @@
D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUIObject.swift; sourceTree = "<group>"; };
D2B18B93236214AD00A9AEDC /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = "<group>"; };
D2B1E3E422F37D6A0065F95C /* ImageHeadlineBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeadlineBody.swift; sourceTree = "<group>"; };
D2B9D0E3265EEE9D0084735C /* MoleculeListProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListProtocol.swift; sourceTree = "<group>"; };
D2C5001621F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIViewControllerMappingObject.h; sourceTree = "<group>"; };
D2C5001721F8ECDD001DA659 /* MVMCoreUIViewControllerMappingObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIViewControllerMappingObject.m; sourceTree = "<group>"; };
D2C521A823EDE79E00CA2634 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
@ -1313,6 +1317,7 @@
0A1C30972620F61A00B47F3B /* Protocols */,
27F97369246750BE00CAB5C5 /* ScreenBrightnessModifierBehavior.swift */,
D23A900826125FFB007E14CE /* GetContactBehavior.swift */,
D270E5662642F77300CDBED2 /* AddRemoveMoleculeBehavior.swift */,
);
path = Behaviors;
sourceTree = "<group>";
@ -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 */,

View File

@ -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)
}
}

View File

@ -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) -> ())?

View File

@ -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]
}
}

View File

@ -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 ?? "")

View File

@ -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)

View File

@ -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)
})}

View File

@ -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)
})
})
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)
})
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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) {}
}

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -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
//--------------------------------------------------

View File

@ -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
}
}

View File

@ -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()

View File

@ -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() {