iOS與flutter混編的一點(diǎn)點(diǎn)分享泽艘,配置好flutter環(huán)境之后:
1.創(chuàng)建flutter module
先創(chuàng)建一個(gè)存放現(xiàn)有項(xiàng)目和flutter模塊的文件夾,例如Desktop上創(chuàng)建一個(gè)名稱Mixed的文件夾,當(dāng)前iOS項(xiàng)目文件夾名稱為MyApp:
a)現(xiàn)有項(xiàng)目和flutter模塊都存在兔毒,直接clone到Mixed文件夾中
b)在Mixed文件夾中新建一個(gè)flutter模塊咖楣,終端操作如下:
cd ~/Desktop/Mixed/
flutter create --template module my_flutter
my_flutter是flutter模塊的名稱,自己定義处渣,需要實(shí)現(xiàn)的dart代碼直接寫在lib/文件夾下面
2.嵌入flutter
a)手動(dòng)嵌入
b)使用CocoaPods嵌入(推薦使用)
反正我使用的CocoaPods伶贰,賊簡(jiǎn)單,手動(dòng)的話還要去增加一些配置罐栈,CocoaPods嵌入過(guò)程如下:
目前Mixed
中結(jié)構(gòu)應(yīng)該是這樣的幕袱,如果不同,則可能需要調(diào)整相對(duì)路徑悠瞬。
Desktop/Mixed/
├── my_flutter/
│ └── .ios/
│ └── Flutter/
│ └── podhelper.rb
└── MyApp/
└── Podfile
1.在Podfile中添加:
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb’)
2.對(duì)每個(gè)需要嵌入flutter的Podfile target添加:
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
3.運(yùn)行:
pod install
自動(dòng)生成Debug 和 Release的配置文件當(dāng)你更改了myflutter/pubspec.yaml中的插件依賴項(xiàng)们豌,需要在flutter項(xiàng)目中運(yùn)行flutter pub get來(lái)刷新podhelper.rb腳本讀取的插件列表涯捻,而且要從/MyApp再次運(yùn)行pod install。
podhelper.rb 腳本把插件望迎、Flutter.framework
和App.framework
添加到你的iOS項(xiàng)目中障癌,Flutter.framework
是Flutter engine的包,App.framework
是這個(gè)項(xiàng)目的編譯的Dart代碼辩尊。
在Xcode打開(kāi)MyApp.xcworkspace涛浙,編譯起來(lái)。
3.iOS和flutter之間的頁(yè)面跳轉(zhuǎn)
FlutterEngine
充當(dāng) Dart VM 和 Flutter 運(yùn)行時(shí)的主機(jī)摄欲; FlutterViewController
依附于 FlutterEngine轿亮,給 Flutter 傳遞 UIKit 的輸入事件,并展示被 FlutterEngine 渲染的每一幀畫(huà)面胸墙。
1.創(chuàng)建一個(gè) FlutterEngine:
在 AppDelegate.h:
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate // More on the FlutterAppDelegate below.
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
在 AppDelegate.m:
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
#import "AppDelegate.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
// Runs the default Dart entrypoint with a default Flutter route.
[self.flutterEngine runWithEntrypoint:nil];
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; // 注冊(cè)插件
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
注意注冊(cè)插件的時(shí)機(jī)我注,如果任何一個(gè)插件庫(kù)中需要使用到當(dāng)前某些對(duì)象,保證注冊(cè)插件時(shí)對(duì)象已存在迟隅,否則會(huì)導(dǎo)致crash.
2.使用 FlutterEngine 展示 FlutterViewController
我將flutter頁(yè)面直接設(shè)置為APP的首頁(yè)
FlutterViewController *vc =
[[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
[vc setInitialRoute:@"home"]; // 在flutter中自定義的路由名稱
UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController: vc];
self.window.rootViewController = navi;
如果是從某個(gè)原生頁(yè)面跳轉(zhuǎn)flutter的頁(yè)面但骨,直接
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] init];
[flutterViewController setInitialRoute:@"/pageA"];
[self.navigationController pushViewController:flutterViewController animated:true];
flutter中main.dart文件是這樣的:
class MyApp extends StatelessWidget {
static BuildContext context=null;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'APP名稱',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: <String, WidgetBuilder>{
"/home": (context) => MainPage(),
"/PageA":(context) => PageA(),
});
}
}
3.頁(yè)面之間的交互
目前我所需要的交互,使用FlutterMethodChannel都可以實(shí)現(xiàn)智袭。
flutter中自定義一個(gè)方法通道名稱奔缠,例如
static const String MethodChannelName = "example.methodChannel";
我從原生很多地方跳轉(zhuǎn)flutter頁(yè)面,都使用的這一個(gè)通道名稱
a) flutter調(diào)用原生的方法
官方示例:flutter頁(yè)面需要原生這邊獲取電池電量吼野,然后在flutter頁(yè)面展示
flutter中編寫:
static const platform = const MethodChannel(MethodChannelName);
// 調(diào)用原生方法校哎,方法名為getBatteryLevel
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
// result是原生獲取到電池電量后傳遞過(guò)來(lái)的
final int result = await platform.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
setState(() {
_batteryLevel = batteryLevel;
});
}
然后在頁(yè)面中顯示電池電量
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RaisedButton(
child: Text('Get Battery Level'),
onPressed: _getBatteryLevel,
),
Text(_batteryLevel),
],
),
),
);
}
flutter中已經(jīng)完成,然后在iOS創(chuàng)建的FlutterViewController這邊添加:
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"example.methodChannel"
binaryMessenger:vc.binaryMessenger];
__weak typeof(self) weakSelf = self;
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([@"getBatteryLevel" isEqualToString:call.method]) {
int batteryLevel = [weakSelf getBatteryLevel];
if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery info unavailable"
details:nil]);
} else {
// 將數(shù)據(jù)傳遞給flutter
result(@(batteryLevel));
}
} else {
result(FlutterMethodNotImplemented);
}
}];
通道名稱example.methodChannel
跟flutter那邊寫的保持一致瞳步,vc
是上一步中創(chuàng)建的FlutterViewController贬蛙,getBatteryLevel
這個(gè)方法名稱也跟flutter中的保持一致
// 原生中獲取電池電量的方法
- (int)getBatteryLevel {
UIDevice* device = UIDevice.currentDevice;
device.batteryMonitoringEnabled = YES;
if (device.batteryState == UIDeviceBatteryStateUnknown) {
return -1;
} else {
return (int)(device.batteryLevel * 100);
}
}
這樣運(yùn)行iOS,就完成了谚攒。
b) 原生調(diào)用flutter的方法
比如說(shuō)阳准,原生登錄之后我要刷新嵌入進(jìn)來(lái)的flutter頁(yè)面,并把登錄成功的用戶信息傳給flutter模塊馏臭。
iOS中野蝇,先獲取到之前創(chuàng)建的方法通道m(xù)ethodChannel,使用invoke直接調(diào)用
[methodChannel invokeMethod:@"flutter_login" arguments:@{@"json": [LoginManager account].mj_JSONString}];
flutter中:
MethodChannel _methodChannel; // 聲明方法通道
_methodChannel = MethodChannel("example.methodChannel");
_methodChannel.setMethodCallHandler(_originMethodCallback);
Future<Map<String, String>> _originMethodCallback(MethodCall methodCall) async {
String methodName = methodCall.method; // 方法名
Map params = methodCall.arguments; // 參數(shù)
Map<String, String> resultMap = Map(); // 返回結(jié)果
switch(methodName) {
case "flutter_login": // 登錄
print("Flutter_Login:${params}");
...
// 刷新flutter模塊的登錄狀態(tài)和用戶信息
break;
default:
}
return resultMap;
}
交互的話括儒,我目前只使用了MethodChannel 绕沈,歡迎補(bǔ)充指正。
PlatformChannel
分為BasicMessageChannel
帮寻、MethodChannel
以及EventChannel
:
BasicMessageChannel: 用于傳遞數(shù)據(jù)乍狐。Flutter與原生項(xiàng)目的資源是不共享的,可以通過(guò)BasicMessageChannel來(lái)獲取Native項(xiàng)目的圖標(biāo)等資源固逗。
MethodChannel: 傳遞方法調(diào)用浅蚪。Flutter主動(dòng)調(diào)用Native的方法藕帜,并獲取相應(yīng)的返回值。比如獲取系統(tǒng)電量惜傲,發(fā)起Toast等調(diào)用系統(tǒng)API洽故,可以通過(guò)這個(gè)來(lái)完成。
EventChannel: 傳遞事件盗誊。這里是Native將事件通知到Flutter时甚。比如Flutter需要監(jiān)聽(tīng)網(wǎng)絡(luò)情況,這時(shí)候MethodChannel就無(wú)法勝任這個(gè)需求了哈踱。EventChannel可以將Flutter的一個(gè)監(jiān)聽(tīng)交給Native荒适,Native去做網(wǎng)絡(luò)廣播的監(jiān)聽(tīng),當(dāng)收到廣播后借助EventChannel調(diào)用Flutter注冊(cè)的監(jiān)聽(tīng)开镣,完成對(duì)Flutter的事件通知刀诬。
想具體了解,請(qǐng)參考 (https://www.cnblogs.com/loaderman/p/11353174.html)