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
的应用程序可以像以前一样继续运行。依赖 Storyboards(和 XIBs)在 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:
中从 Storyboards 实例化的 FlutterViewController
的应用程序,它们有责任适应新的初始化顺序。
迁移选项
- 子类化
FlutterViewController
并将逻辑放在子类的awakeFromNib
中。 - 在
Info.plist
或UIApplicationDelegate
中指定UISceneDelegate
,并将逻辑放在scene:willConnectToSession:options:
中。有关更多信息,请参阅 Apple 的文档。
示例
#@objc class MyViewController: FlutterViewController {
override func awakeFromNib() {
self.awakeFromNib()
doSomethingWithFlutterViewController(self)
}
}
时间线
#- 生效版本:尚未发布
- 稳定版本:尚未发布
- 未知:Apple 将其警告更改为断言,并且尚未采用
UISceneDelegate
的 Flutter 应用将在最新 SDK 上启动时崩溃。
参考资料
#- 问题 167267 - 最初报告的问题。
- Apple 关于指定
UISceneDelegate
的文档