前言

我正在参加技能社区创作者签约计划招募活动,点击链接报名投稿。

公司iOS项目自从20年从原生引进了Flutter以来,生产力来说不可谓提升不大,究竟1个人就能够干两端,其他端的适配只需求简略的适配即可。从Flutter1.22.6开始一向适配到现在的2.10.5,期间大大小小产生的坑也不少。Flutter混编的路由计划咱们采用的是阿里的flutter_boost计划,最近项目也是登录模块用Flutter进行了重构,和原先只在二级页面使用相比,应用冷发动就进入Flutter页面其实非常有应战,究竟引擎的发动要时刻。这不,当你的发动图和登录界面使用了特别的背景图就会有时刻短的闪白屏的效果,如下,其实就是Flutter引擎还没烘托结束的真空时刻。如是就有了下面的解决计划。

iOS原生混编Flutter路由指南及解决Flutter首页闪白屏问题

iOS接入Flutter及解决主页闪白屏全过程

一、创立flutter module项目

咱们经过xcode新建一个demo ios项目,然后在项目目录下创立flutter module项目


//创立flutter module项目
flutter create -t module flutter_module
//pubspec文件引进flutter_boost,我这里采用本地引进方法
flutter_boost:
path: flutter_boost-3.0-null-safety-release.2.1

初始化ios项目 Pod


pod init

Podfile装备代码如下


flutter_application_path = './flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'NaviteMixinFlutterDemo' 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 NaviteMixinFlutterDemo
end

然后pod install。这样子咱们的混编flutter项目就构建完成了


pod install

二、iOS原生注册flutter引擎及实现flutter_boost路由

注册Flutter引擎,咱们只需使用flutter_boost的方法在原生appDelegate注册即可。


func registerFlutter(application: UIApplication) {
FlutterBoost.instance().setup(application, delegate: JFFlutterRoute.shareInstance) { engine in
guard let _ = engine else { return }
print("engine success")
//you can register your channel in there.
}
}

实现flutter_boost路由跳转协议deleagte。获取顶层vc的代码,我往demo项目写了个扩展,这里不再赘述。


extension JFFlutterRoute: FlutterBoostDelegate {
internal func pushNativeRoute(_ pageName: String!, arguments: [AnyHashable : Any]!) {
switch pageName {
case JFFluterRouteName.nativeMainPage:
let vc = ViewController()
let navi = JFNavigationViewController(rootViewController: vc)
AppDelegate.switchRootVC(vc: navi)
break
case JFFluterRouteName.nativePage:
let vc = ViewController()
vc.title = "原生二级页面"
UIApplication.shared.visibleNavigationController()?.pushViewController(vc, animated: true)
break
default:
break
}
}
internal func pushFlutterRoute(_ options: FlutterBoostRouteOptions!) {
guard let vc = JFFlutterViewController() else { return }
vc.setName(options.pageName, uniqueId: options.uniqueId, params: options.arguments, opaque: options.opaque)
UIApplication.shared.visibleNavigationController()?.pushViewController(vc, animated: true)
}
internal func popRoute(_ options: FlutterBoostRouteOptions!) {
//只演示push,pop. present dismiss的处理 自己处理
UIApplication.shared.visibleNavigationController()?.popViewController(animated: true)
}
}

三、flutter端注册路由表


//MyApp Widget中注册
static Map<String, FlutterBoostRouteFactory> pageMap = {
JFRoute.loginPage: (settings, uniqueId) {
return CupertinoPageRoute(
settings: settings,
builder: (_) => const LoginPage(
),
);
},
JFRoute.demoPage: (settings, uniqueId) {
return CupertinoPageRoute(
settings: settings,
builder: (_) => const DemoPage(
),
);
},
};
Route<dynamic>? routeFactory(RouteSettings settings, String? uniqueId) {
FlutterBoostRouteFactory? func = pageMap[settings.name!];
if (func == null) {
return null;
}
return func(settings, uniqueId ?? "");
}
Widget appBuilder(Widget home) {
return MaterialApp(
home: home,
debugShowCheckedModeBanner: true,
///必须加上builder参数,否则showDialog等会出问题
builder: (_, __) {
return home;
},
);
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return FlutterBoostApp(
routeFactory,
appBuilder: appBuilder,
);
}
//简略封装一个路由类
class JFRoute {
static var loginPage = "jf://loginPage";
static var nativeMainPage = "jf://nativeMainPage";
static var nativePage = "jf://nativePage";
static var demoPage = "jf://demoPage";
static pushRoute(String url,
{Map<String, dynamic>? urlParams,
bool opque = true,
bool withContainer = true,}) {
withContainer = true;
BoostNavigator.instance.push(
url, //required
withContainer: withContainer, //optional
arguments: urlParams, //optional
opaque: opque, //optional,default value is true
);
}
static popRoute() {
BoostNavigator.instance.pop();
}
}

至此咱们只要把发动根控制器换成flutter登录页面,咱们一发动就会显现一个Flutter登录页面.


guard let scene = (scene as? UIWindowScene) else { return }
self.window = UIWindow(windowScene: scene)
let vc = JFLoginFluterViewController()
vc.setName(JFFluterRouteName.loginPage, uniqueId: "", params: [:], opaque: true)
self.window?.rootViewController = JFNavigationViewController(rootViewController: vc)
self.window?.makeKeyAndVisible()

四、解决Flutter主页闪白屏问题

分析原因:因为引擎的发动要时刻,当发动图和登录页面都用了同一个背景图时,会有一个白屏的闪缩大约(0.5-1s)左右,机型越好速度越快。那么咱们要如何解决这个问题?

  • 计划一

咱们能够用一个原生的vc带一个背景图,然后flutter vc作为child vc。 这种做法是可行的,可是每次修改都得做一次相同的操作,而且不灵敏。 不太引荐。

  • 计划二

也是我现在实现的一个计划,因为UIColor自带一个api能够经过图片来烘托出一种特别的色彩,咱们只需求在flutter基类,判断需求修改背景色的路由,做一次UIImage烘托成色彩的动作即可。当然因为图片会拉伸,咱们直接new一个image是不可的,我么需求用UIImageView来承载图片,让它自动撑满,再对图片截图然后缓存起来,这样烘托出来的图片就能够发动图一摸一样了。

代码如下,以及最终效果。

iOS原生混编Flutter路由指南及解决Flutter首页闪白屏问题


private func tryChangeLoginBgColorIfNeed() {
guard JFFlutterRoute.needBgViewRoutes.contains(self.name) else { return }
if let color = JFFlutterLoginBgColor {
//use cache color
print("use cache color")
self.view.backgroundColor = color
return
}
let screenSize = UIScreen.main.bounds.size
let launchView = UIImageView(image: UIImage(named: "bg"))
launchView.contentMode = .scaleToFill
launchView.frame = CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height)
if let image = UIImage.jf_convertViewToImage(view: launchView) {
let myColor = UIColor(patternImage: image)
JFFlutterLoginBgColor = myColor
self.view.backgroundColor = myColor
}
}

Demo地址

  • Demo项目GitHub地址: NaviteMixinFlutterDemo

结尾

我正在参加技能社区创作者签约计划招募活动,点击链接报名投稿。