SceneDelegate In Xcode11

参考The Scene Delegate In Xcode 11 And iOS 13

App Delegate

其中的application(:didFinishLaunchingWithOptions:)函数是系统开始应用后调用的第一个函数,继承自UIApplicationDelegate协议

在iOS12时,我们在appDelegate中做的事情:

  • 设置app的第一个view controller
  • 配置应用程序设置和启动组件,例如日志记录和云服务等
  • 注册app的推送处理,以及响应发送到app的推送的处理
  • 响应对app的生命周期事件,例如进入后台、启动app、退出app等
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{   
    let timeline = TimelineViewController()
    let navigation = UINavigationController(rootViewController: timeline)

    let frame = UIScreen.main.bounds
    window = UIWindow(frame: frame)

    window!.rootViewController = navigation
    window!.makeKeyAndVisible()

    return true
}

创建一个ViewController,放入navigation controller中,设置为UIWindow对象的的rootViewController。这个window是appdelegate 的属性,并且是app拥有的一个window。

window是一个重要的概念,本质上window就是我们的应用程序,大多数iOS应用只有这一个window,它包含了app的用户界面,将event事件分发到view中,并且提供了展示app内容的主要背景。

Scene Delegate

在iOS13以上,scene delegate接管了app delegate的一些角色。最重要的是,window的概念已经被scene取代。一个应用程序可以有多个场景,而一个场景现在可以用作app的用户界面和内容的背景。

具有多场景的app概念很有趣,可以允许你在iOS和iPadOS中构建多窗口应用程序。例如,文字处理器应用程序中的每个文本文档都可以有自己的场景。用户还可以创建场景副本,一次有效的运行一个app的多个实例。

在Xcode11实际使用中,使用sceneDelegate的位置:

  1. 一个新的iOS项目具有SceneDelegate类,该类会自动创建,其中包含常用的声明周期事件,例如active、resign和disconnect
  2. Appdelegate类具有和Scene相关的两个函数application(_:configurationForConnecting:options :)application(_:didDiscardSceneSessions :)
  3. Info.plist文件中有Application Scene Manifest,列出此应用程序中包含的Scene,包括其class、delegate、storyboard名称

Scene Delegate Class

SceneDelegate类中,最重要的是scene(_:willConnectTo:options:).函数,某些方面来说,其重要性与iOS12的application(_:didFinishLaunchingWithOptions:)类似。当场景添加到App中后,该函数会被调用,因此这是配置该Scene的理想位置。

需要注意SceneDelegate也使用delegate,并且通常一个delegate会响应任何场景,使用一个delegate去配置应用程度的所有场景

SceneDelegate中包含以下函数:

  • sceneDidDisconnect(_:)当一个场景与应用程序断开连接时被调用(注意场景以后可以重连)
  • sceneDidBecomeActive(_:)当用户开始和场景交互时调用。(例如当从应用程序切换器中选择时)
  • sceneWillResignActive(_ :)当用户停止和场景交互时调用(例如切换到另外一个场景时)
  • sceneWillEnterForeground(_ :)当场景进入前台时,即从背景状态恢复时,调用
  • sceneDidEnterBackground(_ :)当场景进入后台时调用,此时应用已经最小化并存在于后台

这些函数是应用程序的典型生命周期事件

Appdelegate: Scene Sessions

iOS13的AppDelegate,包含了两个scene session管理相关的delegate函数。当app中创建scene后,scene session将会跟踪和该场景相关的所有信息

  • Application(_:configurationForConnecting:options :)在创建新场景时需要返回配置对象
  • Application(_:didDiscardSceneSessions :)当app的用户通过应用切换器关闭一个或者多个场景时调用

目前,Scene session用于指定场景角色,例如External Display或者CarPlay。还可以用于还原scene状态。状态还原在应用启动之间保留并重新创建UI。还可以将用户信息分配给场景会话,这实际是一个可以放入任何内容的字典

application(_:didDiscardSceneSessions :),在app的用户通过应用切换器关闭一个或者多个场景时调用。可以使用此函数处理这些scene所需要的资源,因为不再需要这些资源。

application(_:didDiscardSceneSessions :)sceneDidDisconnect(_ :)对比,后者只会在场景断开连接时调用,但是并不一定会被丢弃,因为可以重新连接,而application(_:didDiscardSceneSessions :)标记为使用应用程序切换器退出场景的时候

info.plist Application Scene Manifest

