如果将Xcode更新到11, 创建项目。默认会创建SceneDelegate.swift, 那么问题来了, 这个代理用来干嘛的了?
在这篇文章中,我们将探索iOS13和Xcode11的改变。我们着重介绍scene和Delegates , 看看他们事如何影响SwiftUI,Storyboard和Xib构建的UI.
我们将学习到:
- AppDelegate和SceneDelegate
- 程序启动时,他们如何一起工作的
- 怎么设置app的scene deleagate
- 在Storyboard和SwiftUI不同环境中如何使用scene delegate
系好安全带,发车。。。
这篇文档项目环境Xcode11 和 iOS13
AppDelegate
我们对于AppDelegate非常熟悉,他是一个App启动的入口,其中的 application(_:didFinishLaunchingWithOptins: ) 方法是系统操作唤醒的*个方法。
AppDelegate 遵守UIKit框架的UIApplicationDeleaget ,但是在iOS13中app delegate发生了改变 ,我们能很快发现。
以下是iOS12 app deleget 的常规操作:
- 设置*个ViewController,我们叫做它root View Controller吧
- 配置app设置和启动模块,比如登录,连接服务器等
- 注册推送通知回调,响应发送到app的推送通知
- 响应app生命周期事件,比如进入后台,唤醒和退出应用
使用Storyboard启动的app, app delegate都是千篇一律的。因为它只返回true 就像下面这个方法:
-
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
-
-
-
一个简单的使用Xib的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()
-
-
-
在上面的代码中,我们创建了一个控制器, 并且把他放在导航栏控制器中,把他分配为给 UIWindow对象的rootController属性。 这个window对象是 app delegate的属性, 是我们app拥有的一个window.
app的window是iOS中一个非常重要的概念, 一般一个window就是一个app,而且大部分iOS 应用只有一个window, 它就是你应用 UI界面的可视, 用户点击事件, 提供后台显示内容。 当然这里的window和微软的Windows不是一个东西, 是不同的概念(谢谢你 Xerox!)。
好现在我们把重点放在scene delegate。
SceneDelegate
在iOS 13及以上 scene delegate 将扮演 一些 app delegate 的作用, 大多数情况窗口(window)的概念被场景(scene)替换。 一个应用可以有多个场景, 而场景又是作为应用的界面和内容的呈放。
一个app有多个场景的概念来说是特别地, 而且这样就允许您创建多窗口的iOS或者iPadOS应用。在一个 文本处理app中,每一个文档都可以有自己的场景。例如, 用户可以创建一个场景的复制场景, 一次可以运行一个app的多实例。
在Xcode 11中跳转使用scene delegate有三个地方:
- 新建一个项目,会自动创建SceneDelegate 类, 它包括 生命周期事件,比如 动作, 注册, 连接等
- AppDelegate中有两个关联scene sessions的方法,叫做 application(_:configurationForConnecting: options:) 和 application(_: didDiscardSceneSessions:)
- 在 Info.plist文件中的属性列表中有一个Application Scene Manifests(应用场景清单)属性(如下图),可以进行相应配置, 比如类、代理、 storyboard名字等
好,让我们一步一步操作。
1. 场景代理类(Scene Delegate Class)
SceneDelegate类如:
-
-
-
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
-
-
-
-
-
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
-
-
-
-
guard let _ = (scene as? UIWindowScene) else { return }
-
-
-
func sceneDidDisconnect(_ scene: UIScene) {
-
-
-
-
-
-
-
func sceneDidBecomeActive(_ scene: UIScene) {
-
-
-
-
-
func sceneWillResignActive(_ scene: UIScene) {
-
-
-
-
-
func sceneWillEnterForeground(_ scene: UIScene) {
-
-
-
-
-
func sceneDidEnterBackground(_ scene: UIScene) {
-
-
-
-
-
-
-
场景代理中*重要的方法是 scene(_:willConnectTo:options:), 一般来说它等同于iOS 13之前的 application(_: didFinishLaunchingWithOptions:)方法, 场景被添加到app,这个方法将被调用, 这是配置场景完美的地方。 在上面的代码中, 我们可以设置controller栈,这个设置我们等会儿再说
重点注意SceneDelegate在使用delegate时,当然 一个delegate 也可以响应任何场景,如果你想使用一个代理配置所有场景的话。
SceneDelegate也包含下面这些方法:
- sceneDidDisConnect(_:) 场景没有连接时调用(等一会儿他会再连接)
- sceneDidBecomeActive(_:) 当用户开始进入场景时调用,比如从app switcher(应用切换)选中app
- scenewillResignActive(_:) 当用户停止进入场景,比如进入另一个场景
- sceneWillEnterForeground(_:) 当用户将要进入前台,比如从后台开始或者唤醒
- sceneDidEnterBackground(_:) 当场景将要进入后台,比如 app *小化,但是依然在后台运行
2. 应用代理:场景会话(AppDelegate : Scene Sessions)
在iOS13中, Appdelegate类现在包含了与secene sessions 相关的方法。 创建一个场景的同是 场景对象也会随之创建,场景会话对象跟踪管理场景的相关信息, 方法如下:
- application(_: configurationForConnecting: optings:) 这个是返回场景配置对象的。
- application(_:didDiscardSceneSessions:) 你的app用户关闭一个或者多个场景时调用。
现在, 场景会话用于指定一个场景, 比如 “External Display” 或者 “CardPlay”, 也被用来存储一个场景的状态, 用于状态恢复非常好用。 状态恢复允许你恢复或者再次创建UI在app启动期间。你也可以分配用户信息到场景会话中, 这是一个你可以放入任何能放入的字典。
application(_:didDiscardSceneSession:) 是非常简单的, 当用户通过应用切换来关闭一个或者多个场景时会调用。你可以在这里释放一些你场景使用的资源,因为他们不在需要使用了。
对比与 sceneDidDisconnect(_:) , 这个方法标识当场景仅仅失去连接,但是不一定丢弃。 它可能会尝试重连, 直到application(_:didDiscardSceneSession:) 使用应用切换标记场景退出。
3.Info.plist中的应用场景清单(Application Scene Manifest)
你使用的每一个场景都需要在应用场景清单中声明过, 简而言之就是清单列出你应用支持的场景。 大多是app只有一个场景,但是你能创建更多个,比如响应通知或者事件的单独场景。
应用场景清单也是Info.plist文件中的一部分,Info.plist也是一个明确知道应用配置的好地方。该文件属性列表包含应用名字,版本号,支持设备方向等
注意在这里声明session的类型, 而不是session本身。 你的app能支持一个场景, 创建一个场景的副本和使用它来创建多窗口app.
以下是Info.plist的列表大概:
在顶层,你可以看见应用场景清单子项,下面时Enable Multiple Windows ,如果你需要支持多窗口,需要设置为YES. 再往下是一个声明应用内场景的Applicaiton Session Role数组。 另一个部分能用于声明外部场景。
*重要的信息是Application Session Role 数组部分, 包括:
- Configeration Name 配置的名字,必备的
- 场景的类名, UIWindowScene
- Delegate Class Name 场景代理类名,一般是SceneDelegate
- 场景初始化storyboard的名字
storyboard的名字部分主要提醒用户主Storyboard,以便于在Xcode 12 的项目属性配置中看到。 一般基本iOS 应用中, 如果你不使用场景这就是你设置或改变主要Storyboard的地方【译者发现,如果需要改变主显示Storyboard,只能在这里修改,直接在项目的General -> Deployment Info -> Main Interface中修改是没有效果的。 】。
如何用SceneDelegate、 AppDelegate中的scene session 应用场景清单怎么创建多窗口app?
- 首先,我们已经了解过Scenedelegate类, 它管理场景的声明周期,响应事件(比如sceneDidBecomeActive(_:)和 sceneDidEnterBackground(_:))
- 之后,我们检查Appdelegate的新方法,它管理场景会话,提供场景配置数据, 用户丢弃场景响应
- *后,我们查看应用场景清单(Application Scene Manifest),它罗列出你应用支持的场景, 代理类和初始化storyboard
太棒了!有了初步了解,我们看看用Xcode11创建UI,看看是如何影响的。
基于SwiftUI的Scene Delegate
iOS13*简单创建项目方法是使用SwiftUI, 简单来说 SwfitUI创建的项目大多是都是通过SceneDelegate设置初始化UI的
首先看看SwiftUI的应用场景清单长什么样子:
这是非常标准的,对于默认app,哪里标准了? 使用Default Configuration标识不包含的Storyboard Name Set 。记住, 如果你想支持多窗口,需要吧Enable Multiple Windows 设置为 YES.
我们跳过AppDelegate, 因为它非常标准,只返回true.
下一步, 看看 SceneDelegate类, 在我们修改之前,场景代理设置了你应用的场景响应。和设置她们的初始化视图。
就像这样:
-
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
-
-
-
-
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)
-
-
window.makeKeyAndVisible()
-
-
上面的代码做了什么了?
首先,认真考虑scene(_:willConnetectTo:options:)协议方法(当一个新场景加入),提供scene对象和session, 这个UIWindowScene对象是app创建的,如此一来你就不需要手动创建。
之后,这个window属性被使用,应用仍然有UIWindow的window对象,只不过现在他们是场景的一部分。在代码中,在if let 闭包内,你可以清楚的看见使用scene参数初始化的UIWindow实例。
设置window的rootViewController属性,然后这个window变为可见(make key add visible),目的是把它放在UI层的*前面。
使用SwiftUI,你要注意,被创建的ContentView作为root view controller 通过使用UIHostingController, 这个控制器把SwiftUI基本视图放在屏幕上。
方法中有一点注意,类型为UIScene的scene参数,实际上UIWindowScene类型的实例。使用as 可选解析(到目前为止,创建的场景通常是UIWindowScene类型,但我猜想在未来,我们会看到更多类型的场景。)
所有的看起来很复杂,但是总体来看非常简单。
- scene delegate 中配置场景,合适的时间,就是当scene(_:willConnectTo:options:) 被调用时。
- app delegate 和Manifest ,有默认的配置,不需要引入storyboard
- scene(_:willConnectTo:options:)方法中创建SwiftUI的视图, 放在hosting controller中,分配它为window属性的root view controller,把他们设置为UI层*前端就好了。
非常棒!让我们开始
你可以使用Xcode11创建基本项目, 选择SwfitUI, 通过选择File -> New -> Project, 之后选择 Single View App, *后选择 SwiftUI作为User Interface
基于Storyboard的SceneDelegate
Storyboards, XIBs, 是创建UI的有效的方式。 但是在iOS 13上是一样的。在以后,我们会看到越来越多SwiftUI 应用, 但是现在, storyboard更常用。
有趣的是,你无法多余操作,只需要选择File-> New -> Project, 选择Single View App, *后选择 Storyboard作为User Interface , 就完成了。
下面使步骤:
- 就像之前所述,在Info.plist,你发现Main storyboard 在 Application Scene Manifest的字典中
- 默认app delegate将使用默认的场景配置
- 默认scene delegate 设置 UIWindow对像,使用Main.storyboard创建初始化UI
设置你应用的编程方式
很开发者使用代码创建UI, 随着SwiftUI的崛起, 我们能看到更多。 那如果你不是用Storyboard,而是使用xib来创建你得app UI , 来看看场景代理如何适配它吧。
首先, app delegate 和 Applicaton Scene Manifest 使完整的, 默认设置的。 我们没有使用storyboard,我们要在 SceneDelegate 的 scene(_:willConnectTo: options:)方法内设置初始化控制器。
就像这样:
-
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
-
-
-
-
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
-
-
-
window.makeKeyAndVisible()
-
-
我们来看看发生了啥:
- 就像之前,我们持有UIWindow类型的window属性,它是用windowScene对象初始化的,该对象是由scene参数进行类型转换的
- 在 if le闭包内, 就像上面的代码, 这就是在iOS 12及之前设置根控制器的方式在AppDelegate, 你厨师化一个view controller, 把他放在导航栏控制器里, 设置给rootViewController
- *后,window常量分配给window属性, 然后让它可见,把他放在屏幕*前面。
很简单,对吧? *核心的是,把你之前在app delegate中代码移到scene delegate ,配置Applicaton Scene Manifest
还在找怎么为已存在的项目添加场景支持吗? 看这个吧
深入学习
完美了!这只是示例的一小部分, 随着我们的深入,场景代理允许你添加多窗口应用
你可能已经学会怎样为SwiftUI、Storyboard设置场景代理, 我们也看了使场景工作的三部分:app delegate , scene delegate , Application Scene Manifest , 非常棒!