SceneDelegate 配置和使用

1、导读

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

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

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

  • SceneDelegate和AppDelegate的新变化
  • 他们是如何合作引导你的app启动的
  • 在纯手写App中使用SceneDelegate
  • 在Storyboards 和 SwiftUI项目中使用SceneDelegate
  • 不是用SceneDelegate的话怎么处理

2、AppDelegate回顾

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

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

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

  • 创建app的第一个view controller也就是 rootViewController
  • 配置并启动一些像日志记录和云服务之类的组件
  • 注册推送通知处理程序,并响应发送到app的推送通知
  • 响应应用程序生命周期事件,例如进入后台,恢复应用程序或退出应用程序(终止)
//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: Framew

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

    return true
}

在上面的代码中,我们创建一个ViewController,并将其放在navigation controller中。然后将其分配给UIWindow对象的rootViewController属性。 这个window对象是AppDelegate的属性,它是我们的应用的一个窗口。

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

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

3、SceneDelegate使用

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

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

3.1 SceneDelegate相关文件介绍

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

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

3.1.1 SceneDelegate类

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, 
                             options connectionOptions: UIScene.ConnectionOptions) {
        //1、SceneDelegate的最重要的函数是:`scene(_:willConnectTo:options:)`。 
        //  在某种程度上,它与iOS 12上的 `application(_:didFinishLaunchingWithOptions:)` 函数的作用最相似。
        //  当将场景添加到app中时`scene(_:willConnectTo:options:)`函数会被调用的,因此这里是配置场景的最理想地方。 
        //2、这里需要特别注意的是,“SceneDelegate”采用了协议模式,并且这个delegate通常会响应任何场景。 
        //   使用一个Delegate来配置App中的所有场景。

        //手动地设置视图控制器堆栈
        if let windowScene = scene as? UIWindowScene {
            window = UIWindow(windowScene: windowScene)

            let vc = ViewController()
            let navigation = UINavigationController(rootViewController: vc)

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

    func sceneDidDisconnect(_ scene: UIScene) {
        // 当场景与app断开连接是调用(注意,以后它可能被重新连接)
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // 当用户开始与场景进行交互(例如从应用切换器中选择场景)时,会调用
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // 当用户停止与场景交互(例如通过切换器切换到另一个场景)时调用
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // 当场景变成活动窗口时调用,即从后台状态变成开始或恢复状态
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // 当场景进入后台时调用,即该应用已最小化但仍存活在后台中
    }
}

3.1.2 AppDelegate 中的 SceneSessions

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    // MARK: UISceneSession Lifecycle
    //在iOS13中AppDelegate中有两个管理Senen Session的代理函数。
    //在您的应用创建scene(场景)后,“scene session”对象将跟踪与该场景相关的所有信息。
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // 会返回一个创建场景时需要的UISceneConfiguration对象
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // 当用户通过“应用切换器”关闭一个或多个场景时会被调用
        // 您可以在该函数中销毁场景所使用的资源,因为不会再需要它们。
        // 了解application(_:didDiscardSceneSessions:)与sceneDidDisconnect(_ :)的区别很重要,后者仅在场景断开连接时调用,不会被丢弃,它可能会重新连接。
        // 而application(_:didDiscardSceneSessions:)发生在使用【应用程序切换器】退出场景时。
    }
}

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

3.1.3 Info.plist 中的Application Scene Manifest

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

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

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

Info.plist 中的Application Scene Manifest

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

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

  • Configuration的名称,必须是唯一的
  • 场景的代理类名,通常为SceneDelegate。
  • 场景用于创建初始UI的storyboard名称
    Storyboard名称这一项可能使您想起Main Interface设置,该设置可以在Xcode 12项目的Project Properties配置中找到。 现在,在iOS应用中,你可以在此处设置或更改主Storyboard名称。

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

  • 首先,我们看SceneDelegate类。 它管理场景的生命周期,处理各种响应,诸如 sceneDidBecomeActive(_:)sceneDidEnterBackground(_:)之类的事件。
  • 然后,我们再看看AppDelegate类中的新函数。 它管理场景会话(scene sessions),提供场景的配置数据,并响应用户丢弃场景的事件。
  • 最后,我们看了一下Application Scene Manifest。 它列出了您的应用程序支持的场景,并将它们连接到delegate类并初始化storyboard。

3.2 在SwiftUI中使用Scene Delegate

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

3.2.1 配置

  • Application Scene Manifest
    ~ 特别注意,配置中没有设置“Storyboard Name”这一项。 请记住,如果要支持多个窗口,则需要将Enable Multiple Windows设置为YES ~
在SwiftUI中Application Scene Manifest配置
  • AppDelegate
    我们将跳过“ 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()
        }
    }
}
上面的代码中发生了什么?

1、首先,必须明确的是 在将新场景添加到应用中后 会调用 'scene(_:willConnectTo:options:) '代理函数。 
  它提供了一个'scene'对象和一个'session'。 这个'UIWindowScene'对象是由应用创建的,您无需进行其他操作。
2、其次,'window'属性会在这里用到。 App仍然使用'UIWindow'对象,但现在它们已成为'scene'(场景)的一部分。
  在'if let'代码块中,您可以清楚地看到如何使用'scene'来初始化'UIWindow'对象的。
3、然后是设置'window'的'rootViewController',将'window'实例分配给了场景的'window'属性,并且设置窗口'makeKeyAndVisible'为true,即将该窗口置于App的前面。
  接着为SwiftUI项目创建了'ContentView'实例,并通过使用'UIHostingController'将其添加为根视图控制器。 
  该控制器用于将基于SwiftUI的视图显示在屏幕上。
4、最后但并非不重要的一点,值得注意的是,'UIScene'的实例化对象'scene'实际上是'UIWindowScene'类型的对象。这就是'as?'对可选类型转换的原因。

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

当'scene(_:willConnectTo:options:)'被调用时,'SceneDelegate'会在正确的时间配置场景。
'AppDelegate'和'Manifest'的默认配置,他们没有涉及'storyboard'的任何东西。
'scene(_:willConnectTo:options :)'函数内,创建一个SwiftUI视图,将其放置在托管控制器中,
然后将控制器分配给'window'属性的根视图控制器,并将该窗口放置在应用程序UI的前面 。

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

3.3 在Storyboards项目中使用SceneDelegate

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

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

设置方法如下:

  • 如我们前面提到过的,您可以在Info.plist中的Application Scene Manifest中找到Main storyboard的设置地方。
  • 默认情况下,AppDelegate将使用默认的UISceneConfiguration。
  • SceneDelegate会设置一个UIWindow对象,并使用Main.storyboard来创建初始UI。

3.3 纯代码编写UI

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

首先,AppDelegateApplication Scene Manifest中保持默认值。
我们不使用storyboard,所以需要在SceneDelegate类的scene(_:willConnectTo:options:)函数中设置初始视图控制器。
然后,删除info.plist中的Main storyboard file base name条目和Application Scene Manifest下的Storyboard Name条目,Main.storyboard文件留与不留看你心意。
大功告成。

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

4、不使用SceneDelegate

传送门:如何不使用SceneDelegate

参考文章:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335