阅读

ReactNative与iOS交互通信

编者按: 跨平台开发在某些特殊情况下需要原生这边的支持,例如原生收到推送通知RN,或者需要传递数据给原生,例如保存图片数据到相册等,这就需要用到原生和RN的相互通讯交互的方法。



一、原生到RN

1. 从原生界面跳转到RN页面的方法

原生页面push一个ViewController即可,在新打开的ViewController中加入RCTRootView视图

jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"testStart"
initialProperties:nil
launchOptions:nil];
self.view = rootView;

2. 原生传递属性到RN

初始化的时候通过RCTRootView的初始化函数将任意属性传递给React Native应用。参数initialProperties必须是NSDictionary的一个实例。这一字典参数会在内部被转化为一个可供JS组件调用的JSON对象。

NSDictionary *props = @{@"images" : @"http://odu43tbrk.bkt.clouddn.com/nice.jpg"};
NSURL *jsCodeLocation;

jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"testStart"
initialProperties:props
launchOptions:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.view = rootView;

js文件获取参数并使用

import React, { Component } from 'react';
import {
AppRegistry,
View,
Image,
StyleSheet,

} from 'react-native';

var styles = StyleSheet.create({
thumb: {
width:80,
height:80,
marginRight:10,
marginTop: 100
},

});
class testStart extends Component {
render() {
console.log('props:'+this.props.images);
return (
<Image source = {{uri: this.props.images}} style={styles.thumb}/>
)
}
}

AppRegistry.registerComponent('testStart', () => testStart);

RCTRootView同样提供了一个可读写的属性appProperties。在appProperties设置之后,React Native应用将会根据新的属性重新渲染。当然,只有在新属性和之前的属性有区别时更新才会被触发。

坑1:appProperties使用会将初始化属性数组覆盖,并不是增加是覆盖

坑2:jsx中只能写表达式,不能写多行语句所以最佳循环写法是使用map函数,而不是for循环等

- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"RN View";

NSArray *imageList = @[@"http://odu43tbrk.bkt.clouddn.com/nice.jpg"];

NSDictionary *props = @{@"images" : imageList};
NSURL *jsCodeLocation;

jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"testStart"
initialProperties:props
launchOptions:nil];

NSArray *imageListMore = @[@"http://odu43tbrk.bkt.clouddn.com/nice.jpg",
@"http://odu43tbrk.bkt.clouddn.com/nice.jpg"];
rootView.appProperties = @{@"images" : imageListMore};
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.view = rootView;

// Do any additional setup after loading the view.
}

js对应属性接收代码

var styles = StyleSheet.create({
thumb: {
width:80,
height:80,
marginRight:10,
marginTop: 100
},
container: {
flex:1,
flexDirection:'row'
}

});
class testStart extends Component {

render() {
console.log('props length:' + this.props.images.length);

return (
<View style = {styles.container}>
{this.props.images.map(function(object, i){
console.log('prop:' + object);
return <Image source={{uri: object}} style={styles.thumb} key={i} />;
})}
</View>
)
}
}

3. 原生给RN发送事件

如果需要从iOS原生方法发送数据到JavaScript中,即使没有被JavaScript调用,本地模块也可以给JavaScript发送事件通知,可以使用eventDispatcher。**

首先需要引入:

#import "RCTEventDispatcher.h"

实现通知RN的方法

