Flutter和Xcode混编

我们做项目的时候难免会遇到flutter和OC或者swift混编的需求,所以本人也尝试着将flutter集成到项目中进行调用,目前已经集成成功,可以做到界面的跳转。下面我们就来详细的说一下具体的步骤吧:

一、Xcode创建Swift项目。

首先我们创建一个Swift的项目,命名为HunHe。

二、创建flutter项目

这里创建flutter项目是关键点,我们通过终端命令行创建。

1、进入到要创建项目的文件夹中,输入如下命令,创建项目名为my_flutter。
bogon:Demo mac$ flutter create --template module my_flutter

稍等片刻,项目创建成功,如下:


251595245082_.pic_hd.jpg

三、在我们的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
261595245213_.pic_hd.jpg

集成成功。

五、代码调用

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

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 原生通信--数据传输更简洁明了

你可能感兴趣的:(Flutter和Xcode混编)