Scene: iOS13之后的UIKit生命周期

2023-01-10  本文已影响0人  Trigger_o

现在iOS16已经发布一段时间,各大应用也都舍弃了对iOS12的兼容,iOS13对响应UI实例生命周期的api做了很大调整,
主要来说就是就是两方面,一方面在单场景的应用中,把App生命周期和UI生命周期分离开了;
另一方面可以在iPad OS中支持多场景,一个应用可以在屏幕上展现多个窗口,进行多任务,每个场景有独立的生命周期.

响应生命周期事件

对开发者来说生命周期是系统响应用户操作(也可能是系统行为),然后再通知应用程序,这种情况下某些场景也会通知应用.
开发者需要处理的是对生命周期事件的响应Respond to life-cycle events.

在iOS12及以前,叫做Respond to app-based life-cycle events,响应基于应用的生命周期事件.
使用UIApplicationDelegate来响应.
启动后,系统将应用置于非活动或后台状态,这取决于UI是否即将出现在屏幕上。当启动到前台时,系统自动将应用程序转换为活动状态。在此之后,状态在活动和后台之间波动,直到应用程序终止。
这种模式下场景即App,或者说窗口即App,应用的生命周期就是场景的生命周期,应用在活跃,窗口也必须是活跃的.

响应基于应用的生命周期事件

在iOS13及以后,叫做Respond to scene-based life-cycle events,响应基于场景的生命周期事件.
使用UISceneDelegate来响应.
UIKit会为每个场景提供独立的生命周期事件。一个场景代表了你的应用UI在设备上运行的一个实例。用户可以为每个应用程序创建多个场景,并分别显示和隐藏它们。因为每个场景都有自己的生命周期,所以每个场景都可以处于不同的执行状态。例如,一个场景可能在前景,而其他场景在背景或暂停。
当用户或系统为你的应用程序请求一个新场景时,UIKit会创建它并将其置于unattached状态,请求的场景会迅速出现在前台屏幕上。系统请求的场景通常会移到后台以便处理事件。例如,系统可能会在后台启动场景来处理位置事件。当用户操作进入后台, UIKit移动相关的场景到后台状态,最终到暂停状态。UIKit可以在任何时候断开后台场景或暂停场景来回收其资源,将该场景返回到unattached状态。
这种模式下生命周期不再是应用程序的生命周期,而是场景的生命周期,近似来说是窗口的生命周期,而应用的生命周期事件仍然由UIApplicationDelegate响应.

响应基于场景的生命周期事件

其他事件的响应

在启用场景的App中,应用程序的启动以及生命周期还是需要UIApplicationDelegate来响应的,也就是didFinishLaunchingWithOptions等.
除了生命周期,原本UIApplicationDelegate响应的其他事件现在也没有变化,仍然需要在UIApplicationDelegate中处理.
比如Open Urls; 内存警告; 切换任务等.
另外,场景的实例,场景会话UISceneSession的生命周期也是由appdelegate响应.后面细说.

版本兼容
如果支持iOS12及以下,并且也使用了SceneDelegate,那么UIKit与窗口生命周期相关的事件,在iOS12及以下只响应AppDelegate,在iOS13及以上只响应SceneDelegate.

配置场景

通常场景由UIKit创建,但是开发者可以配置场景.当用户请求一个新的场景时,UIKit创建相应的场景对象并处理它的初始设置,为了做到这一点,UIKit依赖于你提供的信息,也就是在info.plist中配置.

在info中添加Application Scene Manifest.


info

场景会话和场景对象

场景会话UISceneSession,包含场景的配置UISceneConfiguration对象和场景的唯一标识符等.它的实例只能由UIKit创建.
UIScene的初始化init(session:connectionOptions:),需要一个UISceneSession对象.
因此是这样一个流程:

如果在info.plist中配置了Scene,那么application(_:configurationForConnecting:options:)不会执行,如果没有配置,需要在这个方法中创建并返回UISceneConfiguration实例.
另外如果在info.plist中配置了Scene,也不需要主动初始化UIScene(或者UIWindowScene),在scene(_:willConnectTo:options:)中就可以获取到UIScene实例(可以转换为UIWindowScene).

