diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 1f919dd3..14c42f43 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -11,51 +11,39 @@ import UIKit public struct MoleculeInfo: Hashable, CustomDebugStringConvertible { public func hash(into hasher: inout Hasher) { - hasher.combine(id) + hasher.combine(moleculeId) } public static func == (lhs: MoleculeInfo, rhs: MoleculeInfo) -> Bool { - lhs.id == rhs.id + lhs.moleculeId == rhs.moleculeId } - public let id: String + public var moleculeId: String + // TODO: This is an existing bug in the template handling. cellReuseId is a deep definition of the cell and its children. Structural changes to the cell should re-register. public let cellReuseId: String public let cellType: MoleculeViewProtocol.Type - public var model: ListItemModelProtocol & MoleculeModelProtocol init?(listItemModel: ListItemModelProtocol & MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject? = nil) { guard let moleculeClass = ModelRegistry.getMoleculeClass(listItemModel) else { return nil } - self.model = listItemModel - self.id = model.id + self.moleculeId = listItemModel.id self.cellType = moleculeClass self.cellReuseId = moleculeClass.nameForReuse(with: listItemModel, delegateObject) ?? listItemModel.moleculeName } - func doesMatch(_ molecule: MoleculeModelProtocol) -> Bool { - id == molecule.id - } - - func contains(oneOf moleculeModels: [MoleculeModelProtocol]) -> MoleculeModelProtocol? { - model.findFirstMolecule(by: { existingMolecule in - moleculeModels.contains { moleculeModel in - existingMolecule.moleculeName == moleculeModel.moleculeName && existingMolecule.id == moleculeModel.id - } - }) - } - public var debugDescription: String { - return "\(Self.self) \(id)" + return "\(Self.self) \(moleculeId)" } } extension Array where Element == MoleculeInfo { func filter(byMolecules molecules: [MoleculeModelProtocol]) -> [MoleculeInfo] { - filter { listItemRef in molecules.contains { $0.id == listItemRef.model.id } } + filter { listItemRef in molecules.contains { $0.id == listItemRef.moleculeId } } } + @discardableResult func registerTypes(with tableView: UITableView) -> [MoleculeInfo] { forEach { tableView.register($0.cellType, forCellReuseIdentifier: $0.cellReuseId) } return self @@ -110,13 +98,14 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol let tableView = super.createTableView() dataSource = UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, moleculeInfo in guard let self = self, - let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.cellReuseId) + let cell = tableView.dequeueReusableCell(withIdentifier: moleculeInfo.cellReuseId), + let moleculeModel = molecule(for: moleculeInfo.moleculeId) else { return UITableViewCell() } - cell.isHidden = moleculeInfo.model.gone + cell.isHidden = (moleculeModel as? GoneableProtocol)?.gone == true (cell as? MoleculeViewProtocol)?.reset() (cell as? MoleculeListCellProtocol)?.setLines(with: templateModel?.line, delegateObject: delegateObjectIVar, additionalData: nil, indexPath: indexPath) if let moleculeView = cell as? MoleculeViewProtocol { - updateMoleculeView(moleculeView, from: moleculeInfo.model) + updateMoleculeView(moleculeView, from: moleculeModel) } (cell as? MVMCoreViewProtocol)?.updateView(tableView.bounds.width) // Neded to fix an apple defect where the cell is not the correct size on certain devices for certain cells @@ -127,6 +116,13 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol return tableView } + // TODO: This assumes a molecule is always present in the model tree. What if its in another data store? Also we should attempt O(1) for being in the render phase. + func molecule(for id: String) -> MoleculeModelProtocol? { + templateModel?.findFirstMolecule() { molecule in + molecule.id == id + } + } + open override func parsePageJSON(loadObject: MVMCoreLoadObject) throws -> PageModelProtocol { return try parseTemplate(loadObject: loadObject) } @@ -214,12 +210,15 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol //-------------------------------------------------- open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return dataSource.itemIdentifier(for: indexPath)?.model.gone == true ? 0 : UITableView.automaticDimension + guard let moleculeId = dataSource.itemIdentifier(for: indexPath)?.moleculeId, + let moleculeModel = molecule(for: moleculeId) else { return 0 } + return (moleculeModel as? GoneableProtocol)?.gone == true ? 0 : UITableView.automaticDimension } open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { guard let moleculeInfo = dataSource.itemIdentifier(for: indexPath), - let estimatedHeight = moleculeInfo.cellType.estimatedHeight(with: moleculeInfo.model, delegateObject() as? MVMCoreUIDelegateObject) + let moleculeModel = molecule(for: moleculeInfo.moleculeId), + let estimatedHeight = moleculeInfo.cellType.estimatedHeight(with: moleculeModel, delegateObject() as? MVMCoreUIDelegateObject) else { return 0 } return estimatedHeight @@ -300,14 +299,13 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } var snapshot = dataSource.snapshot() - let updatedListItems: [MoleculeInfo] = snapshot.itemIdentifiers.compactMap { listItemRef -> MoleculeInfo? in - guard let matchedMolecule = listItemRef.contains(oneOf: molecules) else { return nil } - if let matchedMolecule = matchedMolecule as? (ListItemModelProtocol & MoleculeModelProtocol), listItemRef.doesMatch(matchedMolecule) { - var listItemRef = listItemRef - listItemRef.model = matchedMolecule // Replace the top level molecule if it changed. - return listItemRef - } - return listItemRef + let updatedListItems: [MoleculeInfo] = snapshot.itemIdentifiers.filter { listItemRef -> Bool in + guard let model = molecule(for: listItemRef.moleculeId) else { return false } + return model.findFirstMolecule(by: { existingMolecule in + molecules.contains { moleculeModel in + existingMolecule.moleculeName == moleculeModel.moleculeName && existingMolecule.id == moleculeModel.id + } + }) != nil } if #available(iOS 15.0, *) { snapshot.reconfigureItems(updatedListItems) @@ -315,6 +313,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol // A full reload can cause a flicker / animation. Better to avoid with above reconfigure method. snapshot.reloadItems(updatedListItems) } + // TODO: Thoughts on fading on cell structural replacements? + // dataSource.defaultRowAnimation = .fade dataSource.apply(snapshot) // Refresh the cell. (reload loses cell selection). TODO: Check if necessary and timing.