Add basic multi window support to iOS

This commit is contained in:
Maurice Parker 2019-06-28 10:28:02 -05:00
parent 55ab50289c
commit 178e89b1fb
4 changed files with 138 additions and 84 deletions

View File

@ -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 = "<group>"; };
5194B5F122B69FCC00144881 /* SettingsSubscriptionsExportDocumentPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionsExportDocumentPickerView.swift; sourceTree = "<group>"; };
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = "<group>"; };
51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = "<group>"; };
51C45254226507D200C03939 /* AppAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = "<group>"; };
@ -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 */,

View File

@ -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.

View File

@ -52,14 +52,31 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>NetNewsWire.SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>

83
iOS/SceneDelegate.swift Normal file
View File

@ -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
}
}