app支持的每个scene都需要在Application Scene Mainifest中声明。大多数app只有一个场景,但是可以创建更多的场景,例如用于响应推送通知或者特定操作的特定场景

注意:声明的时session type而不是session本身。因此应用程序可以支持一个场景,创建该场景的副本,然后使用该场景副本创建多窗口应用程序

//Object-C版本

//swift版本

顶层入口为Application Scene Mainifest,而在其下为Enable Multiple Windows,需要设置为YES来支持多窗口应用程序。下面是Scene Configuration,其中Application Session Role用于声明app内部的scene,而还有另外一个External Display Session Role用于声明屏幕外的场景。
其中最重要的为Application Session Role数组:

  • configuration名字,必须为唯一的
  • 场景的类名称
  • 场景的代理类名称,通常为SceneDelegate
  • 包含初始UI的storyboard名称

而SceneDelegate、Appdelegate中的scene session、以及Application Scene Manifest是怎么协同工作来创建多窗口应用呢?

  • SceneDelegate类 管理场景的声明周期
  • Appdelegate中有新功能,管理场景会话、提供场景的配置数据

Scene Delegate 和 SwiftUI

最简单的引导iOS13应用程序的方法时使用SwiftUI,而SwiftUI App也主要依靠SceneDelegate来设置app的初始UI

  1. 并没有设置StoryBoard名称
  2. 如果支持多窗口 需要设置Enable Mutiple Window为YES
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        let contentView = ContentView()

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
...
  • scene(_:willConnectTo:options :)代理函数在将新场景添加到应用后调用。提供了一个Scene(以及session).此UIWindowScene是由App创建的,因此无需手动进行操作
  • 还有使用window属性,但是现在已经成为Scene的一部分。在if let代码块中,使用场景初始化UIWindow对象
  • 设置了根视图控制器,将window设置给window属性,并且将该窗口makeKeyAndVisible,即将该窗口设置于App的UI前
  • ContenView是特定于SwiftUI进行创建的,通过UIHostingController将其添加为根视图控制器,将基于SwiftUI视图显示到屏幕上
  • 请注意:UIScene类型的Scene参数是UIWindowScene类型的,但是使用as?类型转换(猜测虽然目前场景大多为UIWindowScene类型,但是将来会有更所类型场景)

SceneDelegate With Storyboards

在未来虽然有可能看到更多SwiftUI应用,但是目前来说storyboards更加常见

目前使用Storyboard我们无需做任何操作,只需要File → New → Project…然后选择singleApp,最后选择Storyboard for User Interface就可以了

  • 此时可以再Application Scene Manifest找到Main Storyboard
  • 默认情况下,App Delegate将会使用Default scene configuration
  • 默认情况下,SceneDelegate设置一个UIWindow对象,并使用Main.STORYBOARD创建初始UI

自己编程方式设置App

当我们不使用storyboards单独使用Xib创建AppUI,此时在SceneDelegatescene(_:willConnectTo:options :)函数中设置初始视图控制器

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
    {
        if let windowScene = scene as? UIWindowScene {

            let window = UIWindow(windowScene: windowScene)
            let timeline = TimelineViewController()

            let navigation = UINavigationController(rootViewController: timeline)
            window.rootViewController = navigation

            self.window = window
            window.makeKeyAndVisible()
        }
    }

    ...
  • 使用windowScene对象(由scene参数通过类型转换)初始化window属性,
  • if letblock中的代码 与iOS12或者更低版本设置视图控制器的方式类似。初始化带导航控制器的视图控制器,将其分配给rootViewController属性
  • 最后将window常量,设置给window属性,并将其设置为keyAndVisible,使其在屏幕最前方

这种方式设置SceneDelegte只是将代码从Appdelegate移过来 并且配置Application Scene Manifest.即可

Scene-Based 声明周期

添加对场景的支持会更改您的应用对生命周期事件的响应方式。在没有Scene的App中,App delegate对象处理到前台或后台的过渡。当您向App添加Scene支持时,UIKit会将职责转移到scene delegate对象上。Scene的生命周期彼此独立,并且独立于应用程序本身,因此scene delegate对象必须处理过渡。

如果您的应用程序还支持iOS 12或更低,则可以在App delegatescene delegate对象中处理生命周期过渡。 UIKit仅只会通知一个delegate对象。在iOS 13及更高版本中,UIKit会通知您的scene delegate对象。在iOS 12及更低版本中,UIKit会通知您的App delegate
有关如何处理生命周期事件的信息,请参阅管理应用程序的生命周期