当前位置:   article > 正文

22、Flutter - 混合开发(三)iOS原生调用Flutter_ios原生跳转到flutter模块中官方使用

ios原生跳转到flutter模块中官方使用

混合开发(三)iOS原生调用Flutter


Flutter 项目 调用一些原生的功能!用的比较多的就是第三方插件,因为比较简单

官方 《Flutter实战》

原生项目中部分页面使用Flutter,这种也是比较常见的。

FLutter本身定位的是开发一个完整的App应用。所以要是只让其做成一个页面的话有些功能是不支持的。Flutter本身有自己的渲染引擎,如果是小项目用Flutter就不划算,只有非常大型的项目将其部分或者全部页面用Flutter来实现。

 

详细代码参见Demo

Demo地址 -> AiOSFlutterModule

 

1、FLutter Module

模块创建

创建出来的工程看一下

打开文件路径可以去看一下,是隐藏文件

隐藏的目的是,官方不希望我们对这些文件进行操作。我们开发的是一个Flutter页面是能运行的,这里的ios 和 Android 文件只是为了让我们做测试用的。当我们把这个Flutter写好之后,是要集成到原生项目中使用的,并不需要那些隐藏的内容。

扩展:

打开终端输入命令:(由于系统不一样,有可能无效,可以自行上网查阅)

1、显示隐藏文件/文件夹

$ defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder

2、隐藏隐藏文件/文件夹

$ defaults write com.apple.finder AppleShowAllFiles -boolean false ; killall Finder

 

2、新建iOS项目

要让Flutter和我们的iOS项目产生管理,使用pod进行管理
先生成Podfiile 文件,直接打开终端,cd 打开iOS项目路径。pod init ,pod install。之后就可以再Xcode 里面查看了。

liujilou@liujiloudeMacBook-Pro ~ % cd /Users/liujilou/Desktop/code/AiOSFlutterModule/NativeDemo
liujilou@liujiloudeMacBook-Pro NativeDemo % pod init
liujilou@liujiloudeMacBook-Pro NativeDemo % pod install

然后用Xcode 打开工程,编辑Podfile 文件。这里的引用格式的Flutter官网提供的,可以去查阅

flutter_application_path = '../flutter_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

platform :ios, '9.0'

target 'NativeDemo' do
  install_all_flutter_pods(flutter_application_path)
  use_frameworks!
 
end

注意:这个Flutter 的路径是相对路径,如果修改改了路径,这里记得要改变


重新 pod install



这样就关联成功了,一些 Flutter 的内容就安装进去了
这个时候我们也可以到ViewController 里面试一下是否成功了。
#import <Flutter/Flutter.h> 头文件能导入就说明成功了

 

3、调用 Flutter 页面(一)不推荐

iOS里面先创建2个按钮,然后去打开Flutter页面

3.1、iOS页面

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. self.view.backgroundColor = [UIColor whiteColor];
  4. CGSize viewSize = self.view.frame.size;
  5. UIButton * button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  6. button1.frame = CGRectMake((viewSize.width-100)/2, 100, 100, 40);
  7. button1.backgroundColor = [UIColor orangeColor];
  8. button1.tag = 1001;
  9. [button1 setTitle:@"按钮一" forState:(UIControlStateNormal)];
  10. [button1 addTarget:self action:@selector(pushFlutter:) forControlEvents:(UIControlEventTouchUpInside)];
  11. [self.view addSubview:button1];
  12. UIButton * button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  13. button2.frame = CGRectMake((viewSize.width-100)/2, 200, 100, 40);
  14. button2.backgroundColor = [UIColor greenColor];
  15. button2.tag = 1002;
  16. [button2 setTitle:@"按钮二" forState:(UIControlStateNormal)];
  17. [button2 addTarget:self action:@selector(pushFlutter:) forControlEvents:(UIControlEventTouchUpInside)];
  18. [self.view addSubview:button2];
  19. }
  1. -(void)pushFlutter:(UIButton *)btn
  2. {
  3. NSString * pageIndex = @"one";
  4. NSString * page = @"one_page";
  5. if (btn.tag == 1002) {
  6. pageIndex = @"two";
  7. page = @"two_page";
  8. }
  9. // 不要每次都去alloc init 一个新的FLutter,这样非常消耗性能
  10. FlutterViewController * vc = [[FlutterViewController alloc] init];
  11. [vc setInitialRoute:pageIndex];//初始化传给Flutter的值
  12. [self presentViewController:vc animated:YES completion:nil];
  13. }

