美文网首页
第十五章:混合开发

第十五章:混合开发

作者: Mr姜饼 | 来源:发表于2021-05-25 11:26 被阅读0次

本节知识点为大家介绍Flutter与iOS双向通信

  • Flutter引用iOS原生

我们将沿用之前的wechatDemo来完成此次的课题研究
我们来实现,点击用户头像,调用iOS原生的选择照片框架,来显示我们新选择的照片

16.png

"#####MethodChannel"

step1:为头像盒子包一个gestor,实现我们的点击事件

GestureDetector(
                onTapDown: (e){
                  _selectHeadIcon();
                },
                child: Container(//装饰盒子(头像)设置圆角
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(5),
                      image: DecorationImage(image:_filePath == null ? AssetImage('images/ice.png') : FileImage(File(_filePath.substring(7))))
                  ),
                ),
              ),//头部
  //选择头像
  _selectHeadIcon(){
    print('选择头像');
    //调用原生的selectHead方法
    _methodChannel.invokeMethod('selectHead');
  }

step2:Flutter端定义方法通道,并实行监听

//定义方法通道minePage、 进行传输
  final MethodChannel _methodChannel = MethodChannel('minePage');
  //选择图片的文件路径
  String _filePath;
 @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //注册回调监听
    _methodChannel.setMethodCallHandler((call){
      print('收到回调');
      if(call.method == 'selectHeadFinish'){//由原生端发送消息至flutter端
          String filePath =  call.arguments.toString();
          print('收到回调$filePath');
          setState(() {
            _filePath = filePath;
          });
      }
      //return null;
    });
  }

step3:在原生端注册

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"

@interface AppDelegate()
@property (nonatomic ,strong)FlutterMethodChannel* methodChannel;
@end

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UIImagePickerController* pickVC = [[UIImagePickerController alloc] init];
    pickVC.delegate = self;
    //获取Flutter页面
    FlutterViewController* flutterVC = (FlutterViewController*)self.window.rootViewController;
    //创建并获取Flutter的方法通道
    self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"minePage" binaryMessenger:flutterVC];
    //实行监听回调
    [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        //监听flutter发起的消息回调,调起原生的相机
        if([call.method isEqual:@"selectHead"]){
            [flutterVC presentViewController:pickVC animated:YES completion:nil];
        }
    }];
  [GeneratedPluginRegistrant registerWithRegistry:self];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info{
    
    [picker dismissViewControllerAnimated:YES completion:^{
        NSURL* url = info[@"UIImagePickerControllerImageURL"];
        //向FLutter发送消息selectHeadFinish
        [self.methodChannel invokeMethod:@"selectHeadFinish" arguments:url.absoluteString];
    }];
}
@end

思路详解: Flutter注册methodChannel并监听回调 -> iOS获取flutter中注册的methodChannel,并且监听回调方法'selectHead' ->当flutter发送‘ selectHead’消息时,iOS监听到回调,会调起原生相册 -> iOS选择完照片之后,将照片的filePath回调给flutter -> flutter监听到iOS回传的回调,获取照片filePath刷新页面

注意:flutter与原生两端,注册的方法和回调都必须统一,一收一发!!!!!

  • iOS原生引用Flutter

可能我们在一个成型的iOS项目中需要引用flutter的指定页面,这时候我们就需要在xcode做如下操作

step1:首先我们在本地的iOS工程通目录下创建一个flutter的module工程

//cd到Xcode工程同级目录下,执行
flutter create -t module flutter_modlue
51.png

step2:在iOS的podfile文件中添加如下引用


flutter_application_path = '../flutter_module'

load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb’)

platform :ios, '9.0'

target 'iOS_flutter' do
  # Comment the next line if you don't want to use dynamic frameworks

  # 引入flutter

  install_flutter_engine_pod

  install_all_flutter_pods(flutter_application_path)

  use_frameworks!

  # Pods for iOS_flutter

  target 'iOS_flutterTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'iOS_flutterUITests' do
    # Pods for testing
  end

end

pod install

mac@icedeMacBook-Pro 原生嵌入Flutter % pod install
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing flutter_modlue (0.0.1)
Generating Pods project
Integrating client project
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.


打开.xcworkspace文件

#import "ViewController.h"
#import <Flutter/Flutter.h>
@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton* btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 50, 50)];
    btn.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(pushFlutter) forControlEvents:UIControlEventTouchUpInside];
    
    // Do any additional setup after loading the view.
}
- (void)pushFlutter{
    FlutterViewController* VC = [[FlutterViewController alloc] init];
    [self presentViewController:VC animated:YES completion:nil];

}
@end

run

52.png

!!!!!特别提醒:千万不要以中文名字命名路径文件夹,否则pod install的时候会找不到flutter_pod的库,楼主被这个蠢问题困扰了半天!!!!!!!

高级进阶,iOS引用指定的flutter页面

熟练掌握FlutterEngine : Flutter引擎

修改flutter项目

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());


class MyApp extends StatefulWidget {
  @override
  _State createState() => _State();
}

class _State extends State<MyApp> {

