scenedelegate_logo.jpg

iOS13 项目中的SceneDelegate类有什么作用?自从Xcode11发布以来,当你使用新XCode创建一个新的iOS项目时,SceneDelegate会被默认创建,它到底有什么用呢。

在本文中,我们将深入探讨iOS 13和Xcode 11的一些变化。我们将重点关注SceneDelegate和AppDelegate,以及它们如何影响SwiftUI、Storyboard和基于XIB的UI项目。

通过阅读本文你将了解到:

让我们开始吧。

本篇文章基于 Xcode 11 和 iOS 13.

AppDelegate

你可能对AppDelegate已经熟悉,他是iOS app的入口,application(_:didFinishLaunchingWithOptions:)是你的app启动后系统调用的第一个函数。

AppDelegate类实现了UIKit库中的UIApplicationDelegate 协议。而到了iOS13 AppDelegate的角色将会发生变化,后面我们会详细讨论。

下面是你在iOS12中一般会在AppDelegate中做的事情:

iOS12及以前,使用Storyboards的app,AppDelegate很简单。 像这样:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
    return true
}

一个使用XIB的简单应用看起来像这样:

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的属性,它是我们的应用的一个窗口。

应用程序的window是一个重要的概念。 本质上,窗口就是应用程序,大多数iOS应用程序只有一个窗口。 它包含您应用的用户界面(UI),将事件调度到视图,并提供了一个主要背景层来显示您的应用内容。 从某种意义上说,“ Windows”的概念就是微软定义的窗口,而在iOS上,这个概念没有什么不同。 (谢谢,Xerox!)

好了,下面让我们继续SceneDelegate。

如果“窗口”的概念仍然不了解,请查看iPhone上的应用程序切换器。 双击Home键或从iPhone底部向上滑动,然后您会看到当前正在运行的应用程序的窗口。 这就是应用程序切换器。

The Scene Delegate

在iOS 13(及以后版本)上,SceneDelegate将负责AppDelegate的某些功能。 最重要的是,window(窗口)的概念已被scene(场景)的概念所代替。 一个应用程序可以具有不止一个场景,而一个场景现在可以作为您应用程序的用户界面和内容的载体(背景)。

尤其是一个具有多场景的App的概念很有趣,因为它使您可以在iOS和iPadOS上构建多窗口应用程序。 例如,文档编辑器App中的每个文本文档都可以有自己的场景。 用户还可以创建场景的副本,同时运行一个应用程序的多个实例(类似多开)。

在Xcode 11中有三个地方可以明显地看到SceneDelegate的身影:

  1. 现在,一个新的iOS项目会自动创建一个SceneDelegate类,其中包括我们熟悉的生命周期事件,例如active,resign和disconnect。
  2. AppDelegate类中多了两个与“scene sessions”相关的新方法:application(_:configurationForConnecting:options:)application(_:didDiscardSceneSessions:)
  3. Info.plist文件中提供了”Application Scene Manifest“配置项,用于配置App的场景,包括它们的场景配置名,delegate类名和storyboard

让我们一次开看一看。

1. Scene Delegate Class

首先,SceneDelegate类:

scene-delegate-1.jpg

SceneDelegate的最重要的函数是:scene(_:willConnectTo:options:)。 在某种程度上,它与iOS 12上的 application(_:didFinishLaunchingWithOptions:) 函数的作用最相似。当将场景添加到app中时scene(_:willConnectTo:options:)函数会被调用的,因此这里是配置场景的最理想地方。 在上面的代码中,我们手动地设置了视图控制器堆栈,稍后会进行详细介绍。

这里需要特别注意的是,“SceneDelegate”采用了协议模式,并且这个delegate通常会响应任何场景。 您使用一个Delegate来配置App中的所有场景。

SceneDelegate 还具有下面这些函数:

看到函数的对称性了吗? Active/inactive, background/foreground, 和 “disconnect”. 。 这些是任何应用程序的典型生命周期事件。

App Delegate中的Scene Sessions

在iOS13中AppDelegate中有两个管理Senen Session的代理函数。在您的应用创建scene(场景)后,“scene session”对象将跟踪与该场景相关的所有信息。

这两个函数是:

目前,SceneSession被用于指定场景,例如“外部显示” 或“ CarPlay” 。 它还可用于还原场景的状态,如果您想使用【状态还原】,SceneSession将非常有用。 状态还原允许您在应用启动之间保留并重新创建UI。 您还可以将用户信息存储到场景会话中,它是一个可以放入任何内容的字典。

application(_:didDiscardSceneSessions:)很简单。 当用户通过“应用程序切换器”关闭一个或多个场景时,即会调用该方法。 您可以在该函数中销毁场景所使用的资源,因为不会再需要它们。