我们这里用的是跟iOS一样的调用方式,每次点击按钮的时候去重新创建Flutter页面。
如果全屏显示Flutter页面,在Flutter中要做回退。我们先不做全屏显示,先演示后面做

3.2、Flutter的页面

  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. void main() => runApp(MyApp(
  4. //window 需要导入import 'dart:ui';
  5. //window.defaultRouteName 拿到的就是 iOS中写的 [vc setInitialRoute:@"one"];带过来的值 one
  6. pageIndex: window.defaultRouteName));
  7. // ----------------------------------------------------
  8. class MyApp extends StatelessWidget {
  9. final String pageIndex;
  10. const MyApp({Key key, this.pageIndex}) : super(key: key);
  11. @override
  12. Widget build(BuildContext context) {
  13. return MaterialApp(
  14. title: 'Flutter Demo',
  15. theme: ThemeData(
  16. primarySwatch: Colors.blue,
  17. ),
  18. home: rootPage(pageIndex),
  19. );
  20. }
  21. rootPage(String pageIndex) {
  22. switch (pageIndex) {
  23. case 'one':
  24. return Scaffold(
  25. appBar: AppBar(title: Text(pageIndex)),
  26. body: Center(
  27. child: RaisedButton(
  28. onPressed: () {
  29. // 直接退出页面了,一般不会这么做。
  30. MethodChannel('one_page').invokeMapMethod('exit');
  31. },
  32. child: Text(pageIndex),
  33. )));
  34. case 'two':
  35. return Scaffold(
  36. appBar: AppBar(title: Text(pageIndex)),
  37. body: Center(
  38. child: RaisedButton(
  39. onPressed: () {
  40. MethodChannel('two_page').invokeMapMethod('exit');
  41. },
  42. child: Text(pageIndex),
  43. )),
  44. );
  45. }
  46. }
  47. }
  • iOS通过初始化的时候传值给Flutter   [vc setInitialRoute:pageIndex];
  • Flutter通过  window.defaultRouteName  拿到iOS传过来的值,然后去判断 显示AppBar 等。然后 可以通过  MethodChannel('one_page').invokeMapMethod('exit');   退出Flutter页面,并给iOS发送消息传值。

 

但是Flutter 创建之后是一直存在在内存中的,而且非常大

通过上图可以看到,一个空的Flutter都非常大。而且每次创建的话Flutter页面的数据不能保存。因为Flutter要有自己的渲染引擎,不能像iOS的页面一样这样创建,所以建议全局建一个 Flutter 引擎。

 

4、Flutter 页面调用(二)推荐

  1. @property (nonatomic, strong) FlutterEngine * flutterEngine;
  2. @property (nonatomic, strong) FlutterViewController * flutterVC;

Flutter 引擎

  1. - (FlutterEngine *)flutterEngine
  2. {
  3. if (!_flutterEngine) {
  4. //这里不要直接用 _flutterEngine ,然后 _flutterEngine.run 因为在页面将要显示的时候才去执行运行,那么Flutter的页面显示的会非常慢
  5. //_flutterEngine = [[FlutterEngine alloc] initWithName:@"liujilou"];
  6. //定义一个局部变量,判断一下如果这个flutterEngine已经运行起来了,那么我们的全局_flutterEngine就等于这个 flutterEngine 。失败的话就返回nil
  7. FlutterEngine * flutterEngine = [[FlutterEngine alloc] initWithName:@"liujilou"];
  8. if (flutterEngine.run) {//Flutter 运行运行起来了
  9. _flutterEngine = flutterEngine;
  10. }
  11. }
  12. return _flutterEngine;
  13. }
  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. self.view.backgroundColor = [UIColor whiteColor];
  4. self.flutterVC = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
  5. self.flutterVC.modalPresentationStyle = UIModalPresentationFullScreen;//模态展示风格(全屏显示)
  1. -(void)pushFlutter:(UIButton *)btn
  2. {
  3. NSString * pageIndex = @"one";
  4. NSString * page = @"one_page";
  5. if (btn.tag == 1002) {
  6. pageIndex = @"two";
  7. page = @"two_page";
  8. }
  9. [self presentViewController:self.flutterVC animated:YES completion:nil];
  10. }

4.1、问题

