本文簡要展示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>
);
}
}
複製程式碼
效果展示: