SceneDelegate 配置和使用

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)用程序。

Info.plist 中的Application Scene Manifest

在上圖中,您會看到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 ~
在SwiftUI中Application Scene Manifest配置
  • 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,該怎么辦?

首先,AppDelegateApplication 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

傳送門:如何不使用SceneDelegate

參考文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容