iOS集成Flutter与路由控制
本文的目的是将原生项目通过集成Flutter Module逐步将iOS和Android代码向Flutter代码迁移,从而最终实现iOS和Android的代码统一。
以下以iOS集成Flutter为例。
通过CocoaPods集成
在当前iOS项目路径下,以下以项目名称为complex的iOS项目为例,创建名称为flutter_module的Flutter部分,可以通过VSCode或Android Studio直接创建对应的Module,还可以通过指令创建:
cd complex/
flutter create -t module flutter_module
然后我们通过CocoaPods进行集成,如下:
pod init
然后修改Podfile文件:
platform :ios, '13.0'
flutter_application_path = 'flutter_module/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'complex' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
install_all_flutter_pods(flutter_application_path)
# Pods for complex
end
执行pod命令:
pod install
发现还缺少flutter_post_install,补充进Podfile,最终为:
# Uncomment the next line to define a global platform for your project
platform :ios, '13.0'
flutter_application_path = 'flutter_module/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'complex' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
install_all_flutter_pods(flutter_application_path)
# Pods for complex
end
post_install do |installer|
flutter_post_install(installer)
end
然后通过Xcode直接Run,发现报错
Sandbox: bash(13818) deny(1) file-read-data xxx/complex/flutter_module/.ios/Flutter/flutter_export_environment.sh
提示很明显,那么我们进入Build Settings->Build Options->User Script Sandboxing,将Yes修改为No。
重新Run,Build Succeed。
iOS与Flutter的路由
之前可以通过- (void)setInitialRoute:(NSString*)route FLUTTER_DEPRECATED("Use FlutterViewController initializer to specify initial route");
设置InitialRoute
,可以改用- (instancetype)initWithProject:(nullable FlutterDartProject*)project initialRoute:(nullable NSString*)initialRoute nibName:(nullable NSString*)nibName bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER;
。
但是实际上我切换到main.dart使用window.defaultRouteName
区分不同的路由路径,发现window.defaultRouteName
已经废弃了,现在使用View.of(context).platformDispatcher.defaultRouteName
来区分不同的路由。
iOS部分完整代码为:
let controller = FlutterViewController(project: nil, initialRoute: "xxxxxx", nibName: nil, bundle: nil)
GeneratedPluginRegistrant.register(with: controller)
navigationController?.pushViewController(controller, animated: true)
第二种方式是通过创建FlutterEngine来启动不同的入口,首先我们在AppDelegate
创建FlutterEngineGroup
,如下:
lazy var engineGroup: FlutterEngineGroup = {
return FlutterEngineGroup(name: "ios.flutter", project: nil)
}()
这是为了应对多个入口,如果只有一个入口直接使用FlutterEngine
也可以。
接下来通过FlutterEngineGroup
创建FlutterEngine
,如下:
guard let delegate = UIApplication.shared.delegate as? AppDelegate else { return }
let engine = delegate.engineGroup.makeEngine(withEntrypoint: "firstFlutterController", libraryURI: nil)
engine.run()
let controller = FirstFlutterController(engine: engine, nibName: nil, bundle: nil)
navigationController?.pushViewController(controller, animated: true)
然后在main.dart文件内添加新的入口,如下:
@pragma('vm:entry-point')
void firstFlutterController() {
runApp(const FirstFlutterController());
}
如果项目内有多个原生页面和Flutter页面交叉切换推荐使用这种方法,因为:
/**
* Creates a running `FlutterEngine` that shares components with this group.
*
* @param entrypoint The name of a top-level function from a Dart library. If this is
* FlutterDefaultDartEntrypoint (or nil); this will default to `main()`. If it is not the app's
* main() function, that function must be decorated with `@pragma(vm:entry-point)` to ensure the
* method is not tree-shaken by the Dart compiler.
* @param libraryURI The URI of the Dart library which contains the entrypoint method. IF nil,
* this will default to the same library as the `main()` function in the Dart program.
*
* @see FlutterEngineGroup
*/
- (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint
libraryURI:(nullable NSString*)libraryURI;
不仅可以指定页面的路径还可以共享运行FlutterEngine,相对而言更灵活且消耗更低。
这里推荐一款路由控制第三方flutter_boost,原有的项目在pub上没有更新,这里需要到github上去下载。