1、導(dǎo)讀
iOS13 項(xiàng)目中的SceneDelegate類有什么作用?自從Xcode11發(fā)布以來,當(dāng)你使用新XCode創(chuàng)建一個新的iOS項(xiàng)目時,SceneDelegate會被默認(rèn)創(chuàng)建,它到底有什么用呢。
在本文中,我們將深入探討iOS 13和Xcode 11的一些變化。我們將重點(diǎn)關(guān)注SceneDelegate和AppDelegate,以及它們?nèi)绾斡绊慡wiftUI、Storyboard和基于XIB的UI項(xiàng)目。
通過閱讀本文你將了解到:
- SceneDelegate和AppDelegate的新變化
- 他們是如何合作引導(dǎo)你的app啟動的
- 在純手寫App中使用SceneDelegate
- 在Storyboards 和 SwiftUI項(xiàng)目中使用SceneDelegate
- 不是用SceneDelegate的話怎么處理
2、AppDelegate回顧
你可能對AppDelegate已經(jīng)熟悉,他是iOS app的入口,{application(_:didFinishLaunchingWithOptions:)}
是你的app啟動后系統(tǒng)調(diào)用的第一個函數(shù)。
AppDelegate類實(shí)現(xiàn)了UIKit庫中的UIApplicationDelegate 協(xié)議。而到了iOS13 AppDelegate的角色將會發(fā)生變化,后面我們會詳細(xì)討論。
下面是你在iOS12中一般會在AppDelegate中做的事情:
- 創(chuàng)建app的第一個view controller也就是 rootViewController
- 配置并啟動一些像日志記錄和云服務(wù)之類的組件
- 注冊推送通知處理程序,并響應(yīng)發(fā)送到app的推送通知
- 響應(yīng)應(yīng)用程序生命周期事件,例如進(jìn)入后臺,恢復(fù)應(yīng)用程序或退出應(yīng)用程序(終止)
//iOS12及以前,使用Storyboards的app,AppDelegate很簡單。 像這樣:
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool{
return true
}
//一個使用XIB的簡單應(yīng)用看起來像這樣:
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
}
在上面的代碼中,我們創(chuàng)建一個ViewController,并將其放在navigation controller中。然后將其分配給UIWindow對象的rootViewController屬性。 這個window對象是AppDelegate的屬性,它是我們的應(yīng)用的一個窗口。
應(yīng)用程序的window是一個重要的概念。 本質(zhì)上,窗口就是應(yīng)用程序,大多數(shù)iOS應(yīng)用程序只有一個窗口。 它包含您應(yīng)用的用戶界面(UI),將事件調(diào)度到視圖,并提供了一個主要背景層來顯示您的應(yīng)用內(nèi)容。 從某種意義上說,“ Windows”的概念就是微軟定義的窗口,而在iOS上,這個概念沒有什么不同。
如果“窗口”的概念仍然不了解,請查看iPhone上的應(yīng)用程序切換器。 雙擊Home鍵或從iPhone底部向上滑動,然后您會看到當(dāng)前正在運(yùn)行的應(yīng)用程序的窗口。 這就是應(yīng)用程序切換器。
3、SceneDelegate使用
在iOS 13(及以后版本)上,SceneDelegate將負(fù)責(zé)AppDelegate的某些功能。 最重要的是,window(窗口)的概念已被scene(場景)的概念所代替。 一個應(yīng)用程序可以具有不止一個場景,而一個場景現(xiàn)在可以作為您應(yīng)用程序的用戶界面和內(nèi)容的載體(背景)。
尤其是一個具有多場景的App的概念很有趣,因?yàn)樗鼓梢栽趇OS和iPadOS上構(gòu)建多窗口應(yīng)用程序。 例如,文檔編輯器App中的每個文本文檔都可以有自己的場景。 用戶還可以創(chuàng)建場景的副本,同時運(yùn)行一個應(yīng)用程序的多個實(shí)例(類似多開)。
3.1 SceneDelegate相關(guān)文件介紹
在Xcode 11中有三個地方可以明顯地看到SceneDelegate的身影:
- 現(xiàn)在,一個新的iOS項(xiàng)目會自動創(chuàng)建一個SceneDelegate類,其中包括我們熟悉的生命周期事件,例如active,resign和disconnect。
- AppDelegate類中多了兩個與“scene sessions”相關(guān)的新方法:
application(_:configurationForConnecting:options:)和application(_:didDiscardSceneSessions:) - Info.plist文件中提供了”Application Scene Manifest“配置項(xiàng),用于配置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的最重要的函數(shù)是:`scene(_:willConnectTo:options:)`。
// 在某種程度上,它與iOS 12上的 `application(_:didFinishLaunchingWithOptions:)` 函數(shù)的作用最相似。
// 當(dāng)將場景添加到app中時`scene(_:willConnectTo:options:)`函數(shù)會被調(diào)用的,因此這里是配置場景的最理想地方。
//2、這里需要特別注意的是,“SceneDelegate”采用了協(xié)議模式,并且這個delegate通常會響應(yīng)任何場景。
// 使用一個Delegate來配置App中的所有場景。
//手動地設(shè)置視圖控制器堆棧
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) {
// 當(dāng)場景與app斷開連接是調(diào)用(注意,以后它可能被重新連接)
}
func sceneDidBecomeActive(_ scene: UIScene) {
// 當(dāng)用戶開始與場景進(jìn)行交互(例如從應(yīng)用切換器中選擇場景)時,會調(diào)用
}
func sceneWillResignActive(_ scene: UIScene) {
// 當(dāng)用戶停止與場景交互(例如通過切換器切換到另一個場景)時調(diào)用
}
func sceneWillEnterForeground(_ scene: UIScene) {
// 當(dāng)場景變成活動窗口時調(diào)用,即從后臺狀態(tài)變成開始或恢復(fù)狀態(tài)
}
func sceneDidEnterBackground(_ scene: UIScene) {
// 當(dāng)場景進(jìn)入后臺時調(diào)用,即該應(yīng)用已最小化但仍存活在后臺中
}
}
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的代理函數(shù)。
//在您的應(yīng)用創(chuàng)建scene(場景)后,“scene session”對象將跟蹤與該場景相關(guān)的所有信息。
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// 會返回一個創(chuàng)建場景時需要的UISceneConfiguration對象
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// 當(dāng)用戶通過“應(yīng)用切換器”關(guān)閉一個或多個場景時會被調(diào)用
// 您可以在該函數(shù)中銷毀場景所使用的資源,因?yàn)椴粫傩枰鼈儭? // 了解application(_:didDiscardSceneSessions:)與sceneDidDisconnect(_ :)的區(qū)別很重要,后者僅在場景斷開連接時調(diào)用,不會被丟棄,它可能會重新連接。
// 而application(_:didDiscardSceneSessions:)發(fā)生在使用【應(yīng)用程序切換器】退出場景時。
}
}
目前,SceneSession被用于指定場景,例如“外部顯示” 或“ CarPlay” 。 它還可用于還原場景的狀態(tài),如果您想使用【狀態(tài)還原】,SceneSession將非常有用。 狀態(tài)還原允許您在應(yīng)用啟動之間保留并重新創(chuàng)建UI。 您還可以將用戶信息存儲到場景會話中,它是一個可以放入任何內(nèi)容的字典。
3.1.3 Info.plist 中的Application Scene Manifest
您的應(yīng)用支持的每個場景都需要在“Application Scene Manifest”(應(yīng)用場景清單)中聲明。 簡而言之,清單列出了您的應(yīng)用支持的每個場景。 大多數(shù)應(yīng)用程序只有一個場景,但是您可以創(chuàng)建更多場景,例如用于響應(yīng)推送通知或特定操作的特定場景。
Application Scene Manifest清單是Info.plist文件的一項(xiàng),都知道該文件包含App的配置信息。 Info.plist包含諸如App的名稱,版本,支持的設(shè)備方向以及現(xiàn)在支持的不同場景等配置。
請務(wù)必注意,您聲明的是會話的“類型”,而不是會話實(shí)例。 您的應(yīng)用程序可以支持一個場景,然后創(chuàng)建該場景的副本,來實(shí)現(xiàn)【多窗口】應(yīng)用程序。

