[iOS開發(fā)]iOS13 Scene Delegate

為了實現(xiàn)iPadOS支持多窗口类溢,Xcode11后創(chuàng)建新工程默認(rèn)會通過 UIScene 創(chuàng)建并管理多個 UIWindow 的應(yīng)用球碉,工程中除了 AppDelegate 外還會有一個 SceneDelegate矢劲。

一邀杏、SceneDelegate介紹

1)驰徊、Window與Scene

iOS13以后义辕,SceneDelegate將負(fù)責(zé)AppDelegate的某些功能虾标。 window(窗口)的概念被window(場景)的概念所代替, 一個scene現(xiàn)在可以作為您應(yīng)用程序的用戶界面和內(nèi)容的載體灌砖。iOS13以前一個應(yīng)用程序可以有不止一個window璧函,同樣現(xiàn)在一個應(yīng)用程序也可以有不止一個scene。

2)基显、SceneDelegate三處新增內(nèi)容

iOS13以后蘸吓,Xcode新建iOS項目中有增加三處新增內(nèi)容:

  • 1> 添加一個新的類SceneDelegate
新增SceneDelegateClass
  • 2> AppDelegate類中新增與scene sessions相關(guān)的新方法:
application(_:configurationForConnecting:options:) 
application(_:didDiscardSceneSessions:)
  • 3> Info.plist文件中新增Application Scene Manifest配置項,用于配置App的scene撩幽,包括它們的scene配置名库继,delegate類名和storyboard

下面分別講解下新增三處內(nèi)容:

二箩艺、SceneDelegate三處新增內(nèi)容詳解

1)、SceneDelegate類

SceneDelegate和AppDelegate中方法名相似, 是任何應(yīng)用程序生命周期都會調(diào)用方法宪萄。

//SceneDelegate.swift 代碼
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    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:)函數(shù) : SceneDelegate的最重要的函數(shù)艺谆,相當(dāng)于iOS 12上的 application(_:didFinishLaunchingWithOptions:) 函數(shù)。當(dāng)將scene添加到app中時scene(_:willConnectTo:options:)函數(shù)會被調(diào)用的拜英,因此在這里對scene進(jìn)行配置静汤。 這里需要特別注意的是,使用一個SceneDelegate來配置App中的所有scene居凶,并且這個delegate通常會響應(yīng)任何scene虫给。在上面的代碼中,我們可以手動地設(shè)置了視圖控制器堆棧侠碧,稍后會進(jìn)行詳細(xì)介紹抹估。

SceneDelegate其他方法:

  • sceneDidDisconnect(_:) 當(dāng)scene與app斷開連接是調(diào)用(注意,以后它可能被重新連接)
  • sceneDidBecomeActive(_:) 當(dāng)用戶開始與scene進(jìn)行交互(例如從應(yīng)用切換器中選擇場景)時弄兜,會調(diào)用
  • sceneWillResignActive(_:) 當(dāng)用戶停止與scene交互(例如通過切換器切換到另一個場景)時調(diào)用
  • sceneWillEnterForeground(_:) 當(dāng)scene變成活動窗口時調(diào)用药蜻,即從后臺狀態(tài)變成開始或恢復(fù)狀態(tài)
  • sceneDidEnterBackground(_:) 當(dāng)scene進(jìn)入后臺時調(diào)用,即該應(yīng)用已最小化但仍存活在后臺中

2)替饿、AppDelegate類新增兩個方法

//AppDelegate.swift 代碼
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        return true
    }
    // MARK: UISceneSession Lifecycle
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    }
}

在iOS13中AppDelegate中新增的兩個函數(shù)是負(fù)責(zé)管理Senen Session的代理函數(shù)谷暮。在應(yīng)用創(chuàng)建scene(場景)后,scene session對象將跟蹤與該scene相關(guān)的所有信息盛垦。