RCT_EXPORT_METHOD(VCOpenRN:(NSDictionary *)dictionary){
NSString *value=[dictionary objectForKey:@"name"];
if([value isEqualToString:@"marvin"]){
[self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder" body:@{@"name":[NSString stringWithFormat:@"%@",value],@"errorCode":@0,@"msg":@"成功"}];
}else{
[self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder" body:@{@"name":[NSString stringWithFormat:@"%@",value],@"errorCode":@1,@"msg":@"输入的name不是jiangqq"}];
}
}

sendAppEventWithName方法包含二个参数:

第一个参数:EventReminder自定义一个事件名称

第二个参数:具体传给RN的数据信息

JS实现如下

async _updateEvents(){
try{
var events=await RNBridgeModule.RNInvokeOCPromise({'name':'marvin'});
this.setState({events});
}catch(e){
this.setState({events:e.message});
}
}
componentDidMount(){
console.log('开始订阅通知...');
subscription = NativeAppEventEmitter.addListener(
'EventReminder',
(reminder) => {
let errorCode=reminder.errorCode;
console.log(reminder.errorCode);

if(errorCode==0){
console.log('errorCode 0' + reminder.name);

this.setState({msg:reminder.name});
}else{

console.log('errorCode not 0' + reminder.name);

this.setState({msg:reminder.msg});
}

}
);
}
componentWillUnmount(){
subscription.remove();
}

首先通过导入NativeAppEventEmitter模块,使用该模块在JavaScript代码中进行注册订阅相应的事件。然后我们在生命周期方法componentDidMount()方法中使用addListener()方法进行添加订阅事件。有订阅当然有取消订阅,所以我们在componentWillUnmount()方法中使用remove()方法进行取消即可。

千万不要忘记忘记取消订阅, 通常在componentWillUnmount函数中实现。

二、RN到原生

RN传递参数给原生,并访问调用原生方法

创建一个类名为XGRNBridgeModule作为RN和iOS/OC桥接模块。

1. 实现”RCTBridgeModule”协议

// .h文件
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"

@interface XGRNBridgeModule : NSObject<RCTBridgeModule>

@end

2. 实现RCT_EXPORT_MODULE()宏定义

括号参数不填为默认桥接类的名称,也可以自定义填写。该名称用来指定在JavaScript中访问这个模块的名称。

// .m文件
#import "XGRNBridgeModule.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"

@implementation XGRNBridgeModule
@synthesize bridge = _bridge;

RCT_EXPORT_MODULE(XGRNBridgeModule)

3. RN传参数给原生,并调用原生方法,通过CallBack返回数据给RN

如果原生的方法要被JavaScript进行访问,那么该方法需要使用RCT_EXPORT_METHOD()宏定义进行声明。

该声明的RNInvokeOCCallBack方法有两个参数:

第一个参数代表从JavaScript传过来的数据,

第二个参数是回调方法,通过该回调方法把原生信息发送到JavaScript中。

callback方法中传入一个参数数组,该数组的第一个参数为一个NSError对象,如果没有错误返回null,其余的数据作为该方法的返回值回调给JavaScript。

//RN传参数给原生,并调用原生OC,通过CallBack返回数据给RN
RCT_EXPORT_METHOD(RNInvokeOCCallBack:(NSDictionary *)dictionary callback:(RCTResponseSenderBlock)callback){
NSLog(@"接收到RN传过来的数据为:%@",dictionary);
RCTLogInfo(@"RCTLogInfo dictionary : %@", dictionary);

NSArray *events = [[NSArray alloc] initWithObjects:@"张三",@"李四", nil];

callback(@[[NSNull null], events]);
}

在JavaScript文件中进行定义导出在原生封装的模块,然后调用封装方法访问即可:

<CustomButton text='RN调用iOS原生方法_CallBack回调'
onPress={()=>{XGRNBridgeModule.RNInvokeOCCallBack(
{'name':'marvin','description':'dev'},
(error,events)=>{
if(error){
console.error(error);
}else{
this.setState({events:events});
}
})}}
/>

4. 另一种方法可以使用Promise进行交互和回调

//RN通过Promise传参数调用原生OC,并且返回数据给RN  
RCT_EXPORT_METHOD(RNInvokeOCPromise:(NSDictionary *)dictionary resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject){
NSLog(@"接收到RN传过来的数据为:%@",dictionary);
NSString *value=[dictionary objectForKey:@"name"];
if([value isEqualToString:@"marvin"]){
resolve(@"回调成功");
}else{
NSError *error=[NSError errorWithDomain:@"传入的name不符合要求,回调失败" code:100 userInfo:nil];
reject(@"100",@"传入的name不符合要求,回调失败",error);
}
}

RNInvokeOCPromise方法有三个参数:

dictionary:JavaScript传入的数据

resolve:成功,回调数据

reject:失败,回调数据

其中resove方法传入具体的成功信息即可,但是reject方法必须传入三个参数分别为:错误代码code ,错误信息message以及NSError对象。

5. RN调用

//获取Promise对象处理
async _updateEvents(){
try{
var events=await XGRNBridgeModule.RNInvokeOCPromise({'name':'marvin'});
this.setState({events});
}catch(e){
this.setState({events:e.message});
}
}

声明了async的异步函数内使用await关键字来调用,并等待其结果返回。(虽然这样写着看起来像同步操作,但实际仍然是异步的,并不会阻塞执行来等待)。


鼓励一下

如果觉得我的文章对您有用,请土豪扫右侧二维码打赏2块钱,帮我买杯咖啡,您的支持将鼓励我继续创作!”


关于Kovli Studio

Kovli Studio是本人全栈开发历程中部分作品的展示平台。作品从衣食住行娱乐等各角度为用户提供实用、高品质、高颜值、高性价比的品质生活方案,欢迎使用。