UISceneDelegate 适配
概述
#Apple 现在要求 iOS 开发者适配 UISceneDelegate 协议,这会改变应用程序启动时的初始化顺序。如果您的 iOS 应用修改了 application:didFinishLaunchingWithOptions:
,则可能需要更新。
背景
#大多数 Flutter 应用在 application:didFinishLaunchingWithOptions:
中不会有自定义逻辑。这些应用无需进行任何代码迁移。在大多数情况下,Flutter 会自动迁移 Info.plist
。
Apple 现在要求适配 UISceneDelegate
,这会重新排列 iOS 应用的初始化顺序。指定 UISceneDelegate
后,Storyboard 的初始化会延迟到调用 application:didFinishLaunchingWithOptions:
之后。这意味着 UIApplicationDelegate.window
和 UIApplicationDelegate.window.rootViewController
无法从 application:didFinishLaunchingWithOptions:
访问。
Apple 正在推动 UISceneDelegate
API 的适配,因为它允许应用程序拥有其 UI 的多个实例,例如在 iPadOS 上的多任务处理。
之前,Flutter 的文档指出 application:didFinishLaunchingWithOptions:
是设置平台通道以在宿主应用程序和 Flutter 之间创建互操作性的好地方。现在这不再是注册这些平台通道的可靠位置,因为 Flutter 引擎尚未创建。
迁移指南
#Info.plist 迁移
#UISceneDelegate
必须在应用的 Info.plist
或 application:configurationForConnectingSceneSession:options:
中指定。如果未指定 UISceneDelegate
,Flutter 工具会尝试自动编辑 Info.plist
,因此除了再次运行 flutter run
或 flutter build
之外,可能不需要任何操作。项目可以通过将以下内容添加到 Info.plist
进行手动升级。FlutterSceneDelegate
是 Flutter 框架中作为 UISceneDelegate
的新类。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
</dict>
在 Xcode 编辑器中显示
在 application:didFinishLaunchingWithOptions:
中创建平台通道
#以编程方式创建 FlutterViewController
的应用可以像以前一样继续运行。依赖 Storyboard(和 XIB)在 application:didFinishLaunchingWithOptions:
中创建平台通道的应用,现在应该使用 FlutterPluginRegistrant
API 来实现相同的功能。
之前
#@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
binaryMessenger: controller.binaryMessenger)
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// This method is invoked on the UI thread.
// Handle battery messages.
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.dev/battery"
binaryMessenger:controller.binaryMessenger];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// This method is invoked on the UI thread.
// TODO
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
之后
#@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, FlutterPluginRegistrant {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
pluginRegistrant = self
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func register(with registry: any FlutterPluginRegistry) {
let registrar = registry.registrar(forPlugin: "battery")
let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
binaryMessenger: registrar!.messenger())
batteryChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
// This method is invoked on the UI thread.
// Handle battery messages.
})
GeneratedPluginRegistrant.register(with: registry)
}
}
@interface AppDelegate () <flutterpluginregistrant>
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
self.pluginRegistrant = self;
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)registerWithRegistry:(NSObject<flutterpluginregistry>*)registry {
NSObject<flutterpluginregistrar>* registrar = [registry registrarForPlugin:@"battery"];
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.dev/battery"
binaryMessenger:registrar.messenger];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// This method is invoked on the UI thread.
// TODO
}];
[GeneratedPluginRegistrant registerWithRegistry:registry];
}
@end
通过 FlutterAppDelegate
以编程方式设置 FlutterPluginRegistrant
。
在 application:didFinishLaunchingWithOptions:
中注册插件
#大多数传统 Flutter 项目在应用启动时使用 GeneratedPluginRegistrant
注册插件。GeneratedPluginRegistrant
对象在底层注册平台通道,应随着平台通道迁移而进行迁移。这将避免任何关于使用 FlutterLaunchEngine
的运行时警告。
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, FlutterPluginRegistrant {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
pluginRegistrant = self
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func register(with registry: any FlutterPluginRegistry) {
GeneratedPluginRegistrant.register(with: registry)
}
}
@interface AppDelegate () <flutterpluginregistrant>
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
self.pluginRegistrant = self;
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)registerWithRegistry:(NSObject<flutterpluginregistry>*)registry {
[GeneratedPluginRegistrant registerWithRegistry:registry];
}
@end
定制化 FlutterViewController 用法
#对于由于创建平台通道以外的原因,在 application:didFinishLaunchingWithOptions:
中使用从 Storyboard 实例化 FlutterViewController
的应用,它们有责任适应新的初始化顺序。
迁移选项
- 子类化
FlutterViewController
并将逻辑放入子类的awakeFromNib
中。 - 在
Info.plist
或UIApplicationDelegate
中指定UISceneDelegate
,并将逻辑放入scene:willConnectToSession:options:
中。更多信息请查看 Apple 的文档。
示例
#@objc class MyViewController: FlutterViewController {
override func awakeFromNib() {
self.awakeFromNib()
doSomethingWithFlutterViewController(self)
}
}
时间线
#- 已合并到 main 分支:待定
- 已合并到 stable 分支:待定
- 未知:Apple 将其警告更改为断言,并且未适配
UISceneDelegate
的 Flutter 应用将在使用最新 SDK 启动时开始崩溃。
参考资料
#- 问题 167267 - 最初报告的问题。
- Apple 关于指定
UISceneDelegate
的文档