Fix handling of how sections were added and remove
This commit is contained in:
parent
bbc7230e76
commit
08a1e79e7d
@ -2071,6 +2071,7 @@
|
|||||||
51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */,
|
51627A6823861DED007B3B4B /* MasterFeedViewController+Drop.swift */,
|
||||||
51CE1C0A23622006005548FC /* RefreshProgressView.swift */,
|
51CE1C0A23622006005548FC /* RefreshProgressView.swift */,
|
||||||
51CE1C0823621EDA005548FC /* RefreshProgressView.xib */,
|
51CE1C0823621EDA005548FC /* RefreshProgressView.xib */,
|
||||||
|
5195C1D92720205F00888867 /* ShadowTableChanges.swift */,
|
||||||
51C45260226508F600C03939 /* Cell */,
|
51C45260226508F600C03939 /* Cell */,
|
||||||
);
|
);
|
||||||
path = MasterFeed;
|
path = MasterFeed;
|
||||||
@ -2691,7 +2692,6 @@
|
|||||||
840D617E2029031C009BC708 /* AppDelegate.swift */,
|
840D617E2029031C009BC708 /* AppDelegate.swift */,
|
||||||
519E743422C663F900A78E47 /* SceneDelegate.swift */,
|
519E743422C663F900A78E47 /* SceneDelegate.swift */,
|
||||||
5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */,
|
5126EE96226CB48A00C22AFC /* SceneCoordinator.swift */,
|
||||||
5195C1D92720205F00888867 /* ShadowTableChanges.swift */,
|
|
||||||
514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */,
|
514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */,
|
||||||
511D4410231FC02D00FB1562 /* KeyboardManager.swift */,
|
511D4410231FC02D00FB1562 /* KeyboardManager.swift */,
|
||||||
51C45254226507D200C03939 /* AppAssets.swift */,
|
51C45254226507D200C03939 /* AppAssets.swift */,
|
||||||
|
@ -572,7 +572,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadFeeds(initialLoad: Bool, changes: [ShadowTableChanges], completion: (() -> Void)? = nil) {
|
func reloadFeeds(initialLoad: Bool, changes: ShadowTableChanges, completion: (() -> Void)? = nil) {
|
||||||
updateUI()
|
updateUI()
|
||||||
|
|
||||||
guard !initialLoad else {
|
guard !initialLoad else {
|
||||||
@ -581,26 +581,36 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for change in changes {
|
|
||||||
guard !change.isEmpty else { continue }
|
|
||||||
|
|
||||||
tableView.performBatchUpdates {
|
tableView.performBatchUpdates {
|
||||||
if let deletes = change.deleteIndexPaths, !deletes.isEmpty {
|
if let deletes = changes.deletes, !deletes.isEmpty {
|
||||||
|
tableView.deleteSections(IndexSet(deletes), with: .middle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let inserts = changes.inserts, !inserts.isEmpty {
|
||||||
|
tableView.insertSections(IndexSet(inserts), with: .middle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let moves = changes.moves, !moves.isEmpty {
|
||||||
|
for move in moves {
|
||||||
|
tableView.moveSection(move.from, toSection: move.to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let rowChanges = changes.rowChanges {
|
||||||
|
for rowChange in rowChanges {
|
||||||
|
if let deletes = rowChange.deleteIndexPaths, !deletes.isEmpty {
|
||||||
tableView.deleteRows(at: deletes, with: .middle)
|
tableView.deleteRows(at: deletes, with: .middle)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let inserts = change.insertIndexPaths, !inserts.isEmpty {
|
if let inserts = rowChange.insertIndexPaths, !inserts.isEmpty {
|
||||||
tableView.insertRows(at: inserts, with: .middle)
|
tableView.insertRows(at: inserts, with: .middle)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let moves = change.moveIndexPaths, !moves.isEmpty {
|
if let moves = rowChange.moveIndexPaths, !moves.isEmpty {
|
||||||
for move in moves {
|
for move in moves {
|
||||||
tableView.moveRow(at: move.0, to: move.1)
|
tableView.moveRow(at: move.0, to: move.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let reloads = change.reloadIndexPaths, !reloads.isEmpty {
|
|
||||||
tableView.reloadRows(at: reloads, with: .middle)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
70
iOS/MasterFeed/ShadowTableChanges.swift
Normal file
70
iOS/MasterFeed/ShadowTableChanges.swift
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
//
|
||||||
|
// ShadowTableChanges.swift
|
||||||
|
// NetNewsWire-iOS
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 10/20/21.
|
||||||
|
// Copyright © 2021 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct ShadowTableChanges {
|
||||||
|
|
||||||
|
struct Move: Hashable {
|
||||||
|
var from: Int
|
||||||
|
var to: Int
|
||||||
|
|
||||||
|
init(_ from: Int, _ to: Int) {
|
||||||
|
self.from = from
|
||||||
|
self.to = to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RowChanges {
|
||||||
|
|
||||||
|
var section: Int
|
||||||
|
var deletes: Set<Int>?
|
||||||
|
var inserts: Set<Int>?
|
||||||
|
var moves: Set<ShadowTableChanges.Move>?
|
||||||
|
|
||||||
|
var isEmpty: Bool {
|
||||||
|
return (deletes?.isEmpty ?? true) && (inserts?.isEmpty ?? true) && (moves?.isEmpty ?? true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteIndexPaths: [IndexPath]? {
|
||||||
|
guard let deletes = deletes else { return nil }
|
||||||
|
return deletes.map { IndexPath(row: $0, section: section) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var insertIndexPaths: [IndexPath]? {
|
||||||
|
guard let inserts = inserts else { return nil }
|
||||||
|
return inserts.map { IndexPath(row: $0, section: section) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var moveIndexPaths: [(IndexPath, IndexPath)]? {
|
||||||
|
guard let moves = moves else { return nil }
|
||||||
|
return moves.map { (IndexPath(row: $0.from, section: section), IndexPath(row: $0.to, section: section)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init(section: Int, deletes: Set<Int>?, inserts: Set<Int>?, moves: Set<Move>?) {
|
||||||
|
self.section = section
|
||||||
|
self.deletes = deletes
|
||||||
|
self.inserts = inserts
|
||||||
|
self.moves = moves
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var deletes: Set<Int>?
|
||||||
|
var inserts: Set<Int>?
|
||||||
|
var moves: Set<Move>?
|
||||||
|
var rowChanges: [RowChanges]?
|
||||||
|
|
||||||
|
init(deletes: Set<Int>?, inserts: Set<Int>?, moves: Set<Move>?, rowChanges: [RowChanges]?) {
|
||||||
|
self.deletes = deletes
|
||||||
|
self.inserts = inserts
|
||||||
|
self.moves = moves
|
||||||
|
self.rowChanges = rowChanges
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -89,7 +89,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
|||||||
|
|
||||||
private var expandedTable = Set<ContainerIdentifier>()
|
private var expandedTable = Set<ContainerIdentifier>()
|
||||||
private var readFilterEnabledTable = [FeedIdentifier: Bool]()
|
private var readFilterEnabledTable = [FeedIdentifier: Bool]()
|
||||||
private var shadowTable = [[FeedNode]]()
|
private var shadowTable = [(sectionID: String, feedNodes: [FeedNode])]()
|
||||||
|
|
||||||
private(set) var preSearchTimelineFeed: Feed?
|
private(set) var preSearchTimelineFeed: Feed?
|
||||||
private var lastSearchString = ""
|
private var lastSearchString = ""
|
||||||
@ -205,8 +205,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
|||||||
let prevIndexPath: IndexPath? = {
|
let prevIndexPath: IndexPath? = {
|
||||||
if indexPath.row - 1 < 0 {
|
if indexPath.row - 1 < 0 {
|
||||||
for i in (0..<indexPath.section).reversed() {
|
for i in (0..<indexPath.section).reversed() {
|
||||||
if shadowTable[i].count > 0 {
|
if shadowTable[i].feedNodes.count > 0 {
|
||||||
return IndexPath(row: shadowTable[i].count - 1, section: i)
|
return IndexPath(row: shadowTable[i].feedNodes.count - 1, section: i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -224,9 +224,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nextIndexPath: IndexPath? = {
|
let nextIndexPath: IndexPath? = {
|
||||||
if indexPath.row + 1 >= shadowTable[indexPath.section].count {
|
if indexPath.row + 1 >= shadowTable[indexPath.section].feedNodes.count {
|
||||||
for i in indexPath.section + 1..<shadowTable.count {
|
for i in indexPath.section + 1..<shadowTable.count {
|
||||||
if shadowTable[i].count > 0 {
|
if shadowTable[i].feedNodes.count > 0 {
|
||||||
return IndexPath(row: 0, section: i)
|
return IndexPath(row: 0, section: i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,7 +316,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
|||||||
|
|
||||||
for sectionNode in treeController.rootNode.childNodes {
|
for sectionNode in treeController.rootNode.childNodes {
|
||||||
markExpanded(sectionNode)
|
markExpanded(sectionNode)
|
||||||
shadowTable.append([FeedNode]())
|
shadowTable.append((sectionID: "", feedNodes: [FeedNode]()))
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidInitialize(_:)), name: .UnreadCountDidInitialize, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidInitialize(_:)), name: .UnreadCountDidInitialize, object: nil)
|
||||||
@ -675,19 +675,19 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func numberOfRows(in section: Int) -> Int {
|
func numberOfRows(in section: Int) -> Int {
|
||||||
return shadowTable[section].count
|
return shadowTable[section].feedNodes.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeFor(_ indexPath: IndexPath) -> Node? {
|
func nodeFor(_ indexPath: IndexPath) -> Node? {
|
||||||
guard indexPath.section < shadowTable.count && indexPath.row < shadowTable[indexPath.section].count else {
|
guard indexPath.section < shadowTable.count && indexPath.row < shadowTable[indexPath.section].feedNodes.count else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return shadowTable[indexPath.section][indexPath.row].node
|
return shadowTable[indexPath.section].feedNodes[indexPath.row].node
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexPathFor(_ node: Node) -> IndexPath? {
|
func indexPathFor(_ node: Node) -> IndexPath? {
|
||||||
for i in 0..<shadowTable.count {
|
for i in 0..<shadowTable.count {
|
||||||
if let row = shadowTable[i].firstIndex(of: FeedNode(node)) {
|
if let row = shadowTable[i].feedNodes.firstIndex(of: FeedNode(node)) {
|
||||||
return IndexPath(row: row, section: i)
|
return IndexPath(row: row, section: i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -699,8 +699,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cappedIndexPath(_ indexPath: IndexPath) -> IndexPath {
|
func cappedIndexPath(_ indexPath: IndexPath) -> IndexPath {
|
||||||
guard indexPath.section < shadowTable.count && indexPath.row < shadowTable[indexPath.section].count else {
|
guard indexPath.section < shadowTable.count && indexPath.row < shadowTable[indexPath.section].feedNodes.count else {
|
||||||
return IndexPath(row: shadowTable[shadowTable.count - 1].count - 1, section: shadowTable.count - 1)
|
return IndexPath(row: shadowTable[shadowTable.count - 1].feedNodes.count - 1, section: shadowTable.count - 1)
|
||||||
}
|
}
|
||||||
return indexPath
|
return indexPath
|
||||||
}
|
}
|
||||||
@ -1502,7 +1502,7 @@ private extension SceneCoordinator {
|
|||||||
|
|
||||||
func addShadowTableToFilterExceptions() {
|
func addShadowTableToFilterExceptions() {
|
||||||
for section in shadowTable {
|
for section in shadowTable {
|
||||||
for feedNode in section {
|
for feedNode in section.feedNodes {
|
||||||
if let feed = feedNode.node.representedObject as? Feed, let feedID = feed.feedID {
|
if let feed = feedNode.node.representedObject as? Feed, let feedID = feed.feedID {
|
||||||
treeControllerDelegate.addFilterException(feedID)
|
treeControllerDelegate.addFilterException(feedID)
|
||||||
}
|
}
|
||||||
@ -1530,26 +1530,27 @@ private extension SceneCoordinator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func rebuildShadowTable() -> [ShadowTableChanges] {
|
func rebuildShadowTable() -> ShadowTableChanges {
|
||||||
var newShadowTable = [[FeedNode]]()
|
var newShadowTable = [(sectionID: String, feedNodes: [FeedNode])]()
|
||||||
|
|
||||||
for i in 0..<treeController.rootNode.numberOfChildNodes {
|
for i in 0..<treeController.rootNode.numberOfChildNodes {
|
||||||
|
|
||||||
var result = [FeedNode]()
|
var feedNodes = [FeedNode]()
|
||||||
let sectionNode = treeController.rootNode.childAtIndex(i)!
|
let sectionNode = treeController.rootNode.childAtIndex(i)!
|
||||||
|
|
||||||
if isExpanded(sectionNode) {
|
if isExpanded(sectionNode) {
|
||||||
for node in sectionNode.childNodes {
|
for node in sectionNode.childNodes {
|
||||||
result.append(FeedNode(node))
|
feedNodes.append(FeedNode(node))
|
||||||
if isExpanded(node) {
|
if isExpanded(node) {
|
||||||
for child in node.childNodes {
|
for child in node.childNodes {
|
||||||
result.append(FeedNode(child))
|
feedNodes.append(FeedNode(child))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newShadowTable.append(result)
|
let sectionID = (sectionNode.representedObject as? Account)?.accountID ?? ""
|
||||||
|
newShadowTable.append((sectionID: sectionID, feedNodes: feedNodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a current Feed IndexPath it is no longer valid and needs reset.
|
// If we have a current Feed IndexPath it is no longer valid and needs reset.
|
||||||
@ -1557,15 +1558,17 @@ private extension SceneCoordinator {
|
|||||||
currentFeedIndexPath = indexPathFor(timelineFeed as AnyObject)
|
currentFeedIndexPath = indexPathFor(timelineFeed as AnyObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the differences in the shadow tables
|
// Compute the differences in the shadow table rows
|
||||||
var changes = [ShadowTableChanges]()
|
var changes = [ShadowTableChanges.RowChanges]()
|
||||||
|
|
||||||
for (index, newSectionNodes) in newShadowTable.enumerated() {
|
for (section, newSectionRows) in newShadowTable.enumerated() {
|
||||||
var moves = Set<ShadowTableChanges.Move>()
|
var moves = Set<ShadowTableChanges.Move>()
|
||||||
var inserts = Set<Int>()
|
var inserts = Set<Int>()
|
||||||
var deletes = Set<Int>()
|
var deletes = Set<Int>()
|
||||||
|
|
||||||
let diff = newSectionNodes.difference(from: shadowTable[index]).inferringMoves()
|
let oldFeedNodes = shadowTable.first(where: { $0.sectionID == newSectionRows.sectionID })?.feedNodes ?? [FeedNode]()
|
||||||
|
|
||||||
|
let diff = newSectionRows.feedNodes.difference(from: oldFeedNodes).inferringMoves()
|
||||||
for change in diff {
|
for change in diff {
|
||||||
switch change {
|
switch change {
|
||||||
case .insert(let offset, _, let associated):
|
case .insert(let offset, _, let associated):
|
||||||
@ -1583,17 +1586,42 @@ private extension SceneCoordinator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changes.append(ShadowTableChanges(section: index, deletes: deletes, inserts: inserts, moves: moves))
|
changes.append(ShadowTableChanges.RowChanges(section: section, deletes: deletes, inserts: inserts, moves: moves))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the difference in the shadow table sections
|
||||||
|
var moves = Set<ShadowTableChanges.Move>()
|
||||||
|
var inserts = Set<Int>()
|
||||||
|
var deletes = Set<Int>()
|
||||||
|
|
||||||
|
let oldSections = shadowTable.map { $0.sectionID }
|
||||||
|
let newSections = newShadowTable.map { $0.sectionID }
|
||||||
|
let diff = newSections.difference(from: oldSections).inferringMoves()
|
||||||
|
for change in diff {
|
||||||
|
switch change {
|
||||||
|
case .insert(let offset, _, let associated):
|
||||||
|
if let associated = associated {
|
||||||
|
moves.insert(ShadowTableChanges.Move(associated, offset))
|
||||||
|
} else {
|
||||||
|
inserts.insert(offset)
|
||||||
|
}
|
||||||
|
case .remove(let offset, _, let associated):
|
||||||
|
if let associated = associated {
|
||||||
|
moves.insert(ShadowTableChanges.Move(offset, associated))
|
||||||
|
} else {
|
||||||
|
deletes.insert(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowTable = newShadowTable
|
shadowTable = newShadowTable
|
||||||
|
|
||||||
return changes
|
return ShadowTableChanges(deletes: deletes, inserts: inserts, moves: moves, rowChanges: changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shadowTableContains(_ feed: Feed) -> Bool {
|
func shadowTableContains(_ feed: Feed) -> Bool {
|
||||||
for section in shadowTable {
|
for section in shadowTable {
|
||||||
for feedNode in section {
|
for feedNode in section.feedNodes {
|
||||||
if let nodeFeed = feedNode.node.representedObject as? Feed, nodeFeed.feedID == feed.feedID {
|
if let nodeFeed = feedNode.node.representedObject as? Feed, nodeFeed.feedID == feed.feedID {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -1742,9 +1770,9 @@ private extension SceneCoordinator {
|
|||||||
let nextIndexPath: IndexPath = {
|
let nextIndexPath: IndexPath = {
|
||||||
if indexPath.row - 1 < 0 {
|
if indexPath.row - 1 < 0 {
|
||||||
if indexPath.section - 1 < 0 {
|
if indexPath.section - 1 < 0 {
|
||||||
return IndexPath(row: shadowTable[shadowTable.count - 1].count - 1, section: shadowTable.count - 1)
|
return IndexPath(row: shadowTable[shadowTable.count - 1].feedNodes.count - 1, section: shadowTable.count - 1)
|
||||||
} else {
|
} else {
|
||||||
return IndexPath(row: shadowTable[indexPath.section - 1].count - 1, section: indexPath.section - 1)
|
return IndexPath(row: shadowTable[indexPath.section - 1].feedNodes.count - 1, section: indexPath.section - 1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return IndexPath(row: indexPath.row - 1, section: indexPath.section)
|
return IndexPath(row: indexPath.row - 1, section: indexPath.section)
|
||||||
@ -1754,7 +1782,7 @@ private extension SceneCoordinator {
|
|||||||
if selectPrevUnreadFeedFetcher(startingWith: nextIndexPath) {
|
if selectPrevUnreadFeedFetcher(startingWith: nextIndexPath) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let maxIndexPath = IndexPath(row: shadowTable[shadowTable.count - 1].count - 1, section: shadowTable.count - 1)
|
let maxIndexPath = IndexPath(row: shadowTable[shadowTable.count - 1].feedNodes.count - 1, section: shadowTable.count - 1)
|
||||||
selectPrevUnreadFeedFetcher(startingWith: maxIndexPath)
|
selectPrevUnreadFeedFetcher(startingWith: maxIndexPath)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1768,7 +1796,7 @@ private extension SceneCoordinator {
|
|||||||
if indexPath.section == i {
|
if indexPath.section == i {
|
||||||
return indexPath.row
|
return indexPath.row
|
||||||
} else {
|
} else {
|
||||||
return shadowTable[i].count - 1
|
return shadowTable[i].feedNodes.count - 1
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -1847,7 +1875,7 @@ private extension SceneCoordinator {
|
|||||||
|
|
||||||
// Increment or wrap around the IndexPath
|
// Increment or wrap around the IndexPath
|
||||||
let nextIndexPath: IndexPath = {
|
let nextIndexPath: IndexPath = {
|
||||||
if indexPath.row + 1 >= shadowTable[indexPath.section].count {
|
if indexPath.row + 1 >= shadowTable[indexPath.section].feedNodes.count {
|
||||||
if indexPath.section + 1 >= shadowTable.count {
|
if indexPath.section + 1 >= shadowTable.count {
|
||||||
return IndexPath(row: 0, section: 0)
|
return IndexPath(row: 0, section: 0)
|
||||||
} else {
|
} else {
|
||||||
@ -1882,7 +1910,7 @@ private extension SceneCoordinator {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for j in startingRow..<shadowTable[i].count {
|
for j in startingRow..<shadowTable[i].feedNodes.count {
|
||||||
|
|
||||||
let nextIndexPath = IndexPath(row: j, section: i)
|
let nextIndexPath = IndexPath(row: j, section: i)
|
||||||
guard let node = nodeFor(nextIndexPath), let unreadCountProvider = node.representedObject as? UnreadCountProvider else {
|
guard let node = nodeFor(nextIndexPath), let unreadCountProvider = node.representedObject as? UnreadCountProvider else {
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
//
|
|
||||||
// ShadowTableChanges.swift
|
|
||||||
// NetNewsWire-iOS
|
|
||||||
//
|
|
||||||
// Created by Maurice Parker on 10/20/21.
|
|
||||||
// Copyright © 2021 Ranchero Software. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public struct ShadowTableChanges {
|
|
||||||
|
|
||||||
public struct Move: Hashable {
|
|
||||||
public var from: Int
|
|
||||||
public var to: Int
|
|
||||||
|
|
||||||
init(_ from: Int, _ to: Int) {
|
|
||||||
self.from = from
|
|
||||||
self.to = to
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var section: Int
|
|
||||||
public var deletes: Set<Int>?
|
|
||||||
public var inserts: Set<Int>?
|
|
||||||
public var moves: Set<Move>?
|
|
||||||
public var reloads: Set<Int>?
|
|
||||||
|
|
||||||
public var isEmpty: Bool {
|
|
||||||
return (deletes?.isEmpty ?? true) && (inserts?.isEmpty ?? true) && (moves?.isEmpty ?? true) && (reloads?.isEmpty ?? true)
|
|
||||||
}
|
|
||||||
|
|
||||||
public var isOnlyReloads: Bool {
|
|
||||||
return (deletes?.isEmpty ?? true) && (inserts?.isEmpty ?? true) && (moves?.isEmpty ?? true)
|
|
||||||
}
|
|
||||||
|
|
||||||
public var deleteIndexPaths: [IndexPath]? {
|
|
||||||
guard let deletes = deletes else { return nil }
|
|
||||||
return deletes.map { IndexPath(row: $0, section: section) }
|
|
||||||
}
|
|
||||||
|
|
||||||
public var insertIndexPaths: [IndexPath]? {
|
|
||||||
guard let inserts = inserts else { return nil }
|
|
||||||
return inserts.map { IndexPath(row: $0, section: section) }
|
|
||||||
}
|
|
||||||
|
|
||||||
public var moveIndexPaths: [(IndexPath, IndexPath)]? {
|
|
||||||
guard let moves = moves else { return nil }
|
|
||||||
return moves.map { (IndexPath(row: $0.from, section: section), IndexPath(row: $0.to, section: section)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
public var reloadIndexPaths: [IndexPath]? {
|
|
||||||
guard let reloads = reloads else { return nil }
|
|
||||||
return reloads.map { IndexPath(row: $0, section: section) }
|
|
||||||
}
|
|
||||||
|
|
||||||
init(section: Int, deletes: Set<Int>? = nil, inserts: Set<Int>? = nil, moves: Set<Move>? = nil, reloads: Set<Int>? = nil) {
|
|
||||||
self.section = section
|
|
||||||
self.deletes = deletes
|
|
||||||
self.inserts = inserts
|
|
||||||
self.moves = moves
|
|
||||||
self.reloads = reloads
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user