Skip to content

Commit d62e5b1

Browse files
committed
Only reconfigure visible supplementary views when applying snapshots
Similar to e2a1478bb3f4ac4dae26f422dc9c62185b48018c. optimize the headers, footers, and other supplementary views that we reconfigure. previously, we already had a lot of escape hatches where we could short circuit this loop through all the sections and all their views. this adds another early return by only checking the sections that are visible. if a section is not visible, we can skip it. when a previously not visible section scrolls on screen, it will go through the whole dequeue + configure flow for all its cells and views. so this should be safe to do. **visibility notes** determining section visibility is a bit of a "hack" -- there's no direct API for this, so instead we need to check for supplementary views that are visible via `indexPathsForVisibleSupplementaryElements`. from there we can derive the visible section indexes, and then resolve their identifiers. all of that is necessary because we cannot query supplementary views directly like we can for items. `DiffableDataSource` provides `self.itemIdentifier(for: indexPath)` but there is no equivalent for supplementary views. it's all a bit roundabout, but it works.
1 parent a2f21c0 commit d62e5b1

File tree

2 files changed

+44
-0
lines changed

2 files changed

+44
-0
lines changed

Sources/CollectionViewModel.swift

+16
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,22 @@ public struct CollectionViewModel: Hashable, DiffableViewModel {
194194
let tuples = allCells.map { ($0.id, $0) }
195195
return Dictionary(uniqueKeysWithValues: tuples)
196196
}
197+
198+
func allSupplementaryViewKinds() -> Set<String> {
199+
var allKinds = Set<String>()
200+
self.sections.forEach {
201+
if let header = $0.header {
202+
allKinds.insert(header.registration.viewType.kind)
203+
}
204+
if let footer = $0.footer {
205+
allKinds.insert(footer.registration.viewType.kind)
206+
}
207+
$0.supplementaryViews.forEach {
208+
allKinds.insert($0.registration.viewType.kind)
209+
}
210+
}
211+
return allKinds
212+
}
197213
}
198214

199215
// MARK: Collection, RandomAccessCollection

Sources/DiffableDataSource.swift

+28
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,16 @@ final class DiffableDataSource: UICollectionViewDiffableDataSource<AnyHashable,
180180
) {
181181
let allSourceSections = source.allSectionsByIdentifier()
182182

183+
let visibleSectionIds = self._visibleSectionIdentifiersFrom(destination: destination)
184+
183185
for sectionIndex in 0..<destination.sections.count {
184186
let destinationSection = destination.sections[sectionIndex]
185187

188+
// If this section is not visible, skip it.
189+
guard visibleSectionIds.contains(destinationSection.id) else {
190+
continue
191+
}
192+
186193
// If this section does not have any supplementary views, skip it.
187194
guard destinationSection.hasSupplementaryViews else {
188195
continue
@@ -243,6 +250,27 @@ final class DiffableDataSource: UICollectionViewDiffableDataSource<AnyHashable,
243250
}
244251
}
245252

253+
private func _visibleSectionIdentifiersFrom(destination: CollectionViewModel) -> Set<UniqueIdentifier> {
254+
// Using supplementary views as a proxy for the sections that are visible.
255+
// Limitations in the APIs require this.
256+
// Unfortunately, we cannot do the same thing as `_visibleItemIdentifiers()` above,
257+
// because we cannot query directly for supplementary view items.
258+
let allKinds = destination.allSupplementaryViewKinds()
259+
let visibleIndexPaths = allKinds.flatMap {
260+
self._collectionView.indexPathsForVisibleSupplementaryElements(ofKind: $0)
261+
}
262+
let visibleSections = visibleIndexPaths.map { $0.section }
263+
264+
// These are the current, existing (that is, "source") section identifiers.
265+
let visibleSourceSectionIdentifiers = visibleSections.compactMap { self.sectionIdentifier(for: $0) }
266+
267+
// This is ok, because in terms of needing to "reload" supplementary views,
268+
// we only need to know what remained visible from the source snapshot.
269+
// Anything that has been newly inserted (from the "destination") will
270+
// be getting configured for the first time.
271+
return Set(visibleSourceSectionIdentifiers)
272+
}
273+
246274
private func _reconfigureSupplementaryView(model: AnySupplementaryViewModel, item: Int, section: Int) {
247275
let indexPath = IndexPath(item: item, section: section)
248276
if let view = self._collectionView.supplementaryView(forElementKind: model._kind, at: indexPath) {

0 commit comments

Comments
 (0)