Flutter iOS跳转FlutterViewController 方案尝试

1,641 阅读4分钟

原生跳转FlutterViewController的几种方式

在将Flutter项目接入到iOS原生项目中,需要使用FlutterViewController进行跳转展示,目前官方给出了三种跳转方式:

  1. 单个Flutter页面
    官方推荐在程序启动时,先预热FlutterEngine,也就是在AppDelegate中先初始化FlutterEnging,具体的代码是: OC代码:
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];//FlutterEngine 名,每个Flutter对应的名称
  // Runs the default Dart entrypoint with a default Flutter route.
  [self.flutterEngine run];//启动FlutterEngine
  // Connects plugins with iOS platform code to this app.
  [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];//如果使用了插件,则需要使用对应的FlutterEngine注册

Swift代码:

lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
flutterEngine.run();
    // Connects plugins with iOS platform code to this app.
    GeneratedPluginRegistrant.register(with: self.flutterEngine);
    return super.application(application, didFinishLaunchingWithOptions: launchOptions);

然后在需要跳转到Flutter界面的时候直接将已经加载好的FlutterEngine传入进FlutterViewController中即可: OC代码:

    FlutterEngine *flutterEngine = ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self presentViewController:flutterViewController animated:YES completion:nil];

Swift代码:

    let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
    let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
    present(flutterViewController, animated: true, completion: nil)

这种的好处是,由于FlutterEngine已经提前预热加载,在进行跳转的时候几乎和原生界面跳转无差异,用户体验良好。

  1. 隐式FlutterEngine创建FlutterViewController
    OC代码:
FlutterViewController *flutterViewController =
      [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
  [self presentViewController:flutterViewController animated:YES completion:nil];

Swift代码:

    let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
    present(flutterViewController, animated: true, completion: nil)

这种情况下,Flutter会在跳转FlutterViewController的时候创建FlutterEngine,虽然操作方便了,但是在跳转的时候会有明显的卡顿情况,用户体验不友好。

  1. 多个Flutter页面时,使用FlutterEngineGroup 代码:
创建EngineGroup
self.flutterEngineGroup = [[FlutterEngineGroup alloc] initWithName:@"flutterGroupName" project:NULL];

使用时:

FlutterEngine *engine = [[SVFlutterEngine shared].engineGroup makeEngineWithEntrypoint:nil libraryURI:nil initialRoute:nil];//使用Group创建一个FlutterEngine
FlutterViewController *flutterVC = [[FlutterViewController alloc]initWithEngine:engine nibName:nil bundle:nil];
[self presentViewController:flutterVC animated:YES completion:nil];

这种效果比第二种好一些,但是也会有稍微的卡顿,感觉就是不彻底。App要商用,肯定不能给这种让用户去体验。效果肯定是第一种最佳,但是第一种会引入新的问题,如果app中仅跳转一个固定页面作为根页面那么到还好,如果需要改变initialRoute页面,则需要寻找额外的方式。

Flutter 设置initialRoute的几种方式

目前Flutter给出这么几种方式设置跳转到FlutterView的方式,

  1. 在进行FlutterEngine初始化的时候进行设置,方法如下:
self.flutterEngine = [[FlutterEngine alloc]initWithName:@"flutter_engine"];
[self.flutterEngine runWithEntrypoint:nil libraryURI:nil initialRoute:@"/home"];

设置路由为/home,需要先在Flutter 的MaterialApp的参数getPages中设置路由 2. 隐式创建FlutterEngine的时候使用FlutterViewController初始化方法设置initialRoute

FlutterViewController *flutterVC = [[FlutterViewController alloc]initWithProject:nil initialRoute:@"/home" nibName:nil bundle:nil];
  1. 使用EngineGroup创建Flutter Engine的时候也有对应的方法
[engineGroup makeEngineWithEntrypoint:nil libraryURI:nil initialRoute:@"/home"];

但是需要注意的是,每个FlutterEngine仅能设置一次initialRoute,如果你的app有多个需要从原生直接跳Flutter页面的话,那么按照目前的情况你可能得维护多个FlutterEngine的缓存和生命周期。

这里顺带提一下,Flutter中的Entrypoint参数和libraryURI参数,这两个参数可以让你在启动Flutter界面是指定启动入口,其中Entrypoint指定启动参数名,libraryURI指定启动文件地址:

//正常不设置Entrypoint和libraryURI情况下
void main() => runApp(const MyApp());
设置Entrypoint:routeParse libraryURI:nil时进这里,如果这个入口写在其他的文件里则在libraryURI参数中传入文件路径
@pragma('vm:entry-point')
void routeParse(){
  debugPrint("ssl current route1:routeParse");
  runApp(const MyApp());
}

最终解决方案

1、用空间换时间:提前缓存多个与FlutterEngine,需要跳转的时候使用对应的FlutterEngine跳转 2、使用FlutterViewController 自带的pushRoute,每次进入的时候传入对应需要push的路由,比如:

定义路由:

static const root_router = '/home';
static const route_add = '/add';
static List<GetPage> pages = [
  GetPage(name: root_router, page: ()=> const SVHome()),
  GetPage(name: route_add, page: ()=> const SVAdd()),  
];
//初始化引擎根视图
_flutterEngine = [[FlutterEngine alloc]initWithName:@"flutter_engine"];
[self.flutterEngine runWithEntrypoint:nil libraryURI:nil initialRoute:@"/home"];

//在FlutterViewController 中调用,跳转到add路由
[self pushRoute:@"/add"];

这种方法和直接跳转到根视图从感官上几乎无感,可以很好的解决需要缓存多个FlutterEngine的问题,但是需要注意的是:当你从add界面直接退出Flutter界面时,需要先返回根视图,否则下次会从add界面push到新页面。 这个需要自己维护了。