Skip to content

本文概要

大家好我是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

重要提醒!:请不要照着文章照抄,建议你先阅读通篇,了解全貌之后再去实践。

image.png

react-native-builder-bob

这个是RN官方的推荐,bob脚手架工具,它能够方便的快速进行React-native-Native-module集成,现在我们按照下面👇的步骤去init一个Native模块,当然了,我们的这个也是从github拿过来的,如果你不想看github的官方英文说明,参考我的这个也是没有问题的

简单的尝试一下

1.直接使用CLI找一个空的文件夹进行创建

shell
cd ~/Desktop
mkdir Project
cd Project
npx create-react-native-library react-native-awesome-module
# 接下来就是各种选择了 按照提示来就好了,我这里选择的是Native module的package是java和Objective-C

image.png

  1. 结束之后我们去yarn它就好了
shell
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
  1. 使用Xcode我们去看看bob默认的的Xcode工程

我们前往文件./IOS ,然后打开这个文件 “AwesomeModule.xcodeproj”

image.png

然后在xocde下就能看到这个工程了,接下来的内容是Objecttiv-C(以下简称OC)的知识了,一般的OC中的class(类,也叫OC类 其实就是OOP中的类这个概念而已)都由两个文件构成 一个头文件(声明类)一般是同名的.h文件(我说的是一般,但是也有另类的同学,他把声明和实现揉杂一个文件中,如果你发现了,请你削它!🙃🙃) ,一个是.m实现的文件,举个例子,在bob默认IOS工程下, 会有一个 AwesomeModule.h 和,m文件

objective-c
// 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工程了

shell
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

tsx
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文件夹中手动全局搜索 和替换。

image.png

然后你在expmle中就看到下面的代码,并且能够计算得出结果 ./expmle/src/index.tsx

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,从而进行调试。在这个过程中我们可能会遇到各种问题,接下来我们都来看看到底会遇到哪些奇怪的问题

  1. 新run 一个RN项目(以下把这个项目称为RNP);然后把我们正在开发中的库 link过去(当然你可以是使用yarn link 或者npm link )这里我在yarn link之后 就直接修改地址了哈,接下来,请按照RN的启动方式启动这个RN项目

// Lib项目终端

shell
yarn link

// RNP项目

json
  "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"
....
}

后续操作

shell
yarn 
# 由于我们改变了新增了native模块因此我们需要去ios下进行 pod install 操作才行
# 然后去启动就好了
  1. 观察RNP 和 RNE 以及 Lib 的IOS工程的这个文件夹(下图)

image.png

可以看到 我们的这个 native 模块是通过 IOS中的一种叫做:“静态库”的方式导入到IOS工程中去的,我们如何确保我们的库更新了呢,主要就是看这个地方的依赖是否更新了。👇将会详细的说明

  1. 我们修改Lib的IOS工程之后,在RNE文件中如何更新呢? 比如我这里修改成了 * 法 不是减法了,
h
  NSNumber *result = @([a floatValue] * [b floatValue]);

我们遵循下面的方式

-> 在Lib IOS的工程中 点击Build 按钮让他构建一次。

-> 去到lib的终端执行 yarn prepare

-> 在RNE项目中的IOS工程中 点击Build按钮重新构建一次

-> 完成✅ ,这样你的Native的静态库就变成最新的了

image.png

  1. 我们修改Lib的IOS工程之后,在RNP文件中如何更新呢? 和上面的道理是一样的,但是需要多一个步骤

-> 在Lib IOS的工程中 点击Build 按钮让他构建一次。

-> 去到lib的终端执行 yarn prepare

-> 在RNE项目中的IOS工程中 点击Build按钮重新构建一次 (这一步可以去掉如果你不想理RNE)

-> 来到RNP 项目的IOS中 进行pod update 操作就好了

image.png

深入

我们简单的尝试过后接下来我们将会深入,注意哈我默认你会OC且具备OC基础。

我会按照下面的知识脉络去介绍 (第一第二部分或许与上面的内容有点重复,但是为了梳理脉络我决定再写一遍)

  • 初始化设置
  • 普通的 调用(不需要异步支持)
  • 异步的调用
  • Observer 和广播📢
  • Native组件的生命周期
  • 集成Native UI

初始化设置

  • 对于Native

对于Native 来说,下面的这些步骤是必须的

  1. 创建对应的文件

