我们做项目的时候难免会遇到flutter和OC或者swift混编的需求,所以本人也尝试着将flutter集成到项目中进行调用,目前已经集成成功,可以做到界面的跳转。下面我们就来详细的说一下具体的步骤吧:
一、Xcode创建Swift项目。
首先我们创建一个Swift的项目,命名为HunHe。
二、创建flutter项目
这里创建flutter项目是关键点,我们通过终端命令行创建。
1、进入到要创建项目的文件夹中,输入如下命令,创建项目名为my_flutter。
bogon:Demo mac$ flutter create --template module my_flutter
稍等片刻,项目创建成功,如下:
三、在我们的Podfile文件中导入本地的my_flutter
# 添加模块所在路径
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'HunHe' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# 安装Flutter模块
install_all_flutter_pods(flutter_application_path)
end
注释:my_flutter项目和HunHe在同一级目录中,所以我们必须要回到上一级目录中用../my_flutter
,其中'podhelper.rb至关重要,这里踩过坑。
四、集成flutter到Swift项目中
cd进入到我们的Swift项目中,准备集成my_flutter。
bogon:Demo mac$ cd /Users/mac/Desktop/Demo/HunHe
bogon:HunHe mac$ pod install
集成成功。
五、代码调用
1、在AppDelegate导入flutter,然后创建引擎,最终启动引擎。
import UIKit
import Flutter
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// 1.创建一个FlutterEngine引擎对象
lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 2、启动引擎
flutterEngine.run()
return true
}
}
2、在ViewController中导入import Flutter,然后创建一个按钮,在事件中获取appdelegate中的引擎,利用引擎获取flutterViewController对象,最后用于push或者present。
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//1、创建一个按钮
let button = UIButton(type: UIButton.ButtonType.custom)
button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
button.setTitle("Show Flutter", for: .normal)
button.frame = CGRect(x: 80, y: 210, width: 160, height: 40)
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
}
@objc func showFlutter() {
// 2.创建FlutterViewController对象(需要先获取flutterEngine)
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine;
let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil);
// navigationController?.pushViewController(flutterViewController, animated: true);
present(flutterViewController, animated: true, completion: nil)
}
}
六、OC代码调用如下
1、Appdelegate.h中调用
@import UIKit;
@import Flutter;
@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
2、Appdelegate.m中调用
#import // Used to connect plugins.
#import "AppDelegate.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
[self.flutterEngine run];
[GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
3、在ViewController.m中,创建按钮,然后调事件- (void)showFlutter
- (void)showFlutter {
FlutterEngine *flutterEngine =
((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
FlutterViewController *flutterViewController =
[[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self presentViewController:flutterViewController animated:YES completion:nil];
}
运行效果如下:
GIF生成网址:https://www.gif.cn/
上面讲述的是原生调用flutter,现在我们了解一下flutter怎么调用原生方法。有些是我们的flutter无法获取的,比如电量、经纬度,所以我们就需要通过原生的方法来获取。这里主要有以下几步。
1、在flutter中通过Dart创建通道名称
。
static const platform = const MethodChannel("my/battery");
下面的代码是搭建UI
int _result = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Battery"),
),
body: Center(
child: Column(
children: [
Text("当前电池信息: $_result"),
RaisedButton(
child: Text("获取电池信息"),
onPressed: getBatteryInfo,
)
],
),
),
);
}
2、通过platform.invokeMethod
调用平台方法。
void getBatteryInfo() async {
// 核心代码二
final int result = await platform.invokeMethod("getBatteryInfo");
setState(() {
_result = result;
});
}
Flutter调用调用原生页面添加代码有以下几步:
通过FlutterMethodChannel获取方法通道,然后监听方法调用。
1、获取FlutterViewController
2、获取方法通道也就是"my/battery"
#import
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
// 1.获取FlutterViewController(是应用程序的默认Controller)
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
// 2.获取MethodChannel(方法通道)
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"my/battery"
binaryMessenger:controller.binaryMessenger];
// 3.监听方法调用(会调用传入的回调函数)
__weak typeof(self) weakSelf = self;
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
// 3.1.判断是否是getBatteryInfo的调用
if ([@"getBatteryInfo" isEqualToString:call.method]) {
// 1.iOS中获取信息的方式
int batteryLevel = [weakSelf getBatteryLevel];
// 2.如果没有获取到,那么返回给Flutter端一个异常
if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery info unavailable"
details:nil]);
} else {
// 3.通过result将结果回调给Flutter端
result(@(batteryLevel));
}
} else {
// 3.2.如果调用的是getBatteryInfo的方法, 那么通过封装的另外一个方法实现回调
result(FlutterMethodNotImplemented);
}
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (int)getBatteryLevel {
// 获取信息的方法
UIDevice* device = UIDevice.currentDevice;
device.batteryMonitoringEnabled = YES;
if (device.batteryState == UIDeviceBatteryStateUnknown) {
return -1;
} else {
return (int)(device.batteryLevel * 100);
}
}
@end
三、原生传参flutter页面
FlutterEventSink
@interface AppDelegate ()
@property (nonatomic, strong) FlutterEventChannel *eventChannel;
@property (nonatomic, copy) FlutterEventSink sink;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.flutterViewController = [[FlutterViewController alloc]init];
self.window.rootViewController = self.flutterViewController;
self.eventChannel = [FlutterEventChannel eventChannelWithName:@"ios_event_channel" binaryMessenger:(NSObject *)self.flutterViewController];
[self.eventChannel setStreamHandler:self];
[self listeningWIFIChange];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateWifi) name:@"updateWifi" object:nil];
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)updateWifi{
NSString *wifiName = [self getWifi];
NSLog(@"ios ---- 网络变化-----wifiName:%@",wifiName);
NSLog(@"%@",self.sink);
//通过该方法传值给flutter
if (self.sink != nil){
//当有多个值返回给flutter的时候,比如获取wifi、获取手机版本号等等,可以给dic添加个参数key区分参数,方便flutter接收的时候区分
NSDictionary *dic = {@"key":@"wifi",@"name":wifiName};
self.sink(dic);
}
}
//监听网络变化
- (void)listeningWIFIChange{
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL,onNotifyCallback, CFSTR("com.apple.system.config.network_change"), NULL,CFNotificationSuspensionBehaviorDeliverImmediately);
}
static void onNotifyCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo){
NSString* notifyName = (__bridge NSString *) name;
if ([notifyName isEqualToString:@"com.apple.system.config.network_change"]) {
NSLog(@"======网络变化");
[[NSNotificationCenter defaultCenter] postNotificationName:@"updateWifi" object:nil];
} else {
NSLog(@"intercepted %@", notifyName);
}
}
#pragma mark -
// 这个onListen是Flutter端开始监听这个channel时的回调,第二个参数 EventSink是用来传数据的载体
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
eventSink:(FlutterEventSink)events {
NSLog(@"onListenWithArguments");
self.sink = events;
return nil;
}
- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
NSLog(@"onCancelWithArguments");
return nil;
}
flutter 端代码
EventChannel _eventChannel = const EventChannel("ios_event_channel");
_eventChannel
.receiveBroadcastStream("init")
.listen(_onEvent, onError: _onError);
void _onEvent(Object event) {
Map map = event;
//此处为从iOS端接收到的数据
if (map['key'] == 'wifi'){
print("wifi名字:${map['name']}");
}
}
void _onError(Object error){
print("--------------error:${error}");
}
总结:
1、原生向flutter传参,使用FlutterEventChannle,跳转的时候自动调用onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)events函数,通过这个events
传参。
//1. 创建事件通道对象,唯一标识 “hometest”,到时flutter是根据该标识来监听原生发送给flutter的参数信息
FlutterEventChannel *evenChannel = [FlutterEventChannel eventChannelWithName:@"hometest" binaryMessenger:flutterViewController.binaryMessenger];
//2. 当原生跳往flutter时会触发下面的- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events回调方法,可以在该方法中给flutter传递参数
[evenChannel setStreamHandler:self];
//原生跳转flutter时,会触发该方法,在该方法中可以传递参数给flutter界面,需要注意的是flutter代码中必须写上对应的监听代码,这里才会被执行
- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events{
if (events) {
events(@{@"key":@"value"});
}
return nil;
}
String str = "flutter之前的参数";
// 用于监听原生调用flutter
static const EventChannel homeChannel = const EventChannel('hometest');
@override
void initState() {
// TODO: implement initState
super.initState();
//_onEventHome 监听回调方法
homeChannel.receiveBroadcastStream(12345).listen(_onEventHome,onError: _onErrorHome);
}
// 回调事件 首页
void _onEventHome(Object event) {
if (event is Map){
str = event["key"];
}else if(event is String){
str = event.toString();
}else{
str = "其他类型";
}
setState(() {
});
}
// 错误返回
void _onErrorHome(Object error) {
}
2、flutter调用OC是用MethodChannel,并可传参。
// 用于调用原生方法 "hometestmethod"标识符与OC中的监听标识符保持一致
var homechannelmethod = MethodChannel("hometestmethod");
onTap: (){
//给原生发送消息并传入参数,原生根据标识homePageCallNativeMethond来做对应的处理
homechannelmethod.invokeMethod('homePageCallNativeMethond',{"key":"value","key1":"value1"});
},
原生接收事件:
// 1.创建方法通道对象,用于监听flutter调用原生时的回调,唯一标识“hometestmethod”与flutter要保持一致
FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"hometestmethod" binaryMessenger:flutterViewController.binaryMessenger];
//2. 设置监听回调block,flutter端通过通道调用原生方法时会进入以下回调
[channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
//call的属性method是flutter调用原生方法的方法名,我们进行字符串判断然后写入不同的逻辑
if ([call.method isEqualToString:@"homePageCallNativeMethond"]) {
//flutter传给原生的参数
id para = call.arguments;
NSLog(@"flutter传给原生的参数:%@", para);
//可以做界面跳转
[self.navigationController pushViewController:[TestViewController new] animated:YES];
//获取一个字符串
NSString *nativeFinalStr = @"原生给flutter回传的值";
if (nativeFinalStr!=nil) {
//把获取到的字符串传值给flutter
result(nativeFinalStr);
}else{
//异常(比如改方法是调用原生的getString获取一个字符串,但是返回的是nil(空值),这显然是不对的,就可以向flutter抛出异常 进入catch处理)
result([FlutterError errorWithCode:@"001" message:[NSString stringWithFormat:@"进入异常处理"] details:@"进入flutter的trycatch方法的catch方法"]);
}
}else{
//调用的方法原生没有对应的处理 抛出未实现的异常
result(FlutterMethodNotImplemented);
}
}];
3、OC要跳转到具体的flutter页面
// 1.初始化flutter控制器,并指定路由 “home”,flutter中根据该路由标识显示对应的界面
FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithProject:nil initialRoute:@"home" nibName:nil bundle:nil];
//2. 设置flutter HomePage标题
flutterViewController.navigationItem.title = @"原生项目设置的首页title";
//3. 跳转
[self.navigationController pushViewController:flutterViewController animated:YES];
Flutter 跟 IOS 原生通信--数据传输(一)
Flutter 跟 IOS 原生通信--数据传输更简洁明了