Completely replace all items when jumping to present
This commit is contained in:
parent
0485400c1f
commit
80f9800fd6
|
@ -343,21 +343,19 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
|
|
||||||
private func insertPresentItemsIfNecessary(_ presentItems: [String]) {
|
private func insertPresentItemsIfNecessary(_ presentItems: [String]) {
|
||||||
var snapshot = dataSource.snapshot()
|
let snapshot = dataSource.snapshot()
|
||||||
guard snapshot.indexOfSection(.statuses) != nil else {
|
guard snapshot.indexOfSection(.statuses) != nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let currentItems = snapshot.itemIdentifiers(inSection: .statuses)
|
let currentItems = snapshot.itemIdentifiers(inSection: .statuses)
|
||||||
if case .status(id: let firstID, state: _) = currentItems.first,
|
if case .status(id: let firstID, state: _) = currentItems.first,
|
||||||
// if there's no overlap between presentItems and the existing items in the data source, insert the present items and prompt the user
|
// if there's no overlap between presentItems and the existing items in the data source, prompt the user
|
||||||
!presentItems.contains(firstID) {
|
!presentItems.contains(firstID) {
|
||||||
|
|
||||||
// remove any existing gap, if there is one
|
// create a new snapshot to reset the timeline to the "present" state
|
||||||
if let index = currentItems.lastIndex(of: .gap) {
|
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||||
snapshot.deleteItems(Array(currentItems[index...]))
|
snapshot.appendSections([.statuses])
|
||||||
}
|
snapshot.appendItems(presentItems.map { .status(id: $0, state: .unknown) }, toSection: .statuses)
|
||||||
snapshot.insertItems([.gap], beforeItem: currentItems.first!)
|
|
||||||
snapshot.insertItems(presentItems.map { .status(id: $0, state: .unknown) }, beforeItem: .gap)
|
|
||||||
|
|
||||||
var config = ToastConfiguration(title: "Jump to present")
|
var config = ToastConfiguration(title: "Jump to present")
|
||||||
config.edge = .top
|
config.edge = .top
|
||||||
|
@ -366,12 +364,34 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
config.action = { [unowned self] toast in
|
config.action = { [unowned self] toast in
|
||||||
toast.dismissToast(animated: true)
|
toast.dismissToast(animated: true)
|
||||||
|
|
||||||
|
let origSnapshot = self.dataSource.snapshot()
|
||||||
|
let origItemAtTop: (Item, CGFloat)?
|
||||||
|
if let statusesSection = origSnapshot.indexOfSection(.statuses),
|
||||||
|
let indexPath = self.collectionView.indexPathsForVisibleItems.sorted().first(where: { $0.section == statusesSection }),
|
||||||
|
let cell = self.collectionView.cellForItem(at: indexPath),
|
||||||
|
let item = self.dataSource.itemIdentifier(for: indexPath) {
|
||||||
|
origItemAtTop = (item, cell.convert(.zero, to: self.view).y - self.view.safeAreaInsets.top)
|
||||||
|
} else {
|
||||||
|
origItemAtTop = nil
|
||||||
|
}
|
||||||
|
|
||||||
self.dataSource.apply(snapshot, animatingDifferences: true) {
|
self.dataSource.apply(snapshot, animatingDifferences: true) {
|
||||||
// TODO: we can't set prevScrollOffsetBeforeScrollToTop here to allow undoing the scroll-to-top
|
|
||||||
// because that would involve scrolling through unmeasured-cell which fucks up the content offset values.
|
|
||||||
// we probably need a data-source aware implementation of scrollToTop which uses item & offset w/in item
|
|
||||||
// to track the restore position
|
|
||||||
self.collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
|
self.collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
|
||||||
|
|
||||||
|
var config = ToastConfiguration(title: "Go back")
|
||||||
|
config.edge = .top
|
||||||
|
config.systemImageName = "arrow.down"
|
||||||
|
config.dismissAutomaticallyAfter = 4
|
||||||
|
config.action = { [unowned self] toast in
|
||||||
|
toast.dismissToast(animated: true)
|
||||||
|
// todo: it would be nice if we could animate this, but that doesn't work with the screen-position-maintaining stuff
|
||||||
|
if let (item, offset) = origItemAtTop {
|
||||||
|
self.applySnapshot(snapshot, maintainingScreenPosition: offset, ofItem: item)
|
||||||
|
} else {
|
||||||
|
self.dataSource.apply(origSnapshot, animatingDifferences: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.showToast(configuration: config, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.showToast(configuration: config, animated: true)
|
self.showToast(configuration: config, animated: true)
|
||||||
|
@ -380,32 +400,35 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
|
|
||||||
// NOTE: this only works when items are being inserted ABOVE the item to maintain
|
// NOTE: this only works when items are being inserted ABOVE the item to maintain
|
||||||
private func applySnapshot(_ snapshot: NSDiffableDataSourceSnapshot<Section, Item>, maintainingBottomRelativeScrollPositionOf itemToMaintain: Item) {
|
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
|
var firstItemAfterOriginalGapOffsetFromTop: CGFloat = 0
|
||||||
if let indexPath = dataSource.indexPath(for: itemToMaintain),
|
if let indexPath = dataSource.indexPath(for: itemToMaintain),
|
||||||
let cell = collectionView.cellForItem(at: indexPath) {
|
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
|
// 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
|
firstItemAfterOriginalGapOffsetFromTop = cell.convert(.zero, to: view).y - view.safeAreaInsets.top
|
||||||
}
|
}
|
||||||
|
applySnapshot(snapshot, maintainingScreenPosition: firstItemAfterOriginalGapOffsetFromTop, ofItem: itemToMaintain)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applySnapshot(_ snapshot: NSDiffableDataSourceSnapshot<Section, Item>, maintainingScreenPosition offsetFromTop: CGFloat, ofItem 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)
|
||||||
|
|
||||||
dataSource.apply(snapshot, animatingDifferences: false) {
|
dataSource.apply(snapshot, animatingDifferences: false) {
|
||||||
if let indexPathOfItemAfterOriginalGap = self.dataSource.indexPath(for: itemToMaintain) {
|
if let indexPathOfItemToMaintain = self.dataSource.indexPath(for: itemToMaintain) {
|
||||||
// scroll up until we've accumulated enough MEASURED height that we can put the
|
// 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
|
// firstItemAfterOriginalGapCell at the top of the screen and then scroll down by
|
||||||
// firstItemAfterOriginalGapOffsetFromTop without intruding into unmeasured area
|
// firstItemAfterOriginalGapOffsetFromTop without intruding into unmeasured area
|
||||||
var cur = indexPathOfItemAfterOriginalGap
|
var cur = indexPathOfItemToMaintain
|
||||||
var amountScrolledUp: CGFloat = 0
|
var amountScrolledUp: CGFloat = 0
|
||||||
while true {
|
while true {
|
||||||
if cur.row <= 0 {
|
if cur.row <= 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if let cell = self.collectionView.cellForItem(at: indexPathOfItemAfterOriginalGap),
|
if let cell = self.collectionView.cellForItem(at: indexPathOfItemToMaintain),
|
||||||
cell.convert(.zero, to: self.view).y - self.view.safeAreaInsets.top > firstItemAfterOriginalGapOffsetFromTop {
|
cell.convert(.zero, to: self.view).y - self.view.safeAreaInsets.top > offsetFromTop {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
cur = IndexPath(row: cur.row - 1, section: cur.section)
|
cur = IndexPath(row: cur.row - 1, section: cur.section)
|
||||||
|
@ -415,7 +438,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
amountScrolledUp += attrs.size.height
|
amountScrolledUp += attrs.size.height
|
||||||
}
|
}
|
||||||
self.collectionView.contentOffset.y += amountScrolledUp
|
self.collectionView.contentOffset.y += amountScrolledUp
|
||||||
self.collectionView.contentOffset.y -= firstItemAfterOriginalGapOffsetFromTop
|
self.collectionView.contentOffset.y -= offsetFromTop
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotView.removeFromSuperview()
|
snapshotView.removeFromSuperview()
|
||||||
|
|
Loading…
Reference in New Issue