//    直接这样写就会出错,崩溃.
window.defaultRouteName 是空的
为什么是空的呢?因为在run的时候就去运行了Flutter,但是这一个时候并没有去传值。
可以通过单独运行Flutter 代码看一下值,和报错信息

调试

 

因为  [vc setInitialRoute:pageIndex]; 传值是在初始化的时候传的,我们现在不每次都去创建Flutter了所以需要改一下。Flutter也不能通过  window.defaultRouteName 来取值了。

修改Flutter代码

4.2、Flutter

  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. void main() => runApp(_MyApp());
  4. // ----------------------------------------
  5. class _MyApp extends StatefulWidget {
  6. @override
  7. __MyAppState createState() => __MyAppState();
  8. }
  9. class __MyAppState extends State<_MyApp> {
  10. String pageIndex = 'one';
  11. // 这里是解码器,互相调用
  12. final MethodChannel _oneChannel = MethodChannel('one_page');
  13. final MethodChannel _twoChannel = MethodChannel('two_page');
  14. @override
  15. void initState() {
  16. // 调用方法一次接收信息
  17. _oneChannel.setMethodCallHandler((call) {
  18. setState(() {
  19. pageIndex = call.method;
  20. });
  21. return null;
  22. });
  23. _twoChannel.setMethodCallHandler((call) {
  24. setState(() {
  25. pageIndex = call.method;
  26. });
  27. return null;
  28. });
  29. }
  30. @override
  31. Widget build(BuildContext context) {
  32. return MaterialApp(
  33. title: 'Flutter Demo',
  34. theme: ThemeData(
  35. primarySwatch: Colors.blue,
  36. ),
  37. home: rootPage(pageIndex),
  38. );
  39. }
  40. rootPage(String pageIndex) {
  41. switch (pageIndex) {
  42. case 'one':
  43. return Scaffold(
  44. appBar: AppBar(title: Text(pageIndex)),
  45. body: Column(
  46. mainAxisAlignment: MainAxisAlignment.center,
  47. children: <Widget>[
  48. RaisedButton(
  49. onPressed: () {
  50. // 直接退出页面了,一般不会这么做。
  51. MethodChannel('one_page').invokeMapMethod('exit');
  52. },
  53. child: Text(pageIndex),
  54. ),
  55. ],
  56. ));
  57. case 'two':
  58. return Scaffold(
  59. appBar: AppBar(title: Text(pageIndex)),
  60. body: Center(
  61. child: RaisedButton(
  62. onPressed: () {
  63. MethodChannel('two_page').invokeMapMethod('exit');
  64. },
  65. child: Text(pageIndex),
  66. )),
  67. );
  68. }
  69. }
  70. }

 

5、通讯

1、FlutterMethodChannel   调用方法(method invocation)一次通讯


//下面的这里两种都是持续通讯的
2、 FlutterBasicMessageChannel    :传递字符串&半结构化的信息
3、FlutterEventChannel   :用于数据流(stream)的通讯

完整的代码