這兩個函數(shù)是:

  • application(_:configurationForConnecting:options:) : 方法會返回一個UISceneConfiguration對象,其中包含場景詳細(xì)信息瓤漏,包括要創(chuàng)建的場景類型腾夯,用于管理場景的代理對象以及包含要顯示的初始視圖控制器的StoryBoard。 如果未實現(xiàn)此方法蔬充,則必須在應(yīng)用程序的Info.plist文件中提供場景配置數(shù)據(jù)蝶俱。
    注意:該代理方法中返回UISceneConfiguration對象的配置名為Default Configuration,則系統(tǒng)就會自動去調(diào)用SceneDelegate這個類饥漫。這樣SceneDelegate和AppDelegate產(chǎn)生了關(guān)聯(lián)榨呆。

  • application(_:didDiscardSceneSessions:) : 在分屏中關(guān)閉其中一個或多個scene時候回調(diào)用,可以在該函數(shù)中銷毀場景所使用的資源庸队。
    該方法與application(_:didDiscardSceneSessions:)的區(qū)別是积蜻,該方法僅在場景斷開連接時調(diào)用,不會被丟棄彻消,它可能會重新連接竿拆。而application(_: didDiscardSceneSessions:)發(fā)生在使用應(yīng)用程序切退出場景時。

3)宾尚、Info.plist 中的Application Scene Manifest

Info.plist文件文件包含App的配置信息丙笋,如App的名稱谢澈,版本,支持的設(shè)備方向御板,現(xiàn)在我們可以通過配置Application Scene Manifest項來支持的不同場景锥忿。大多數(shù)應(yīng)用程序只有一個場景,但是可以通過配置該項創(chuàng)建更多場景怠肋,如用于響應(yīng)推送通知或特定操作的特定場景敬鬓。

  • Enable Multiple Windows: 默認(rèn)為NO,其設(shè)置為YES可以支持多個窗口灶似。

  • Application Session Role: 是一個數(shù)組列林,用于在應(yīng)用程序中聲明場景。 該數(shù)組每個元素是一個字典酪惭,字典中有三個鍵值,分別為
    Configuration Name: 當(dāng)前配置的名字希痴,必須是唯一的;
    Delegate Class Name: 場景的代理類名,將與該Scene代理對象關(guān)聯(lián);
    StoryBoard name: 場景用于創(chuàng)建初始UI的storyboard名稱春感。

  • AppDelegate方法application(_:configurationForConnecting:options:)返回值為UISceneConfiguration實例砌创,上邊三個鍵值分別對應(yīng)UISceneConfiguration三個屬性namedelegateClass鲫懒、storyboard 嫩实。

  • 默認(rèn)在info.plist中進(jìn)行了配置, 不用application(_:configurationForConnecting:options:)方法也沒有關(guān)系窥岩。如果沒有在info.plist配置Application Scene Manifest項就需要實現(xiàn)這個方法并返回一個UISceneConfiguration對象甲献。

那么AppDelegate中的SceneDelegateUISceneSession和Info.plist中的Application Scene Manifest是如何一起創(chuàng)建多窗口應(yīng)用的呢颂翼?

  • 首先晃洒,SceneDelegate類管理場景的生命周期,處理各種響應(yīng)朦乏,如 sceneDidBecomeActive(_:) and sceneDidEnterBackground(_:)之類的事件球及。
  • 然后,AppDelegate類中的新函數(shù)呻疹。 它管理scene sessions(場景會話)吃引,提供場景的配置數(shù)據(jù),并響應(yīng)用戶丟棄場景的事件刽锤。
  • 最后镊尺,Application Scene Manifest 列出了當(dāng)前應(yīng)用程序支持的場景,并將它們連接到delegate類并初始化storyboard姑蓝。

三鹅心、SceneDelegate適配

從iOS13開始AppDelegate不再有window屬性,window屬性被定義在SceneDelegate中纺荧。這是因為iOS13中AppDelegate的職責(zé)發(fā)現(xiàn)了改變:

  • iOS13之前旭愧,AppDelegate的職責(zé)全權(quán)處理App生命周期和UI生命周期颅筋;
  • iOS13之后,AppDelegate的職責(zé)是:
    1输枯、處理 App 生命周期
    2议泵、新的 Scene Session 生命周期
    3、UI的生命周期交給新增的Scene Delegate處理桃熄。

因此先口,iOS13以前創(chuàng)建項目如果不需要多窗口就不需要任何改動,而iOS13以后創(chuàng)建新項目時瞳收,就要做一些適配:

