前言
公司iOS項目自從20年從原生引入了Flutter以來,生產力來說不可謂提升不大呀狼,畢竟1個人就可以干兩端,其他端的適配只需要簡單的適配即可损离。從Flutter1.22.6開始一直適配到現(xiàn)在的2.10.5,期間大大小小產生的坑也不少哥艇。Flutter混編的路由方案我們采用的是阿里的flutter_boost方案,最近項目也是登錄模塊用Flutter進行了重構僻澎,和原先只在二級頁面使用相比貌踏,應用冷啟動就進入Flutter頁面其實十分有挑戰(zhàn),畢竟引擎的啟動要時間窟勃。這不祖乳,當你的啟動圖和登錄界面使用了特殊的背景圖就會有短暫的閃白屏的效果,如下秉氧,其實就是Flutter引擎還沒渲染完畢的真空時間眷昆。如是就有了下面的解決方案。
iOS接入Flutter及解決首頁閃白屏全過程
一汁咏、創(chuàng)建flutter module項目
我們通過xcode新建一個demo ios項目亚斋,然后在項目目錄下創(chuàng)建flutter module項目
//創(chuàng)建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引擎及實現(xiàn)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.
}
}
實現(xiàn)flutter_boost路由跳轉協(xié)議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參數(shù)磷仰,否則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泵殴。 這種做法是可行的,但是每次修改都得做一次相同的操作拼苍,而且不靈活笑诅。 不太推薦调缨。
- 方案二
也是我目前實現(xiàn)的一個方案,由于UIColor自帶一個api可以通過圖片來渲染出一種特殊的顏色吆你,我們只需要在flutter基類弦叶,判斷需要修改背景色的路由,做一次UIImage渲染成顏色的動作即可妇多。當然由于圖片會拉伸伤哺,我們直接new一個image是不行的,我么需要用UIImageView來承載圖片者祖,讓它自動撐滿立莉,再對圖片截圖然后緩存起來,這樣渲染出來的圖片就可以啟動圖一摸一樣了七问。
代碼如下蜓耻,以及最終效果。
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