博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS 13 Scene Delegate and multiple windows
阅读量:4296 次
发布时间:2019-05-27

本文共 7081 字,大约阅读时间需要 23 分钟。

iOS 13 Scene Delegate and multiple windows

iOS 13的一大改进就是支持multiple windows(多窗口)功能,虽然多窗口仅在iPadOS上获得支持,但这已经是一个很大到的进步,它将会大大提升一些场景的使用体验。本文将结合WWDC 2019相关topic介绍与多窗口相关的内容。

Scene Delegate

为了实现多窗口功能,苹果修改了使用多年的AppDelegate。在Xcode 11中新建工程时,会发现工程文件中包含AppDelegate.swift,SceneDelegate.swift。多出来的SceneDelegate.swift就是本文的基础内容。

回忆一下在iOS 13之前我们是怎么使用AppDelegate的?

在didFinishLaunchingWithOptions启动入口方法中,进行初始化数据库,启动必要的服务,注册通知等基础工作,然后创建UIWindow,执行UIWindow的makeKeyAndVisible方法,即将页面显示在屏幕上。

除了didFinishLaunchingWithOptions启动入口方法,UIApplicationDelegate还提供有一系列的方法以供管理APP的生命周期:

func applicationWillResignActive(_ application: UIApplication) {    //即将变为非活动状态}func applicationDidEnterBackground(_ application: UIApplication) {    //已进入后台}func applicationWillEnterForeground(_ application: UIApplication) {    //即将进入前台}func applicationDidBecomeActive(_ application: UIApplication) {    //已进入前台,成为活跃进程}func applicationWillTerminate(_ application: UIApplication) {    //进程终止}

从iOS 13开始,这几个从iOS 2.0起沿用至今的方法将有所变化:原有的UIApplicationDelegate UI生命周期的几个方法将拆分到UISceneDelegate中:

// 新建窗口时调用func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)// 通过app switcher关闭该窗口时调用,至此窗口生命结束。func sceneDidDisconnect(_ scene: UIScene)func sceneDidBecomeActive(_ scene: UIScene)func sceneWillResignActive(_ scene: UIScene)func sceneWillEnterForeground(_ scene: UIScene)func sceneDidEnterBackground(_ scene: UIScene)

相应的AppDelegate中新增对scene的支持:

//新建场景,返回场景配置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
) {}

UIApplicationDelegate提供的applicationWillTerminate接口保持不变,即UIApplicationDelegate不再负责UI的生命周期,仅负责APP的生命周期,包括启动和终止。其他四个方法分别替换为UISceneDelegate提供的sceneWillResignActive等,UISceneDelegate额外新增willConnectTo,sceneDidDisconnect接口以控制场景的新建和关闭。

即iOS 13中引入了scene的概念,每一个scene对应一个UIWindow,UIWindow由UISceneDelegate管理。iOS 13之前一个APP也是可以创建多个UIWindow的,但只能有一个keyWindow。引入scene之后,每一个scene都会有一个keyWindow。

Info.plist:Application Scene Manifest

APP支持的场景需要在Info.plist中声明,由Info.plist->Application Scene Manifest->Scene Configuration->Application Session Role节点指定场景List,每一项包含以下节点:

  • Configuration Name:场景配置的唯一标识;
  • Delegate Class Name:实现UIWindowSceneDelegate代理类的名称;
  • Storyboard Name:Storyboard的名称(如果采用的是Storyboard方式实现UI),可选。

一种类型的场景对应一个Scene Configuration。以iPadOS 13原生APP为例,Safari支持多窗口,但它的每一个窗口都是一样的(一个场景),都是一个web浏览器;而Mail则支持多窗口也有多个窗口类型(邮件列表和写邮件窗口是两个不同的场景)。当然,你完全可以定义两个Configuration Name不同,但页面一样的窗口,从技术层面这样没问题,只是不符合Apple的设计规范。

iOS中适配Scene Delegate

使用Scene Delegate之后,UIApplicationDelegate将不再持有UIWindow,它将转移至UIWindowSceneDelegate代理中。由于iOS目前还不支持多窗口,所以大部分情况下iOS项目仅需要一个场景配置。典型的改造方式如下:

  • didFinishLaunchingWithOptions