IOS的话就是 .h .m 和我们上文说过的一样 并没有什么区别

image.png

  1. 实现对应的class,只有实现了才能把东西暴露给JS
Objective-C
// 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
js
// 很简单
const {CalendarModuleFoo} = ReactNative.NativeModules;
  • 依赖相关

React Native将自动创建并初始化任何注册的本机模块。但是,您可能希望创建并初始化自己的模块实例,例如注入依赖项。

您可以通过创建实现rctbridgedElegate协议的类来做到这一点,并以代表为参数初始化rctbridge,并用初始化的桥初始化rctrootview。

oc
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
objective-C
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)  
{  
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}

关于同步方法,RN官方还提供了一个 可以实现同步的方法,但是RN不推荐使用。因为同步调用方法可能会带来严重的性能损失,并且会在本机模块中引入与线程相关的 bug。

Objective-c
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
{
return [[UIDevice currentDevice] name];
}
  • JS
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-CJAVASCRIPT
NSStringstring, ?string
BOOLboolean
doublenumber
NSNumber?number
NSArrayArray, ?Array
NSDictionaryObject, ?Object
RCTResponseSenderBlockFunction (success)
RCTResponseSenderBlock, RCTResponseErrorBlockFunction (failure)
RCTPromiseResolveBlock, RCTPromiseRejectBlockPromise

JS 我们都很熟悉了 我们来唠一下 IOS中的一个例子🌰

js
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);
    });
OBJECTIVE-C
#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
c++
#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定义的常量 可以这样使用
oc
- (NSDictionary *)constantsToExport
{
 return @{ @"DEFAULT_EVENT_NAME": @"New Event" };
}
js
const {DEFAULT_EVENT_NAME} = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);

异步调用

对于 Native 的异步方法, 有两种方式实现,1谁callback 2是promise

  1. callback
oc
RCT_EXPORT_METHOD(createCalendarEvent:
             (NSString *) title 
    location:(NSString *)location
    callback: (RCTResponseSenderBlock)callback
    )
{

 NSNumber *eventId = [NSNumber numberWithInt:123];  
 callback(@[[NSNull null], eventId]);

}
js
const onPress = () => {
  CalendarModule.createCalendarEventCallback(
    'testName',
    'testLocation',
    (error, eventId) => {
      if (error) {
        console.error(`Error found! ${error}`);
      }
      console.log(`event id ${eventId} returned`);
    },
  );
};

我们还有另一种形式

oc
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]);
  }
}
js
const onPress = () => {
  CalendarModule.createCalendarEventCallback(
    'testName',
    'testLocation',
    error => {
      console.error(`Error found! ${error}`);
    },
    eventId => {
      console.log(`event id ${eventId} returned`);
    },
  );
};
  1. promise

这个就比较简单了,我一直在用它

oc
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);
  }
}
js
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
oc
// 我们在.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}];
  }
}
js
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

oc
// 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
js
import { DeviceEventEmitter } from 'react-native';

DeviceEventEmitter.addListener('EventName', event => {
  console.log('Received event:', event);
});
  • 多线程 GCD

你可以开业使用 observer 或者 promise 都可以

oc
// 多线程 (返回的是一个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自己的) image.png

  1. 首先新增一个OC-class (OC基础知识这里不展开) 然后写入如下的代码
h
// 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
  1. 在lib的js文件中去获取它(但是在我的仓库中弄 我是在 indx.tsx写的,再导出 是一样的效果)
ts
// 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 };
  1. 在RNE中去试试看
tsx

import {
  MapKitVIew,
  multiply,
} from 'react-native-awesome-module';

...

render() {
  return <MapKitVIew style={{ flex: 1 }} />;
}

4.别忘记去进行yarn watch操作 和update ios的静态库依赖 最后你就能够看到你希望的效果了

  1. 增加属性

    如果你希望为你的UI组件新增一些属性 你需要这样做,

h
// 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

h
// 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中自己使用就好哦了

tsx
// 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 }}
    />
  );
}
  1. 增加事件 这路我想实现一个拖拽获取位置的方法onRegionoChange我应该像下面的样子去实现
h
// 在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中验证一下就好了

tsx
// 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”,找到构建选型,编辑 命令 成这样

image.png

shell
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 和自动寻找一遍 要不然有问题,这样就没有问题了

image.png

参考

react-native-builder-bob仓库

RN官方文档

OC文档