在上圖中,您會看到Application Scene Manifest 這一條。 在它下面一條是Enable Multiple Windows,需要將其設(shè)置為“ YES”以支持多個窗口。 再往下Application Session Role的值是一個數(shù)組,用于在應(yīng)用程序中聲明場景。 你也可以在數(shù)組中添加一條【外部屏幕】的場景聲明。
最重要的信息保存在Application Session Role數(shù)組中。 從中我們可以看到以下內(nèi)容:
- Configuration的名稱,必須是唯一的
- 場景的代理類名,通常為SceneDelegate。
- 場景用于創(chuàng)建初始UI的storyboard名稱
Storyboard名稱這一項(xiàng)可能使您想起Main Interface設(shè)置,該設(shè)置可以在Xcode 12項(xiàng)目的Project Properties配置中找到。 現(xiàn)在,在iOS應(yīng)用中,你可以在此處設(shè)置或更改主Storyboard名稱。
AppDelegate中的SceneDelegate、UISceneSession和Application Scene Manifest是如何一起創(chuàng)建多窗口應(yīng)用的呢?
- 首先,我們看SceneDelegate類。 它管理場景的生命周期,處理各種響應(yīng),諸如
sceneDidBecomeActive(_:)和sceneDidEnterBackground(_:)之類的事件。 - 然后,我們再看看AppDelegate類中的新函數(shù)。 它管理場景會話(scene sessions),提供場景的配置數(shù)據(jù),并響應(yīng)用戶丟棄場景的事件。
- 最后,我們看了一下Application Scene Manifest。 它列出了您的應(yīng)用程序支持的場景,并將它們連接到delegate類并初始化storyboard。
3.2 在SwiftUI中使用Scene Delegate
不久將來,SwiftUI將是創(chuàng)建iOS項(xiàng)目最簡單的方法。 簡言之,SwiftUI應(yīng)用程序主要依靠SceneDelegate來設(shè)置應(yīng)用程序的初始UI。
3.2.1 配置
- Application Scene Manifest
~ 特別注意,配置中沒有設(shè)置“Storyboard Name”這一項(xiàng)。 請記住,如果要支持多個窗口,則需要將Enable Multiple Windows設(shè)置為YES ~