和UIApplicationDelegate一样,UIWindowSceneDelegate也有一个window属性;
如果在info.plist的Application Scene Manifest中指定了Storyboard,UIKit会自动初始化Window.
否则需要在scene(_:willConnectTo:options:)中手动初始化.
另外和UIApplicationDelegate一样,UIWindowSceneDelegate也需要继承UIResponder.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow.init(windowScene: windowScene)
        window?.rootViewController = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController")
        window?.makeKeyAndVisible()
    }

场景的生命周期

UIApplicationDelegate负责响应生成一个场景的请求以及销毁一个场景的通知

func application(_ application: UIApplication,
                 configurationForConnecting connectingSceneSession: UISceneSession,
                 options: UIScene.ConnectionOptions) -> UISceneConfiguration {
    // It's important that each UISceneConfiguration have a unique configuration name.
    var configurationName: String!

    switch options.userActivities.first?.activityType {
    case UserActivity.GalleryOpenInspectorActivityType:
        configurationName = "Inspector Configuration" // Create a photo inspector window scene.
    default:
        configurationName = "Default Configuration" // Create a default gallery window scene.
    }
    
    return UISceneConfiguration(name: configurationName, sessionRole: connectingSceneSession.role)
}

UISceneDelegate负责响应场景在生命周期内的事件

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        print("SceneDelegate willConnectTo")

        guard let winScene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: winScene)
        if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
            //恢复
        } else {
            //初始化
            let vc = ViewController()
            let nc = UINavigationController(rootViewController: vc)
            nc.restorationIdentifier = "RootNC"

            self.window?.rootViewController = nc
            window?.makeKeyAndVisible()
        }
    }

UIWindowScene

获取window
场景不一定有窗口,所以UIScene没有windows的属性,子类UIWindowScene才有,并且可以有多个window.
UIScene的delegate是UISceneDelegate类型,UIWindowSceneDelegate是继承自UISceneDelegate的.
因此使用UIWindowScene的时候可以把delegate转换成UIWindowSceneDelegate类型.

UIWindowSceneDelegate有一个window属性,它是main window.

optional var window: UIWindow? { get set }

过去给UIApplicationDelegate的window赋值,而现在给UIWindowSceneDelegate赋值,所以AppDelegate.window是nil了,
并且UIApplication的keyWindow属性也废弃了.
因此获取window变成了获取WindowScene -> 获取Delegate -> 获取window
获取windowScene使用UIApplication的connectedScenes属性,它是一个集合.

如果是多场景的话.可以通过场景获取到会话session,再从session.persistentIdentifier或者session.configuration.name来区分

for scene in UIApplication.shared.connectedScenes{
            let session = scene.session
            if let name = session.configuration.name{
                print(name)
            }
}

如果是单场景的情况.main window就是windows中的唯一元素.输出三个window是同一个实例.


        if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
           let firstWindow = windowScene.windows.first{
            if let delegate = windowScene.delegate as? UIWindowSceneDelegate,
               let window = delegate.window as? UIWindow{
                print(firstWindow)
                print(window)
            }
            if let key = windowScene.keyWindow{
                print(key)
            }
        }
       
    

输出结果

<UIWindow: 0x14cf06d70; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x60000009cb10>; layer = <UIWindowLayer: 0x60000009ccf0>>
<UIWindow: 0x14cf06d70; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x60000009cb10>; layer = <UIWindowLayer: 0x60000009ccf0>>
<UIWindow: 0x14cf06d70; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x60000009cb10>; layer = <UIWindowLayer: 0x60000009ccf0>>

UIScene和UISceneSession互相持有,UIWindow会弱引用它的windowScene,都可以互相获取

//UISceneSession
open var scene: UIScene? { get }

//UIScene
open var session: UISceneSession { get }

//UIWindow
weak open var windowScene: UIWindowScene?

//UIWindowScene
open var windows: [UIWindow] { get }

UIScreen.main废弃了,UIWindow的screen属性也废弃了.现在需要通过windowScene来获取Screen

if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene{
      print(windowScene.screen.bounds)
}
上一篇下一篇

猜你喜欢

热点阅读