forked from zelo72/mastodon-ios
fix : maintain contentOffset after refresh timeline
This commit is contained in:
parent
08dfe42aba
commit
5aa917e7bd
|
@ -14,6 +14,7 @@
|
|||
2D32EAAC25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D32EAAB25CB96DC00C9ED86 /* TimelineMiddleLoaderTableViewCell.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 */; };
|
||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */; };
|
||||
2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */ = {isa = PBXBuildFile; productRef = 2D42FF6025C8177C004A627A /* ActiveLabel */; };
|
||||
2D42FF6B25C817D2004A627A /* MastodonContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D42FF6A25C817D2004A627A /* MastodonContent.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -371,6 +373,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */,
|
||||
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */,
|
||||
);
|
||||
path = Protocol;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1099,6 +1102,7 @@
|
|||
2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */,
|
||||
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */,
|
||||
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
|
||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
||||
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
|
||||
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */,
|
||||
2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// ContentOffsetAdjustableTimelineViewControllerDelegate.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by sxiaojian on 2021/2/5.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol ContentOffsetAdjustableTimelineViewControllerDelegate: class {
|
||||
func navigationBar() -> UINavigationBar?
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ extension PublicTimelineViewController {
|
|||
])
|
||||
|
||||
viewModel.tableView = tableView
|
||||
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
||||
tableView.delegate = self
|
||||
viewModel.setupDiffableDataSource(
|
||||
for: tableView,
|
||||
|
@ -122,6 +123,14 @@ extension PublicTimelineViewController: UITableViewDelegate {
|
|||
viewModel.cellFrameCache.setObject(NSValue(cgRect: frame), forKey: NSNumber(value: key))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
||||
extension PublicTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
|
||||
func navigationBar() -> UINavigationBar? {
|
||||
return navigationController?.navigationBar
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - LoadMoreConfigurableTableViewContainer
|
||||
extension PublicTimelineViewController: LoadMoreConfigurableTableViewContainer {
|
||||
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
||||
|
|
|
@ -68,7 +68,6 @@ extension PublicTimelineViewModel.State {
|
|||
break
|
||||
}
|
||||
} receiveValue: { response in
|
||||
viewModel.isFetchingLatestTimeline.value = false
|
||||
let resposeTootIDs = response.value.compactMap { $0.id }
|
||||
var newTootsIDs = resposeTootIDs
|
||||
let oldTootsIDs = viewModel.tootIDs.value
|
||||
|
|
|
@ -27,6 +27,9 @@ class PublicTimelineViewModel: NSObject {
|
|||
let loadMiddleSateMachineList = CurrentValueSubject<[String: GKStateMachine], Never>([:])
|
||||
|
||||
weak var tableView: UITableView?
|
||||
|
||||
weak var contentOffsetAdjustableTimelineViewControllerDelegate: ContentOffsetAdjustableTimelineViewControllerDelegate?
|
||||
|
||||
//
|
||||
var tootIDsWhichHasGap = [String]()
|
||||
// output
|
||||
|
@ -74,6 +77,9 @@ class PublicTimelineViewModel: NSObject {
|
|||
.sink { [weak self] items in
|
||||
guard let self = self 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)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<TimelineSection, Item>()
|
||||
|
@ -87,7 +93,21 @@ class PublicTimelineViewModel: NSObject {
|
|||
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)
|
||||
|
||||
|
@ -109,4 +129,37 @@ class PublicTimelineViewModel: NSObject {
|
|||
deinit {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue