Switches to UIScene-based app lifecycle

This commit is contained in:
Riley Testut 2022-07-25 17:22:11 -05:00
parent 4829b393c5
commit 52a68e28dd
5 changed files with 228 additions and 2 deletions

View File

@ -169,6 +169,7 @@
D524F4A3273DE9C000D500B2 /* ProcessInfo+JIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */; }; D524F4A3273DE9C000D500B2 /* ProcessInfo+JIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */; };
D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */; }; D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */; };
D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */; }; D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */; };
D5A98CE2284EF14B00E023E5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -365,6 +366,7 @@
D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+JIT.swift"; sourceTree = "<group>"; }; D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+JIT.swift"; sourceTree = "<group>"; };
D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerManager+Delta.swift"; sourceTree = "<group>"; }; D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerManager+Delta.swift"; sourceTree = "<group>"; };
D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Filename.swift"; sourceTree = "<group>"; }; D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Filename.swift"; sourceTree = "<group>"; };
D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Delta.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Delta.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -828,6 +830,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */, BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */,
D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */,
BFFA71E01AAC406100EE9DD1 /* Main.storyboard */, BFFA71E01AAC406100EE9DD1 /* Main.storyboard */,
BFFC46211D59848000AF2CC6 /* Launch */, BFFC46211D59848000AF2CC6 /* Launch */,
BF46894D1AAC469800A2586D /* Game Selection */, BF46894D1AAC469800A2586D /* Game Selection */,
@ -1192,6 +1195,7 @@
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */, BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */, BF80E1D21F13117000847008 /* ControllerInputsViewController.swift in Sources */,
BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */, BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */,
D5A98CE2284EF14B00E023E5 /* SceneDelegate.swift in Sources */,
BFE56E1923EB7BE00014FECD /* UIImage+SymbolFallback.swift in Sources */, BFE56E1923EB7BE00014FECD /* UIImage+SymbolFallback.swift in Sources */,
BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */, BF6EE5E91F7C5F860051AD6C /* _GameControllerInputMapping.swift in Sources */,
BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */, BF5942871E09BC8B0051894B /* _ControllerSkin.swift in Sources */,

View File

@ -114,6 +114,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate
} }
} }
@available(iOS 13, *)
extension AppDelegate
{
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration
{
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Main", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>)
{
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
private extension AppDelegate private extension AppDelegate
{ {
func registerCores() func registerCores()

View File

@ -23,8 +23,16 @@ extension UIViewController
struct DeepLinkController struct DeepLinkController
{ {
private var window: UIWindow? { private var window: UIWindow? {
guard let delegate = UIApplication.shared.delegate, let window = delegate.window else { return nil } if #available(iOS 13, *)
return window {
guard let delegate = UIApplication.shared.connectedScenes.lazy.compactMap({ $0.delegate as? UIWindowSceneDelegate }).first, let window = delegate.window else { return nil }
return window
}
else
{
guard let delegate = UIApplication.shared.delegate, let window = delegate.window else { return nil }
return window
}
} }
private var topViewController: UIViewController? { private var topViewController: UIViewController? {

175
Delta/SceneDelegate.swift Normal file
View File

@ -0,0 +1,175 @@
//
// SceneDelegate.swift
// Delta
//
// Created by Riley Testut on 6/6/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import UIKit
import DeltaCore
import Harmony
@objc(SceneDelegate) @available(iOS 13, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate
{
var window: UIWindow?
private let deepLinkController = DeepLinkController()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
{
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
self.window?.tintColor = .deltaPurple
if let context = connectionOptions.urlContexts.first
{
self.handle(.url(context.url))
}
if let shortcutItem = connectionOptions.shortcutItem
{
self.handle(.shortcut(shortcutItem))
}
}
func sceneDidDisconnect(_ scene: UIScene)
{
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene)
{
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene)
{
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene)
{
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene)
{
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
@available(iOS 13, *)
extension SceneDelegate
{
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
{
guard let context = URLContexts.first else { return }
self.handle(.url(context.url))
}
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void)
{
self.handle(.shortcut(shortcutItem))
completionHandler(true)
}
}
@available(iOS 13, *)
private extension SceneDelegate
{
func handle(_ deepLink: DeepLink)
{
guard DatabaseManager.shared.isStarted else {
// Wait until DatabaseManager is ready before handling deep link.
// NotificationCenter.default.notifications requires iOS 15 or later :(
// _ = await NotificationCenter.default.notifications(named: DatabaseManager.didStartNotification).first(where: { _ in true })
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(forName: DatabaseManager.didStartNotification, object: DatabaseManager.shared, queue: .main) { [weak observer] _ in
observer.map { NotificationCenter.default.removeObserver($0) }
self.handle(deepLink)
}
return
}
DispatchQueue.main.async {
// DeepLinkController expects to be called from main thread.
switch deepLink
{
case .shortcut:
_ = self.deepLinkController.handle(deepLink)
case .url(let url):
if url.isFileURL
{
if GameType(fileExtension: url.pathExtension) != nil || url.pathExtension.lowercased() == "zip"
{
self.importGame(at: url)
}
else if url.pathExtension.lowercased() == "deltaskin"
{
self.importControllerSkin(at: url)
}
}
else if url.scheme?.hasPrefix("db-") == true
{
_ = DropboxService.shared.handleDropboxURL(url)
}
else if url.scheme?.lowercased() == "delta"
{
_ = self.deepLinkController.handle(deepLink)
}
}
}
}
func importGame(at url: URL)
{
DatabaseManager.shared.importGames(at: [url]) { (games, errors) in
if errors.count > 0
{
let alertController = UIAlertController.alertController(for: .games, with: errors)
self.present(alertController)
}
}
}
func importControllerSkin(at url: URL)
{
DatabaseManager.shared.importControllerSkins(at: [url]) { (games, errors) in
if errors.count > 0
{
let alertController = UIAlertController.alertController(for: .controllerSkins, with: errors)
self.present(alertController)
}
}
}
func present(_ alertController: UIAlertController)
{
var rootViewController = self.window?.rootViewController
while rootViewController?.presentedViewController != nil
{
rootViewController = rootViewController?.presentedViewController
}
rootViewController?.present(alertController, animated: true, completion: nil)
}
}

View File

@ -203,6 +203,27 @@
<string>Delta uses your microphone to emulate the Nintendo DS microphone.</string> <string>Delta uses your microphone to emulate the Nintendo DS microphone.</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>Press "OK" to allow Delta to use images from your Photo Library as game artwork.</string> <string>Press "OK" to allow Delta to use images from your Photo Library as game artwork.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISceneConfigurationName</key>
<string>Main</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array/> <array/>
<key>UIFileSharingEnabled</key> <key>UIFileSharingEnabled</key>