AppDelegate
我們將跳過“ AppDelegate”,因?yàn)樗喈?dāng)標(biāo)準(zhǔn)。在SwiftUI項(xiàng)目中,只會返回“true”。SceneDelegate
正如我們之前討論的,SceneDelegate負(fù)責(zé)設(shè)置您應(yīng)用中的場景,以及設(shè)置首個頁面。
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()
}
}
}
上面的代碼中發(fā)生了什么?
1、首先,必須明確的是 在將新場景添加到應(yīng)用中后 會調(diào)用 'scene(_:willConnectTo:options:) '代理函數(shù)。
它提供了一個'scene'對象和一個'session'。 這個'UIWindowScene'對象是由應(yīng)用創(chuàng)建的,您無需進(jìn)行其他操作。
2、其次,'window'屬性會在這里用到。 App仍然使用'UIWindow'對象,但現(xiàn)在它們已成為'scene'(場景)的一部分。
在'if let'代碼塊中,您可以清楚地看到如何使用'scene'來初始化'UIWindow'對象的。
3、然后是設(shè)置'window'的'rootViewController',將'window'實(shí)例分配給了場景的'window'屬性,并且設(shè)置窗口'makeKeyAndVisible'為true,即將該窗口置于App的前面。
接著為SwiftUI項(xiàng)目創(chuàng)建了'ContentView'實(shí)例,并通過使用'UIHostingController'將其添加為根視圖控制器。
該控制器用于將基于SwiftUI的視圖顯示在屏幕上。
4、最后但并非不重要的一點(diǎn),值得注意的是,'UIScene'的實(shí)例化對象'scene'實(shí)際上是'UIWindowScene'類型的對象。這就是'as?'對可選類型轉(zhuǎn)換的原因。
所有這些看起來似乎很復(fù)雜,但是從高層次的概述來看,這很簡單:
當(dāng)'scene(_:willConnectTo:options:)'被調(diào)用時,'SceneDelegate'會在正確的時間配置場景。
'AppDelegate'和'Manifest'的默認(rèn)配置,他們沒有涉及'storyboard'的任何東西。
'scene(_:willConnectTo:options :)'函數(shù)內(nèi),創(chuàng)建一個SwiftUI視圖,將其放置在托管控制器中,
然后將控制器分配給'window'屬性的根視圖控制器,并將該窗口放置在應(yīng)用程序UI的前面 。
您可以通過選擇File(文件)→New(新建)→Project(項(xiàng)目)來建立一個基本的Xcode 11項(xiàng)目。 然后,選擇Single View App, 在User Interface處選擇SwiftUI來創(chuàng)建一個SwiftUI項(xiàng)目
3.3 在Storyboards項(xiàng)目中使用SceneDelegate
Storyboards和XIB是為iOS應(yīng)用程序構(gòu)建UI的有效方法。 在iOS 13、14也是如此。 在將來,我們將看到更多的SwiftUI應(yīng)用,但目前,Storyboards更常見。
有趣的是,即使有了SceneDelegate,通過Storyboards創(chuàng)建iOS項(xiàng)目你也不需要做任何額外的事情 只需選擇File → New → Project。 然后,選擇Single View App。 最后,為 User Interface 處選擇 Storyboard ,就完成了。
設(shè)置方法如下:
- 如我們前面提到過的,您可以在Info.plist中的
Application Scene Manifest中找到Main storyboard的設(shè)置地方。 - 默認(rèn)情況下,AppDelegate將使用默認(rèn)的UISceneConfiguration。
- SceneDelegate會設(shè)置一個
UIWindow對象,并使用Main.storyboard來創(chuàng)建初始UI。
3.3 純代碼編寫UI
許多開發(fā)人員喜歡手寫UI,而隨著SwiftUI的興起,使用SwiftUI手寫代碼將會越來越常見。 如果您不使用storyboards,而使用XIB創(chuàng)建應(yīng)用程序UI,該怎么辦?
首先,AppDelegate和Application Scene Manifest中保持默認(rèn)值。
我們不使用storyboard,所以需要在SceneDelegate類的scene(_:willConnectTo:options:)函數(shù)中設(shè)置初始視圖控制器。
然后,刪除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
參考文章: