From 8acab45cf99590d6b423c8a8ff8bc1bdb2c624cf Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 27 Jul 2019 19:49:33 -0700 Subject: [PATCH 1/5] Make formatting changes to SidebarViewController.swift so it conforms to current formatting thinking. --- .../Sidebar/SidebarViewController.swift | 72 +------------------ 1 file changed, 2 insertions(+), 70 deletions(-) diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index c206be8aa..36fe43f80 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -43,7 +43,6 @@ protocol SidebarDelegate: class { // MARK: - NSViewController override func viewDidLoad() { - sidebarCellAppearance = SidebarCellAppearance(fontSize: AppDefaults.sidebarFontSize) outlineView.dataSource = dataSource @@ -78,30 +77,9 @@ protocol SidebarDelegate: class { } } - // MARK: State Restoration - -// private static let stateRestorationSelectedRowIndexes = "selectedRowIndexes" -// -// override func encodeRestorableState(with coder: NSCoder) { -// -// super.encodeRestorableState(with: coder) -// -// coder.encode(outlineView.selectedRowIndexes, forKey: SidebarViewController.stateRestorationSelectedRowIndexes) -// } -// -// override func restoreState(with coder: NSCoder) { -// -// super.restoreState(with: coder) -// -// if let restoredRowIndexes = coder.decodeObject(of: [NSIndexSet.self], forKey: SidebarViewController.stateRestorationSelectedRowIndexes) as? IndexSet { -// outlineView.selectRowIndexes(restoredRowIndexes, byExtendingSelection: false) -// } -// } - // MARK: - Notifications @objc func unreadCountDidChange(_ note: Notification) { - guard let representedObject = note.object else { return } @@ -125,7 +103,6 @@ protocol SidebarDelegate: class { } @objc func userDidAddFeed(_ notification: Notification) { - guard let feed = notification.userInfo?[UserInfoKey.feed] else { return } @@ -133,12 +110,10 @@ protocol SidebarDelegate: class { } @objc func faviconDidBecomeAvailable(_ note: Notification) { - applyToAvailableCells(configureFavicon) } @objc func feedSettingDidChange(_ note: Notification) { - guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else { return } @@ -148,7 +123,6 @@ protocol SidebarDelegate: class { } @objc func displayNameDidChange(_ note: Notification) { - guard let object = note.object else { return } @@ -159,7 +133,6 @@ protocol SidebarDelegate: class { } @objc func userDidRequestSidebarSelection(_ note: Notification) { - guard let feed = note.userInfo?[UserInfoKey.feed] else { return } @@ -175,7 +148,6 @@ protocol SidebarDelegate: class { // MARK: - Actions @IBAction func delete(_ sender: AnyObject?) { - if outlineView.selectionIsEmpty { return } @@ -183,7 +155,6 @@ protocol SidebarDelegate: class { } @IBAction func openInBrowser(_ sender: Any?) { - guard let feed = singleSelectedFeed, let homePageURL = feed.homePageURL else { return } @@ -191,29 +162,24 @@ protocol SidebarDelegate: class { } @IBAction func gotoToday(_ sender: Any?) { - outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.todayFeed, treeController) } @IBAction func gotoAllUnread(_ sender: Any?) { - outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.unreadFeed, treeController) } @IBAction func gotoStarred(_ sender: Any?) { - outlineView.revealAndSelectRepresentedObject(SmartFeedsController.shared.starredFeed, treeController) } @IBAction func copy(_ sender: Any?) { - NSPasteboard.general.copyObjects(selectedObjects) } // MARK: - Navigation func canGoToNextUnread() -> Bool { - if let _ = nextSelectableRowWithUnreadArticle() { return true } @@ -221,7 +187,6 @@ protocol SidebarDelegate: class { } func goToNextUnread() { - guard let row = nextSelectableRowWithUnreadArticle() else { assertionFailure("goToNextUnread called before checking if there is a next unread.") return @@ -230,26 +195,19 @@ protocol SidebarDelegate: class { NSCursor.setHiddenUntilMouseMoves(true) outlineView.selectRowIndexes(IndexSet([row]), byExtendingSelection: false) outlineView.scrollTo(row: row) - } func focus() { - - guard let window = outlineView.window else { - return - } - window.makeFirstResponderUnlessDescendantIsFirstResponder(outlineView) + outlineView.window?.makeFirstResponderUnlessDescendantIsFirstResponder(outlineView) } // MARK: - Contextual Menu func contextualMenuForSelectedObjects() -> NSMenu? { - return menu(for: selectedObjects) } func contextualMenuForClickedRows() -> NSMenu? { - let row = outlineView.clickedRow guard row != -1, let node = nodeForRow(row) else { return nil @@ -264,7 +222,7 @@ protocol SidebarDelegate: class { return menu(for: [object]) } - // MARK: NSMenuDelegate + // MARK: - NSMenuDelegate public func menuNeedsUpdate(_ menu: NSMenu) { menu.removeAllItems() @@ -278,7 +236,6 @@ protocol SidebarDelegate: class { // MARK: - NSOutlineViewDelegate func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { - let node = item as! Node if node.isGroupItem { @@ -294,13 +251,11 @@ protocol SidebarDelegate: class { } func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool { - let node = item as! Node return node.isGroupItem } func outlineView(_ outlineView: NSOutlineView, selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet) -> IndexSet { - // Don’t allow selecting group items. // If any index in IndexSet contains a group item, // return the current selection (not a modified version of the proposed selection). @@ -315,19 +270,16 @@ protocol SidebarDelegate: class { } func outlineView(_ outlineView: NSOutlineView, shouldSelectItem item: Any) -> Bool { - return !self.outlineView(outlineView, isGroupItem: item) } func outlineViewSelectionDidChange(_ notification: Notification) { selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects) -// self.invalidateRestorableState() } //MARK: - Node Manipulation func deleteNodes(_ nodes: [Node]) { - let nodesToDelete = treeController.normalizedSelectedNodes(nodes) guard let undoManager = undoManager, let deleteCommand = DeleteCommand(nodesToDelete: nodesToDelete, treeController: treeController, undoManager: undoManager) else { @@ -374,7 +326,6 @@ protocol SidebarDelegate: class { extension SidebarViewController: NSUserInterfaceValidations { func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { - if item.action == #selector(copy(_:)) { return NSPasteboard.general.canCopyAtLeastOneObject(selectedObjects) } @@ -412,7 +363,6 @@ private extension SidebarViewController { } func rebuildTreeAndReloadDataIfNeeded() { - if !animatingChanges && !BatchUpdate.shared.isPerforming { treeController.rebuild() outlineView.reloadData() @@ -444,7 +394,6 @@ private extension SidebarViewController { } func updateUnreadCounts(for objects: [AnyObject]) { - // On selection, update unread counts for folders and feeds. // For feeds, actually fetch from database. @@ -459,7 +408,6 @@ private extension SidebarViewController { } func nodeForItem(_ item: AnyObject?) -> Node { - if item == nil { return treeController.rootNode } @@ -467,7 +415,6 @@ private extension SidebarViewController { } func nodeForRow(_ row: Int) -> Node? { - if row < 0 || row >= outlineView.numberOfRows { return nil } @@ -479,7 +426,6 @@ private extension SidebarViewController { } func rowHasAtLeastOneUnreadArticle(_ row: Int) -> Bool { - if let oneNode = nodeForRow(row) { if let unreadCountProvider = oneNode.representedObject as? UnreadCountProvider { if unreadCountProvider.unreadCount > 0 { @@ -491,7 +437,6 @@ private extension SidebarViewController { } func rowIsGroupItem(_ row: Int) -> Bool { - if let node = nodeForRow(row), outlineView.isGroupItem(node) { return true } @@ -499,7 +444,6 @@ private extension SidebarViewController { } func nextSelectableRowWithUnreadArticle() -> Int? { - // Skip group items, because they should never be selected. let selectedRow = outlineView.selectedRow @@ -533,12 +477,10 @@ private extension SidebarViewController { } func configureUnreadCount(_ cell: SidebarCell, _ node: Node) { - cell.unreadCount = unreadCountFor(node) } func configureFavicon(_ cell: SidebarCell, _ node: Node) { - cell.image = imageFor(node) } @@ -547,7 +489,6 @@ private extension SidebarViewController { } func imageFor(_ node: Node) -> NSImage? { - if let smallIconProvider = node.representedObject as? SmallIconProvider { return smallIconProvider.smallIcon } @@ -555,7 +496,6 @@ private extension SidebarViewController { } func nameFor(_ node: Node) -> String { - if let displayNameProvider = node.representedObject as? DisplayNameProvider { return displayNameProvider.nameForDisplay } @@ -563,7 +503,6 @@ private extension SidebarViewController { } func unreadCountFor(_ node: Node) -> Int { - if let unreadCountProvider = node.representedObject as? UnreadCountProvider { return unreadCountProvider.unreadCount } @@ -571,14 +510,11 @@ private extension SidebarViewController { } func cellForRowView(_ rowView: NSTableRowView) -> SidebarCell? { - return rowView.view(atColumn: 0) as? SidebarCell } func applyToAvailableCells(_ callback: (SidebarCell, Node) -> Void) { - outlineView.enumerateAvailableRowViews { (rowView: NSTableRowView, row: Int) -> Void in - guard let cell = cellForRowView(rowView), let node = nodeForRow(row) else { return } @@ -595,23 +531,19 @@ private extension SidebarViewController { } func configureCellsForRepresentedObject(_ representedObject: AnyObject) { - applyToCellsForRepresentedObject(representedObject, configure) } func configureUnreadCountForCellsForRepresentedObject(_ representedObject: AnyObject) { - applyToCellsForRepresentedObject(representedObject, configureUnreadCount) } @discardableResult func revealAndSelectRepresentedObject(_ representedObject: AnyObject) -> Bool { - return outlineView.revealAndSelectRepresentedObject(representedObject, treeController) } } - private extension Node { func representsSidebarObject(_ object: AnyObject) -> Bool { From 05faea53c45daf76623a1f643278534a49686edf Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 27 Jul 2019 19:52:05 -0700 Subject: [PATCH 2/5] =?UTF-8?q?Remove=20reference=20to=20NSCalendarDayChan?= =?UTF-8?q?ged=20=E2=80=94=C2=A0it=E2=80=99s=20no=20longer=20necessary=20t?= =?UTF-8?q?o=20update=20the=20Today=20feed=20unread=20count=20when=20the?= =?UTF-8?q?=20day=20changes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mac/MainWindow/Sidebar/SidebarViewController.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 36fe43f80..ffcad2574 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -59,7 +59,6 @@ protocol SidebarDelegate: class { NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(userDidRequestSidebarSelection(_:)), name: .UserDidRequestSidebarSelection, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(calendarDayChanged(_:)), name: .NSCalendarDayChanged, object: nil) outlineView.reloadData() @@ -139,12 +138,6 @@ protocol SidebarDelegate: class { revealAndSelectRepresentedObject(feed as AnyObject) } - @objc func calendarDayChanged(_ note: Notification) { - DispatchQueue.main.async { - SmartFeedsController.shared.todayFeed.fetchUnreadCounts() - } - } - // MARK: - Actions @IBAction func delete(_ sender: AnyObject?) { From 44ec6a026ddd8e3221f8a47891242637531b2af8 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 27 Jul 2019 22:53:27 -0700 Subject: [PATCH 3/5] =?UTF-8?q?Get=20the=20unread=20count=20from=20the=20t?= =?UTF-8?q?imeline=20for=20the=20currently=20selected=20node=20in=20the=20?= =?UTF-8?q?sidebar.=20This=20ensures=20that=20transients=20in=20the=20time?= =?UTF-8?q?line=20are=20accounted=20for.=20(The=20database=20query=20for?= =?UTF-8?q?=20the=20unread=20count=20wouldn=E2=80=99t=20necessarily=20matc?= =?UTF-8?q?h.)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mac/MainWindow/MainWindowController.swift | 14 ++++++++ .../Sidebar/SidebarViewController.swift | 9 +++++ .../TimelineContainerViewController.swift | 2 +- .../Timeline/TimelineViewController.swift | 35 +++++++++++++++++-- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index de5bdfa30..18a965024 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -387,6 +387,16 @@ extension MainWindowController: SidebarDelegate { updateWindowTitle() NotificationCenter.default.post(name: .InspectableObjectsDidChange, object: nil) } + + func unreadCount(for representedObject: AnyObject) -> Int { + guard let timelineViewController = regularTimelineViewController else { + return 0 + } + guard timelineViewController.representsThisObjectOnly(representedObject) else { + return 0 + } + return timelineViewController.unreadCount + } } // MARK: - TimelineContainerViewControllerDelegate @@ -545,6 +555,10 @@ private extension MainWindowController { return timelineContainerViewController?.currentTimelineViewController } + var regularTimelineViewController: TimelineViewController? { + return timelineContainerViewController?.regularTimelineViewController + } + var sidebarSplitViewItem: NSSplitViewItem? { return splitViewController?.splitViewItems[0] } diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index ffcad2574..0f24a4240 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -14,6 +14,7 @@ import RSCore protocol SidebarDelegate: class { func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?) + func unreadCount(for: AnyObject) -> Int } @objc class SidebarViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate, UndoableCommandRunner { @@ -496,6 +497,14 @@ private extension SidebarViewController { } func unreadCountFor(_ node: Node) -> Int { + // If this node is the one and only selection, + // then the unread count comes from the timeline. + // This ensures that any transients in the timeline + // are accounted for in the unread count. + if selectedNodes.count == 1 && node === selectedNodes.first! { + return delegate?.unreadCount(for: node.representedObject) ?? 0 + } + if let unreadCountProvider = node.representedObject as? UnreadCountProvider { return unreadCountProvider.unreadCount } diff --git a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift index 12f6d62d2..a8fa3bb84 100644 --- a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift @@ -30,7 +30,7 @@ final class TimelineContainerViewController: NSViewController { weak var delegate: TimelineContainerViewControllerDelegate? - private lazy var regularTimelineViewController = { + lazy var regularTimelineViewController = { return TimelineViewController(delegate: self) }() private lazy var searchTimelineViewController: TimelineViewController = { diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index 7249da5bb..26750287d 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -19,14 +19,14 @@ protocol TimelineDelegate: class { func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?) } -final class TimelineViewController: NSViewController, UndoableCommandRunner { +final class TimelineViewController: NSViewController, UndoableCommandRunner, UnreadCountProvider { @IBOutlet var tableView: TimelineTableView! var representedObjects: [AnyObject]? { didSet { if !representedObjectArraysAreEqual(oldValue, representedObjects) { - + unreadCount = 0 if let representedObjects = representedObjects { if representedObjects.count == 1 && representedObjects.first is Feed { showFeedNames = false @@ -76,11 +76,21 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner { // Just reload visible cells in this case: don’t call reloadData. articleRowMap = [String: Int]() reloadVisibleCells() + updateUnreadCount() return } updateShowAvatars() articleRowMap = [String: Int]() tableView.reloadData() + updateUnreadCount() + } + } + + var unreadCount: Int = 0 { + didSet { + if unreadCount != oldValue { + postUnreadCountDidChangeNotification() + } } } @@ -221,6 +231,16 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner { return selectedArticles.canMarkAllAsRead() } + func representsThisObjectOnly(_ object: AnyObject) -> Bool { + guard let representedObjects = representedObjects else { + return false + } + if representedObjects.count != 1 { + return false + } + return representedObjects.first! === object + } + // MARK: - Actions @objc func openArticleInBrowser(_ sender: Any?) { @@ -448,6 +468,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner { return } reloadVisibleCells(for: articles) + updateUnreadCount() } @objc func feedIconDidBecomeAvailable(_ note: Notification) { @@ -810,6 +831,16 @@ private extension TimelineViewController { } } + func updateUnreadCount() { + var count = 0 + for article in articles { + if !article.status.read { + count += 1 + } + } + unreadCount = count + } + func queueReloadAvailableCells() { CoalescingQueue.standard.add(self, #selector(reloadAvailableCells)) From 2441de960fa82dd209574826ffbc8ed0e5cb2318 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 28 Jul 2019 15:31:17 -0700 Subject: [PATCH 4/5] =?UTF-8?q?Skip=20reloading=20the=20sidebar=20after=20?= =?UTF-8?q?the=20tree=20controller=20rebuilds=20when=20the=20rebuilt=20ver?= =?UTF-8?q?sion=20is=20the=20same=20as=20the=20previous=20version.=20This?= =?UTF-8?q?=20saves=20some=20cycles,=20and=20it=E2=80=99s=20part=20of=20fi?= =?UTF-8?q?xing=20#791.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mac/MainWindow/Sidebar/SidebarViewController.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 0f24a4240..1b9f1d187 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -358,8 +358,9 @@ private extension SidebarViewController { func rebuildTreeAndReloadDataIfNeeded() { if !animatingChanges && !BatchUpdate.shared.isPerforming { - treeController.rebuild() - outlineView.reloadData() + if treeController.rebuild() { + outlineView.reloadData() + } } } From 887f8e0b53d3a0bf701564496f610c77b02277b3 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 28 Jul 2019 16:00:43 -0700 Subject: [PATCH 5/5] =?UTF-8?q?Check=20to=20see=20if=20the=20timeline=20al?= =?UTF-8?q?ready=20has=20the=20expected=20representedObjects=20when=20the?= =?UTF-8?q?=20sidebar=20selection=20(apparently)=20has=20changed.=20If=20s?= =?UTF-8?q?o,=20then=20don=E2=80=99t=20set=20the=20representedObjects=20ag?= =?UTF-8?q?ain,=20and=20don=E2=80=99t=20knock=20the=20user=20out=20of=20se?= =?UTF-8?q?arch=20mode=20if=20searching.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mac/MainWindow/MainWindowController.swift | 9 ++++--- .../TimelineContainerViewController.swift | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 18a965024..bc00c98d1 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -381,9 +381,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { extension MainWindowController: SidebarDelegate { func sidebarSelectionDidChange(_: SidebarViewController, selectedObjects: [AnyObject]?) { - // TODO: if searching, cancel search - timelineContainerViewController?.setRepresentedObjects(selectedObjects, mode: .regular) - forceSearchToEnd() + // Don’t update the timeline if it already has those objects. + let representedObjectsAreTheSame = timelineContainerViewController?.regularTimelineViewControllerHasRepresentedObjects(selectedObjects) ?? false + if !representedObjectsAreTheSame { + timelineContainerViewController?.setRepresentedObjects(selectedObjects, mode: .regular) + forceSearchToEnd() + } updateWindowTitle() NotificationCenter.default.post(name: .InspectableObjectsDidChange, object: nil) } diff --git a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift index a8fa3bb84..b4f3b3a27 100644 --- a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift @@ -54,6 +54,31 @@ final class TimelineContainerViewController: NSViewController { func showTimeline(for mode: TimelineSourceMode) { currentTimelineViewController = timelineViewController(for: mode) } + + func regularTimelineViewControllerHasRepresentedObjects(_ representedObjects: [AnyObject]?) -> Bool { + // Use this to find out if the regular timeline view already has the specified representedObjects. + // This is used in determining whether a search should end. + // The sidebar may think that the selection has changed, and therefore search should end — + // but it could be that the regular timeline already has these representedObjects, + // and therefore the selection hasn’t actually changed, + // and therefore search shouldn’t end. + // https://github.com/brentsimmons/NetNewsWire/issues/791 + if representedObjects == nil && regularTimelineViewController.representedObjects == nil { + return true + } + guard let currentObjects = regularTimelineViewController.representedObjects, let representedObjects = representedObjects else { + return false + } + if currentObjects.count != representedObjects.count { + return false + } + for object in representedObjects { + guard let _ = currentObjects.firstIndex(where: { $0 === object } ) else { + return false + } + } + return true + } } extension TimelineContainerViewController: TimelineDelegate {