1. 不需要多窗口(multiple windows)

  • 刪除掉info.plist中Application Scene Manifest選項碉京,同時,注釋SceneDelegate文件中所有代碼螟深,SceneDelegate文件刪不刪除都可以谐宙。
  • 注釋 AppDelegate中關(guān)于Scene的代理方法

如果使用純代碼來實現(xiàn)顯示界面,需要在AppDelegate.h中手動添加window屬性界弧,添加以下代碼即可:

class AppDelegate: UIResponder, UIApplicationDelegate {
    //手動添加window屬性
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        self.window = UIWindow(frame:UIScreen.main.bounds)
        self.window!.backgroundColor = UIColor.white
        //設(shè)置root
        let rootVC = UIViewController()
        self.window!.rootViewController = rootVC
        self.window!.makeKeyAndVisible()
        return true
    }

    // MARK: UISceneSession Lifecycle
//    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
//        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
//    }
//    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
//    }
}

2. 支持多窗口適配

iOS 13后新項目中info.plist中的配置項Application Scene Manifest是針對iPad multiple windows功能推出的凡蜻。在保留Application Scene Manifest配置項不予刪除時(其中,項目是否支持多窗口功能是個可勾選項)垢箕,AppDelegate的生命周期方法不再起作用划栓,需要在SceneDelegate中使用UIScene提供的生命周期方法,并且需要針對 iOS 13 在Scene中配置和 iOS 13 以下在AppDelegate中做兩套配置条获。

下面是純代碼實現(xiàn)界面顯示的代碼:
Swift適配代碼步驟:

  • 1)第一步忠荞,SceneDelegate中添加@available(iOS 13, *)
//SceneDelegate.swift
@available(iOS 13, *)  //在類的頭部@available(iOS 13, *)添加即可
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
....
....
}
  • 2)第二步,AppDelegate中聲明window屬性帅掘,didFinishLaunchingWithOptions中添加版本判斷钻洒,AppDelegate中新增兩個方法前添加@available(iOS 13, *)。也可以將這兩個方法添加到AppDelegate分類中,分類前添加@available(iOS 13, *)锄开。
// AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    //手動添加window屬性
    var window: UIWindow?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        if #available(iOS 13, *) {
            
        } else {
            window = UIWindow(frame:UIScreen.main.bounds)
            window!.backgroundColor = UIColor.blue
            //設(shè)置root
            let rootVC = UIViewController()
            window!.rootViewController = rootVC
            window!.makeKeyAndVisible()
        }
        return true
    }
    
    //新增方法添加@available(iOS 13, *)
    @available(iOS 13.0, *)
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
    @available(iOS 13.0, *)
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    }
}
  • 3)第三步,SceneDelegate中初始化UIWindow称诗,并添加根視圖控制器
@available(iOS 13, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)
        let vc = ViewController()
        vc.view.backgroundColor = .red
        let navigation = UINavigationController(rootViewController: vc)
        window.rootViewController = navigation
        window.makeKeyAndVisible()
        self.window = window
    }
...
...
}

OC適配代碼:

// AppDelegate.m中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    if (@available(iOS 13.0, *)) {

    } else {
        self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        [self.window setBackgroundColor:[UIColor whiteColor]];
        
        ViewController *vc = [[ViewController alloc] init];
        UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
        [self.window setRootViewController:nav];
        [self.window makeKeyAndVisible];
    }
    return YES;
}
// SceneDelegate.m中
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    
    //在這里手動創(chuàng)建新的window
    if (@available(iOS 13.0, *)) {
        UIWindowScene *windowScene = (UIWindowScene *)scene;
        self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        [self.window setWindowScene:windowScene];
        [self.window setBackgroundColor:[UIColor whiteColor]];
        
        ViewController *con = [[ViewController alloc] init];
        UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:con];
        [self.window setRootViewController:nav];
        [self.window makeKeyAndVisible];
    }
}
  • 注意:如果不使用storyboard萍悴,需要將配置中的storyboard項刪除
  • 注意2:AppDelegate中的有關(guān)事件循環(huán)的方法,在iOS 13后是不會走的寓免,iOS13以下的才會收到事件回調(diào)的癣诱。iOS13以上會走SceneDelegate對應(yīng)的方法事件循環(huán)方法