了解application(_:didDiscardSceneSessions:)sceneDidDisconnect(_ :)的区别很重要,后者仅在场景断开连接时调用,不会被丢弃,它可能会重新连接。而application(_:didDiscardSceneSessions:)发生在使用【应用程序切换器】退出场景时。

3. Info.plist 中的Application Scene Manifest

您的应用支持的每个场景都需要在“Application Scene Manifest”(应用场景清单)中声明。 简而言之,清单列出了您的应用支持的每个场景。 大多数应用程序只有一个场景,但是您可以创建更多场景,例如用于响应推送通知或特定操作的特定场景。

Application Scene Manifest清单是Info.plist文件的一项,都知道该文件包含App的配置信息。 Info.plist包含诸如App的名称,版本,支持的设备方向以及现在支持的不同场景等配置。

请务必注意,您声明的是会话的“类型”,而不是会话实例。 您的应用程序可以支持一个场景,然后创建该场景的副本,来实现【多窗口】应用程序。

下面看一下的 Info.plist中清单的一些配置:

scene-manifest-2.jpg

在红框内,您会看到Application Scene Manifest 这一条。 在它下面一条是Enable Multiple Windows,需要将其设置为“ YES”以支持多个窗口。 再往下Application Session Role的值是一个数组,用于在应用程序中声明场景。 你也可以在数组中添加一条【外部屏幕】的场景声明。

最重要的信息保存在Application Session Role数组中。 从中我们可以看到以下内容:

Storyboard名称这一项可能使您想起Main Interface设置,该设置可以在Xcode 12项目的Project Properties配置中找到。 现在,在iOS应用中,你可以在此处设置或更改主Storyboard名称。

AppDelegate中的SceneDelegate、UISceneSession和Application Scene Manifest是如何一起创建多窗口应用的呢?

Awesome! Now that we’ve got a playing field, let’s find out how scenes affects building UIs in Xcode 11.

太棒了! 现在让我们了解scenes(场景)是如何影响Xcode 11中的用户界面的吧。

在SwiftUI中使用Scene Delegate

不久将来,SwiftUI将是创建iOS 13项目最简单的方法。 简言之,SwiftUI应用程序主要依靠SceneDelegate来设置应用程序的初始UI。

首先,SwiftUI项目中“Application Scene Manifest ”将长这样:

scene-manifest-3.jpg

特别注意,配置中没有设置“Storyboard Name”这一项。 请记住,如果要支持多个窗口,则需要将Enable Multiple Windows设置为YES

我们将跳过“ AppDelegate”,因为它相当标准。在SwiftUI项目中,只会返回“true”。

接下来是SceneDelegate类。 正如我们之前讨论的,SceneDelegate负责设置您应用中的场景,以及设置首个页面。

像下面一样:

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()
        }
    }
}

上面的代码中发生了什么?

所有这些看起来似乎很复杂,但是从高层次的概述来看,这很简单:

太棒了! 让我们继续。

您可以通过选择File(文件)→New(新建)→Project(项目)来建立一个基本的Xcode 11项目。 然后,选择Single View App, 在User Interface处选择SwiftUI来创建一个SwiftUI项目

在Storyboards项目中使用SceneDelegate

Storyboards和XIB是为iOS应用程序构建UI的有效方法。 在iOS 13上也是如此。 在将来,我们将看到更多的SwiftUI应用,但目前,Storyboards更常见。

有趣的是,即使有了SceneDelegate,通过Storyboards创建iOS项目你也不需要做任何额外的事情 只需选择File → New → Project。 然后,选择Single View App。 最后,为 User Interface 处选择 Storyboard ,就完成了。

设置方法如下:

纯代码编写UI

许多开发人员喜欢手写UI,而随着SwiftUI的兴起,使用SwiftUI手写代码将会越来越常见。 如果您不使用storyboards,而使用XIB创建应用程序UI,该怎么办? 让我们看看SceneDelegate如何修改。

首先,AppDelegate和Application Scene Manifest中保持默认值。 我们不使用storyboard,所以需要在SceneDelegate类的scene(_: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()
        }
    }
}

上面代码很简单我们就不详细介绍了。

很简单,对吧? 使用SceneDelegate的核心是将一些代码从AppDelegate移至到SceneDelegate中,并正确配置 Application Scene Manifest 。

想要为现有的iOS项目添加scene(场景)支持? 可以查看Apple官方文档https://developer.apple.com/documentation/uikit/app_and_environment/scenes/specifying_the_scenes_your_app_supports。

作者:Reinder de Vries 翻译:乐Coding