美文网首页Flutter学习flutterflutter
iOS原生与Flutter页面交互--从如何集成到相互跳转与传值

iOS原生与Flutter页面交互--从如何集成到相互跳转与传值

作者: wg刚 | 来源:发表于2019-08-18 22:23 被阅读20次

    先提供demo,目录如下图

    做到效果如下:
    1、原生项目如何集成,配置flutter,实现混编
    2、主工程是原生的老项目,调用一些flutter页面,实现页面跳转;
    3、flutter页面主动与原生交互,传值给原生,并接受原生的回调;
    4、原生页面主动与flutter页面交互,传值给flutter;
    5、flutter页面,导航栏使用flutter自己的,可以返回
    效果演示图


    1、原生项目和flutter如何混合开发,如何配置

    1、在和项目根目录上一级,平级目录下,新建一个flutter_module,怎么做如下
    1、cd到指定目录

    例如我的项目

    2、执行如下命令:
    flutter create -t module flutter_module
    
    2、原生项目支持cocoapod

    不支持的,在项目根目录
    执行pod init

    3、podfile文件中添加如下代码(没有的,请添加)
    flutter_application_path = '../flutter_module/'
    eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')),binding)
    
    4、执行pod install
    5、项目配置bitcode为NO
    6、新建run script phase
    添加代码
    "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" buiObject-cld
    "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
    
    7、拖动这个新建的run script到target dependencies 下面(看上图,在下面了)

    到这里就完成了混编的集成配置,下面可以混编了

    2、原生页面跳转到flutter,flutter页面主动传值给原生,并回调给flutter

    1、跳转通过FlutterViewController

    导入头文件#import <Flutter/Flutter.h>
    //如果用到flutter插件时,导入下面头文件
    import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>

    2、确定跳到那个页面

    3、接受flutter主动传过来的方法名字,传的值,以及可以再次回调给flutter

    FlutterMethodChannel

    flutter代码

    import 'package:flutter/material.dart';
    //引入下面这个,是为了调用window的defaultRouteName拿到route判断跳转哪个界面
    import 'dart:ui';
    import 'package:flutter/services.dart';
    import 'package:flutter_module/flutter_first.dart';
    
    void main() => runApp(_widgetForRoute(window.defaultRouteName));
    
    //根据原生给的民资,确定显示那个界面
    Widget _widgetForRoute(String route){
      switch (route){
        case 'myApp':
          return MyApp();
        case 'home':
          return FlutterFirst();
        default:
          return MyApp();
      }
    
    }
    
    class MyApp extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'MyApp',
          theme: ThemeData(
            primarySwatch: Colors.pink,
          ),
          routes: <String, WidgetBuilder>{
            "/home":(BuildContext context) => FlutterFirst(),
          },
          home: MyHomePage(title: '我是flutter-->MyHomePage页面',),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title,}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
    
      //创建一个给native的channel (类似iOS的通知)
      static const methodChannel = const MethodChannel('wg/native_get');
    
      //告诉ios,需要跳转到下一页了。ios自己跳,并传给ios一个字符串
      _iosPushToVC() async {
        //invokeMethod:两个参数Method:方法名(两端要统一),arguments:此方法名下需要传的参数
        await methodChannel.invokeMethod('iOSFlutter', '这是flutter传的字符串');
      }
    
      //传给ios一个map
      _iosGetMap() async {
        Map<String, dynamic> value = {"code": "200", "data":[1,2,3]};
        await methodChannel.invokeMapMethod('iOSFlutter1',value);
      }
    
      //拿到ios的返回值
      _getIosValue() async {
       dynamic result;
       try{
         result = await methodChannel.invokeMethod('iOSFlutter2');
       }on PlatformException{
         result = "error";
       }
    
       if(result is String){
         showModalBottomSheet(context: context, builder: (BuildContext context){
           return Container(
             child: Center(
               child: Text(result,style: TextStyle(color: Colors.brown), textAlign: TextAlign.center,),
             ),
             height: 40,
           );
         });
       }
      }
    
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        //添加观察者
        WidgetsBinding.instance.addObserver(this);
      }
    
      @override
      void didChangeAppLifecycleState(AppLifecycleState state) {
        // TODO: implement didChangeAppLifecycleState
        super.didChangeAppLifecycleState(state);
    
        if(state != AppLifecycleState.resumed){
          methodChannel.invokeMethod('changeNavStatus', 'didChangeAppLifecycleState:${state}-show');
        }else{
          methodChannel.invokeMethod('changeNavStatus', 'didChangeAppLifecycleState:${state}-hiden');
        }
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
        WidgetsBinding.instance.removeObserver(this); //销毁观察者
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
    
        return Scaffold(
          appBar: AppBar(
    
            title: Text(widget.title),
    //        automaticallyImplyLeading: true,
            leading: Container(
              color: Colors.green,
              child: RaisedButton(
                onPressed: (){
                  methodChannel.invokeMethod('backToViewController');
                },
                child: Icon(
                  Icons.arrow_back,
    //              color: Colors.white,
                ),
              ),
            ),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                InkWell(
                    onTap: _iosPushToVC,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Icon(Icons.forward),
                        Text('跳转ios新界面,参数是字符串'),
                      ],
                    ),
                ),
                InkWell(
                  onTap: _iosGetMap,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Icon(Icons.forward),
                      Text('传参,参数是map'),
                    ],
                  )
                ),
                InkWell(
                  onTap: _getIosValue,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Icon(Icons.forward),
                      Text('接收iOS返回的内容'),
                    ],
                  )
                ),
              ],
            ),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }
    

    ios代码

    //
    #define Screen_width [UIScreen mainScreen].bounds.size.width
    #define Screen_height [UIScreen mainScreen].bounds.size.height
    
    #import "ViewController.h"
    #import <Flutter/Flutter.h>
    //如果用到flutter插件时,导入下面头文件
    #import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h>
    
    #import "FirstViewController.h"
    
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.view.backgroundColor = UIColor.whiteColor;
        self.title = @"我是原生-ViewController页面";
        
        UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(15, 100, Screen_width-30, 30)];
        btn.layer.cornerRadius = 15;
        btn.layer.masksToBounds = YES;
        btn.backgroundColor = UIColor.greenColor;
        [btn setTitle:@"调用flutter页面,flutter主动和iOS交互" forState:UIControlStateNormal];
        [btn addTarget:self action:@selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:btn];
    }
    
    - (void)btnClicked{
        
        [self pushToFlutterView];
    }
    
    - (void)pushToFlutterView{
        
        FlutterViewController *flutterVC = [FlutterViewController new];
        [flutterVC setInitialRoute:@"myApp"];
        
        __weak __typeof(self) weakSelf = self;
        
        NSString *channelName = @"wg/native_get";
        
        //FlutterMethodChannel是flutter页面主动交互iOS页面
        FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:flutterVC];
        //flutter页面方法触发
        [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            
            // call.method 获取 flutter 给回到的方法名,要匹配到 channelName 对应的多个 发送方法名,一般需要判断区分
            // call.arguments 获取到 flutter 给到的参数,(比如跳转到另一个页面所需要参数)
            // result 是给flutter的回调, 该回调只能使用一次
            
            NSLog(@"---->call.method=%@ \n call.arguments = %@", call.method, call.arguments);
            
            // method和WKWebView里面JS交互很像
            // flutter点击事件执行后需要iOS做的事
            if ([call.method isEqualToString:@"iOSFlutter"]) {
                
                FirstViewController *firstVC = [[FirstViewController alloc] init];
                [weakSelf.navigationController pushViewController:firstVC animated:YES];
            }
            
            // flutter给iOS传的参数
            if ([call.method isEqualToString:@"iOSFlutter1"]) {
                
                NSDictionary *dic = call.arguments;
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"flutter页面传过来的字典" message:[NSString stringWithFormat:@"%@",dic] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
                [alert show];
            }
            
            //iOS给flutter返回值
            if ([call.method isEqualToString:@"iOSFlutter2"]) {
                if (result) {
                    result(@"这是iOS返回给flutter的内容");
                }
            }
            
            if ([call.method isEqualToString:@"changeNavStatus"]) {
                NSString *arguments = call.arguments;
                if ([arguments containsString:@"show"]) {
                    weakSelf.navigationController.navigationBarHidden = NO;
                }else{
                    weakSelf.navigationController.navigationBarHidden = YES;
                }
            }
            
            if ([call.method isEqualToString:@"backToViewController"]) {
                [weakSelf.navigationController popViewControllerAnimated:YES];
            }
        }];
        
        //原生iOS跳转到Flutter页面
        [self.navigationController pushViewController:flutterVC animated:YES];
    }
    @end
    

    3、iOS主动传值给flutter

    FlutterEventChannel

    设置代理

    实现代理

    flutter代码

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    
    class FlutterFirst extends StatefulWidget {
      @override
      _FlutterFirstState createState() => _FlutterFirstState();
    }
    
    class _FlutterFirstState extends State<FlutterFirst> with WidgetsBindingObserver {
    
      // 注册一个通知,监听原生传给自己的值
      static const EventChannel eventChannel = const EventChannel('wg/native_post');
      static const MethodChannel methodChannel = const MethodChannel('wg/native_get');
    
      // 渲染前的操作,类似viewDidLoad
      @override
      void initState() {
        super.initState();
        // 监听事件,同时发送参数12345
        eventChannel.receiveBroadcastStream(12345).listen(_onEvent,onError: _onError);
        //添加生命周期观察者
        WidgetsBinding.instance.addObserver(this);
      }
    
      @override
      void dispose() {
        // TODO: implement dispose
        super.dispose();
        //销毁生命周期观察者
        WidgetsBinding.instance.removeObserver(this);
      }
    
      String naviTitle = 'title';
    
      // 回调事件
      void _onEvent(Object event){
        setState(() {
          naviTitle = event.toString();
        });
      }
    
      // 错误返回
      void _onError(Object error) {
    
      }
    
      @override
      void didChangeAppLifecycleState(AppLifecycleState state) {
        // TODO: implement didChangeAppLifecycleState
        super.didChangeAppLifecycleState(state);
    
        if(state != AppLifecycleState.resumed){
          methodChannel.invokeMethod('changeNavStatus', 'didChangeAppLifecycleState:${state}-show');
        }else{
          methodChannel.invokeMethod('changeNavStatus', 'didChangeAppLifecycleState:${state}-hiden');
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: '我是flutter-->FlutterFirst页面',
          theme: ThemeData(
            primarySwatch: Colors.pink
          ),
          home: Material(
            child: Scaffold(
              appBar: AppBar(
                title: Text('我是flutter-->FlutterFirst页面'),
                leading: Container(
                  color: Colors.green,
                  child: RaisedButton(
                    onPressed: (){
                      methodChannel.invokeMethod('backToViewController');
                    },
                    child: Icon(
                      Icons.arrow_back,
    //              color: Colors.white,
                    ),
                  ),
                ),
              ),
              body: Center(
                child: Text(naviTitle),
              ),
            ),
          ),
        );
      }
    }
    

    iOS代码

    #define Screen_width [UIScreen mainScreen].bounds.size.width
    #define Screen_height [UIScreen mainScreen].bounds.size.height
    
    
    #import "FirstViewController.h"
    #import <Flutter/Flutter.h>
    
    
    @interface FirstViewController ()<FlutterStreamHandler>
    
    @property (nonatomic, copy) FlutterEventSink eventSink;
    
    @end
    
    @implementation FirstViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = UIColor.whiteColor;
        self.title = @"我是原生FirstViewController页面";
        
        UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 100, Screen_width, 30)];
        btn.layer.cornerRadius = 15;
        btn.layer.masksToBounds = YES;
        btn.backgroundColor = UIColor.redColor;
        [btn setTitle:@"调用flutter页面-->iOS主动和flutter交互" forState:UIControlStateNormal];
        [btn addTarget:self action:@selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:btn];
    }
    
    - (void)btnClicked{
        FlutterViewController *flutterVC = [FlutterViewController new];
        flutterVC.navigationItem.title = @"Demo";
        [flutterVC setInitialRoute:@"home"];
        
        NSString *eventChannelName = @"wg/native_post";
        NSString *methodChannelName = @"wg/native_get";
        
        /*---------FlutterEventChannel是IOS主动与flutter交互-----------*/
        FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:eventChannelName binaryMessenger:flutterVC];
        
        // 代理FlutterStreamHandler
        [eventChannel setStreamHandler:self];
        /*---------FlutterEventChannel是IOS主动与flutter交互-----------*/
    
        
        /*---------FlutterMethodChannel是flutter主动与IOS交互-----------*/
        FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:methodChannelName binaryMessenger:flutterVC];
        [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            
            if ([call.method isEqualToString:@"changeNavStatus"]) {
                NSString *arguments = call.arguments;
                if ([arguments containsString:@"show"]) {
                    self.navigationController.navigationBarHidden = NO;
                }else{
                    self.navigationController.navigationBarHidden = YES;
                }
            }
            
            if ([call.method isEqualToString:@"backToViewController"]) {
                [self.navigationController popViewControllerAnimated:YES];
            }
        }];
        /*---------FlutterMethodChannel是flutter主动与IOS交互-----------*/
        
        [self.navigationController pushViewController:flutterVC animated:YES];
        
    }
    
    #pragma mark - <FlutterStreamHandler>
    //这个onListen是Flutter端开始监听这个channel时的回调,第二个参数 EventSink是用来传数据的载体。
    -(FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events{
        
        self.eventSink = events;
        // arguments flutter监听时,给native传送的参数
        if (self.eventSink) {
            self.eventSink(@"原生push到fluttet页面,传给flutter的值");
        }
        return nil;
    }
    
    // flutter不再接收
    -(FlutterError *)onCancelWithArguments:(id)arguments{
        
        //
        self.eventSink = nil;
        return nil;
    }
    @end
    

    导航栏控制

    通过FlutterMethodChannel传值给iOS,让iOS实现原生导航的隐藏于显示

    下载demo,运行时如果报

    解决:
    重新cd到原生项目根目录,执行pod install即可

    相关文章

      网友评论

        本文标题:iOS原生与Flutter页面交互--从如何集成到相互跳转与传值

        本文链接:https://www.haomeiwen.com/subject/mqjdsctx.html