func applicationWillResignActive(_ application: UIApplication) { }
...
...

四、SwiftUI中SceneDelegate

SwiftUI創(chuàng)建的iOS 13項目袜香,所以SwiftUI應(yīng)用程序主要依靠SceneDelegate來設(shè)置應(yīng)用程序的初始UI撕予。

SwiftUI項目info.plist文件中Application Scene Manifest項配置如下:

  • 默認(rèn)配置中沒有設(shè)置“Storyboard Name”這一項。但是如果要配置支持多個窗口蜈首,則需要將Enable Multiple Windows設(shè)置為YES实抡。
  • AppDelegate類欠母,和上邊iOS新建項目AppDelegate一樣。
  • SceneDelegate類中實現(xiàn)代碼吆寨,如下
//SceneDelegate.swift
import UIKit
import SwiftUI

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ā)生了什么赏淌?
使用此方法可以有選擇地配置UIWindow窗口并將其附加到提供的UIWindowScene場景。
如果使用storyboard啄清,則window屬性將自動初始化并附加到場景中六水。

  • 首先,添加新場景會調(diào)用scene(_: willConnectTo: options:)方法辣卒。 方法傳入一個scene對象和一個session掷贾,傳入的scene對象是由應(yīng)用程序創(chuàng)建的。

  • 其次荣茫,window屬性會在這里用到想帅。 App仍然使用UIWindow對象,但現(xiàn)在它們已成為scene(場景)的一部分计露。 在if let代碼塊中博脑,使用scene來初始化UIWindow對象。

  • 然后設(shè)置window的rootViewController票罐,將window實例賦值給場景的window屬性叉趣,并且設(shè)置窗口makeKeyAndVisible為true,即將該窗口置于App的前面该押。
    接著為SwiftUI項目創(chuàng)建了ContentView實例疗杉,并通過使用UIHostingController將其添加為根視圖控制器。 該控制器用于將基于SwiftUI的視圖顯示在屏幕上蚕礼。

  • 最后烟具,UIScene的實例化對象scene實際上是UIWindowScene類型的對象。 這就是as?對可選類型轉(zhuǎn)換的原因奠蹬。 (到目前為止朝聋,已創(chuàng)建的場景通常為“ UIWindowScene”類型,但將來可能還會有更多類型的場景囤躁。)

將上邊歸納如下內(nèi)容:

  • 當(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的前面 满哪。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末婿斥,一起剝皮案震驚了整個濱河市劝篷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌受扳,老刑警劉巖携龟,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異勘高,居然都是意外死亡峡蟋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門华望,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕊蝗,“玉大人,你說我怎么就攤上這事赖舟∨钇荩” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵宾抓,是天一觀的道長子漩。 經(jīng)常有香客問我,道長石洗,這世上最難降的妖魔是什么幢泼? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮讲衫,結(jié)果婚禮上缕棵,老公的妹妹穿的比我還像新娘。我一直安慰自己涉兽,他們只是感情好招驴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枷畏,像睡著了一般别厘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拥诡,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天丹允,我揣著相機(jī)與錄音,去河邊找鬼袋倔。 笑死,一個胖子當(dāng)著我的面吹牛折柠,可吹牛的內(nèi)容都是我干的宾娜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼扇售,長吁一口氣:“原來是場噩夢啊……” “哼前塔!你這毒婦竟也來了嚣艇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤华弓,失蹤者是張志新(化名)和其女友劉穎食零,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寂屏,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贰谣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了迁霎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吱抚。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖考廉,靈堂內(nèi)的尸體忽然破棺而出秘豹,到底是詐尸還是另有隱情,我是刑警寧澤昌粤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布既绕,位于F島的核電站,受9級特大地震影響涮坐,放射性物質(zhì)發(fā)生泄漏凄贩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一膊升、第九天 我趴在偏房一處隱蔽的房頂上張望怎炊。 院中可真熱鬧,春花似錦廓译、人聲如沸评肆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓜挽。三九已至,卻和暖如春征绸,著一層夾襖步出監(jiān)牢的瞬間久橙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工管怠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淆衷,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓渤弛,卻偏偏與公主長得像祝拯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345