Digital PCT265 defect MVAPCT-272: Fix view async updates using Apple identity strategy.

This commit is contained in:
Hedden, Kyle Matthew 2024-09-16 18:12:36 -04:00
parent 5c3c452e9a
commit 81676b701b

View File

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