From 178e89b1fbdef1d31cde9c989e1654bf439ae40a Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 28 Jun 2019 10:28:02 -0500 Subject: [PATCH] Add basic multi window support to iOS --- NetNewsWire.xcodeproj/project.pbxproj | 16 ++-- iOS/AppDelegate.swift | 102 +++++++------------------- iOS/Resources/Info.plist | 21 +++++- iOS/SceneDelegate.swift | 83 +++++++++++++++++++++ 4 files changed, 138 insertions(+), 84 deletions(-) create mode 100644 iOS/SceneDelegate.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 84686e455..bf238bdb4 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 5183CCED22711DCE0010922C /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5183CCEC22711DCE0010922C /* Settings.storyboard */; }; 5183CCEF227125970010922C /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCEE227125970010922C /* SettingsViewController.swift */; }; 519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; }; + 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; }; 51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; }; 51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 51C451B9226377C900C03939 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; }; @@ -695,6 +696,7 @@ 5194B5ED22B6965300144881 /* SettingsSubscriptionsImportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsImportDocumentPickerView.swift; sourceTree = ""; }; 5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsExportDocumentPickerView.swift; sourceTree = ""; }; 519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; }; + 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = ""; }; 51C45254226507D200C03939 /* AppAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; @@ -1706,6 +1708,7 @@ 84C9FCA22262A1B800D921D6 /* LaunchScreen.storyboard */, 84C9FC9F2262A1B300D921D6 /* Main.storyboard */, 840D617E2029031C009BC708 /* AppDelegate.swift */, + 519E743422C663F900A78E47 /* SceneDelegate.swift */, 51C45254226507D200C03939 /* AppAssets.swift */, 51C45255226507D200C03939 /* AppDefaults.swift */, 51E3EB3C229AB08300645299 /* ErrorHandler.swift */, @@ -1963,12 +1966,12 @@ ORGANIZATIONNAME = "Ranchero Software"; TargetAttributes = { 6581C73220CED60000F4AD34 = { - DevelopmentTeam = 96VR936H35; - ProvisioningStyle = Automatic; + DevelopmentTeam = SHJK2V3AJG; + ProvisioningStyle = Manual; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = 96VR936H35; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -1978,8 +1981,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 96VR936H35; - ProvisioningStyle = Automatic; + DevelopmentTeam = SHJK2V3AJG; + ProvisioningStyle = Manual; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -1988,7 +1991,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 96VR936H35; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2397,6 +2400,7 @@ 51C45268226508F600C03939 /* MasterFeedUnreadCountView.swift in Sources */, 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */, 51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */, + 519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */, 51E595AD228E1C2100FCC42B /* AddAccountViewController.swift in Sources */, 51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */, 51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */, diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index 9fc4b4728..673d3e665 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -33,7 +33,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele } var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "application") - var window: UIWindow? var faviconDownloader: FaviconDownloader! var imageDownloader: ImageDownloader! @@ -67,15 +66,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele registerBackgroundTasks() - // Set up the split view - let splitViewController = window!.rootViewController as! UISplitViewController - - let detailNavController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController - detailNavController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem - splitViewController.delegate = self - - window!.tintColor = AppAssets.netNewsWireBlueColor - AppDefaults.registerDefaults() let isFirstRun = AppDefaults.isFirstRun if isFirstRun { @@ -119,31 +109,29 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele } - // func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool { - // - // let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String - // coder.encode(versionNumber, forKey: "VersionNumber") - // - // return true - // - // } - // - // func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool { - // if let storedVersionNumber = coder.decodeObject(forKey: "VersionNumber") as? String { - // let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String - // if versionNumber == storedVersionNumber { - // return true - // } - // } - // return false - // } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + func applicationWillTerminate(_ application: UIApplication) { + shuttingDown = true } - func applicationDidEnterBackground(_ application: UIApplication) { + // MARK: Notifications + + @objc func unreadCountDidChange(_ note: Notification) { + if note.object is AccountManager { + unreadCount = AccountManager.shared.unreadCount + } + } + + @objc func userDefaultsDidChange(_ note: Notification) { + scheduleBackgroundFeedRefresh() + } + + @objc func accountRefreshDidFinish(_ note: Notification) { + AppDefaults.lastRefresh = Date() + } + + // MARK: - API + + func prepareAccountsForBackground() { syncTimer?.invalidate() // Schedule background app refresh @@ -169,57 +157,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele } } - func applicationWillEnterForeground(_ application: UIApplication) { - AccountManager.shared.syncArticleStatusAll() - syncTimer?.update() - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // If we haven't refreshed the database for 15 minutes, run a refresh automatically + func prepareAccountsForForeground() { if let lastRefresh = AppDefaults.lastRefresh { if Date() > lastRefresh.addingTimeInterval(15 * 60) { AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) + } else { + AccountManager.shared.syncArticleStatusAll() + syncTimer?.update() } } else { AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) } - } - func applicationWillTerminate(_ application: UIApplication) { - shuttingDown = true - } - - // MARK: - Split view - - func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { - guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } - guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false } - if topAsDetailController.navState?.currentArticle == nil { - // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. - return true - } - return false - } - - // MARK: Notifications - - @objc func unreadCountDidChange(_ note: Notification) { - if note.object is AccountManager { - unreadCount = AccountManager.shared.unreadCount - } - } - - @objc func userDefaultsDidChange(_ note: Notification) { - scheduleBackgroundFeedRefresh() - } - - @objc func accountRefreshDidFinish(_ note: Notification) { - AppDefaults.lastRefresh = Date() - } - - // MARK: - API - func logMessage(_ message: String, type: LogItem.ItemType) { print("logMessage: \(message) - \(type)") @@ -234,7 +184,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele // MARK: - Background Tasks private extension AppDelegate { - + /// Register all background tasks. func registerBackgroundTasks() { // Register background feed refresh. diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist index 9df415275..629c451a9 100644 --- a/iOS/Resources/Info.plist +++ b/iOS/Resources/Info.plist @@ -52,14 +52,31 @@ NSAllowsArbitraryLoads + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + NetNewsWire.SceneDelegate + UISceneStoryboardFile + Main + + + + UIBackgroundModes fetch UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities arm64 diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift new file mode 100644 index 000000000..b95da416d --- /dev/null +++ b/iOS/SceneDelegate.swift @@ -0,0 +1,83 @@ +// +// AppDelegate.swift +// NetNewsWire +// +// Created by Maurice Parker on 6/28/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewControllerDelegate { + + var window: UIWindow? + + // UIWindowScene delegate + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + + window!.tintColor = AppAssets.netNewsWireBlueColor + + let storyboard = UIStoryboard(name: "Main", bundle: .main) + let splitViewController = storyboard.instantiateInitialViewController() as! UISplitViewController + splitViewController.delegate = self + window!.rootViewController = splitViewController + + let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController + navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem + + +// if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity { +// if !configure(window: window, with: userActivity) { +// print("Failed to restore from \(userActivity)") +// } +// } + + // If there were no user activities, we don't have to do anything. + // The `window` property will automatically be loaded with the storyboard's initial view controller. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + appDelegate.prepareAccountsForBackground() + } + + func sceneWillEnterForeground(_ scene: UIScene) { + appDelegate.prepareAccountsForForeground() + } + +// func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { +// return scene.userActivity +// } +// + // Utilities + +// func configure(window: UIWindow?, with activity: NSUserActivity) -> Bool { +// if activity.title == GalleryOpenDetailPath { +// if let photoID = activity.userInfo?[GalleryOpenDetailPhotoIdKey] as? String { +// +// if let photoDetailViewController = PhotoDetailViewController.loadFromStoryboard() { +// photoDetailViewController.photo = Photo(name: photoID) +// +// if let navigationController = window?.rootViewController as? UINavigationController { +// navigationController.pushViewController(photoDetailViewController, animated: false) +// return true +// } +// } +// } +// } +// return false +// } + + // MARK: UISplitViewControllerDelegate + + func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool { + guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false } + guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false } + if topAsDetailController.navState?.currentArticle == nil { + // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded. + return true + } + return false + } + +}