iPadOS 13开始支持多窗口。什么是多窗口?以系统自带的日历app为例:
日历app可以打开多个窗口。但是多窗口并不是应用多开,虽然我们在多任务切换中可以看到貌似开了两个日历app,但它们只是日历app的两个窗口。
目前只有iPadOS支持多窗口,iOS并不支持多窗口。
多窗口的实现方式
为了实现多窗口,iPadOS 13引入了新的概念:scene(场景),每一个窗口就是一个scene。以前一个app只能存在一个key window,现在每个scene都有自己的key window。
如何使用scene
要使用scene,首先保证系统在iOS 13&iPadOS 13以上,然后对Xcode项目进行如下修改:
- Info.plist文件中添加Application Scene Manifest配置;
- 实现UIApplicationDelegate新增的管理scene生命周期的方法;
- 实现scene代理。
如果使用Xcode 11来创建新项目,会自带以上配置。如果是旧项目,就要手动添加以上配置了。
Application Scene Manifest
首先要在Info.plist文件里面添加Application Scene Manifest配置来让项目使用scene。
如果要让项目不使用scene,就要在Info.plist文件中删除Application Scene Manifest配置
Application Scene Manifest是一个字典,他有Enable Multiple Windows和Scene Configuration两个键:
Enable Multiple Windows是一个布尔值,标记该app是否支持多窗口。目前iOS是不支持多窗口的,只有iPadOS支持多窗口。如果你需要开发一个需要支持多窗口的iPad应用,那就需要设置为YES,否则就设置为NO就可以了。
就算不需要多窗口功能,也不妨使用scene来管理窗口,因为现在使用scene是Xcode的默认配置了。
Scene Configuration是一个字典,用来添加scene的配置信息:
有两种角色(role)的scene:External Display Session Role
和Application Session Role
。External Display Session Role
与使用外部显示设备有关,用的较少。一般配置Application Session Role
就可以了。
可配置的scene信息有四个:Class Name
、Configuration Name
、Delegate Class Name
和Storyboard Name
,其中Configuration Name
、Delegate Class Name
是必选的:
- Class Name。scene的类名,必须是
UIScene
的子类。如果是Application Session Role
,必须是UIWindowScene
的子类; - Configuration Name。为这个配置起一个名字,必选值;
- Delegate Class Name。scene代理的类名,必须是实现
UISceneDelegate
的类,如果是Application Session Role
,必须是实现UIWindowSceneDelegate
的类。必选值; - Storyboard Name。scene的窗口内容来源的Storyboard的名字,可选值。
scene对象和scene代理都不允许手动创建,只能在Info.plist中指定,然后由UIKit自动创建。
UIApplicationDelegate中管理scene生命周期的方法
UIApplicationDelegate
为了支持scene新增两个方法:
#pragma mark -- UIScene Support --
// Called when the UIKit is about to create & vend a new UIScene instance to the application.
// The application delegate may modify the provided UISceneConfiguration within this method.
// If the UISceneConfiguration instance returned from this method does not have a systemType which matches the connectingSession's, UIKit will assert
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0));
// Called when the system, due to a user interaction or a request from the application itself, removes one or more representation from the -[UIApplication openSessions] set
// If sessions are discarded while the application is not running, this method is called shortly after the applications next launch.
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0));
其中-[application:configurationForConnectingSceneSession:options:]
很重要,因为需要通过它获取scene的配置信息来创建新的scene:
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
UISceneConfiguration *config = [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
return config;
}
-[UISceneConfiguration initWithName:sessionRole:]
只能获取Info.plist中Application Scene Manifest已经填写的Scene Configure信息。
scene代理
scene代理,一个实现UISceneDelegate
的类。如果是Application Session Role
的scene,那就是一个实现UIWindowSceneDelegate
的类。scene代理只能在Info.plist中的Scene Configuration指定,然后被UIKit自动创建。
对于Application Session Role
的scene,需要创建root window。如果已经在Info.plist中的Scene Configuration指定了Storyboard Name,那么root window会自动从Storyboard中创建。如果没有指定Storyboard Name,那么就需要在-[UIWindowSceneDelegate scene:willConnectToSession:options:]
中手动创建root window。