diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift index 6e2964c2d..4e71c23ae 100644 --- a/iOS/MasterFeed/MasterFeedViewController.swift +++ b/iOS/MasterFeed/MasterFeedViewController.swift @@ -627,6 +627,14 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner { } } + if let rowChanges = changes.rowChanges { + for rowChange in rowChanges { + if let reloads = rowChange.reloadIndexPaths, !reloads.isEmpty { + tableView.reloadRows(at: reloads, with: .none) + } + } + } + completion?() } diff --git a/iOS/MasterFeed/ShadowTableChanges.swift b/iOS/MasterFeed/ShadowTableChanges.swift index dd8a12801..49c4a9f9f 100644 --- a/iOS/MasterFeed/ShadowTableChanges.swift +++ b/iOS/MasterFeed/ShadowTableChanges.swift @@ -25,6 +25,7 @@ struct ShadowTableChanges { var section: Int var deletes: Set? var inserts: Set? + var reloads: Set? var moves: Set? var isEmpty: Bool { @@ -41,15 +42,21 @@ struct ShadowTableChanges { return inserts.map { IndexPath(row: $0, section: section) } } + var reloadIndexPaths: [IndexPath]? { + guard let reloads = reloads else { return nil } + return reloads.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?, inserts: Set?, moves: Set?) { + init(section: Int, deletes: Set?, inserts: Set?, reloads: Set?, moves: Set?) { self.section = section self.deletes = deletes self.inserts = inserts + self.reloads = reloads self.moves = moves } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index bed102e59..278e0ba9f 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -87,8 +87,16 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { private var fetchSerialNumber = 0 private let fetchRequestQueue = FetchRequestQueue() + // Which Containers are expanded private var expandedTable = Set() + + // Which Containers used to be expanded. Reset by rebuilding the Shadow Table. + private var lastExpandedTable = Set() + + // Which Feeds have the Read Articles Filter enabled private var readFilterEnabledTable = [FeedIdentifier: Bool]() + + // Flattened tree structure for the Sidebar private var shadowTable = [(sectionID: String, feedNodes: [FeedNode])]() private(set) var preSearchTimelineFeed: Feed? @@ -719,8 +727,11 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { rebuildBackingStores() } + /// This is a special function that expects the caller to change the disclosure arrow state outside this function. + /// Failure to do so will get the Sidebar into an invalid state. func expand(_ node: Node) { guard let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID else { return } + lastExpandedTable.insert(containerID) expand(containerID) } @@ -742,8 +753,11 @@ class SceneCoordinator: NSObject, UndoableCommandRunner { clearTimelineIfNoLongerAvailable() } + /// This is a special function that expects the caller to change the disclosure arrow state outside this function. + /// Failure to do so will get the Sidebar into an invalid state. func collapse(_ node: Node) { guard let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID else { return } + lastExpandedTable.remove(containerID) collapse(containerID) } @@ -1548,9 +1562,10 @@ private extension SceneCoordinator { currentFeedIndexPath = indexPathFor(timelineFeed as AnyObject) } - // Compute the differences in the shadow table rows + // Compute the differences in the shadow table rows and the expanded table entries var changes = [ShadowTableChanges.RowChanges]() - + let expandedTableDifference = lastExpandedTable.symmetricDifference(expandedTable) + for (section, newSectionRows) in newShadowTable.enumerated() { var moves = Set() var inserts = Set() @@ -1576,9 +1591,22 @@ private extension SceneCoordinator { } } - changes.append(ShadowTableChanges.RowChanges(section: section, deletes: deletes, inserts: inserts, moves: moves)) + // We need to reload the difference in expanded rows to get the disclosure arrows correct when programmatically changing their state + var reloads = Set() + + for (index, newFeedNode) in newSectionRows.feedNodes.enumerated() { + if let newFeedNodeContainerID = (newFeedNode.node.representedObject as? Container)?.containerID { + if expandedTableDifference.contains(newFeedNodeContainerID) { + reloads.insert(index) + } + } + } + + changes.append(ShadowTableChanges.RowChanges(section: section, deletes: deletes, inserts: inserts, reloads: reloads, moves: moves)) } + lastExpandedTable = expandedTable + // Compute the difference in the shadow table sections var moves = Set() var inserts = Set()