forked from shadowfacts/Tusker
Change timeline gap-filling to do a proper job of maintaining the bottom-relative scroll position
This commit is contained in:
parent
276691efbf
commit
47b838a386
|
@ -246,17 +246,9 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
snapshot.insertItems(presentItems.map { .status(id: $0, state: .unknown) }, beforeItem: .gap)
|
snapshot.insertItems(presentItems.map { .status(id: $0, state: .unknown) }, beforeItem: .gap)
|
||||||
|
|
||||||
if applySnapshotBeforeScrolling {
|
if applySnapshotBeforeScrolling {
|
||||||
// use a snapshot of the collection view to hide the flicker as the content offset changes and then changes back
|
let firstVisibleIndexPath = collectionView.indexPathsForVisibleItems.min()!
|
||||||
let snapshotView = collectionView.snapshotView(afterScreenUpdates: false)!
|
let firstVisibleItem = dataSource.itemIdentifier(for: firstVisibleIndexPath)!
|
||||||
snapshotView.layer.zPosition = 1000
|
applySnapshot(snapshot, maintainingBottomRelativeScrollPositionOf: firstVisibleItem)
|
||||||
snapshotView.frame = view.bounds
|
|
||||||
view.addSubview(snapshotView)
|
|
||||||
|
|
||||||
let bottomOffset = collectionView.contentSize.height - collectionView.contentOffset.y
|
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: false) {
|
|
||||||
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
|
|
||||||
snapshotView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = ToastConfiguration(title: "Jump to present")
|
var config = ToastConfiguration(title: "Jump to present")
|
||||||
|
@ -276,6 +268,50 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: this only works when items are being inserted ABOVE the item to maintain
|
||||||
|
private func applySnapshot(_ snapshot: NSDiffableDataSourceSnapshot<Section, Item>, maintainingBottomRelativeScrollPositionOf itemToMaintain: Item) {
|
||||||
|
// use a snapshot of the collection view to hide the flicker as the content offset changes and then changes back
|
||||||
|
let snapshotView = collectionView.snapshotView(afterScreenUpdates: false)!
|
||||||
|
snapshotView.layer.zPosition = 1000
|
||||||
|
snapshotView.frame = view.bounds
|
||||||
|
view.addSubview(snapshotView)
|
||||||
|
|
||||||
|
var firstItemAfterOriginalGapOffsetFromTop: CGFloat = 0
|
||||||
|
if let indexPath = dataSource.indexPath(for: itemToMaintain),
|
||||||
|
let cell = collectionView.cellForItem(at: indexPath) {
|
||||||
|
// subtract top safe area inset b/c scrollToItem at .top aligns the top of the cell to the top of the safe area
|
||||||
|
firstItemAfterOriginalGapOffsetFromTop = cell.convert(.zero, to: view).y - view.safeAreaInsets.top
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource.apply(snapshot, animatingDifferences: false) {
|
||||||
|
if let indexPathOfItemAfterOriginalGap = self.dataSource.indexPath(for: itemToMaintain) {
|
||||||
|
// scroll up until we've accumulated enough MEASURED height that we can put the
|
||||||
|
// firstItemAfterOriginalGapCell at the top of the screen and then scroll down by
|
||||||
|
// firstItemAfterOriginalGapOffsetFromTop without intruding into unmeasured area
|
||||||
|
var cur = indexPathOfItemAfterOriginalGap
|
||||||
|
var amountScrolledUp: CGFloat = 0
|
||||||
|
while true {
|
||||||
|
if cur.row <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if let cell = self.collectionView.cellForItem(at: indexPathOfItemAfterOriginalGap),
|
||||||
|
cell.convert(.zero, to: self.view).y - self.view.safeAreaInsets.top > firstItemAfterOriginalGapOffsetFromTop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cur = IndexPath(row: cur.row - 1, section: cur.section)
|
||||||
|
self.collectionView.scrollToItem(at: cur, at: .top, animated: false)
|
||||||
|
self.collectionView.layoutIfNeeded()
|
||||||
|
let attrs = self.collectionView.layoutAttributesForItem(at: cur)!
|
||||||
|
amountScrolledUp += attrs.size.height
|
||||||
|
}
|
||||||
|
self.collectionView.contentOffset.y += amountScrolledUp
|
||||||
|
self.collectionView.contentOffset.y -= firstItemAfterOriginalGapOffsetFromTop
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TimelineViewController {
|
extension TimelineViewController {
|
||||||
|
@ -539,29 +575,8 @@ extension TimelineViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if addedItems {
|
if addedItems {
|
||||||
// use a snapshot of the collection view to hide the flicker as the content offset changes and then changes back
|
let firstItemAfterOriginalGap = statusItems[gapIndex + 1]
|
||||||
let snapshotView = collectionView.snapshotView(afterScreenUpdates: false)!
|
applySnapshot(snapshot, maintainingBottomRelativeScrollPositionOf: firstItemAfterOriginalGap)
|
||||||
snapshotView.layer.zPosition = 1000
|
|
||||||
snapshotView.frame = view.bounds
|
|
||||||
view.addSubview(snapshotView)
|
|
||||||
|
|
||||||
// Yes, these are all load bearing. Setting the contentOffset seems to cause the collection view to recalculate the size
|
|
||||||
// of some cells, thus changing the contentSize and the offset necessary to match to match the bottom offset.
|
|
||||||
// Three DispatchQueue.main.async's seems to be the fewest we can reliably get away with.
|
|
||||||
let bottomOffset = collectionView.contentSize.height - collectionView.contentOffset.y
|
|
||||||
dataSource.apply(snapshot, animatingDifferences: false) {
|
|
||||||
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
|
|
||||||
snapshotView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
dataSource.apply(snapshot, animatingDifferences: true) {}
|
dataSource.apply(snapshot, animatingDifferences: true) {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue