Tweak timeline state restoration to maintain scroll position of center item

This commit is contained in:
Shadowfacts 2022-11-24 11:05:56 -05:00
parent d409d26478
commit 366834e2e4
1 changed files with 24 additions and 22 deletions

View File

@ -186,37 +186,39 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
func stateRestorationActivity() -> NSUserActivity? {
let visible = collectionView.indexPathsForVisibleItems.sorted()
let snapshot = dataSource.snapshot()
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let midPoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
guard let currentAccountID = mastodonController.accountInfo?.id,
!visible.isEmpty,
let statusesSection = snapshot.sectionIdentifiers.firstIndex(of: .statuses),
let firstVisible = visible.first(where: { $0.section == statusesSection }),
let lastVisible = visible.last(where: { $0.section == statusesSection }) else {
let rawCenterVisible = collectionView.indexPathForItem(at: midPoint),
let centerVisible = visible.first(where: { $0.section == statusesSection && $0 >= rawCenterVisible }) else {
return nil
}
let allItems = snapshot.itemIdentifiers(inSection: .statuses)
let startIndex = max(0, firstVisible.row - 20)
let endIndex = min(allItems.count - 1, lastVisible.row + 20)
let startIndex = max(0, centerVisible.row - 20)
let endIndex = min(allItems.count - 1, centerVisible.row + 20)
let firstVisibleItem: Item
let centerVisibleItem: Item
var items = allItems[startIndex...endIndex]
if let gapIndex = items.firstIndex(of: .gap) {
// if the gap is above the top visible item, we take everything below the gap
// otherwise, we take everything above the gap
if gapIndex <= firstVisible.row {
if gapIndex <= centerVisible.row {
items = allItems[(gapIndex + 1)...endIndex]
if gapIndex == firstVisible.row {
firstVisibleItem = allItems.first!
if gapIndex == centerVisible.row {
centerVisibleItem = allItems.first!
} else {
assert(items.indices.contains(firstVisible.row))
firstVisibleItem = allItems[firstVisible.row]
assert(items.indices.contains(centerVisible.row))
centerVisibleItem = allItems[centerVisible.row]
}
} else {
items = allItems[startIndex..<gapIndex]
firstVisibleItem = allItems[firstVisible.row]
centerVisibleItem = allItems[centerVisible.row]
}
} else {
firstVisibleItem = allItems[firstVisible.row]
centerVisibleItem = allItems[centerVisible.row]
}
let ids = items.map {
if case .status(id: let id, state: _) = $0 {
@ -225,18 +227,18 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
fatalError()
}
}
let firstVisibleID: String
if case .status(id: let id, state: _) = firstVisibleItem {
firstVisibleID = id
let centerVisibleID: String
if case .status(id: let id, state: _) = centerVisibleItem {
centerVisibleID = id
} else {
fatalError()
}
stateRestorationLogger.debug("TimelineViewController: creating state restoration activity with topID \(firstVisibleID)")
stateRestorationLogger.debug("TimelineViewController: creating state restoration activity with topID \(centerVisibleID)")
let activity = UserActivityManager.showTimelineActivity(timeline: timeline, accountID: currentAccountID)!
activity.addUserInfoEntries(from: [
"statusIDs": ids,
"topID": firstVisibleID,
"centerID": centerVisibleID,
])
activity.isEligibleForPrediction = false
return activity
@ -260,8 +262,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
let items = statusIDs.map { Item.status(id: $0, state: .unknown) }
snapshot.appendItems(items, toSection: .statuses)
dataSource.apply(snapshot, animatingDifferences: false) {
if let topID = activity.userInfo?["topID"] as? String,
let index = statusIDs.firstIndex(of: topID),
if let centerID = activity.userInfo?["centerID"] as? String ?? activity.userInfo?["topID"] as? String,
let index = statusIDs.firstIndex(of: centerID),
let indexPath = self.dataSource.indexPath(for: items[index]) {
// it sometimes takes multiple attempts to convert on the right scroll position
// since we're dealing with a bunch of unmeasured cells, so just try a few times in a loop
@ -270,15 +272,15 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
count += 1
let origOffset = self.collectionView.contentOffset
self.collectionView.layoutIfNeeded()
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: false)
self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false)
let newOffset = self.collectionView.contentOffset
if abs(origOffset.y - newOffset.y) <= 1 {
break
}
}
stateRestorationLogger.fault("TimelineViewController: restored statuses with top ID \(topID)")
stateRestorationLogger.fault("TimelineViewController: restored statuses with center ID \(centerID)")
} else {
stateRestorationLogger.fault("TimelineViewController: restored statuses, but couldn't find top ID")
stateRestorationLogger.fault("TimelineViewController: restored statuses, but couldn't find center ID")
}
}
}