上一期我們聊了下<Flutter與原生項目Android混合開發(fā)的集成>,移動端的開發(fā)IOS自然是少不了,那么今天我們來聊一聊Flutter與原生項目IOS混合開發(fā)的集成。
還是說一下前提吧:大家在項目的開發(fā)中除了那種新立項的而且規(guī)模較小的項目可能是純Flutter開發(fā),大部分在項目中都可能是混合開發(fā)的模式,而且是以原生為主Flutter為輔的開發(fā)模式,很大部分原因就是你原來原生的項目不可能馬上全部用Flutter重寫一遍,而且是集成一部分Flutter的頁面而已,像這種情況的話那么混合開發(fā)就是必須的,那么我們今天就先來聊聊怎么在原生項目去集成Flutter
我們還是回到最初看看一個完成Flutter項目是什么樣的:

如圖一個新建的Flutter項目里面就包括了他的Android/IOS宿主,如果是Flutter為主原生為輔的開發(fā)可以就在這里面進(jìn)行,而且這種項目適合一個全新的項目,就像上面提到的那樣,不適合那種原生項目就很成熟了再集成Flutter框架的情況
如果我們要愛原有成熟的原生項目里面再集成Flutter的話首先要創(chuàng)建一個Flutter Module,如下:

創(chuàng)建的項目路徑要選擇好,一般我們會與原生項目目錄并排,如下:

Flutter Module已經(jīng)創(chuàng)建好了,我們來看看一個Flutter Module大概是什么樣子的,如圖:

是不是與一個Flutter Project很像啊,是的可以說本質(zhì)沒有什么變化,F(xiàn)lutter Module依然是一個可以運(yùn)行的項目,你可以進(jìn)入該目錄下運(yùn)行 flutter run 就可以看到運(yùn)行效果,上面我們說過一個Flutter的Project需要他們原生的宿主才能運(yùn)行,F(xiàn)lutter Module也一樣他們也需要原生宿主,就是紅框里面的部分,不過這里面不建議你去修改(這一點(diǎn)不像Flutter Project)所以他們的文件夾在mac里面隱藏的
那么我們今天看看IOS項目怎么去集成:
目前為止有兩種方案可以將flutter集成進(jìn)iOS項目中
使用CocoaPods和已安裝的Flutter SDK(推薦使用這一種)。
為Flutter引擎,已編譯的Dart代碼和所有Flutter插件創(chuàng)建生成 frameworks,手動在Xocde中嵌入這些frameworks。
第一種方案的優(yōu)點(diǎn):該方案是Flutter官方推薦的方案;集成過程相對簡單;
第一種方案的缺點(diǎn): 需要求項目中工程師的電腦上都要配置Flutter環(huán)境;并且 iOS項目里面要摻著一些Flutter相關(guān)的項目工程代碼(不過這個不能說為一個明顯的缺點(diǎn))
第二種方案的優(yōu)點(diǎn):如果團(tuán)隊成員無法在本地安裝Flutter SDK
第二種方案的缺點(diǎn):集成步驟過于復(fù)雜;而且Flutter相關(guān)的代碼有改動(哪怕是你想升級Flutter框架)則需要 flutter build ios 生成新的Frameworks集成在項目中;
所以我們今天就只介紹第一種集成的方式:使用CocoaPods和已安裝的Flutter SDK,目前該版本的Flutter對于這種集成方式做得更加的優(yōu)化了,目前只需要一個步驟即可:
我們只需要在iOS工程的podfile文件中添加如下命令:
# Flutter
flutter_application_path = '../flutter_module/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'XXXAPP' do
use_frameworks!
# Flutter
install_all_flutter_pods(flutter_application_path)
end
就可以一次性將flutter的編譯的庫由此依賴進(jìn)入iOS宿主項目中, 不用再每次去在Xcode->Build Phases中去添加設(shè)置腳本文件路徑等繁瑣操作,簡化了集成的步驟。
我做過實(shí)驗?zāi)阒灰贔lutter項目修改的代碼只需要在Xcode運(yùn)行他的宿主項目就可以,我想這個是因為你在Xcode點(diǎn)擊Run的時候他也會編譯Flutter里面的代碼,這樣編譯運(yùn)行就會變得方便很多
現(xiàn)在我們需要開發(fā)一個Flutter頁面去驗證是否集成成功
IOS混合開發(fā)的頁面也分兩種情況:
一種是從原生跳轉(zhuǎn)到一個Flutter頁面,
一種是Flutter與原生混合在一個頁面,
我們先看第一種的情況:
import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlutterHybird',
theme: ThemeData(
primarySwatch: Colors.blue,
),
//window.defaultRouteName可以接收從Native傳過來的參數(shù)
home: _widgetForRoute(window.defaultRouteName),
);
}
}
Widget _widgetForRoute(String route) {
switch (route) {
case 'route1':
return Center(
child: Text("route1"),
);
case 'route2':
return Center(
child: Text("route2"),
);
default:
return Center(
child: Text('route3’),
);
}
}
其中通過window.defaultRouteName可以按照Native傳遞過來的參數(shù)來選擇啟動的Widget
我們再開發(fā)一個Native的頁面用于承載上面的Flutter,用于被原生頁面跳轉(zhuǎn)
#import "ViewController.h"
#import "AppDelegate.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setFrame:CGRectMake(100, 100, 200, 100)];
[button setTitle:@"Click" forState:UIControlStateNormal];
[button addTarget:self action:@selector(button_click) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)button_click {
FlutterViewController *flutterViewController = [[FlutterViewController alloc] init];
[flutterViewController setInitialRoute:self.inputParams];
[self.navigationController pushViewController:flutterViewController animated:YES];
}
然后在原生進(jìn)行跳轉(zhuǎn)即可,通過setInitialRoute傳遞參數(shù),對應(yīng)就是Flutter的window.defaultRouteName來獲取參數(shù)
是的你沒有看錯,就是這樣就可以了,他內(nèi)部幫你做了很多處理,例如默認(rèn)找到了Flutter的main.dark,默認(rèn)執(zhí)行里面的main方法,這種情況再ReactNative是想都不敢想的,因為我們知道ReactNative的JS module至少是要指定跳轉(zhuǎn)的moduleName的,因為在ReactNative里面每個module都是需要注冊的,這個在Flutter沒有這個步驟
initialRoute從名稱上看起來是Flutter提供給我們進(jìn)行Native與Flutter交互的路由跳轉(zhuǎn)的,
但是實(shí)際上他就是一個字符串,我們可以傳遞一個路由名稱,有時候我們也可以通過這個參數(shù)傳遞一個字符串,然后在Flutter端進(jìn)行解析,就像上面一樣,下面我們來一個傳遞路由頁面的例子來使用他
我們先指定一個注冊了路由的Flutter頁面(因為只有你注冊了路由才知道要跳轉(zhuǎn)到哪個路由),如下:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'one_page':(context){
return OnePage();
},
'two_page':(context){
return TwoPage();
}
},
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
然后我們使用initialRoute傳遞跳轉(zhuǎn)的路由頁面即可:
FlutterViewController *flutterViewController1 = [[FlutterViewController alloc] init];
[flutterViewController1 setInitialRoute:@“one_page”];
[self.navigationController pushViewController:flutterViewController animated:YES];
OK 上面聊了下Flutter里面簡單的頁面跳轉(zhuǎn),我們再來聊聊在IOS原生頁面中嵌入FlutterUI組件:
- 第一種方式非常的簡單,直接把FlutterViewController當(dāng)做子VC集成在主ViewController里面即可如下:
FlutterViewController *fluvc = [[FlutterViewController alloc]init];
[self addChildViewController:fluvc];
fluvc.view.frame = self.view1.bounds;
[fluvc didMoveToParentViewController:self];
[self.view1 addSubview:fluvc.view];
[self.navigationController pushViewController:fluvc animated:YES];
這種方式非常的簡單,也是我非常推薦的一種方式
- 第二種方式針對于Xib或者StoryBoard開發(fā)的情況操作也是非常的簡單,這種方式也是我從網(wǎng)上看到的,如下:

