fix : maintain contentOffset after refresh timeline

This commit is contained in:
sunxiaojian 2021-02-05 16:50:40 +08:00
parent 08dfe42aba
commit 5aa917e7bd
5 changed files with 80 additions and 2 deletions

View File

@ -14,6 +14,7 @@
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; }; 2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */; };
2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAB925CB9B0500C9ED86 /* UIView.swift */; }; 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAB925CB9B0500C9ED86 /* UIView.swift */; };
2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */; }; 2D32EADA25CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */; };
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; };
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; }; 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; };
2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonContent.swift */; }; 2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonContent.swift */; };
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; }; 2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */; };
@ -161,6 +162,7 @@
2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = "<group>"; }; 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineMiddleLoaderTableViewCell.swift; sourceTree = "<group>"; };
2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; }; 2D32EAB925CB9B0500C9ED86 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = "<group>"; };
2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; }; 2D32EAD925CBCC3300C9ED86 /* PublicTimelineViewModel+LoadMiddleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewModel+LoadMiddleState.swift"; sourceTree = "<group>"; };
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentOffsetAdjustableTimelineViewControllerDelegate.swift; sourceTree = "<group>"; };
2D42FF6A25C817D2004A627A /* MastodonContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonContent.swift; sourceTree = "<group>"; }; 2D42FF6A25C817D2004A627A /* MastodonContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonContent.swift; sourceTree = "<group>"; };
2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionToolBarContainer.swift; sourceTree = "<group>"; }; 2D42FF7D25C82218004A627A /* ActionToolBarContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionToolBarContainer.swift; sourceTree = "<group>"; };
2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestExpandedButton.swift; sourceTree = "<group>"; }; 2D42FF8425C8224F004A627A /* HitTestExpandedButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestExpandedButton.swift; sourceTree = "<group>"; };
@ -371,6 +373,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */, 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */,
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */,
); );
path = Protocol; path = Protocol;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1099,6 +1102,7 @@
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */, 2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */, DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */,
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */, DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */, DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */, DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */,
2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */, 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */,

View File

@ -0,0 +1,13 @@
//
// ContentOffsetAdjustableTimelineViewControllerDelegate.swift
// Mastodon
//
// Created by sxiaojian on 2021/2/5.
//
import UIKit
protocol ContentOffsetAdjustableTimelineViewControllerDelegate: class {
func navigationBar() -> UINavigationBar?
}

View File

@ -70,6 +70,7 @@ extension PublicTimelineViewController {
]) ])
viewModel.tableView = tableView viewModel.tableView = tableView
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
tableView.delegate = self tableView.delegate = self
viewModel.setupDiffableDataSource( viewModel.setupDiffableDataSource(
for: tableView, for: tableView,
@ -122,6 +123,14 @@ extension PublicTimelineViewController: UITableViewDelegate {
viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key)) viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key))
} }
} }
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
extension PublicTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
func navigationBar() -> UINavigationBar? {
return navigationController?.navigationBar
}
}
// MARK: - LoadMoreConfigurableTableViewContainer // MARK: - LoadMoreConfigurableTableViewContainer
extension PublicTimelineViewController: LoadMoreConfigurableTableViewContainer { extension PublicTimelineViewController: LoadMoreConfigurableTableViewContainer {
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell

View File

@ -68,7 +68,6 @@ extension PublicTimelineViewModel.State {
break break
} }
} receiveValue: { response in } receiveValue: { response in
viewModel.isFetchingLatestTimeline.value = false
let resposeTootIDs = response.value.compactMap { $0.id } let resposeTootIDs = response.value.compactMap { $0.id }
var newTootsIDs = resposeTootIDs var newTootsIDs = resposeTootIDs
let oldTootsIDs = viewModel.tootIDs.value let oldTootsIDs = viewModel.tootIDs.value

View File

@ -27,6 +27,9 @@ class PublicTimelineViewModel: NSObject {
let loadMiddleSateMachineList = CurrentValueSubject<[String: GKStateMachine], Never>([:]) let loadMiddleSateMachineList = CurrentValueSubject<[String: GKStateMachine], Never>([:])
weak var tableView: UITableView? weak var tableView: UITableView?
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
// //
var tootIDsWhichHasGap = [String]() var tootIDsWhichHasGap = [String]()
// output // output
@ -74,6 +77,9 @@ class PublicTimelineViewModel: NSObject {
.sink { [weak self] items in .sink { [weak self] items in
guard let self = self else { return } guard let self = self else { return }
guard let diffableDataSource = self.diffableDataSource else { return } guard let diffableDataSource = self.diffableDataSource else { return }
guard let navigationBar = self.contentOffsetAdjustableTimelineViewControllerDelegate?.navigationBar() else { return }
guard let tableView = self.tableView else { return }
let oldSnapshot = diffableDataSource.snapshot()
os_log("%{public}s[%{public}ld], %{public}s: items did change", (#file as NSString).lastPathComponent, #line, #function) os_log("%{public}s[%{public}ld], %{public}s: items did change", (#file as NSString).lastPathComponent, #line, #function)
var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, Item>() var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, Item>()
@ -87,7 +93,21 @@ class PublicTimelineViewModel: NSObject {
break break
} }
} }
diffableDataSource.apply(snapshot, animatingDifferences: !items.isEmpty)
DispatchQueue.main.async {
guard let difference = self.calculateReloadSnapshotDifference(navigationBar: navigationBar, tableView: tableView, oldSnapshot: oldSnapshot, newSnapshot: snapshot) else {
diffableDataSource.apply(snapshot)
self.isFetchingLatestTimeline.value = false
return
}
diffableDataSource.apply(snapshot, animatingDifferences: false) {
tableView.scrollToRow(at: difference.targetIndexPath, at: .top, animated: false)
tableView.contentOffset.y = tableView.contentOffset.y - difference.offset
self.isFetchingLatestTimeline.value = false
}
}
} }
.store(in: &disposeBag) .store(in: &disposeBag)
@ -109,4 +129,37 @@ class PublicTimelineViewModel: NSObject {
deinit { deinit {
os_log("%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function) os_log("%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
} }
private struct Difference<T> {
let item: T
let sourceIndexPath: IndexPath
let targetIndexPath: IndexPath
let offset: CGFloat
}
private func calculateReloadSnapshotDifference<T: Hashable>(
navigationBar: UINavigationBar,
tableView: UITableView,
oldSnapshot: NSDiffableDataSourceSnapshot<TimelineSection, T>,
newSnapshot: NSDiffableDataSourceSnapshot<TimelineSection, T>
) -> Difference<T>? {
guard oldSnapshot.numberOfItems != 0 else { return nil }
// old snapshot not empty. set source index path to first item if not match
let sourceIndexPath = UIViewController.topVisibleTableViewCellIndexPath(in: tableView, navigationBar: navigationBar) ?? IndexPath(row: 0, section: 0)
guard sourceIndexPath.row < oldSnapshot.itemIdentifiers(inSection: .main).count else { return nil }
let timelineItem = oldSnapshot.itemIdentifiers(inSection: .main)[sourceIndexPath.row]
guard let itemIndex = newSnapshot.itemIdentifiers(inSection: .main).firstIndex(of: timelineItem) else { return nil }
let targetIndexPath = IndexPath(row: itemIndex, section: 0)
let offset = UIViewController.tableViewCellOriginOffsetToWindowTop(in: tableView, at: sourceIndexPath, navigationBar: navigationBar)
return Difference(
item: timelineItem,
sourceIndexPath: sourceIndexPath,
targetIndexPath: targetIndexPath,
offset: offset
)
}
} }