  String _pageName;//page的名字
  //定义两个页面管道
  MethodChannel _oneMethodChannel = MethodChannel('onePage');
  MethodChannel _twoMethodChannel = MethodChannel('twoPage');
  @override
  void initState() {
    super.initState();
    print('super.initState');
    //注册回调
    _oneMethodChannel.setMethodCallHandler((call){
      print('收到onePage的创建回调');
      if(call.method == 'init'){//实例化
        setState(() {
          _pageName =  'onePage';
        });
      }
    });
    _twoMethodChannel.setMethodCallHandler((call){
      print('收到twoPage的创建回调');
      if(call.method == 'init'){//实例化
        setState(() {
          _pageName =  'twoPage';
        });
      }
    });

  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
      ),
      home: buildPage(_pageName),
    );
  }

  Widget buildPage(String pageName){
    switch(pageName){
      case 'onePage':{
        return Scaffold(
          appBar: AppBar(
            title: Text('onePage'),
          ),
          body: Center(
            child: IconButton(icon: Icon(Icons.add),onPressed: (){
              //回退页面事件
              _oneMethodChannel.invokeMethod('exit');
            },),
          ),
        );
      }
      break;
      case 'towPage' :{
        return Scaffold(
          appBar: AppBar(
            title: Text('TwoPage'),
          ),
          body: Center(
            child: IconButton(icon: Icon(Icons.add),onPressed: (){
              //回退页面事件
              _oneMethodChannel.invokeMethod('exit');
            },),
          ),
        );
      }
      break;
      default :{
        return Scaffold(
          appBar: AppBar(
            title: Text('defalutPage'),
          ),
          body: Center(
            child: Text('defalutPage'),
          ),
        );
      }
      break;
    }
  }
}

原生修改

//
//  ViewController.m
//  iOS_flutter
//
//  Created by jiangbin on 2021/5/25.
//

#import "ViewController.h"
#import <Flutter/Flutter.h>

@interface ViewController ()
@property (nonatomic , strong)FlutterEngine* flutterEngine;
@property (nonatomic , strong)FlutterViewController* flutterVC;
@end

@implementation ViewController

//懒加载
- (FlutterEngine*)flutterEngine{
    FlutterEngine* tmpFlutterEngine = [[FlutterEngine alloc] initWithName:@"ice"];
    if(tmpFlutterEngine.run){//提前启动引擎
        _flutterEngine = tmpFlutterEngine;
    }
    return _flutterEngine;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton* btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];
    btn.backgroundColor = [UIColor redColor];
    [btn setTitle:@"进入flutterPageOne页面" forState:UIControlStateNormal];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(pushOneFlutter) forControlEvents:UIControlEventTouchUpInside];
    
    
    UIButton* btn2 = [[UIButton alloc] initWithFrame:CGRectMake(100, 300, 200, 50)];
    btn2.backgroundColor = [UIColor redColor];
    [btn2 setTitle:@"进入flutterPageTwo页面" forState:UIControlStateNormal];
    [self.view addSubview:btn2];
    [btn2 addTarget:self action:@selector(pushTwoFlutter) forControlEvents:UIControlEventTouchUpInside];

    
    UIButton* btn3 = [[UIButton alloc] initWithFrame:CGRectMake(100, 500, 200, 50)];
    btn3.backgroundColor = [UIColor redColor];
    [btn3 setTitle:@"进入flutter默认页面" forState:UIControlStateNormal];
    [self.view addSubview:btn3];
    [btn3 addTarget:self action:@selector(pushDefaultFlutter) forControlEvents:UIControlEventTouchUpInside];

    self.flutterVC = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
    self.flutterVC.modalPresentationStyle = 0;
    
    // Do any additional setup after loading the view.
}
- (void)pushOneFlutter{
    FlutterMethodChannel* methodChannel = [FlutterMethodChannel methodChannelWithName:@"onePage" binaryMessenger:self.flutterVC];
    [methodChannel invokeMethod:@"init" arguments:nil];
    [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        if([call.method isEqualToString:@"exit"]){
            [self.flutterVC dismissViewControllerAnimated:YES completion:nil];
        }
    }];
    [self presentViewController:self.flutterVC animated:YES completion:nil];
}
- (void)pushTwoFlutter{
    FlutterMethodChannel* methodChannel = [FlutterMethodChannel methodChannelWithName:@"twoPage" binaryMessenger:self.flutterVC];
    [methodChannel invokeMethod:@"init" arguments:nil];
    [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        if([call.method isEqualToString:@"exit"]){
            [self.flutterVC dismissViewControllerAnimated:YES completion:nil];
        }
    }];
    [self presentViewController:self.flutterVC animated:YES completion:nil];
}
- (void)pushDefaultFlutter{
    FlutterViewController* VC = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];
    VC.modalPresentationStyle = 0;
    [self presentViewController:VC animated:YES completion:nil];
}

@end

run

53.png

点击进入对应的flutter页面

小结:

项目中不建议把引擎和flutterViewController当做属性,而应该当做一个单例来创建获取,一旦iOS工程引用了flutter文件后,会一直占用着内存,不要试图回收再重新创建,而是应该一直保留在项目内存中,避免出现原生页面切换之间的卡顿!!!!!

因此我们把它们抽出来:

#import "KFlutterEngineManager.h"

static FlutterEngine* g_flutterEndine = nil;

static FlutterViewController* g_flutterVC = nil;

@implementation KFlutterEngineManager

+ (void)start{
    [self shareEngine];
}

//项目启动 会执行一次此方法
+ (void)load{
    NSLog(@"KFlutterEngineManager");
    [self start];
}

+ (FlutterEngine*)shareEngine{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        FlutterEngine* tmpFlutterEngine = [[FlutterEngine alloc] initWithName:@"ice"];
        if(tmpFlutterEngine.run){//提前启动引擎
            g_flutterEndine = tmpFlutterEngine;
        }
    });
    return g_flutterEndine;
}


+ (FlutterViewController*)shareFlutterVC{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        g_flutterVC = [[FlutterViewController alloc] initWithEngine:[self shareEngine] nibName:nil bundle:nil];
        g_flutterVC.modalPresentationStyle = 0;

    });
    return g_flutterVC;
}
@end

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>

NS_ASSUME_NONNULL_BEGIN

@interface KFlutterEngineManager : NSObject
+ (FlutterViewController*)shareFlutterVC;
@end

相关文章

网友评论

      本文标题:第十五章:混合开发

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