Serialize access to the UITableView for scrolling and diffable datasource updates. Issue #1806
This commit is contained in:
parent
fd0363aad2
commit
48e856fc04
|
@ -244,6 +244,8 @@
|
|||
51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
|
||||
51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */; };
|
||||
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; };
|
||||
51DC37072402153E0095D371 /* UpdateSelectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC37062402153E0095D371 /* UpdateSelectionOperation.swift */; };
|
||||
51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51DC37082402F1470095D371 /* MasterFeedDataSourceOperation.swift */; };
|
||||
51E36E71239D6610006F47A5 /* AddWebFeedSelectFolderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E36E70239D6610006F47A5 /* AddWebFeedSelectFolderTableViewCell.swift */; };
|
||||
51E36E8C239D6765006F47A5 /* AddWebFeedSelectFolderTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51E36E8B239D6765006F47A5 /* AddWebFeedSelectFolderTableViewCell.xib */; };
|
||||
51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; };
|
||||
|
@ -1379,6 +1381,8 @@
|
|||
51CE1C0A23622006005548FC /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = "<group>"; };
|
||||
51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineDataSource.swift; sourceTree = "<group>"; };
|
||||
51D87EE02311D34700E63F03 /* ActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityType.swift; sourceTree = "<group>"; };
|
||||
51DC37062402153E0095D371 /* UpdateSelectionOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateSelectionOperation.swift; sourceTree = "<group>"; };
|
||||
51DC37082402F1470095D371 /* MasterFeedDataSourceOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedDataSourceOperation.swift; sourceTree = "<group>"; };
|
||||
51E36E70239D6610006F47A5 /* AddWebFeedSelectFolderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedSelectFolderTableViewCell.swift; sourceTree = "<group>"; };
|
||||
51E36E8B239D6765006F47A5 /* AddWebFeedSelectFolderTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddWebFeedSelectFolderTableViewCell.xib; sourceTree = "<group>"; };
|
||||
51E3EB32229AB02C00645299 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
|
||||
|
@ -1947,11 +1951,13 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
51C45264226508F600C03939 /* MasterFeedViewController.swift */,
|
||||
51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */,
|
||||
51627A6623861DA3007B3B4B /* MasterFeedViewController+Drag.swift */,
|
||||
51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */,
|
||||
51627A6A238629D8007B3B4B /* MasterFeedDataSource.swift */,
|
||||
51DC37082402F1470095D371 /* MasterFeedDataSourceOperation.swift */,
|
||||
51CE1C0A23622006005548FC /* RefreshProgressView.swift */,
|
||||
51CE1C0823621EDA005548FC /* RefreshProgressView.xib */,
|
||||
51DC37062402153E0095D371 /* UpdateSelectionOperation.swift */,
|
||||
51C45260226508F600C03939 /* Cell */,
|
||||
);
|
||||
path = MasterFeed;
|
||||
|
@ -4004,6 +4010,7 @@
|
|||
514219372352510100E07E2C /* ImageScrollView.swift in Sources */,
|
||||
516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */,
|
||||
C5A6ED5223C9AF4300AB6BE2 /* TitleActivityItemSource.swift in Sources */,
|
||||
51DC37092402F1470095D371 /* MasterFeedDataSourceOperation.swift in Sources */,
|
||||
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */,
|
||||
84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
|
||||
512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */,
|
||||
|
@ -4016,6 +4023,7 @@
|
|||
FFD43E412340F488009E5CA3 /* MarkAsReadAlertController.swift in Sources */,
|
||||
51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */,
|
||||
51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */,
|
||||
51DC37072402153E0095D371 /* UpdateSelectionOperation.swift in Sources */,
|
||||
51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */,
|
||||
5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */,
|
||||
51627A6723861DA3007B3B4B /* MasterFeedViewController+Drag.swift in Sources */,
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import RSCore
|
||||
import RSTree
|
||||
import Account
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// MasterFeedDataSourceOperation.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 2/23/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RSCore
|
||||
import RSTree
|
||||
|
||||
class MasterFeedDataSourceOperation: MainThreadOperation {
|
||||
|
||||
// MainThreadOperation
|
||||
public var isCanceled = false
|
||||
public var id: Int?
|
||||
public weak var operationDelegate: MainThreadOperationDelegate?
|
||||
public var name: String? = "MasterFeedDataSourceOperation"
|
||||
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
|
||||
|
||||
private var dataSource: UITableViewDiffableDataSource<Node, Node>
|
||||
private var snapshot: NSDiffableDataSourceSnapshot<Node, Node>
|
||||
private var animating: Bool
|
||||
|
||||
init(dataSource: UITableViewDiffableDataSource<Node, Node>, snapshot: NSDiffableDataSourceSnapshot<Node, Node>, animating: Bool) {
|
||||
self.dataSource = dataSource
|
||||
self.snapshot = snapshot
|
||||
self.animating = animating
|
||||
}
|
||||
|
||||
func run() {
|
||||
dataSource.apply(snapshot, animatingDifferences: animating) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,8 +17,10 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
@IBOutlet weak var filterButton: UIBarButtonItem!
|
||||
private var refreshProgressView: RefreshProgressView?
|
||||
@IBOutlet weak var addNewItemButton: UIBarButtonItem!
|
||||
|
||||
|
||||
private let operationQueue = MainThreadOperationQueue()
|
||||
lazy var dataSource = makeDataSource()
|
||||
|
||||
var undoableCommands = [UndoableCommand]()
|
||||
weak var coordinator: SceneCoordinator!
|
||||
|
||||
|
@ -27,7 +29,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
return keyboardManager.keyCommands
|
||||
}
|
||||
|
||||
var restoreSelection = false
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
@ -73,17 +74,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
updateUI()
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
// We have to delay the selection of the Feed until the subviews have been layed out
|
||||
// so that the visible area is correct and we scroll correctly.
|
||||
if restoreSelection {
|
||||
restoreSelectionIfNecessary(adjustScroll: true)
|
||||
restoreSelection = false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
|
@ -144,10 +134,6 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
}
|
||||
}
|
||||
|
||||
@objc func webFeedMetadataDidChange(_ note: Notification) {
|
||||
reloadAllVisibleCells()
|
||||
}
|
||||
|
||||
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
||||
resetEstimatedRowHeight()
|
||||
applyChanges(animated: false)
|
||||
|
@ -517,24 +503,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||
}
|
||||
|
||||
func updateFeedSelection(animations: Animations) {
|
||||
if dataSource.snapshot().numberOfItems > 0 {
|
||||
if let indexPath = coordinator.currentFeedIndexPath {
|
||||
if tableView.indexPathForSelectedRow != indexPath {
|
||||
tableView.selectRowAndScrollIfNotVisible(at: indexPath, animations: animations)
|
||||
}
|
||||
} else {
|
||||
if animations.contains(.select) {
|
||||
// This nasty bit of duct tape is because there is something, somewhere
|
||||
// interrupting the deselection animation, which will leave the row selected.
|
||||
// This seems to get it far enough away the problem that it always works.
|
||||
DispatchQueue.main.async {
|
||||
self.tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
|
||||
}
|
||||
} else {
|
||||
self.tableView.selectRow(at: nil, animated: false, scrollPosition: .none)
|
||||
}
|
||||
}
|
||||
}
|
||||
operationQueue.add(UpdateSelectionOperation(coordinator: coordinator, dataSource: dataSource, tableView: tableView, animations: animations))
|
||||
}
|
||||
|
||||
func reloadFeeds(initialLoad: Bool, completion: (() -> Void)? = nil) {
|
||||
|
@ -646,7 +615,7 @@ private extension MasterFeedViewController {
|
|||
func reloadNode(_ node: Node) {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.reloadItems([node])
|
||||
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
||||
queueApply(snapshot: snapshot, animatingDifferences: false) { [weak self] in
|
||||
self?.restoreSelectionIfNecessary(adjustScroll: false)
|
||||
}
|
||||
}
|
||||
|
@ -661,13 +630,22 @@ private extension MasterFeedViewController {
|
|||
snapshot.appendItems(shadowTableNodes, toSection: sectionNode)
|
||||
}
|
||||
|
||||
dataSource.apply(snapshot, animatingDifferences: animated) { [weak self] in
|
||||
queueApply(snapshot: snapshot, animatingDifferences: animated) { [weak self] in
|
||||
self?.restoreSelectionIfNecessary(adjustScroll: adjustScroll)
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
func queueApply(snapshot: NSDiffableDataSourceSnapshot<Node, Node>, animatingDifferences: Bool = true, completion: (() -> Void)? = nil) {
|
||||
let operation = MasterFeedDataSourceOperation(dataSource: dataSource, snapshot: snapshot, animating: animatingDifferences)
|
||||
operation.completionBlock = { _ in
|
||||
completion?()
|
||||
}
|
||||
operationQueue.add(operation)
|
||||
}
|
||||
|
||||
func makeDataSource() -> UITableViewDiffableDataSource<Node, Node> {
|
||||
|
||||
func makeDataSource() -> MasterFeedDataSource {
|
||||
let dataSource = MasterFeedDataSource(tableView: tableView, cellProvider: { [weak self] tableView, indexPath, node in
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MasterFeedTableViewCell
|
||||
self?.configure(cell, node)
|
||||
|
@ -774,7 +752,7 @@ private extension MasterFeedViewController {
|
|||
private func reloadCells(_ nodes: [Node], completion: (() -> Void)? = nil) {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.reloadItems(nodes)
|
||||
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
||||
queueApply(snapshot: snapshot, animatingDifferences: false) { [weak self] in
|
||||
self?.restoreSelectionIfNecessary(adjustScroll: false)
|
||||
completion?()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// UpdateSelectionOperation.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 2/22/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import RSCore
|
||||
|
||||
class UpdateSelectionOperation: MainThreadOperation {
|
||||
|
||||
// MainThreadOperation
|
||||
public var isCanceled = false
|
||||
public var id: Int?
|
||||
public weak var operationDelegate: MainThreadOperationDelegate?
|
||||
public var name: String? = "UpdateSelectionOperation"
|
||||
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
|
||||
|
||||
private var coordinator: SceneCoordinator
|
||||
private var dataSource: MasterFeedDataSource
|
||||
private var tableView: UITableView
|
||||
private var animations: Animations
|
||||
|
||||
init(coordinator: SceneCoordinator, dataSource: MasterFeedDataSource, tableView: UITableView, animations: Animations) {
|
||||
self.coordinator = coordinator
|
||||
self.dataSource = dataSource
|
||||
self.tableView = tableView
|
||||
self.animations = animations
|
||||
}
|
||||
|
||||
func run() {
|
||||
if dataSource.snapshot().numberOfItems > 0 {
|
||||
if let indexPath = coordinator.currentFeedIndexPath {
|
||||
CATransaction.begin()
|
||||
CATransaction.setCompletionBlock {
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
}
|
||||
tableView.selectRowAndScrollIfNotVisible(at: indexPath, animations: animations)
|
||||
CATransaction.commit()
|
||||
} else {
|
||||
tableView.selectRow(at: nil, animated: animations.contains(.select), scrollPosition: .none)
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
}
|
||||
} else {
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1738,7 +1738,9 @@ private extension SceneCoordinator {
|
|||
}
|
||||
|
||||
@objc func fetchAndMergeArticlesAsync() {
|
||||
fetchAndMergeArticlesAsync(animated: true, completion: nil)
|
||||
fetchAndMergeArticlesAsync(animated: true) {
|
||||
self.masterTimelineViewController?.reinitializeArticles(resetScroll: false)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAndMergeArticlesAsync(animated: Bool = true, completion: (() -> Void)? = nil) {
|
||||
|
@ -2002,7 +2004,6 @@ private extension SceneCoordinator {
|
|||
}
|
||||
|
||||
treeControllerDelegate.addFilterException(feedIdentifier)
|
||||
masterFeedViewController.restoreSelection = true
|
||||
|
||||
switch feedIdentifier {
|
||||
|
||||
|
@ -2087,8 +2088,6 @@ private extension SceneCoordinator {
|
|||
return false
|
||||
}
|
||||
|
||||
masterFeedViewController.restoreSelection = true
|
||||
|
||||
switch feedIdentifier {
|
||||
|
||||
case .smartFeed:
|
||||
|
|
Loading…
Reference in New Issue