5.1、iOS

  1. // ViewController.m
  2. // NativeDemo
  3. //
  4. // Created by liujilou on 2020/6/28.
  5. // Copyright © 2020 liujilou. All rights reserved.
  6. //
  7. // FlutterMethodChannel 调用方法(method invocation)一次通讯
  8. //下面的这里两种都是持续通讯的
  9. // FlutterBasicMessageChannel :传递字符串&半结构化的信息
  10. // FlutterEventChannel :用于数据流(stream)的通讯
  11. #import "ViewController.h"
  12. #import <Flutter/Flutter.h>
  13. @interface ViewController ()
  14. @property (nonatomic, strong) FlutterEngine * flutterEngine;
  15. @property (nonatomic, strong) FlutterViewController * flutterVC;
  16. @property (nonatomic, strong) FlutterBasicMessageChannel * msgChannel;
  17. @end
  18. @implementation ViewController
  19. - (FlutterEngine *)flutterEngine
  20. {
  21. if (!_flutterEngine) {
  22. //这里不要直接用 _flutterEngine ,然后 _flutterEngine.run 因为在页面将要显示的时候才去执行运行,那么Flutter的页面显示的会非常慢
  23. //_flutterEngine = [[FlutterEngine alloc] initWithName:@"liujilou"];
  24. //定义一个局部变量,判断一下如果这个flutterEngine已经运行起来了,那么我们的全局_flutterEngine就等于这个 flutterEngine 。失败的话就返回nil
  25. FlutterEngine * flutterEngine = [[FlutterEngine alloc] initWithName:@"liujilou"];
  26. if (flutterEngine.run) {//Flutter 运行运行起来了
  27. _flutterEngine = flutterEngine;
  28. }
  29. }
  30. return _flutterEngine;
  31. }
  32. - (void)viewDidLoad {
  33. [super viewDidLoad];
  34. self.view.backgroundColor = [UIColor whiteColor];
  35. self.flutterVC = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
  36. self.flutterVC.modalPresentationStyle = UIModalPresentationFullScreen;//模态展示风格(全屏显示)
  37. // 接收Flutter 的数据
  38. // 这里因为messenger 需要 FlutterBinaryMessenger 类型所以报警告
  39. self.msgChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messageChannel" binaryMessenger:self.flutterVC];
  40. [self.msgChannel setMessageHandler:^(id _Nullable message, FlutterReply _Nonnull callback) {
  41. NSLog(@"收到Flutter 的%@",message);
  42. }];
  43. CGSize viewSize = self.view.frame.size;
  44. UIButton * button1 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  45. button1.frame = CGRectMake((viewSize.width-100)/2, 100, 100, 40);
  46. button1.backgroundColor = [UIColor orangeColor];
  47. button1.tag = 1001;
  48. [button1 setTitle:@"按钮一" forState:(UIControlStateNormal)];
  49. [button1 addTarget:self action:@selector(pushFlutter:) forControlEvents:(UIControlEventTouchUpInside)];
  50. [self.view addSubview:button1];
  51. UIButton * button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  52. button2.frame = CGRectMake((viewSize.width-100)/2, 200, 100, 40);
  53. button2.backgroundColor = [UIColor greenColor];
  54. button2.tag = 1002;
  55. [button2 setTitle:@"按钮二" forState:(UIControlStateNormal)];
  56. [button2 addTarget:self action:@selector(pushFlutter:) forControlEvents:(UIControlEventTouchUpInside)];
  57. [self.view addSubview:button2];
  58. }
  59. -(void)pushFlutter:(UIButton *)btn
  60. {
  61. NSString * pageIndex = @"one";
  62. NSString * page = @"one_page";
  63. if (btn.tag == 1002) {
  64. pageIndex = @"two";
  65. page = @"two_page";
  66. }
  67. // 不要每次都去alloc init 一个新的FLutter,这样非常消耗性能
  68. // FlutterViewController * vc = [[FlutterViewController alloc] init];
  69. // 直接这样写就会出错,奔溃.
  70. // window.defaultRouteName 是空的
  71. // FlutterViewController * vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
  72. //
  73. // vc.modalPresentationStyle = UIModalPresentationFullScreen;//模态跳转页面全屏显示
  74. // 问题就出来 Route,Route在run之后,所以Flutter收不到初始化的数据,这里就不用Route 了。
  75. // 因为Route本身就是在初始化的时候传值用的,我们既然不想让每次都去重新创建,那就不会每次都初始化所以这里用Route 就不合适了
  76. // [vc setInitialRoute:pageIndex];//初始化的时候带过去的值
  77. // [self presentViewController:vc animated:YES completion:nil];
  78. // 使用Channel 通道传值
  79. FlutterMethodChannel * methodChannel = [FlutterMethodChannel methodChannelWithName:page binaryMessenger:self.flutterVC];
  80. // 发送消息
  81. [methodChannel invokeMethod:pageIndex arguments:nil];
  82. [self presentViewController:self.flutterVC animated:YES completion:nil];
  83. // 监听 Flutter 回调回来的参数
  84. [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
  85. if ([call.method isEqualToString:@"exit"]) {
  86. [self.flutterVC dismissViewControllerAnimated:YES completion:nil];
  87. }
  88. }];
  89. }
  90. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
  91. {
  92. static int a = 0;
  93. [self.msgChannel sendMessage:[NSString stringWithFormat:@"%d",a++]];
  94. }
  95. @end

做持续通讯

iOS点击屏幕,就向Flutter发送消息。将a 值传过去

 

