最近在要把IOS原生端的百度人脸离线采集SDK移植到React-Native上,就学习了IOS原生平台与RN之间的通信机制。做了一个Demo,现在把知识点梳理了一下,主要有以下两个:
(1)RN调用IOS原生平台的方法,并传递参数。
(2)IOS原生平台向RN发送事件,并传递参数。
Demo主要业务流程如下:
(1)RN端跳转到IOS原生页面(我们假设这个是人脸识别页面)。这一步就是实现RN调用IOS原生平台的方法,并传递参数。
(2)IOS原生页面主动向RN发送事件,模拟页面获取到人脸的数据,并传递到RN端。这一步就是实现IOS原生平台向RN发送事件,并传递参数。
一、在Xcode创建一个负责IOS原生平台与RN通信的类
这个通信类有两个作用:一是导出IOS原生平台的方法给RN调用;二是用于IOS原生页面主动向RN发送事件,传递数据。
创建这个通信类,有以下两个步骤:
1、与RN通信的类继承RCTEventEmitter(RCTEventEmitter用于向RN发送事件),实现RCTBridgeModule协议(这个协议使得RN可以调用IOS原生平台的方法)。
IOSFaceDetection.h代码
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
NS_ASSUME_NONNULL_BEGIN
// IOS原生端和RN端通信的接口
// 继承RCTEventEmitter,原生模块也可以给 JavaScript 发送事件通知。
// 最好的方法是继承RCTEventEmitter,实现suppportEvents方法并调用self sendEventWithName:
@interface IOSFaceDetection : RCTEventEmitter<RCTBridgeModule>
@end
NS_ASSUME_NONNULL_END
2、实现suppportEvents方法,这个方法返回一个RN回调事件的名称数组。有多少个事件,就写多少个名称。RN通过这些监听事件,从而实现IOS原生平台向RN发送事件。
RCT_EXPORT_METHOD导出一个方法给RN端调用,RN端调用的形式为:NativeModules.IOSFaceDetection.presentfFaceDetectionViewController([参数])。
IOSFaceDetection.m代码
#import "IOSFaceDetection.h"
#import "IOSFaceDetectionViewController.h"
#import "AppDelegate.h"
@implementation IOSFaceDetection
// RN的回调事件名称列表
-(NSArray<NSString *> *)supportedEvents{
return @[
@"onFaceLogin",
@"onFaceCollection",
@"onCloseFaceDetection"
];
}
// 为了实现RCTBridgeModule协议,你的类需要包含RCT_EXPORT_MODULE()宏。
RCT_EXPORT_MODULE();
-(dispatch_queue_t)methodQueue{
//因为是显示页面,所以让原生接口运行在主线程
return dispatch_get_main_queue();
}
// 导出方法给RN调用
RCT_EXPORT_METHOD(presentfFaceDetectionViewController:(int)type){
//创建人脸识别页面ViewController
IOSFaceDetectionViewController *faceDetectionViewController = [IOSFaceDetectionViewController new];
//参数赋值
faceDetectionViewController.type = type;
//记录faceDetection,用于向RN发送事件
faceDetectionViewController.faceDetection = self;
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// UIViewController *rootViewController = (UIViewController *)[[app.window] rootViewController];
//获取根视图控制器
UIViewController *rootViewController = app.window.rootViewController;
//跳转页面
[rootViewController presentViewController:faceDetectionViewController animated:YES completion:nil];
}
@end
二、IOS原生页面向RN端发生事件,传递参数
在第一步,创建了一个负责IOS原生平台与RN通信的类。接下来,我们需要在IOS原生页面获得这个通信类的实例对象,然后通过[sendEventWithName]向RN发送事件。具体看一下代码:
IOSFaceDetectionViewController.h 头文件代码:
#import <UIKit/UIKit.h>
#import "IOSFaceDetection.h"
#define TYPE_FACE_LOGIN 100 //人脸登录操作标识
#define TYPE_FACE_COLLECTION 200 //人脸采集操作标识
NS_ASSUME_NONNULL_BEGIN
@interface IOSFaceDetectionViewController : UIViewController
//在这里定义React Native传递过来的参数对象
// type操作类型
@property int type;
// 用于发送事件RN
@property(nonatomic,assign) IOSFaceDetection *faceDetection;
@end
NS_ASSUME_NONNULL_END
注意:在这个人脸识别原生页面类当中定义了一个IOSFaceDetection的属性,为什么要定义这个呢?答案是为了获取负责IOS与RN通信类的实例对象,在RN调用IOS原生平台方法跳转到人脸识别页面的时候,把这个通信类的实例对象存放到页面的属性当中(导出给RN调用的方法中赋值代码:faceDetectionViewController.faceDetection = self;),这样人脸识别页面就能通过这个实例对象向RN发送事件了。因为不能页面当中创建IOSFaceDetection类的实例来向RN发送事件,这样会导致APP闪退。
IOSFaceDetectionViewController.m代码:
#import "IOSFaceDetectionViewController.h"
#import "IOSFaceDetection.h"
@interface IOSFaceDetectionViewController ()
@end
// IOS原生人脸识别页面
@implementation IOSFaceDetectionViewController
//viewDidLoad方法是我们最常用的方法的,类中成员对象和变量的初始化我们都会放在这个方法中,在类创建后,无论视图的展现或消失,这个方法也是只会在将要布局时调用一次。
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"viewDidLoad");
self.view.backgroundColor = UIColor.whiteColor;
// Do any additional setup after loading the view.
//创建一个文本组件
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(20,20,200,60)];
//设置文字
textView.text = @"IOS原生人脸识别页面";
//设置字体大小
[textView setFont:[UIFont systemFontOfSize:20]];
//添加文件组件到页面当中
[self.view addSubview:textView];
//创建一个按钮组件
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = CGRectMake(20, 80, 60, 40);
[button setTitle:@"返回" forState:UIControlStateNormal];
[button addTarget:self action:@selector(closeAction) forControlEvents:UIControlEventTouchUpInside];
//添加按钮组件到页面当中
[self.view addSubview:button];
//创建一个按钮组件
UIButton *sendRNEventButton = [UIButton buttonWithType:UIButtonTypeSystem];
sendRNEventButton.frame = CGRectMake(20, 150, 100, 40);
[sendRNEventButton setTitle:@"回调RN事件" forState:UIControlStateNormal];
[sendRNEventButton addTarget:self action:@selector(sendEventToReactNative) forControlEvents:UIControlEventTouchUpInside];
//添加按钮组件到页面当中
[self.view addSubview:sendRNEventButton];
}
- (void)viewDidAppear:(BOOL)animated{
//[super viewDidAppear:animated];
// UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"操作类型标识:" message:[NSString stringWithFormat:@"%d",self.type] preferredStyle:UIAlertControllerStyleAlert];
NSString *message = self.type == TYPE_FACE_LOGIN ? @"人脸登录":@"人脸采集";
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"操作类型标识:" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
[alertController addAction:cancelAction];
//弹出对话框
[self presentViewController:alertController animated:YES completion:nil];
}
// viewWillDisappare 在视图变换时,当前视图在即将被移除、或者被覆盖时,会调用这个方法进行一些善后的处理和设置。
- (void)viewWillDisappear:(BOOL)animated{
[self.faceDetection sendEventWithName:@"onCloseFaceDetection" body:@{}];
}
- (void)closeAction {
//关闭页面
[self dismissViewControllerAnimated:YES completion:nil];
}
// 发送事件到RN
- (void)sendEventToReactNative{
// 不能通过实例化新的对象来发送事件到RN,这样会出错。
//IOSFaceDetection *faceDetection = [IOSFaceDetection new];
//[faceDetection sendEventWithName:@"onFaceLogin" body:@{@"faceBase64":@"人脸数据"}];
if(self.type == TYPE_FACE_LOGIN){
// 人脸登录回调
[self.faceDetection sendEventWithName:@"onFaceLogin" body:@{@"faceBase64":@"人脸登录-人脸数据"}];
}else if(self.type == TYPE_FACE_COLLECTION){
[self.faceDetection sendEventWithName:@"onFaceCollection" body:@{@"faceBase64":@"人脸采集-人脸数据"}];
}
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
在IOS原生人脸识别页面当中,通过按钮点击事件模拟了人脸识别回调事件,实现了IOS原生平台向RN发送事件,并传递参数。
三、RN端调用IOS原生平台的方法,并监听IOS原生平台的回调事件
IOS原生平台与RN的通信细节封装在一个模块里面,只提供业务方法给外部调用。
代码如下:
import {
NativeModules,
NativeEventEmitter
} from 'react-native';
const IOSFaceDetectionEmitter = new NativeEventEmitter(NativeModules.IOSFaceDetection);
/**
* Created by chenlw on 2019/03/21.
* React Native调用原生IOS平台的人脸识别模块。
*/
export default class IOSFaceDetection {
static TYPE_FACE_LOGIN = 100; //人脸登录标识
static TYPE_FACE_COLLECTION = 200; //人脸采集标识
/**
* 进入人脸登录页面
* @param callback
*/
static jumpFaceLoginPage = (callback) => {
IOSFaceDetectionListener.addFaceLoginListener(callback);
IOSFaceDetectionListener.addCloseFaceDetectionListener();
NativeModules.IOSFaceDetection.presentfFaceDetectionViewController(IOSFaceDetection.TYPE_FACE_LOGIN);
};
/**
* 进入人脸采集页面
* @param callback
*/
static jumpFaceCollectionPage = (callback) => {
IOSFaceDetectionListener.addFaceCollectionListener(callback);
IOSFaceDetectionListener.addCloseFaceDetectionListener();
NativeModules.IOSFaceDetection.presentfFaceDetectionViewController(IOSFaceDetection.TYPE_FACE_COLLECTION);
};
}
/**
* RN与IOS原生平台的通信细节封装在内部,只提供业务方法给外部调用。
*/
class IOSFaceDetectionListener {
static FACE_LOGIN_CALLBACK_NAME = 'onFaceLogin'; //人脸登录回调方法名称
static FACE_COLLECTION_CALLBACK_NAME = 'onFaceCollection'; //人脸采集回调方法名称
static CLOSE_FACE_DETECTION_CALLBACK_NAME = 'onCloseFaceDetection'; //关闭IOS人脸识别模块回调方法名称
/**
* 不赋值null,则static成员的初始值是undefine
* @type {null}
*/
static faceLoginCallback;
static faceCollectionCallback;
static faceLoginSubscription;
static faceCollectionSubscription;
static closeFaceDetectionSubscription;
///人脸登录
static addFaceLoginListener = (callback) => {
if (!IOSFaceDetectionListener.faceLoginCallback) {
console.log('');
console.log('addFaceLoginListener');
IOSFaceDetectionListener.faceLoginCallback = callback;
//监听IOS原生端发送的事件
IOSFaceDetectionListener.faceLoginSubscription = IOSFaceDetectionEmitter.addListener(
IOSFaceDetectionListener.FACE_LOGIN_CALLBACK_NAME,
IOSFaceDetectionListener.onFaceLogin
);
}
};
static onFaceLogin = (params) => {
console.log('');
console.log('onFaceLogin');
console.log(params);
IOSFaceDetectionListener.faceLoginCallback && IOSFaceDetectionListener.faceLoginCallback(params.faceBase64);
};
///人脸采集
static addFaceCollectionListener = (callback) => {
if (!IOSFaceDetectionListener.faceCollectionCallback) {
console.log('');
console.log('addFaceCollectionListener');
IOSFaceDetectionListener.faceCollectionCallback = callback;
//监听IOS原生端发送的事件
IOSFaceDetectionListener.faceCollectionSubscription = IOSFaceDetectionEmitter.addListener(
IOSFaceDetectionListener.FACE_COLLECTION_CALLBACK_NAME,
IOSFaceDetectionListener.onFaceCollection
);
}
};
static onFaceCollection = (params) => {
console.log('');
console.log('onFaceCollection');
console.log(params);
IOSFaceDetectionListener.faceCollectionCallback && IOSFaceDetectionListener.faceCollectionCallback(params.faceBase64);
};
static addCloseFaceDetectionListener = () => {
if (!IOSFaceDetectionListener.closeFaceDetectionSubscription) {
console.log('');
console.log('addCloseFaceDetectionListener');
//监听IOS原生端发送的事件
IOSFaceDetectionListener.closeFaceDetectionSubscription = IOSFaceDetectionEmitter.addListener(
IOSFaceDetectionListener.CLOSE_FACE_DETECTION_CALLBACK_NAME,
IOSFaceDetectionListener.onCloseFaceDetection
);
}
};
/**
* 从IOS原生端关闭人脸识别界面时,要移除相关监听。这个通过IOS原生页面生命周期方法实现。
*/
static onCloseFaceDetection = (params) => {
console.log('');
console.log('onCloseFaceDetection');
console.log(params);
IOSFaceDetectionListener.removeAllListener();
};
static removeAllListener = () => {
if (IOSFaceDetectionListener.faceLoginSubscription) {
IOSFaceDetectionListener.faceLoginSubscription.remove();
IOSFaceDetectionListener.faceLoginSubscription = null;
IOSFaceDetectionListener.faceLoginCallback = null;
}
if (IOSFaceDetectionListener.faceCollectionSubscription) {
IOSFaceDetectionListener.faceCollectionSubscription.remove();
IOSFaceDetectionListener.faceCollectionSubscription = null;
IOSFaceDetectionListener.faceCollectionCallback = null;
}
if (IOSFaceDetectionListener.closeFaceDetectionSubscription) {
IOSFaceDetectionListener.closeFaceDetectionSubscription.remove();
IOSFaceDetectionListener.closeFaceDetectionSubscription = null;
}
};
}
RN测试页面代码:
import React, {Component} from 'react';
import {StyleSheet, Text, View, Button} from 'react-native';
import IOSFaceDetection from './IOSFaceDetection';
export default class IOSFaceDetectionExample extends Component {
static navigationOptions = {
title: 'IOS人脸离线采集',
};
constructor(props, context) {
super(props, context);
}
render() {
return (
<View style={styles.container}>
<Button title={'人脸登录'} onPress={() => {
IOSFaceDetection.jumpFaceLoginPage((faceBase64) => {
alert(faceBase64);
});
}}/>
<Button title={'人脸采集'} onPress={() => {
IOSFaceDetection.jumpFaceCollectionPage((faceBase64) => {
alert(faceBase64);
});
}}/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
});
到这里,就成功实现IOS原生平台与RN双向的事件调用和数据传递了。