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