From cd695848f05ce599efba97c50c9f5ba4f46f3b55 Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Fri, 15 Feb 2019 11:16:38 -0500 Subject: [PATCH 1/2] Change split view state restoration from user defaults to standard window restoration mechanism. I don't think you need to include the manual window frame autosave name stuff either because I think it's automatically handled by AppKit's default state restoration. --- NetNewsWire/AppDefaults.swift | 10 ---- NetNewsWire/AppDelegate.swift | 1 - .../MainWindow/MainWindowController.swift | 48 ++++++++++--------- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/NetNewsWire/AppDefaults.swift b/NetNewsWire/AppDefaults.swift index 44a19f7a5..fd04f2be0 100644 --- a/NetNewsWire/AppDefaults.swift +++ b/NetNewsWire/AppDefaults.swift @@ -53,7 +53,6 @@ struct AppDefaults { static let timelineSortDirection = "timelineSortDirection" static let detailFontSize = "detailFontSize" static let openInBrowserInBackground = "openInBrowserInBackground" - static let mainWindowWidths = "mainWindowWidths" static let refreshInterval = "refreshInterval" // Hidden prefs @@ -120,15 +119,6 @@ struct AppDefaults { } } - static var mainWindowWidths: [Int]? { - get { - return UserDefaults.standard.object(forKey: Key.mainWindowWidths) as? [Int] - } - set { - UserDefaults.standard.set(newValue, forKey: Key.mainWindowWidths) - } - } - static var refreshInterval: RefreshInterval { get { let rawValue = UserDefaults.standard.integer(forKey: Key.refreshInterval) diff --git a/NetNewsWire/AppDelegate.swift b/NetNewsWire/AppDelegate.swift index 733609a41..fb8d1fb04 100644 --- a/NetNewsWire/AppDelegate.swift +++ b/NetNewsWire/AppDelegate.swift @@ -576,7 +576,6 @@ private extension AppDelegate { func saveState() { inspectorWindowController?.saveState() - mainWindowController?.saveState() } func updateSortMenuItems() { diff --git a/NetNewsWire/MainWindow/MainWindowController.swift b/NetNewsWire/MainWindow/MainWindowController.swift index 7323d10a5..995aeaeb0 100644 --- a/NetNewsWire/MainWindow/MainWindowController.swift +++ b/NetNewsWire/MainWindow/MainWindowController.swift @@ -11,12 +11,11 @@ import Articles import Account import RSCore -class MainWindowController : NSWindowController, NSUserInterfaceValidations { +class MainWindowController : NSWindowController, NSUserInterfaceValidations, NSWindowDelegate { @IBOutlet var toolbarDelegate: MainWindowToolbarDelegate? private var sharingServicePickerDelegate: NSSharingServicePickerDelegate? - private let windowAutosaveName = NSWindow.FrameAutosaveName("MainWindow") static var didPositionWindowOnFirstRun = false private var currentFeedOrFolder: AnyObject? = nil { @@ -43,7 +42,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { window?.titleVisibility = .hidden } - window?.setFrameUsingName(windowAutosaveName, force: true) if AppDefaults.isFirstRun && !MainWindowController.didPositionWindowOnFirstRun { if let window = window { @@ -56,9 +54,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } detailSplitViewItem?.minimumThickness = CGFloat(MainWindowController.detailViewMinimumThickness) - restoreSplitViewState() - - NotificationCenter.default.addObserver(self, selector: #selector(applicationWillTerminate(_:)), name: NSApplication.willTerminateNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidBegin, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(refreshProgressDidChange(_:)), name: .AccountRefreshDidFinish, object: nil) @@ -75,12 +70,6 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { // MARK: - API - func saveState() { - - saveSplitViewState() - } - - func selectedObjectsInSidebar() -> [AnyObject]? { return sidebarViewController?.selectedObjects @@ -88,10 +77,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { // MARK: - Notifications - @objc func applicationWillTerminate(_ note: Notification) { + func window(_ window: NSWindow, willEncodeRestorableState state: NSCoder) { + saveSplitViewState(to: state) + } - saveState() - window?.saveFrame(usingName: windowAutosaveName) + func window(_ window: NSWindow, didDecodeRestorableState state: NSCoder) { + restoreSplitViewState(from: state) } @objc func refreshProgressDidChange(_ note: Notification) { @@ -396,7 +387,9 @@ extension MainWindowController : ScriptingMainWindowController { // MARK: - Private private extension MainWindowController { - + + private static let mainWindowWidthsStateKey = "mainWindowWidths" + var splitViewController: NSSplitViewController? { guard let viewController = contentViewController else { return nil @@ -583,7 +576,7 @@ private extension MainWindowController { } - func saveSplitViewState() { + func saveSplitViewState(to coder: NSCoder) { // TODO: Update this for multiple windows. @@ -592,16 +585,25 @@ private extension MainWindowController { } let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) } - if AppDefaults.mainWindowWidths != widths { - AppDefaults.mainWindowWidths = widths - } + coder.encode(widths, forKey: MainWindowController.mainWindowWidthsStateKey) + } - func restoreSplitViewState() { + func arrayOfIntFromCoder(_ coder: NSCoder, withKey: String) -> [Int]? { + let decodedFloats: [Int]? + do { + decodedFloats = try coder.decodeTopLevelObject(forKey: MainWindowController.mainWindowWidthsStateKey) as? [Int]? ?? nil + } + catch { + decodedFloats = nil + } + return decodedFloats + } + + func restoreSplitViewState(from coder: NSCoder) { // TODO: Update this for multiple windows. - - guard let splitView = splitViewController?.splitView, let widths = AppDefaults.mainWindowWidths, widths.count == 3, let window = window else { + guard let splitView = splitViewController?.splitView, let widths = arrayOfIntFromCoder(coder, withKey: MainWindowController.mainWindowWidthsStateKey), widths.count == 3, let window = window else { return } From c54d2f94cc62ff27fc75c87ec38ccef5f3ece6db Mon Sep 17 00:00:00 2001 From: Daniel Jalkut Date: Fri, 15 Feb 2019 13:38:07 -0500 Subject: [PATCH 2/2] Fix #547 by implementing state restoration in the sidebar and timeline view controllers. --- .../MainWindow/MainWindowController.swift | 6 +++++ .../Sidebar/SidebarViewController.swift | 22 +++++++++++++++++++ .../Timeline/TimelineViewController.swift | 22 +++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/NetNewsWire/MainWindow/MainWindowController.swift b/NetNewsWire/MainWindow/MainWindowController.swift index 995aeaeb0..70bda4247 100644 --- a/NetNewsWire/MainWindow/MainWindowController.swift +++ b/NetNewsWire/MainWindow/MainWindowController.swift @@ -78,11 +78,17 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations, NSW // MARK: - Notifications func window(_ window: NSWindow, willEncodeRestorableState state: NSCoder) { + saveSplitViewState(to: state) } func window(_ window: NSWindow, didDecodeRestorableState state: NSCoder) { + restoreSplitViewState(from: state) + + // Make sure the timeline view is first responder if possible, to start out viewing + // whatever preserved selection might have been restored + makeTimelineViewFirstResponder() } @objc func refreshProgressDidChange(_ note: Notification) { diff --git a/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift b/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift index 9bf6a37dd..308b51f68 100644 --- a/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift +++ b/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift @@ -70,6 +70,26 @@ import RSCore } } + // 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) { @@ -278,6 +298,8 @@ import RSCore func outlineViewSelectionDidChange(_ notification: Notification) { postSidebarSelectionDidChangeNotification(selectedObjects.isEmpty ? nil : selectedObjects) + + self.invalidateRestorableState() } //MARK: - Node Manipulation diff --git a/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift b/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift index 69e285e4b..f19d47770 100644 --- a/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift +++ b/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift @@ -143,6 +143,26 @@ class TimelineViewController: NSViewController, UndoableCommandRunner { sharingServiceDelegate = SharingServiceDelegate(self.view.window) } + // MARK: State Restoration + + private static let stateRestorationSelectedArticles = "selectedArticles" + + override func encodeRestorableState(with coder: NSCoder) { + + super.encodeRestorableState(with: coder) + + coder.encode(self.selectedArticleIDs(), forKey: TimelineViewController.stateRestorationSelectedArticles) + } + + override func restoreState(with coder: NSCoder) { + + super.restoreState(with: coder) + + if let restoredArticleIDs = (try? coder.decodeTopLevelObject(forKey: TimelineViewController.stateRestorationSelectedArticles)) as? [String] { + self.restoreSelection(restoredArticleIDs) + } + } + // MARK: Appearance Change private func fontSizeDidChange() { @@ -618,6 +638,8 @@ extension TimelineViewController: NSTableViewDelegate { } postTimelineSelectionDidChangeNotification(selectedArticles) + + self.invalidateRestorableState() } private func postTimelineSelectionDidChangeNotification(_ selectedArticles: ArticleArray?) {