From 9561b58a70d8f819f694ff5fb33c2c7016d1c210 Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 15 Jul 2022 02:46:48 +0800 Subject: [PATCH] fix: table reload in the background cannot keep scroll position issue --- .../HomeTimelineViewModel+Diffable.swift | 10 +- .../Thread/ThreadViewModel+Diffable.swift | 162 +----------------- 2 files changed, 15 insertions(+), 157 deletions(-) diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift index 35f44683a..92c28242d 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewModel+Diffable.swift @@ -155,8 +155,14 @@ extension HomeTimelineViewModel { ) -> Difference? { guard let sourceIndexPath = (tableView.indexPathsForVisibleRows ?? []).sorted().first else { return nil } let rectForSourceItemCell = tableView.rectForRow(at: sourceIndexPath) - let sourceDistanceToTableViewTopEdge = tableView.convert(rectForSourceItemCell, to: nil).origin.y - tableView.safeAreaInsets.top - + let sourceDistanceToTableViewTopEdge: CGFloat = { + if tableView.window != nil { + return tableView.convert(rectForSourceItemCell, to: nil).origin.y - tableView.safeAreaInsets.top + } else { + return rectForSourceItemCell.origin.y - tableView.contentOffset.y - tableView.safeAreaInsets.top + } + }() + guard sourceIndexPath.section < oldSnapshot.numberOfSections, sourceIndexPath.row < oldSnapshot.numberOfItems(inSection: oldSnapshot.sectionIdentifiers[sourceIndexPath.section]) else { return nil } diff --git a/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift b/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift index a6b4848c1..ededcb044 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel+Diffable.swift @@ -139,130 +139,6 @@ extension ThreadViewModel { } // end Task } .store(in: &disposeBag) - - -// Publishers.CombineLatest3( -// rootItem.removeDuplicates(), -// ancestorItems.removeDuplicates(), -// descendantItems.removeDuplicates() -// ) -// .receive(on: RunLoop.main) -// .sink { [weak self] rootItem, ancestorItems, descendantItems in -// guard let self = self else { return } -// var items: [Item] = [] -// rootItem.flatMap { items.append($0) } -// items.append(contentsOf: ancestorItems) -// items.append(contentsOf: descendantItems) -// self.updateDeletedStatus(for: items) -// } -// .store(in: &disposeBag) -// -// Publishers.CombineLatest4( -// rootItem, -// ancestorItems, -// descendantItems, -// existStatusFetchedResultsController.objectIDs -// ) -// .debounce(for: .milliseconds(100), scheduler: RunLoop.main) // some magic to avoid jitter -// .sink { [weak self] rootItem, ancestorItems, descendantItems, existObjectIDs in -// guard let self = self else { return } -// guard let tableView = self.tableView, -// let navigationBar = self.contentOffsetAdjustableTimelineViewControllerDelegate?.navigationBar() -// else { return } -// -// guard let diffableDataSource = self.diffableDataSource else { return } -// let oldSnapshot = diffableDataSource.snapshot() -// -// var newSnapshot = NSDiffableDataSourceSnapshot() -// newSnapshot.appendSections([.main]) -// -// let currentState = self.loadThreadStateMachine.currentState -// -// // reply to -// if self.rootNode.value?.replyToID != nil, !(currentState is LoadThreadState.NoMore) { -// newSnapshot.appendItems([.topLoader], toSection: .main) -// } -// -// let ancestorItems = ancestorItems.filter { item in -// guard case let .reply(statusObjectID, _) = item else { return false } -// return existObjectIDs.contains(statusObjectID) -// } -// newSnapshot.appendItems(ancestorItems, toSection: .main) -// -// // root -// if let rootItem = rootItem, -// case let .root(objectID, _) = rootItem, -// existObjectIDs.contains(objectID) { -// newSnapshot.appendItems([rootItem], toSection: .main) -// } -// -// // leaf -// if !(currentState is LoadThreadState.NoMore) { -// newSnapshot.appendItems([.bottomLoader], toSection: .main) -// } -// -// let descendantItems = descendantItems.filter { item in -// switch item { -// case .leaf(let statusObjectID, _): -// return existObjectIDs.contains(statusObjectID) -// default: -// return true -// } -// } -// newSnapshot.appendItems(descendantItems, toSection: .main) -// -// // difference for first visible item exclude .topLoader -// guard let difference = self.calculateReloadSnapshotDifference(navigationBar: navigationBar, tableView: tableView, oldSnapshot: oldSnapshot, newSnapshot: newSnapshot) else { -// diffableDataSource.apply(newSnapshot) -// return -// } -// -// // additional margin for .topLoader -// let oldTopMargin: CGFloat = { -// let marginHeight = TimelineTopLoaderTableViewCell.cellHeight -// if oldSnapshot.itemIdentifiers.contains(.topLoader) { -// return marginHeight -// } -// if !ancestorItems.isEmpty { -// return marginHeight -// } -// -// return .zero -// }() -// -// let oldRootCell: UITableViewCell? = { -// guard let rootItem = rootItem else { return nil } -// guard let index = oldSnapshot.indexOfItem(rootItem) else { return nil } -// guard let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0)) else { return nil } -// return cell -// }() -// // save height before cell reuse -// let oldRootCellHeight = oldRootCell?.frame.height -// -// diffableDataSource.reloadData(snapshot: newSnapshot) { -// guard let _ = rootItem else { -// return -// } -// if let oldRootCellHeight = oldRootCellHeight { -// // set bottom inset. Make root item pin to top (with margin). -// let bottomSpacing = tableView.safeAreaLayoutGuide.layoutFrame.height - oldRootCellHeight - oldTopMargin -// tableView.contentInset.bottom = max(0, bottomSpacing) -// } -// -// // set scroll position -// tableView.scrollToRow(at: difference.targetIndexPath, at: .top, animated: false) -// let contentOffsetY: CGFloat = { -// var offset: CGFloat = tableView.contentOffset.y - difference.offset -// if tableView.contentInset.bottom != 0.0 && descendantItems.isEmpty { -// // needs restore top margin if bottom inset adjusted AND no descendantItems -// offset += oldTopMargin -// } -// return offset -// }() -// tableView.setContentOffset(CGPoint(x: 0, y: contentOffsetY), animated: false) -// } -// } -// .store(in: &disposeBag) } } @@ -379,7 +255,13 @@ extension ThreadViewModel { let sourceIndexPath = IndexPath(row: index, section: 0) let rectForSourceItemCell = tableView.rectForRow(at: sourceIndexPath) - let sourceDistanceToTableViewTopEdge = tableView.convert(rectForSourceItemCell, to: nil).origin.y - tableView.safeAreaInsets.top + let sourceDistanceToTableViewTopEdge: CGFloat = { + if tableView.window != nil { + return tableView.convert(rectForSourceItemCell, to: nil).origin.y - tableView.safeAreaInsets.top + } else { + return rectForSourceItemCell.origin.y - tableView.contentOffset.y - tableView.safeAreaInsets.top + } + }() guard sourceIndexPath.section < oldSnapshot.numberOfSections, sourceIndexPath.row < oldSnapshot.numberOfItems(inSection: oldSnapshot.sectionIdentifiers[sourceIndexPath.section]) @@ -403,33 +285,3 @@ extension ThreadViewModel { ) } } - -//extension ThreadViewModel { -// private func updateDeletedStatus(for items: [Item]) { -// let parentManagedObjectContext = context.managedObjectContext -// let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) -// managedObjectContext.parent = parentManagedObjectContext -// managedObjectContext.perform { -// var statusIDs: [Status.ID] = [] -// for item in items { -// switch item { -// case .root(let objectID, _): -// guard let status = managedObjectContext.object(with: objectID) as? Status else { continue } -// statusIDs.append(status.id) -// case .reply(let objectID, _): -// guard let status = managedObjectContext.object(with: objectID) as? Status else { continue } -// statusIDs.append(status.id) -// case .leaf(let objectID, _): -// guard let status = managedObjectContext.object(with: objectID) as? Status else { continue } -// statusIDs.append(status.id) -// default: -// continue -// } -// } -// DispatchQueue.main.async { [weak self] in -// guard let self = self else { return } -// self.existStatusFetchedResultsController.statusIDs.value = statusIDs -// } -// } -// } -//}