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) {
// 当用户通过“应用切换器”关闭一个或多个场景时会被调用
// 您可以在该函数中销毁场景所使用的资源,因为不会再需要它们。
// 了解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的名称,版本,支持的设备方向以及现在支持的不同场景等配置。
请务必注意,您声明的是会话的“类型”,而不是会话实例。 您的应用程序可以支持一个场景,然后创建该场景的副本,来实现【多窗口】应用程序。
在上图中,您会看到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 ~
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,该怎么办?
首先,AppDelegate
和Application 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
参考文章:
- iOS13 Scene Delegate详解