From 7c947b935d9c06d1bc4b467b2ed8db9f4e714f3f Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Wed, 20 Nov 2019 16:41:13 -0600 Subject: [PATCH] Rewrite three panel mode so that background screenshooting will work. --- NetNewsWire.xcodeproj/project.pbxproj | 4 - iOS/RootSplitViewController.swift | 4 +- iOS/SceneCoordinator.swift | 169 ++++++++++-------- iOS/SceneDelegate.swift | 2 +- .../UISplitViewController-Extensions.swift | 20 --- 5 files changed, 100 insertions(+), 99 deletions(-) delete mode 100644 iOS/UIKit Extensions/UISplitViewController-Extensions.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 92322aad5..0f1f6dde2 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -35,7 +35,6 @@ 512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; }; 512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; }; 512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */; }; - 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */; }; 512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; }; 5131463E235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 51314668235A7E4600387FDC /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51314666235A7E4600387FDC /* IntentHandler.swift */; }; @@ -1233,7 +1232,6 @@ 512AF9C1236ED52C0066F8BE /* ImageHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageHeaderView.swift; sourceTree = ""; }; 512AF9DC236F05230066F8BE /* InteractiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveLabel.swift; sourceTree = ""; }; 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedTableViewSectionHeader.swift; sourceTree = ""; }; - 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISplitViewController-Extensions.swift"; sourceTree = ""; }; 51314617235A797400387FDC /* NetNewsWire_iOSintentextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSintentextension_target.xcconfig; sourceTree = ""; }; 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NetNewsWire iOS Intents Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 51314665235A7E4600387FDC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1863,7 +1861,6 @@ 5108F6D723763094001ABC45 /* TickMarkSlider.swift */, 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */, 51F85BF622749FA100C787DC /* UIFont-Extensions.swift */, - 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */, 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */, 51FFF0C3235EE8E5002762AA /* VibrantButton.swift */, 5186A634235EF3A800C97195 /* VibrantLabel.swift */, @@ -3951,7 +3948,6 @@ 51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */, 51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */, 5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */, - 512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */, 51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */, 51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */, 51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */, diff --git a/iOS/RootSplitViewController.swift b/iOS/RootSplitViewController.swift index 007425424..2e2ca5e68 100644 --- a/iOS/RootSplitViewController.swift +++ b/iOS/RootSplitViewController.swift @@ -22,9 +22,7 @@ class RootSplitViewController: UISplitViewController { } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - if UIApplication.shared.applicationState != .background { - self.coordinator.configureThreePanelMode(for: size) - } + self.coordinator.configurePanelMode(for: size) super.viewWillTransition(to: size, with: coordinator) } diff --git a/iOS/SceneCoordinator.swift b/iOS/SceneCoordinator.swift index 71bd4dc91..9a18ae7e5 100644 --- a/iOS/SceneCoordinator.swift +++ b/iOS/SceneCoordinator.swift @@ -13,6 +13,11 @@ import Articles import RSCore import RSTree +enum PanelMode { + case unset + case three + case standard +} enum SearchScope: Int { case timeline = 0 case global = 1 @@ -25,6 +30,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return rootSplitViewController.undoManager } + private var panelMode: PanelMode = .unset + private var activityManager = ActivityManager() private var isShowingExtractedArticle = false @@ -34,10 +41,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { private var masterNavigationController: UINavigationController! private var masterFeedViewController: MasterFeedViewController! private var masterTimelineViewController: MasterTimelineViewController? - - private var subSplitViewController: UISplitViewController? { - return rootSplitViewController.children.last as? UISplitViewController - } + private var subSplitViewController: UISplitViewController? private var articleViewController: ArticleViewController? { if let detail = masterNavigationController.viewControllers.last as? ArticleViewController { @@ -55,6 +59,8 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { return nil } + private var wasRootSplitViewControllerCollapsed = false + private let fetchAndMergeArticlesQueue = CoalescingQueue(name: "Fetch and Merge Articles", interval: 0.5) private var fetchSerialNumber = 0 private let fetchRequestQueue = FetchRequestQueue() @@ -103,7 +109,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { } var isThreePanelMode: Bool { - return subSplitViewController != nil + return panelMode == .three } var rootNode: Node { @@ -297,7 +303,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { let detailNavigationController = addNavControllerIfNecessary(articleViewController, showButton: true) rootSplitViewController.showDetailViewController(detailNavigationController, sender: self) - configureThreePanelMode(for: size) + configurePanelMode(for: size) return rootSplitViewController } @@ -325,19 +331,24 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { handleReadArticle(userInfo) } - func configureThreePanelMode(for size: CGSize) { - guard rootSplitViewController.traitCollection.userInterfaceIdiom == .pad && !rootSplitViewController.isCollapsed else { + func configurePanelMode(for size: CGSize) { + guard rootSplitViewController.traitCollection.userInterfaceIdiom == .pad else { return } + if (size.width / size.height) > 1.2 { - if !isThreePanelMode { - transitionToThreePanelMode() + if panelMode == .unset || panelMode == .standard { + panelMode = .three + configureThreePanelMode() } } else { - if isThreePanelMode { - transitionFromThreePanelMode() + if panelMode == .unset || panelMode == .three { + panelMode = .standard + configureStandardPanelMode() } } + + wasRootSplitViewControllerCollapsed = rootSplitViewController.isCollapsed } func selectFirstUnreadInAllUnread() { @@ -608,9 +619,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { let currentArticleViewController: ArticleViewController if articleViewController == nil { - currentArticleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) - currentArticleViewController.coordinator = self - installArticleController(currentArticleViewController, animated: animated) + currentArticleViewController = installArticleController(animated: animated) } else { currentArticleViewController = articleViewController! } @@ -963,15 +972,36 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { extension SceneCoordinator: UISplitViewControllerDelegate { func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { + guard !isThreePanelMode else { + return true + } + + if let articleViewController = (secondaryViewController as? UINavigationController)?.topViewController as? ArticleViewController { + masterNavigationController.pushViewController(articleViewController, animated: false) + return false + } + return currentArticle == nil } func splitViewController(_ splitViewController: UISplitViewController, separateSecondaryFrom primaryViewController: UIViewController) -> UIViewController? { - if currentArticle == nil { + guard !isThreePanelMode else { + return subSplitViewController + } + + guard currentArticle != nil else { let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) articleViewController.coordinator = self - return articleViewController + let controller = addNavControllerIfNecessary(articleViewController, showButton: true) + return controller } + + if let articleViewController = masterNavigationController.viewControllers.last as? ArticleViewController { + masterNavigationController.popViewController(animated: false) + let controller = addNavControllerIfNecessary(articleViewController, showButton: true) + return controller + } + return nil } @@ -986,7 +1016,6 @@ extension SceneCoordinator: UINavigationControllerDelegate { if UIApplication.shared.applicationState == .background { return } - // If we are showing the Feeds and only the feeds start clearing stuff if viewController === masterFeedViewController && !isThreePanelMode && !isTimelineViewControllerPending { @@ -1518,26 +1547,39 @@ private extension SceneCoordinator { } } - func installArticleController(_ articleController: UIViewController, animated: Bool) { + @discardableResult + func installArticleController(_ recycledArticleController: ArticleViewController? = nil, animated: Bool) -> ArticleViewController { isArticleViewControllerPending = true + let articleController: ArticleViewController = { + if let controller = recycledArticleController { + return controller + } else { + let controller = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) + controller.coordinator = self + return controller + } + }() + if let subSplit = subSplitViewController { let controller = addNavControllerIfNecessary(articleController, showButton: false) subSplit.showDetailViewController(controller, sender: self) - } else if rootSplitViewController.isCollapsed { - let controller = addNavControllerIfNecessary(articleController, showButton: false) - masterNavigationController.pushViewController(controller, animated: animated) + } else if rootSplitViewController.isCollapsed || wasRootSplitViewControllerCollapsed { + masterNavigationController.pushViewController(articleController, animated: animated) } else { let controller = addNavControllerIfNecessary(articleController, showButton: true) rootSplitViewController.showDetailViewController(controller, sender: self) } + return articleController + } func addNavControllerIfNecessary(_ controller: UIViewController, showButton: Bool) -> UIViewController { - if rootSplitViewController.traitCollection.horizontalSizeClass == .compact { + // You will sometimes get a compact horizontal size class while in three panel mode. Dunno why it lies. + if rootSplitViewController.traitCollection.horizontalSizeClass == .compact && !isThreePanelMode { return controller @@ -1560,14 +1602,16 @@ private extension SceneCoordinator { } - func configureDoubleSplit() { + func installSubSplit() { rootSplitViewController.preferredPrimaryColumnWidthFraction = 0.30 - let subSplit = UISplitViewController.template() - subSplit.preferredDisplayMode = .allVisible - subSplit.preferredPrimaryColumnWidthFraction = 0.4285 + subSplitViewController = UISplitViewController() + subSplitViewController!.preferredDisplayMode = .allVisible + subSplitViewController!.viewControllers = [InteractiveNavigationController.template()] + subSplitViewController!.preferredPrimaryColumnWidthFraction = 0.4285 - rootSplitViewController.showDetailViewController(subSplit, sender: self) + rootSplitViewController.showDetailViewController(subSplitViewController!, sender: self) + rootSplitViewController.setOverrideTraitCollection(UITraitCollection(horizontalSizeClass: .regular), forChild: subSplitViewController!) } func navControllerForTimeline() -> UINavigationController { @@ -1578,67 +1622,50 @@ private extension SceneCoordinator { } } - @discardableResult - func transitionToThreePanelMode() -> UIViewController { - + func configureThreePanelMode() { + let recycledArticleController = articleViewController defer { masterNavigationController.viewControllers = [masterFeedViewController] } - let controller: UIViewController = { - if let result = articleViewController { - return result - } else { - let articleViewController = UIStoryboard.main.instantiateController(ofType: ArticleViewController.self) - articleViewController.coordinator = self - return articleViewController - } - }() - configureDoubleSplit() + if rootSplitViewController.viewControllers.last is InteractiveNavigationController { + _ = rootSplitViewController.viewControllers.popLast() + } + + installSubSplit() installTimelineControllerIfNecessary(animated: false) masterTimelineViewController?.navigationItem.leftBarButtonItem = rootSplitViewController.displayModeButtonItem masterTimelineViewController?.navigationItem.leftItemsSupplementBackButton = true - // Create the new sub split controller and add the timeline in the primary position - let masterTimelineNavController = subSplitViewController!.viewControllers.first as! UINavigationController - masterTimelineNavController.viewControllers = [masterTimelineViewController!] - - // Put the detail or no selection controller in the secondary (or detail) position of the sub split - let navController = addNavControllerIfNecessary(controller, showButton: false) - subSplitViewController!.showDetailViewController(navController, sender: self) + installArticleController(recycledArticleController, animated: false) masterFeedViewController.restoreSelectionIfNecessary(adjustScroll: true) masterTimelineViewController!.restoreSelectionIfNecessary(adjustScroll: false) - - // We made sure this was there above when we called configureDoubleSplit - return subSplitViewController! - } - func transitionFromThreePanelMode() { - + func configureStandardPanelMode() { + let recycledArticleController = articleViewController rootSplitViewController.preferredPrimaryColumnWidthFraction = UISplitViewController.automaticDimension - if let subSplit = rootSplitViewController.viewControllers.last as? UISplitViewController { - - // Push a new timeline on to the master navigation controller. For some reason recycling the timeline can freak - // the system out and throw it into an infinite loop. - if currentFeedIndexPath != nil { - masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) - masterTimelineViewController!.coordinator = self - masterNavigationController.pushViewController(masterTimelineViewController!, animated: false) - } - - // Pull the detail or no selection controller out of the sub split second position and move it to the root split controller - // secondary (detail) position. - if let detailNav = subSplit.viewControllers.last as? UINavigationController, let topController = detailNav.topViewController { - let newNav = addNavControllerIfNecessary(topController, showButton: true) - rootSplitViewController.showDetailViewController(newNav, sender: self) - } + // Set the is Pending flags early to prevent the navigation controller delegate from thinking that we + // swiping around in the user interface + isTimelineViewControllerPending = true + isArticleViewControllerPending = true + masterNavigationController.viewControllers = [masterFeedViewController] + if rootSplitViewController.viewControllers.last is UISplitViewController { + subSplitViewController = nil + _ = rootSplitViewController.viewControllers.popLast() } - + + if currentFeedIndexPath != nil { + masterTimelineViewController = UIStoryboard.main.instantiateController(ofType: MasterTimelineViewController.self) + masterTimelineViewController!.coordinator = self + masterNavigationController.pushViewController(masterTimelineViewController!, animated: false) + } + + installArticleController(recycledArticleController, animated: false) } // MARK: NSUserActivity diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift index 05df42d02..0a8868d1a 100644 --- a/iOS/SceneDelegate.swift +++ b/iOS/SceneDelegate.swift @@ -58,7 +58,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneWillEnterForeground(_ scene: UIScene) { appDelegate.prepareAccountsForForeground() - self.coordinator.configureThreePanelMode(for: window!.frame.size) + self.coordinator.configurePanelMode(for: window!.frame.size) } func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { diff --git a/iOS/UIKit Extensions/UISplitViewController-Extensions.swift b/iOS/UIKit Extensions/UISplitViewController-Extensions.swift deleted file mode 100644 index 63e9acc18..000000000 --- a/iOS/UIKit Extensions/UISplitViewController-Extensions.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// UISplitViewController-Extensions.swift -// NetNewsWire -// -// Created by Maurice Parker on 4/18/19. -// Copyright © 2019 Ranchero Software. All rights reserved. -// - -import UIKit - -extension UISplitViewController { - - static func template() -> UISplitViewController { - let splitViewController = UISplitViewController() - splitViewController.preferredDisplayMode = .automatic - splitViewController.viewControllers = [InteractiveNavigationController.template()] - return splitViewController - } - -}