5.2、Flutter

  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/services.dart';
  3. void main() => runApp(_MyApp()
  4. // MyApp(
  5. // window 需要导入import 'dart:ui';
  6. // window.defaultRouteName 拿到的就是 iOS中写的 [vc setInitialRoute:@"one"];带过来的值 one
  7. // pageIndex: window.defaultRouteName
  8. // )
  9. );
  10. // ----------------------------------------------------
  11. class MyApp extends StatelessWidget {
  12. final String pageIndex;
  13. const MyApp({Key key, this.pageIndex}) : super(key: key);
  14. @override
  15. Widget build(BuildContext context) {
  16. return MaterialApp(
  17. title: 'Flutter Demo',
  18. theme: ThemeData(
  19. primarySwatch: Colors.blue,
  20. ),
  21. home: rootPage(pageIndex),
  22. );
  23. }
  24. rootPage(String pageIndex) {
  25. switch (pageIndex) {
  26. case 'one':
  27. return Scaffold(
  28. appBar: AppBar(title: Text(pageIndex)),
  29. body: Center(
  30. child: RaisedButton(
  31. onPressed: () {
  32. // 直接退出页面了,一般不会这么做。
  33. MethodChannel('one_page').invokeMapMethod('exit');
  34. },
  35. child: Text(pageIndex),
  36. )));
  37. case 'two':
  38. return Scaffold(
  39. appBar: AppBar(title: Text(pageIndex)),
  40. body: Center(
  41. child: RaisedButton(
  42. onPressed: () {
  43. MethodChannel('two_page').invokeMapMethod('exit');
  44. },
  45. child: Text(pageIndex),
  46. )),
  47. );
  48. }
  49. }
  50. }
  51. // ----------------------------------------
  52. class _MyApp extends StatefulWidget {
  53. @override
  54. __MyAppState createState() => __MyAppState();
  55. }
  56. class __MyAppState extends State<_MyApp> {
  57. String pageIndex = 'one';
  58. // 这里是解码器,互相调用
  59. final MethodChannel _oneChannel = MethodChannel('one_page');
  60. final MethodChannel _twoChannel = MethodChannel('two_page');
  61. // 这个是通讯,也需要一个解码器
  62. final BasicMessageChannel _messageChannel =
  63. BasicMessageChannel('messageChannel', StandardMessageCodec());
  64. @override
  65. void initState() {
  66. // 可以持续接收信息
  67. _messageChannel.setMessageHandler((message) {
  68. print('收到了来自iOS的:$message');
  69. return null;
  70. });
  71. // 调用方法一次接收信息
  72. _oneChannel.setMethodCallHandler((call) {
  73. setState(() {
  74. pageIndex = call.method;
  75. });
  76. return null;
  77. });
  78. _twoChannel.setMethodCallHandler((call) {
  79. setState(() {
  80. pageIndex = call.method;
  81. });
  82. return null;
  83. });
  84. }
  85. @override
  86. Widget build(BuildContext context) {
  87. return MaterialApp(
  88. title: 'Flutter Demo',
  89. theme: ThemeData(
  90. primarySwatch: Colors.blue,
  91. ),
  92. home: rootPage(pageIndex),
  93. );
  94. }
  95. rootPage(String pageIndex) {
  96. switch (pageIndex) {
  97. case 'one':
  98. return Scaffold(
  99. appBar: AppBar(title: Text(pageIndex)),
  100. body: Column(
  101. mainAxisAlignment: MainAxisAlignment.center,
  102. children: <Widget>[
  103. RaisedButton(
  104. onPressed: () {
  105. // 直接退出页面了,一般不会这么做。
  106. MethodChannel('one_page').invokeMapMethod('exit');
  107. },
  108. child: Text(pageIndex),
  109. ),
  110. TextField(
  111. // 输入框写数据,向iOS发送数据
  112. onChanged: (String str) {
  113. _messageChannel.send(str);
  114. },
  115. )
  116. ],
  117. ));
  118. case 'two':
  119. return Scaffold(
  120. appBar: AppBar(title: Text(pageIndex)),
  121. body: Center(
  122. child: RaisedButton(
  123. onPressed: () {
  124. MethodChannel('two_page').invokeMapMethod('exit');
  125. },
  126. child: Text(pageIndex),
  127. )),
  128. );
  129. }
  130. }
  131. }

 

Flutter 和原生页面不要频繁来回切换,内存消耗会非常大。
同时Flutter 销毁是不会完全销毁的,所以就不要去销毁了,就整体保存一份引擎避免重复创建。

混合开发
可以利用FLutter 作为项目的主要开发框架

也可以如上,将FLutter作为一个业务,iOS做框架。但是还是那句话不要频繁的来回切换。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/669951
推荐阅读
相关标签
  

闽ICP备14008679号