本文概要
大家好我是Joney,在我的职业生涯中 曾经做过RN的开发(从0到1到上架的研发)。深有体会:“如果你不会一些Native的东西,这里的水很深,玩RN建议一定是要懂一些Native的东西(Android/IOS)”。在RN的官方文档中,虽然是说了如何去集成Native的模块,但是!它只说了你要如何如何写,却没有一个完整的示例。因此本文从RN在哪里OS(OC语法)角度出发,分享我使用RN时的IOS集成方法。
重要提示 🚗,本文基于RN 0.70.1, 对于 最新的RN 目前官方还在推他们的新设计,TurboModule 但是还在试验阶段,等它稳定之后我们再去讨论,
文章主要脉络如下 1.工程化工具bod🔧 -->
2.仅仅是集成js部分 --->
3.集成UI组件 (地图) --->
4.集成Native的其他模块比如Native的多进程在OC中就是GCD
重要提醒!:请不要照着文章照抄,建议你先阅读通篇,了解全貌之后再去实践。
react-native-builder-bob
这个是RN官方的推荐,bob脚手架工具,它能够方便的快速进行React-native-Native-module集成,现在我们按照下面👇的步骤去init一个Native模块,当然了,我们的这个也是从github拿过来的,如果你不想看github的官方英文说明,参考我的这个也是没有问题的
简单的尝试一下
1.直接使用CLI找一个空的文件夹进行创建
cd ~/Desktop
mkdir Project
cd Project
npx create-react-native-library react-native-awesome-module
# 接下来就是各种选择了 按照提示来就好了,我这里选择的是Native module的package是java和Objective-C
- 结束之后我们去yarn它就好了
cd ./react-native-awesome-module
npx react-native-builder-bob init
# 或者直接 yarn 也是可以的
# 在packageJson下我们发现有许许多多的配置,这些在bob官方的文档都有详细的说明这里不展开,我们看看开发流程
# 1,yarn 安装依赖
yarn
# 2. yarn watch可以在开发的时候自动的构建(这里有坑我们一会儿说)
yarn watch
# 3. ts构建和lint
yarn typescript
yarn lint
# 额外的我经常使用 下面命令在开发期间去更新 构建的包
yarn prepare
- 使用Xcode我们去看看bob默认的的Xcode工程
我们前往文件./IOS ,然后打开这个文件 “AwesomeModule.xcodeproj”
然后在xocde下就能看到这个工程了,接下来的内容是Objecttiv-C(以下简称OC)的知识了,一般的OC中的class(类,也叫OC类 其实就是OOP中的类这个概念而已)都由两个文件构成 一个头文件(声明类)一般是同名的.h文件(我说的是一般,但是也有另类的同学,他把声明和实现揉杂一个文件中,如果你发现了,请你削它!🙃🙃) ,一个是.m实现的文件,举个例子,在bob默认IOS工程下, 会有一个 AwesomeModule.h 和,m文件
// h文件
#import <React/RCTBridgeModule.h>
@interface AwesomeModule : NSObject <RCTBridgeModule>
// 它需要是一个RCTBridgeModule类型的类,因为这个是RN包装类一下,不是纯的OC类
@end
// .m
#import "AwesomeModule.h" // 导入头文件
// 开始实现这个class
@implementation AwesomeModule
// 这个需这样写RN要求的,表示这个是一个Native Module 到时候,
// 在indx.tsx中就是NativeModules.AwesomeModule
RCT_EXPORT_MODULE()
// 这个是RN要求的,表示这是一个Nativee Module中的Method ,
// 这个方法接受ab两个值返回 a - b的 结果
RCT_REMAP_METHOD(multiply,
multiplyWithA:(**nonnull** NSNumber*)a withB:(**nonnull** NSNumber*)b
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
{
NSNumber *result = @([a floatValue] - [b floatValue]);
resolve(result);
}
@end
4.进行调试
我们现在启动项目,前往/expempl文件下,如同一般的RN的项目(以下称为RNE)一样去启动它 (yarn 然后 cd ios && pod install ) 最后使用Xcode打开里面的workspace文件,就能看到一个标准的额RN工程了
cd ./expmale && yarn
cd ./ios && pod install
# 启动开发服务器
cd .. && yarn start
# 如果你的yanr ios无效的话,请手动去xcode build它
在bob生产的工程结构下,我们把这个Native Module项目分成两个部分去看,一个是Native lib的这个库的本身,一个是example示例项目。Naitve lib 中又可以分成两个部分来看 一个是IOS的一个Android的,其中每个部分又可以划分为更加详细的模块即UI组件和功能组件。本文只介绍了IOS的集成不涉及Android。有关于Android的部分内容会在另一篇文章放出,一通百通原理是一样的
接下来我们来看./src/index.tsc
import { NativeModules, Platform, NativeEventEmitter } from 'react-native';
import { requireNativeComponent } from 'react-native';
import React from 'react';
const LINKING_ERROR =
`The package 'react-native-awesome-module' doesn't seem to be linked. Make sure: \n\n` +
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
'- You rebuilt the app after installing the package\n' +
'- You are not using Expo managed workflow\n';
const AwesomeModule = NativeModules.AwesomeModule
? NativeModules.AwesomeModule
: new Proxy(
{},
{
get() {
throw new Error(LINKING_ERROR);
},
}
);
// 我们先不看上面的,我们看看下面的这方法 multiply 它返回 AwesomeModule.multiply(a, b);的掉用
// 实际上它就是前面的OC中的 multiply方法
export function multiply(a: number, b: number): Promise<number> {
return AwesomeModule.multiply(a, b);
}
特别注意 ⚠️,由于我的开发环境 默认的port 不能在8081启动于是我换成了8083,尽管你start --port=8083 但是在oc中不会自动给你设置,因此你需要在IOS文件夹中手动全局搜索 和替换。
然后你在expmle中就看到下面的代码,并且能够计算得出结果 ./expmle/src/index.tsx
import * as React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { multiply } from 'react-native-awesome-module'; //这个就是你lib包
export default function App() {
const [result, setResult] = React.useState<number | undefined>();
React.useEffect(() => {
multiply(3, 7).then(setResult);
}, []);
return (
<View style={styles.container}>
<Text>Result: {result}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 60,
height: 60,
marginVertical: 20,
},
});
我如何调试?
这里我们说的调试是指 如何把这个lib 想npm 的像他lib 一样,在别的项目中使用,我们希望能够使用本地link的方式,在项目中link到我们的正在开发的这个lib,从而进行调试。在这个过程中我们可能会遇到各种问题,接下来我们都来看看到底会遇到哪些奇怪的问题
- 新run 一个RN项目(以下把这个项目称为RNP);然后把我们正在开发中的库 link过去(当然你可以是使用yarn link 或者npm link )这里我在yarn link之后 就直接修改地址了哈,接下来,请按照RN的启动方式启动这个RN项目
// Lib项目终端
yarn link
// RNP项目
"dependencies": {
"@react-navigation/bottom-tabs": "^6.0.5",
"@react-navigation/native": "^6.0.2",
"@react-navigation/native-stack": "^6.1.0",
"react": "17.0.1",
"react-native": "0.64.1",
"react-native-safe-area-context": "^3.3.2",
"react-native-screens": "^3.7.2",
"react-native-vector-icons": "^8.1.0",
"react-native-awesome-module": "/Users/lishizeng/Desktop/origin/react-native-native-module"
....
}
后续操作
yarn
# 由于我们改变了新增了native模块因此我们需要去ios下进行 pod install 操作才行
# 然后去启动就好了
- 观察RNP 和 RNE 以及 Lib 的IOS工程的这个文件夹(下图)
可以看到 我们的这个 native 模块是通过 IOS中的一种叫做:“静态库”的方式导入到IOS工程中去的,我们如何确保我们的库更新了呢,主要就是看这个地方的依赖是否更新了。👇将会详细的说明
- 我们修改Lib的IOS工程之后,在RNE文件中如何更新呢? 比如我这里修改成了 * 法 不是减法了,
NSNumber *result = @([a floatValue] * [b floatValue]);
我们遵循下面的方式
-> 在Lib IOS的工程中 点击Build 按钮让他构建一次。
-> 去到lib的终端执行 yarn prepare
-> 在RNE项目中的IOS工程中 点击Build按钮重新构建一次
-> 完成✅ ,这样你的Native的静态库就变成最新的了
- 我们修改Lib的IOS工程之后,在RNP文件中如何更新呢? 和上面的道理是一样的,但是需要多一个步骤
-> 在Lib IOS的工程中 点击Build 按钮让他构建一次。
-> 去到lib的终端执行 yarn prepare
-> 在RNE项目中的IOS工程中 点击Build按钮重新构建一次 (这一步可以去掉如果你不想理RNE)
-> 来到RNP 项目的IOS中 进行pod update 操作就好了
深入
我们简单的尝试过后接下来我们将会深入,注意哈我默认你会OC且具备OC基础。
我会按照下面的知识脉络去介绍 (第一第二部分或许与上面的内容有点重复,但是为了梳理脉络我决定再写一遍)
- 初始化设置
- 普通的 调用(不需要异步支持)
- 异步的调用
- Observer 和广播📢
- Native组件的生命周期
- 集成Native UI
初始化设置
- 对于Native
对于Native 来说,下面的这些步骤是必须的
- 创建对应的文件
IOS的话就是 .h .m 和我们上文说过的一样 并没有什么区别
- 实现对应的class,只有实现了才能把东西暴露给JS
// RCTCalendarModule.h
#import <React/RCTBridgeModule.h>
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
@end
// RCTBridgeModule 是需要传入的泛型,如果你有其他的Native要求
// 可以传递其他的泛型
// RCTCalendarModule.m
#import "RCTCalendarModule.h"
@implementation RCTCalendarModule
// To export a module named RCTCalendarModule
// 必须要实现的 当然可以在里面传递一些 别名那么JS就会以别名为主 呀不然就是class Name (要删除“RCT" or "RK"后缀)
// RCT_EXPORT_MODULE();
RCT_EXPORT_MODULE(CalendarModuleFoo)
@end
- 对于js
// 很简单
const {CalendarModuleFoo} = ReactNative.NativeModules;
- 依赖相关
React Native将自动创建并初始化任何注册的本机模块。但是,您可能希望创建并初始化自己的模块实例,例如注入依赖项。
您可以通过创建实现rctbridgedElegate协议的类来做到这一点,并以代表为参数初始化rctbridge,并用初始化的桥初始化rctrootview。
id<RCTBridgeDelegate> moduleInitialiser = [[classThatImplementsRCTBridgeDelegate alloc] init];
// classThatImplementsRCTBridgeDelegate 就是你自己的实现 RCTBridgeDelegate的 class
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:nil];
RCTRootView *rootView = [[RCTRootView alloc]
initWithBridge:bridge
moduleName:kModuleName
initialProperties:nil];
注意哈 你写完之后需要按照bob 提供的CLI 进行一定的操作能够关联到 本地RNE项目或者RNP项目中去,具体的方法我在上文有说明。
以上初始化就完成了
普通的调用
现在我们介绍一下 普通的方法的调用(一般来说我们认为它是同步的,但是RN的通信实际上并不是完全的同步)
- Native
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
关于同步方法,RN官方还提供了一个 可以实现同步的方法,但是RN不推荐使用。因为同步调用方法可能会带来严重的性能损失,并且会在本机模块中引入与线程相关的 bug。
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
{
return [[UIDevice currentDevice] name];
}
- JS
import React from 'react';
import {NativeModules, Button} from 'react-native';
const {CalendarModule} = NativeModules;
const NewModuleButton = () => {
const onPress = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};
return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};
export default NewModuleButton;
- 关于参数的类型和传递的问题
JS和Native是两种不同的东西,传递参数的时候 就需要注意了这些参数类型是如何转化的
下面是JS到IOS的类型一览图
OBJECTIVE-C | JAVASCRIPT |
---|---|
NSString | string, ?string |
BOOL | boolean |
double | number |
NSNumber | ?number |
NSArray | Array, ?Array |
NSDictionary | Object, ?Object |
RCTResponseSenderBlock | Function (success) |
RCTResponseSenderBlock, RCTResponseErrorBlock | Function (failure) |
RCTPromiseResolveBlock, RCTPromiseRejectBlock | Promise |
JS 我们都很熟悉了 我们来唠一下 IOS中的一个例子🌰
import { NativeModules } from 'react-native';
const {CalendarModule} = NativeModules;
// 调用原生模块的方法,并传递参数
CalendarModule.processData('hello', true, 123, [1, 2, 3], { key: 'value' })
.then(result => {
console.log('Received result from native:', result);
})
.catch(error => {
console.error('Error:', error);
});
#import <React/RCTBridgeModule.h>
@interface CalendarModule : NSObject <RCTBridgeModule>
@end
@implementation CalendarModule
// ++++
RCT_EXPORT_METHOD(processData:(NSString *)string
boolParam:(BOOL)boolParam
numberParam:(NSNumber *)numberParam
arrayParam:(NSArray *)arrayParam
dictionaryParam:(NSDictionary *)dictionaryParam
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
// 解析并处理 JS 传递来的参数
NSLog(@"Received string: %@", string);
NSLog(@"Received boolean: %d", boolParam);
NSLog(@"Received number: %@", numberParam);
NSLog(@"Received array: %@", arrayParam);
NSLog(@"Received dictionary: %@", dictionaryParam);
// 模拟处理逻辑,返回结果给 JS
NSDictionary *result = @{@"message": @"Data processed successfully"};
resolve(result);
}
@end
- 如果你要数据给js 注意使用 RCTConvert
#import <React/RCTConvert.h>
#import <React/RCTBridgeModule.h>
@interface CalendarModule : NSObject <RCTBridgeModule>
@end
@implementation CalendarModule
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(processData:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
// 构建对象
NSDictionary *object = @{@"key": @"value"};
// 构建数组
NSArray *array = @[@"item1", @"item2"];
// 将对象和数组转换为 JavaScript 中的对象和数组
NSDictionary *result = @{
@"object": [RCTConvert NSDictionary:object],
@"array": [RCTConvert NSArray:array]
};
// 通过 resolve 方法返回结果给 JavaScript
resolve(result);
}
@end
- 如果你希望获取Native定义的常量 可以这样使用
- (NSDictionary *)constantsToExport
{
return @{ @"DEFAULT_EVENT_NAME": @"New Event" };
}
const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);
异步调用
对于 Native 的异步方法, 有两种方式实现,1谁callback 2是promise
- callback
RCT_EXPORT_METHOD(createCalendarEvent:
(NSString *) title
location:(NSString *)location
callback: (RCTResponseSenderBlock)callback
)
{
NSNumber *eventId = [NSNumber numberWithInt:123];
callback(@[[NSNull null], eventId]);
}
const onPress = () => {
CalendarModule.createCalendarEventCallback(
'testName',
'testLocation',
(error, eventId) => {
if (error) {
console.error(`Error found! ${error}`);
}
console.log(`event id ${eventId} returned`);
},
);
};
我们还有另一种形式
RCT_EXPORT_METHOD(createCalendarEventCallback:(NSString *)title
location:(NSString *)location
errorCallback: (RCTResponseSenderBlock)errorCallback
successCallback: (RCTResponseSenderBlock)successCallback)
{
@try {
NSNumber *eventId = [NSNumber numberWithInt:123];
successCallback(@[eventId]);
}
@catch ( NSException *e ) {
errorCallback(@[e]);
}
}
const onPress = () => {
CalendarModule.createCalendarEventCallback(
'testName',
'testLocation',
error => {
console.error(`Error found! ${error}`);
},
eventId => {
console.log(`event id ${eventId} returned`);
},
);
};
- promise
这个就比较简单了,我一直在用它
RCT_EXPORT_METHOD(createCalendarEvent:
(NSString *)title
location:(NSString *)location
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSInteger eventId = createCalendarEvent();
if (eventId) {
resolve(@(eventId));
} else {
reject(@"event_failure", @"no event id returned", nil);
}
}
const onSubmit = async () => {
try {
const eventId = await CalendarModule.createCalendarEvent(
'Party',
'my house',
);
console.log(`Created a new event with id ${eventId}`);
} catch (e) {
console.error(e);
}
};
observer和广播
这里描述的是 Native向 JS 发送的广播的具体的实现。比如 通知 消息什么的。
在官方的文档中只有一种,但是实际上有两种。下面我们来看看
- 模块自己的observer
// 我们在.h 头文件还需要实现它的一个 RCTBridgeModule接口 才能使用 这个功能
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarModule : RCTEventEmitter <RCTBridgeModule>
@end
// 然后就可以使用了
@implementation CalendarModule
{
bool hasListeners;
}
// 这里定义了你想要发送的事件名称
- (NSArray<NSString *> *)supportedEvents {
return @[@"EventReminder"];
}
-(void)startObserving {
hasListeners = YES;
// Set up any upstream listeners or background tasks as necessary
}
// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
hasListeners = NO;
// Remove upstream listeners, stop unnecessary background tasks
}
- (void)calendarEventReminderReceived:(NSNotification *)notification
{
NSString *eventName = notification.userInfo[@"name"];
if (hasListeners) {
// Only send events if anyone is listening
[self sendEventWithName:@"EventReminder" body:@{@"name": eventName}];
}
}
import { NativeModules, NativeEventEmitter } from 'react-native';
const {CalendarModule} = NativeModules;
const myEventEmitter
= new NativeEventEmitter( CalendarModule );
const subscription
= myEventEmitter.addListener( 'EventReminder', (reminder) => console.log(reminder.name));
//别忘了取消订阅,通常在componentWillUnmount生命周期方法中实现。
subscription.remove();
- 全局的observer
在上述中我们可以看到这样的代码:NativeEventEmitter( CalendarManager ); 我们在创建observer 的时候是具体到 某一个模块的。其实RN有一个全局的DeviceEventEmitter
// MyEventEmitter.h
#import <React/RCTEventEmitter.h>
@interface MyEventEmitter : RCTEventEmitter
+ (void)sendEventToJS;
@end
// MyEventEmitter.m
#import "MyEventEmitter.h"
@implementation MyEventEmitter
RCT_EXPORT_MODULE();
+ (void)sendEventToJS {
NSDictionary *eventData = @{@"key": @"value"}; // 事件数据
[self sendEventWithName:@"EventName" body:eventData];
}
@end
import { DeviceEventEmitter } from 'react-native';
DeviceEventEmitter.addListener('EventName', event => {
console.log('Received event:', event);
});
- 多线程 GCD
你可以开业使用 observer 或者 promise 都可以
// 多线程 (返回的是一个promise)
RCT_EXPORT_METHOD(doSomethingExpensive:
(NSString *)param
callback:(RCTResponseSenderBlock)callback)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Call long-running code on background thread
...
// You can invoke callback from any thread/queue
callback(@[...]);
});
}
集成 Native UI Component
按照官方的文档来说,我们目前先集成 一个UI地图组件(MKMapView这个组件是UIKit自己的)
- 首先新增一个OC-class (OC基础知识这里不展开) 然后写入如下的代码
// RNTMapManager.m (这里我们只有m文件没有h文件哈,我们的h文件都哦书在这里一起声明的)
#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>
// 声明头文件
@interface RNTMapManager : RCTViewManager
@end
// 类 实现
@implementation RNTMapManager
RCT_EXPORT_MODULE(RNTMap)
- (UIView *)view
{
return [[MKMapView alloc] init];
}
@end
- 在lib的js文件中去获取它(但是在我的仓库中弄 我是在 indx.tsx写的,再导出 是一样的效果)
// MapView.js
import { requireNativeComponent } from 'react-native';
// requireNativeComponent 自动把'RNTMap'解析为'RNTMapManager'
coonst RNTMap = requireNativeComponent('RNTMap');
const MapKitVIew: React.FC<any> = (props) => {
return (
<RNTMap
// @ts-ignore
style={{ flex: 1 }}
{...props}
/>
);
};
export { MapKitVIew };
- 在RNE中去试试看
import {
MapKitVIew,
multiply,
} from 'react-native-awesome-module';
...
render() {
return <MapKitVIew style={{ flex: 1 }} />;
}
4.别忘记去进行yarn watch操作 和update ios的静态库依赖 最后你就能够看到你希望的效果了
增加属性
如果你希望为你的UI组件新增一些属性 你需要这样做,
// RNTMapManager.m // 使用宏 定义类型(注意类型这个非常关键,因为你是JS Native是OC所以要用这个转化)
// 下面代码的意思是定义一个zoomnabled属性,类型是Boolean
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
// 在js中你只需要就好了 (别忘了yarn watch 和 update IOS)
<MapView zoomEnabled={false} style={{ flex: 1 }} />
如果我希望 添加 更加复杂的属性呢?你需要这样 :使用OC的语法 依据RN的方法戏一个类型转换class
// RNTMapManager.m
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
{
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
}
// RNTMapManager.m
// 导入 你自己写的 头文件
#import "RCTConvert+Mapkit.h"
// RCTConvert+Mapkit.h
#import <MapKit/MapKit.h>
#import <React/RCTConvert.h>
#import <CoreLocation/CoreLocation.h>
#import <React/RCTConvert+CoreLocation.h>
@interface RCTConvert (Mapkit)
+ (MKCoordinateSpan)MKCoordinateSpan:(id)json;
+ (MKCoordinateRegion)MKCoordinateRegion:(id)json;
@end
@implementation RCTConvert(MapKit)
+ (MKCoordinateSpan)MKCoordinateSpan:(id)json
{
json = [self NSDictionary:json];
return (MKCoordinateSpan){
[self CLLocationDegrees:json[@"latitudeDelta"]],
[self CLLocationDegrees:json[@"longitudeDelta"]]
};
}
+ (MKCoordinateRegion)MKCoordinateRegion:(id)json
{
return (MKCoordinateRegion){
[self CLLocationCoordinate2D:json],
[self MKCoordinateSpan:json]
};
}
@end
最后只需要在Lib定义类型文件,然后在RNE中自己使用就好哦了
// RNE/index.tsx
render() {
const region = {
latitude: 37.48,
longitude: -122.16,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
};
return (
<MapView
region={region}
zoomEnabled={false}
style={{ flex: 1 }}
/>
);
}
- 增加事件 这路我想实现一个拖拽获取位置的方法onRegionoChange我应该像下面的样子去实现
// 在OC中我们海没有直接为 MKMapView 添加新属性,我们需要创建一个Delegate(OC开发模式中的一种)代理类 帮助我们实现
// RNTMapView.h
#import <MapKit/MapKit.h>
#import <React/RCTComponent.h>
@interface RNTMapView: MKMapView
@property (nonatomic, copy) RCTBubblingEventBlock onRegionChange;
@end
// RNTMapView.m
#import "RNTMapView.h"
@implementation RNTMapView
@end
// 最后在RNTapManger中实现具体的方法就好了
// RNTMapManager.m
#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>
#import "RNTMapView.h"
#import "RCTConvert+Mapkit.h"
@interface RNTMapManager : RCTViewManager <MKMapViewDelegate>
@end
@implementation RNTMapManager
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTBubblingEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
{
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
}
- (UIView *)view
{
RNTMapView *map = [RNTMapView new];
map.delegate = self;
return map;
}
#pragma mark MKMapViewDelegate
- (void)mapView:(RNTMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
if (!mapView.onRegionChange) {
return;
}
MKCoordinateRegion region = mapView.region;
mapView.onRegionChange(@{
@"region": @{
@"latitude": @(region.center.latitude),
@"longitude": @(region.center.longitude),
@"latitudeDelta": @(region.span.latitudeDelta),
@"longitudeDelta": @(region.span.longitudeDelta),
}
});
}
@end
最后我们把lib 加上类型(过于简单直接忽略),之后在RNE中验证一下就好了
// RNE index,tsx
+++
return (
<MapKitVIew
zoomEnabled={true}
region={{
latitude: 37.48,
longitude: -122.16,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
}}
onRegionChange={(value: any) => {
console.log(value);
console.log('====================================');
}}
// @ts-ignore
style={{ width: '100%', height: '100%' }}
/>
);
上述就是RN native模块的UI部分的研发要点
问题补充
下面的问题是 2022/6/19日 个人整理材料的时候 发现的 仓库的项目无法正常使用
更新几个问题修复几个问题, 项目你dow 下来也许允许不了,请按照下面的步骤尝试,主要出问题的原因是 IOS 的 install 这一步没有弄好,还有是 example这个项目的 里面的 node_moudle 和 外面的 node_module 有点问题\
本人亲测 可恢复的步骤如下
a. 根目录下, 把yarn.lock和 example 下的 yarn.lock 删除
b. 根目录下 ,yarn (遇到报错不要管)
c. 根目录下,执行 yarn prepare,( 遇到报错大部分说你的example目录中的view 组件没有 xxx 属性 )
d. 根目录下,打开ios工程,先行构建build 一个 static lib 出来
e. example目录下,执行 yarn add @types/react-native -D ,让 example目录下的东西 只引自己的的node-Module 不要跑去引用上级的 要不然会有问题
f. example目录下,打开iOS 进行 pod install,(如果有错请反复执行!直到成功)
g. xcode 打开 example 的工程 ,点击target 找到构建的包 “AwesomeModuleExample”,找到构建选型,编辑 命令 成这样
cd $PROJECT_DIR/.. export NODE_BINARY=node ./node_modules/react-native/scripts/react-native-xcode.sh\
https://github.com/facebook/react-native/issues/29351,官方issuse 和解决方案。
h. 去到example 删除 ios 工程下的 js.boudle
i. 先启动 example,(记得修改端口)
j. 在启动xcode 工程中的 example 项目,注意要选在 警告的地方把project 移动的文件 重新cv 和自动寻找一遍 要不然有问题,这样就没有问题了