From 2e7f8ae9db875c5fc24e89e929c78b9aaefc7d26 Mon Sep 17 00:00:00 2001 From: Andrew Nickell Date: Mon, 13 Nov 2023 14:34:15 -0800 Subject: [PATCH] feat(TableViewDriver): allow lightweightDiffing --- CHANGELOG.md | 5 +++ ReactiveLists.podspec | 2 +- Sources/TableViewDriver.swift | 83 ++++++++++++++++++++++------------- 3 files changed, 59 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c799fff..169ef89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The changelog for `ReactiveLists`. Also see the [releases](https://github.com/pl ------ NEXT +---- +0.8.3 +----- +- Added `lightweightDiffing` option to `TableViewDriver` + ---- 0.8.2 ----- diff --git a/ReactiveLists.podspec b/ReactiveLists.podspec index a9731e2..97a3e98 100644 --- a/ReactiveLists.podspec +++ b/ReactiveLists.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ReactiveLists" - s.version = "0.8.2" + s.version = "0.8.3" s.summary = "React-like API for UITableView and UICollectionView" s.homepage = "https://github.com/plangrid/ReactiveLists" diff --git a/Sources/TableViewDriver.swift b/Sources/TableViewDriver.swift index 76b7d22..a1cb611 100644 --- a/Sources/TableViewDriver.swift +++ b/Sources/TableViewDriver.swift @@ -72,6 +72,8 @@ open class TableViewDriver: NSObject { private let _automaticDiffingEnabled: Bool + private let _lightweightDiffing: Bool + /// Initializes a data source that drives a `UITableView` based on a `TableViewModel`. /// /// - Parameters: @@ -82,14 +84,19 @@ open class TableViewDriver: NSObject { /// - automaticDiffingEnabled: defines whether or not this data source updates the table /// view automatically when cells/sections are moved/inserted/deleted. /// Defaults to `true`. + /// - lightweightDiffing: when enabled, simply diff the count of rows rather than generating full changesets. + /// Defaults to `false`. public init( tableView: UITableView, tableViewModel: TableViewModel? = nil, shouldDeselectUponSelection: Bool = true, - automaticDiffingEnabled: Bool = true) { + automaticDiffingEnabled: Bool = true, + lightweightDiffing: Bool = false + ) { self._tableViewModel = tableViewModel self.tableView = tableView self._automaticDiffingEnabled = automaticDiffingEnabled + self._lightweightDiffing = lightweightDiffing self._shouldDeselectUponSelection = shouldDeselectUponSelection super.init() tableView.dataSource = self @@ -182,40 +189,56 @@ open class TableViewDriver: NSObject { guard let newModel = newModel else { return } - if self._automaticDiffingEnabled { + if self._automaticDiffingEnabled, self._lightweightDiffing { + let old = oldModel?.sectionModels.reduce(into: 0) { $0 += $1.cellViewModels.count } + let new = newModel.sectionModels.reduce(into: 0) { $0 += $1.cellViewModels.count } - let visibleIndexPaths = tableView.indexPathsForVisibleRows ?? [] - let old: [DiffableTableSectionViewModel] = oldModel?.sectionModelsForDiffing(inVisibleIndexPaths: visibleIndexPaths) ?? [] - let changeset = StagedChangeset( - source: old, - target: newModel.sectionModelsForDiffing(inVisibleIndexPaths: visibleIndexPaths) - ) - if changeset.isEmpty { - self._tableViewModel = newModel + self._tableViewModel = newModel + if old == new { + self.refreshViews(refreshContext: .contentOnly) } else { - self.tableView.reload( - using: changeset, - deleteSectionsAnimation: self.deletionAnimation, - insertSectionsAnimation: self.insertionAnimation, - reloadSectionsAnimation: self.insertionAnimation, - deleteRowsAnimation: self.deletionAnimation, - insertRowsAnimation: self.insertionAnimation, - reloadRowsAnimation: self.insertionAnimation - ) { - self._tableViewModel = $0.makeTableViewModel(sectionIndexTitles: oldModel?.sectionIndexTitles) + // We need to call reloadData here to ensure UITableView is in-sync with the data source before we start + // making calls to access visible cells. In the automatic diffing case, this is handled by calls to + // beginUpdates() endUpdates() + self.tableView.reloadData() + self.refreshViews() + } + } else { + if self._automaticDiffingEnabled { + + let visibleIndexPaths = tableView.indexPathsForVisibleRows ?? [] + let old: [DiffableTableSectionViewModel] = oldModel?.sectionModelsForDiffing(inVisibleIndexPaths: visibleIndexPaths) ?? [] + let changeset = StagedChangeset( + source: old, + target: newModel.sectionModelsForDiffing(inVisibleIndexPaths: visibleIndexPaths) + ) + if changeset.isEmpty { + self._tableViewModel = newModel + } else { + self.tableView.reload( + using: changeset, + deleteSectionsAnimation: self.deletionAnimation, + insertSectionsAnimation: self.insertionAnimation, + reloadSectionsAnimation: self.insertionAnimation, + deleteRowsAnimation: self.deletionAnimation, + insertRowsAnimation: self.insertionAnimation, + reloadRowsAnimation: self.insertionAnimation + ) { + self._tableViewModel = $0.makeTableViewModel(sectionIndexTitles: oldModel?.sectionIndexTitles) + } + self._tableViewModel = newModel } + // always refresh visible cells, in case some + // state changed that isn't captured by the diff + self.refreshViews(refreshContext: .contentOnly) + } else { self._tableViewModel = newModel + // We need to call reloadData here to ensure UITableView is in-sync with the data source before we start + // making calls to access visible cells. In the automatic diffing case, this is handled by calls to + // beginUpdates() endUpdates() + self.tableView.reloadData() + self.refreshViews() } - // always refresh visible cells, in case some - // state changed that isn't captured by the diff - self.refreshViews(refreshContext: .contentOnly) - } else { - self._tableViewModel = newModel - // We need to call reloadData here to ensure UITableView is in-sync with the data source before we start - // making calls to access visible cells. In the automatic diffing case, this is handled by calls to - // beginUpdates() endUpdates() - self.tableView.reloadData() - self.refreshViews() } }