ReactNative与iOS的交互
本文简要展示RN与iOS原生的交互功能。
1.1 RCTRootView初始化问题
/**
* - Designated initializer -
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;
/**
* - Convenience initializer -
* A bridge will be created internally.
* This initializer is intended to be used when the app has a single RCTRootView,
* otherwise create an `RCTBridge` and pass it in via `initWithBridge:moduleName:`
* to all the instances.
*/
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions;
1、当Native APP内只有一处RN的入口时,可以使用
initWithBundleURL
,否则的话就要使用initWithBridge
方法。
2、因为initWithBundleURL
会在内部创建一个RCTBridge
,当有多个RCTRootView
入口时,就会存在多个RCTBridge
,容易导致Native端与RN交互时多次响应,出现BUG。
1.2 创建自定义的RNBridgeManager
由于APP内有RN多入口的需求,所以共用一个
RCTBridge
RNBridgeManager.h
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
NS_ASSUME_NONNULL_BEGIN
@interface RNBridgeManager : RCTBridge
/**
RNBridgeManager单例
*/
+ (instancetype)sharedManager;
@end
NS_ASSUME_NONNULL_END
RNBridgeManager.m
#import "RNBridgeManager.h"
#import <React/RCTBundleURLProvider.h>
//dev模式下:RCTBridge required dispatch_sync to load RCTDevLoadingView Error Fix
#if RCT_DEV
#import <React/RCTDevLoadingView.h>
#endif
/**
自定义类,实现RCTBridgeDelegate
*/
@interface BridgeHandle : NSObject<RCTBridgeDelegate>
@end
@implementation BridgeHandle
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge{
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
}
@end
@implementation RNBridgeManager
+ (instancetype)sharedManager{
static RNBridgeManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[RNBridgeManager alloc] initWithDelegate:[[BridgeHandle alloc] init] launchOptions:nil];
#if RCT_DEV
[manager moduleForClass:[RCTDevLoadingView class]];
#endif
});
return manager;
}
@end
1.3 Native进入RN页面
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:[RNBridgeManager sharedManager] moduleName:@"RNTest" initialProperties:nil];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self.navigationController pushViewController:vc animated:YES];
1.4 RN调用Native方法
- 创建一个交互的类,实现
<RCTBridgeModule>
协议; - 固定格式:在.m的实现中,首先导出模块名字
RCT_EXPORT_MODULE();
,RCT_EXPORT_MODULE
接受字符串作为其Module的名称,如果不设置名称的话默认就使用类名作为Modul的名称; - 使用
RCT_EXPORT_METHOD
导出Native的方法;
1.4.1 比如我们导出Native端的SVProgressHUD
提示方法:
RNInterractModule.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
NS_ASSUME_NONNULL_BEGIN
@interface RNInterractModule : NSObject<RCTBridgeModule>
@end
NS_ASSUME_NONNULL_END
RNInterractModule.m
import "RNInterractModule.h"
#import "Util.h"
#import <SVProgressHUD.h>
@implementation RNInterractModule
////RCT_EXPORT_MODULE接受字符串作为其Module的名称,如果不设置名称的话默认就使用类名作为Modul的名称
RCT_EXPORT_MODULE();
//==============1、提示==============
RCT_EXPORT_METHOD(showInfo:(NSString *) info){
dispatch_sync(dispatch_get_main_queue(), ^{
[SVProgressHUD showInfoWithStatus:info];
});
}
@end
1.4.2 RN端调用导出的showInfo方法:
我们在RN端把Native的方法通过一个共同的utils工具类引入,如下
import { NativeModules } from 'react-native';
//导出Native端的方法
export const { showInfo} = NativeModules.RNInterractModule;
具体的RN页面使用时:
import { showInfo } from "../utils";
//通过Button点击事件触发
<Button
title='1、调用Native提示'
onPress={() => showInfo('我是原生端的提示!')}
/>
调用效果:
1.4.3 RN回调Native
RN文档显示,目前iOS端的回调还处于实验阶段
我们提供一个例子来模拟:目前的需求是做面包,RN端能提供面粉,但是不会做,Native端是有做面包的功能;所以我们需要先把面粉,传给Native端,Native加工好面包之后,再通过回调回传给RN端。
Native端提供方法
// 比如调用原生的方法处理图片、视频之类的,处理完成之后再把结果回传到RN页面里去
//TODO(RN文档显示,目前iOS端的回调还处于实验阶段)
RCT_EXPORT_METHOD(patCake:(NSString *)flour successBlock:(RCTResponseSenderBlock)successBlock errorBlock:(RCTResponseErrorBlock)errorBlock){
__weak __typeof(self)weakSelf = self;
dispatch_sync(dispatch_get_main_queue(), ^{
NSString *cake = [weakSelf patCake:flour];
//模拟成功、失败的block判断
if([flour isKindOfClass:[NSString class]]){
successBlock(@[@[cake]]);//此处参数需要放在数组里面
}else{
NSError *error = [NSError errorWithDomain:@"com.RNTest" code:-1 userInfo:@{@"message":@"类型不匹配"}];
errorBlock(error);
}
});
}
//使用RN端传递的参数字符串:"",调用Native端的做面包方法,加工成面包,再回传给RN
- (NSString *)patCake:(NSString *)flour{
NSString * cake = [NSString stringWithFormat:@"使用%@,做好了:🎂🍞🍞🍰🍰🍰",flour];
return cake;
}
RN端调用:
//首先工具类里先引入
export const { showInfo,patCake } = NativeModules.RNInterractModule;
//具体页面使用
<Button
title='4、回调:使用面粉做蛋糕'
onPress={() => patCake('1斤面粉',
(cake) => alert(cake),
(error) => alert('出错了' + error.message))}
/>
调用效果:
1.4.4 使用Promise回调
Native端提供方法
RCT_EXPORT_METHOD(callNameTointroduction:(NSString *)name resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock) reject){
__weak __typeof(self)weakSelf = self;
dispatch_sync(dispatch_get_main_queue(), ^{
if ([name isKindOfClass:NSString.class]) {
resolve([weakSelf introduction:name]);
}else{
NSError *error = [NSError errorWithDomain:@"com.RNTest" code:-1 userInfo:@{@"message":@"类型不匹配"}];
reject(@"class_error",@"Needs NSString Class",error);
}
});
}
- (NSString *)introduction:(NSString *)name{
return [NSString stringWithFormat:@"我的名字叫%@,今年18岁,喜欢运动、听歌...",name];
}
RN端调用:
//首先工具类里先引入
export const { showInfo,patCake, callNameTointroduction} = NativeModules.RNInterractModule;
//具体页面使用
<Button
title='5、Promise:点名自我介绍'
onPress={
async () => {
try {
let introduction = await callNameTointroduction('小明');
showInfo(introduction);
} catch (e) {
alert(e.message);
}
}
}
/>
调用效果:
1.5 Native端发送通知到RN
Native端继承RCTEventEmitter
,实现发送RN通知类:
RNNotificationManager.h
#import <Foundation/Foundation.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTBridgeModule.h>
NS_ASSUME_NONNULL_BEGIN
@interface RNNotificationManager : RCTEventEmitter
+ (instancetype)sharedManager;
@end
NS_ASSUME_NONNULL_END
RNNotificationManager.m
#import "RNNotificationManager.h"
@implementation RNNotificationManager
{
BOOL hasListeners;
}
+ (instancetype)sharedManager{
static RNNotificationManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
manager = [[self alloc] init];
});
return manager;
}
- (instancetype)init{
self = [super init];
if (self) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self];
[center addObserver:self selector:@selector(handleEventNotification:) name:@"kRNNotification_Login" object:nil];
[center addObserver:self selector:@selector(handleEventNotification:) name:@"kRNNotification_Logout" object:nil];
};
return self;
}
RCT_EXPORT_MODULE()
- (NSArray<NSString *> *)supportedEvents{
return @[
@"kRNNotification_Login",
@"kRNNotification_Logout"
];
}
//优化无监听处理的事件
//在添加第一个监听函数时触发
- (void)startObserving{
//setup any upstream listenerse or background tasks as necessary
hasListeners = YES;
NSLog(@"----------->startObserving");
}
//will be called when this mdules's last listener is removed,or on dealloc.
- (void)stopObserving{
//remove upstream listeners,stop unnecessary background tasks.
hasListeners = NO;
NSLog(@"----------->stopObserving");
}
+ (BOOL)requiresMainQueueSetup{
return YES;
}
- (void)handleEventNotification:(NSNotification *)notification{
if (!hasListeners) {
return;
}
NSString *name = notification.name;
NSLog(@"通知名字-------->%@",name);
[self sendEventWithName:name body:notification.userInfo];
}
@end
RN端注册监听:
//utils工具类中导出
export const NativeEmitterModuleIOS = new NativeEventEmitter(NativeModules.RNNotificationManager);
//具体页面使用
import { NativeEmitterModuleIOS } from "../utils";
export default class ActivityScene extends Component {
constructor(props) {
super(props);
this.subscription = null;
this.state = {
loginInfo: '当前未登录',
};
}
updateLoginInfoText = (reminder) => {
this.setState({loginInfo: reminder.message})
};
//添加监听
componentWillMount() {
this.subscription = NativeEmitterModuleIOS.addListener('kRNNotification_Login', this.updateLoginInfoText);
}
//移除监听
componentWillUnmount() {
console.log('ActivityScene--------->', '移除通知');
this.subscription.remove();
}
render() {
return (
<View style={{flex: 1, backgroundColor: 'white'}}>
<Button
title='3、RN Push到Native 发送通知页面'
onPress={() => pushNative(RNEmitter)}
/>
<Text style={{fontSize: 20, color: 'red', textAlign: 'center',marginTop:50}}>{this.state.loginInfo}</Text>
</View>
);
}
}
效果展示:
1.6 完整Demo(包含iOS & Android)
作者:BulldogX
链接:https://juejin.cn/post/6844903744061046791
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。