AppDelegate中,didFinishLaunchingWithOptions仅负责处理除UI之外的初始化工作:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {    // init database,register notification,and so on.    return true}
  • configurationForConnecting

iOS 13系统将调用configurationForConnecting接口,返回UISceneConfiguration对象,创建scene。

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {    if let activity = options.userActivities.first, activity.activityType == "com.easeapi.scene0" {        return UISceneConfiguration(name: "scene0", sessionRole: connectingSceneSession.role)    }    return UISceneConfiguration(name: "scene1", sessionRole: connectingSceneSession.role)}

UISceneSession是管理scene的实例,一个scene对应一个UISceneSession,UISceneSession包含唯一标识和场景的配置细节。UISceneSession的生命周期由UIKit维护,当scene关闭时UISceneSession销毁。开发者无法直接创建UISceneSession,除了在configurationForConnecting接口由系统传入UISceneSession的实例外,开发者也可以通过UIApplication.shared.requestSceneSessionActivation的方式动态创建新的场景。

UIScene.ConnectionOptions则提供了创建场景时携带的额外信息,以便启动不同的场景配置。

  • willConnectTo

接着系统将在场景配置list中找到Configuration Name(scene0)指定的Delegate Class Name(Scene0Delegate),控制权交由Scene Delegate,将执行willConnectTo方法,在这个方法中完成创建UIWindow的操作。

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {    if let windowScene = scene as? UIWindowScene {        let window = UIWindow(windowScene: windowScene)        window.rootViewController = ViewController()        window.backgroundColor = .white        self.window = window        window.makeKeyAndVisible()    }}

之后,页面进入前后台的事件全由Scene Delegate接管。在上述示例中,configurationForConnecting方法中有区分是走scene0场景还是scene1场景,options的参数是调用时传递的:当直接打开APP时,options中数据为空;通过UIApplication.shared.requestSceneSessionActivation方式切换场景时,options将包含携带的用户数据。

iOS各个版本的适配

由于Scene Delegate仅在iOS 13获得支持,iOS 13之前的系统还是需要走UIApplicationDelegate相关的方法。两个方案可供适配:

  • 不支持Scene Delegate:直接删除Info.plist的Application Scene Manifest节点,回到使用UIApplicationDelegate的方式;
  • 支持Scene Delegate:通过#available(iOS 13.0, *)等方式区分版本,iOS 13之前系统走原始的UIApplicationDelegate,iOS 13之后走Scene Delegate。

iPadOS 多窗口

自iOS 13开始,苹果推出了iPad专用的iPadOS系统,表明了苹果将iPad打造成生产力工具的野心。期待已久的multiple windows功能终于在iPadOS 13上得以实现。

上面讲到的iOS适配Scene Delegate是非常简单的,就是将UIWindow的创建及UI生命周期管理放置在Scene Delegate中,其它的比如页面栈管理等和之前完全一样。Scene Delegate的更大的用途是用来支持iPadOS的多窗口。

想要iPad App获得多窗口的能力非常简单,在Xcode项目中勾选“Supports multiple windows”即可,运行打开APP->选中APP长按->Show All Windows->点击屏幕右上角+号,即可新建一个窗口。

就是这么简单,你甚至不需要添加任何代码就可实现多窗口的能力。但我们的需求肯定不止于此,我们还需要在代码中控制窗口的新建,关闭以及用户数据交互。苹果提供了简洁的接口方便我们编程控制。

新建scene

let activity = NSUserActivity(activityType: "com.easeapi.scene0")activity.userInfo = ["website": "http://easeapi.com/blog"]UIApplication.shared.requestSceneSessionActivation(nil, userActivity: activity, options: nil, errorHandler: nil)

为了达到各个场景之间切换时状态保留的目的,用户数据的传递就变得非常关键。苹果使用NSUserActivity(iOS 8加入的,很多地方都用到了)来存储用户数据。NSUserActivity亦可从UISceneSession的stateRestorationActivity方法获取,典型的使用场景如下:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {    let vc: ViewController    if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity,    let identifier = activity.targetContentIdentifier {        vc = ViewController(catName: identifier)    } else {        vc = ViewController(catName: "default")    }    if let windowScene = scene as? UIWindowScene {        let window = UIWindow(windowScene: windowScene)        window.rootViewController = vc        window.backgroundColor = .white        self.window = window        window.makeKeyAndVisible()    }}

这样就可以在载入场景时获得暂存的用户数据,以便UI平滑过渡。关于NSUserActivity的内容可以参考文档。

关闭scene

if let session = self.view.window?.windowScene?.session {    let options = UIWindowSceneDestructionRequestOptions()    options.windowDismissalAnimation = .commit    UIApplication.shared.requestSceneSessionDestruction(session, options: options, errorHandler: nil)}

上面讲解了Scene Delegate和multiple windows的一般知识,更酷的高级用法比如拖拽多窗口交互等后续有时间精力在研究。

其他文章

转载请注明原文出处:

你可能感兴趣的文章
Laravel框架学习笔记之任务调度(定时任务)
查看>>
laravel 定时任务秒级执行
查看>>
浅析 Laravel 官方文档推荐的 Nginx 配置
查看>>
Swagger在Laravel项目中的使用
查看>>
Laravel 的生命周期
查看>>
CentOS Docker 安装
查看>>
Nginx
查看>>
Navicat远程连接云主机数据库
查看>>
Nginx配置文件nginx.conf中文详解(总结)
查看>>
Mysql出现Table 'performance_schema.session_status' doesn't exist
查看>>
MySQL innert join、left join、right join等理解
查看>>
vivado模块封装ip/edf
查看>>
sdc时序约束
查看>>
Xilinx Jtag Access/svf文件/BSCANE2
查看>>
NoC片上网络
查看>>
开源SoC整理
查看>>
【2020-3-21】Mac安装Homebrew慢,解决办法
查看>>
influxdb 命令行输出时间为 yyyy-MM-dd HH:mm:ss(年月日时分秒)的方法
查看>>
已知子网掩码,确定ip地址范围
查看>>
判断时间或者数字是否连续
查看>>