里面拖拉了兩個Container View ,與兩個ViewController,兩個Container View與兩個ViewController通過Segue相連,Segue的連接方式為Embed即可,這樣的話你的XIB或者StoryBoard上面也可以通過拖拉控件去與Flutter頁面混合集成
最后說一說Flutter頁面的性能問題,加載 FlutterActivity 頁面時明顯看到一段時間的黑屏,這段時間主要是啟動 Flutter 引擎(FlutterEngine),F(xiàn)lutter 引擎啟動的時間在不同手機(jī)上不同,每一個 FlutterActivity 頁面都會重新啟動一個Flutter 引擎,所以不要在一個項目中創(chuàng)建多個 FlutterActivity(或者啟動多個 FlutterActivity 實(shí)例),否則內(nèi)存會越來越大,為了減少 FlutterActivity 頁面的延遲時間和多個 FlutterActivity 實(shí)例內(nèi)存一直增長問題,我們可以緩存 Flutter 引擎(FlutterEngine),在啟動 App 的時候先啟動 Flutter 引擎,然后使用緩存的引擎加載頁面,通常將其放在 AppDelegate 中:
import UIKit
import Flutter
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
flutterEngine.run();
return super.application(application, didFinishLaunchingWithOptions: launchOptions);
}
}
使用的時候再取出來就可以了:
@objc func showFlutter() {
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
let flutterViewController =
FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
present(flutterViewController, animated: true, completion: nil)
}
好了,我們的《Flutter與原生混合開發(fā)項目集成之IOS篇》已經(jīng)到結(jié)尾了,相信上面的內(nèi)容還是比較簡單易懂的,如果你有什么問題可以給我留言,歡迎大家一起討論,如果想看<Flutter與原生項目Android混合開發(fā)的集成>的話可以點(diǎn)擊這里,F(xiàn)lutter是一個非常龐大的框架,希望接下來的學(xué)習(xí)中可以輸出更多高質(zhì)量的文章,好了我們一起期待吧···