This article summary
So far, RN is still hot and mature when it comes to cross-app solutions. Despite all the cross-end solutions available in the community and around the world (Flutter, Uniapp, Coadva, Ionic) RN is still very strong, given github’s start and community maturity. Students who have been RN have experienced this: “If you do not know some Native things, the water here is very deep, playing RN advice must be to know some Native things Android/IOS”. RN’s official documentation says how to integrate Native modules, but! It tells you how to do it, but it doesn’t give you a complete example of where to write it, how to integrate it, or anything like that. Therefore, from the perspective of RN integration with IOS (OC syntax), this article shares my IOS integration method when USING RN.
The main thread of the article is as follows: 1. About the BOD tool 🔧 --> 2. Only integration js part --> 3. Integrate UI components (maps) --> 4. Integrate other modules such as Native multi-process in OC is an important reminder of GCD! : Please do not copy the article. It is recommended that you read the whole article first and understand the whole picture before you practice.Copy the code
react-native-builder-bob
I found this Bob scaffolding tool from the official article, which can easily and quickly integrate react-native -native- module. Now we init a native module according to the following steps 👇, of course, our one is also from Github. If you don’t want to read github’s official English description, it’s ok to refer to mine
Based on using
1. Use the CLI to find an empty folder
cd ~/Desktop
mkdir Project
cd Project
npx create-react-native-library react-native-awesome-module
#Here I choose the package of Native Module is Java and Objective-C
Copy the code
- And then we go to Yarn and it’s done
cd ./react-native-awesome-module
npx react-native-builder-bob init
#Alternatively, yarn can be used directly
#Under packageJson we found a number of configurations, all of which are explained in detail in Bob's official documentation
#1. Yarn installation dependency
yarn
#2. Yarn Watch can be built automatically at development time.
yarn watch
#3. Ts builds and Lint
yarn typescript
yarn lint
#In addition I often use the following commands to update built packages during development
yarn prepare
Copy the code
- Using Xcode Let’s look at Bob’s default Xcode project
Let’s go to file./IOS and open this file “awesomemodule.xcodeProj”
Objecttiv -c = Objecttiv -c = Objecttiv -c = Objecttiv -c = Objecttiv -c An OC class is an OOP class that has two files that form a header file (declaration class), usually with the same name. 🙃🙃), one of which is a.m implementation file. For example, under Bob’s default IOS project, there is an awesomemodule. h and m file
#import <React/RCTBridgeModule. H > @interface AwesomeModule: NSObject <RCTBridgeModule> // It needs to be a class of type RCTBridgeModule, because this is RN wrapper class, Class @implementation AwesomeModule @implementation AwesomeModule @implementation AwesomeModule @implementation AwesomeModule @implementation AwesomeModule Said this is a Native Module, then the indx. The TSX is NativeModules. AwesomeModule RCT_EXPORT_MODULE () / / this is a requirement for an RN, Method in the Nativee Module, This method takes two values ab and returns a - b. multiplyWithA:(**nonnull** NSNumber*)a withB:(**nonnull** NSNumber*)b withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) { NSNumber *result = @([a floatValue] - [b floatValue]); resolve(result); } @endCopy the code
4. Perform debugging
To start the project, go to the /expempl file and start it as a normal RN project (yarn then CD ios && pod install). Finally, use Xcode to open the workspace file. You can see a standard RN project
cd ./expmale && yarn
cd ./ios && pod install
#Start the development server
cd .. && yarn start
#If your Yanr ios is not working, please manually build it in Xcode
Copy the code
We will divide this Native Module project into two parts, one is the Native lib library itself, and the other is the example project. Naitve Lib can be divided into two parts, one for IOS and one for Android, each of which can be divided into more detailed modules, namely UI components and functional components. This article only covers IOS integration, not 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 (a, b) returns awesomemodule.multiply (a, b); The fall in
// It is actually the multiply method in OC above
export function multiply(a: number, b: number) :Promise<number> {
return AwesomeModule.multiply(a, b);
}
Copy the code
Pay special attention to ⚠️, since the default port of my development environment does not start at 8081, I changed it to 8083. Although you start –port=8083, this will not be set for you in OC automatically, so you need to manually search and replace globally in the IOS folder
Then you can see the following code in expmle and calculate the result./expmle/ SRC /index.tsx
import * as React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { multiply } from 'react-native-awesome-module'; // This is your lib package
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,}});Copy the code
How do I debug?
By debugging we mean how to use this lib in other projects like other lib of NPM. We want to be able to use the local link in the project to link to the lib we are developing for debugging. We may encounter all kinds of files in this process, but let’s take a look at some of the strange problems we encounter
- Run a new RN project (hereinafter referred to as an RNP); Yarn Link (NPM link or yarn Link) is the repository we are working on. If you want to use yarn Link or NPM link, you can use yarn Link or NPM link
// Lib project terminal
yarn link
Copy the code
/ / RNP projects
"dependencies": {
"@react-navigation/bottom-tabs": "^ 6.0.5/6.5.4." "."@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 rainfall distribution on 10-12"."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". }Copy the code
The subsequent operation
yarn
#Since we have changed and added native module, we need to go to ios for pod install operation
#Then go and start it
Copy the code
- Look at the folder in the IOS project for RNP and RNE and Lib (below)
It can be seen that our native module is imported into the IOS project through a way called “static library” in IOS. How do we ensure that our library is updated? It mainly depends on whether the local dependencies are updated. 👇 will explain in detail
- After we modify the Lib IOS project, how do we update it in the RNE file?
Let’s say I changed it to star instead of subtraction,
NSNumber *result = @([a floatValue] * [b floatValue]);
Copy the code
We follow the following pattern
-> Click the Build button in the Lib IOS project and let it Build once.
-> Go to the terminal in lib and run YARN Prepare
-> Click the Build button in the IOS project in the RNE project to rebuild again
-> Complete ✅ so that your Native static library is up to date
- How do we update the Lib IOS project in the RNP file?
Same as above, but need one more step
-> Click the Build button in the Lib IOS project and let it Build once.
-> Go to the terminal in lib and run YARN Prepare
-> Click the Build button in the IOS project in the RNE project to rebuild again (this step can be removed if you don’t want to deal with RNE)
-> Just go to the IOS of the RNP project and do the POD update
Start the basic Native module
The previous knowledge is the basic knowledge, after understanding, we will start the formal integration of IOS OC Native module. Note that I default that you know OC and have the basis of OC. After we have the above basis, we will be very happy to follow the official documents of RN
Integrate a Map as a UI component according to the official documentation
According to the official documentation, we currently integrate a UI map component (MKMapView is UIKit’s own component).
- First add an OC-class.
Then write the following code
// rntmapmanager. m (rntmapmanager. m)
#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>
// Declare the header file
@interface RNTMapManager : RCTViewManager
@end
/ / class implements
@implementation RNTMapManager
RCT_EXPORT_MODULE(RNTMap)
- (UIView *)view
{
return [[MKMapView alloc] init];
}
@end
Copy the code
- Get it in lib’s js file (but in my repository I wrote it in indx.tsx, and export it is the same)
// MapView.js
import { requireNativeComponent } from 'react-native';
RequireNativeComponent Automatically resolves 'RNTMap' to 'RNTMapManager'
coonst RNTMap = requireNativeComponent('RNTMap');
const MapKitVIew: React.FC<any> = (props) = > {
return (
<RNTMap/ / @ts-ignore
style={{ flex: 1 }}
{. props} / >
);
};
export { MapKitVIew };
Copy the code
- Try it in RNE
import {
MapKitVIew,
multiply,
} from 'react-native-awesome-module'; .render() {
return <MapKitVIew style={{ flex: 1}} / >;
}
Copy the code
4. Don’t forget to do yarn Watch and update ios static library dependencies so you can see what you want
-
Increase the attributes
If you want to add some properties to your UI component you need to do this,
Rntmapmanager. m // use macros to define types.
// The following code means to define a Zoomnabled property of type Boolean
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
// In JS you just need to (don't forget yarn Watch and update IOS)
<MapView zoomEnabled={false} style={{ flex: 1}} / >Copy the code
What if I want to add more complex attributes? You need to use the syntax of OC to cast a class based on RN’s methods
// RNTMapManager.m
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, MKMapView)
{
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
}
// RNTMapManager.m
// Import your own header file
#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
Copy the code
Finally, you just need to define the type file in Lib and use it in 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}} / >
);
}
Copy the code
- Add event
This way I want to implement a drag-and-drop location method onRegionoChange and I should implement it as follows
// Instead of adding new attributes to the MKMapView directly in OC, we need to create a Delegate class to help us do this
// 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
// Finally, implement the specific methods in 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(a)
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
Copy the code
Finally we add lib to the type (too simple to ignore directly) and then verify it in 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%' }}
/>
);
Copy the code
These are the key points of UI development of RN Native module
Integrate other parts according to official documentation
The rest of this refers to functions that do not require a UI to use. That is, the function module of the non-native UI component. In the official documentation of RN, it is said to obtain the functions like calendar and its events. However, after practice, I found that there is no oh ha, but a lot of other functions, so the code is all in the following, and the annotations are relatively clear, so there is no lonely disassembly description
- OC part
// For the OC part our code is all done in CalendarManager class, calendarManager.h
// CalendarManager.h
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarManager : RCTEventEmitter <RCTBridgeModule>
@end
Copy the code
// For the OC part our code is all done in the CalendarManager class, calendarManager.m
// CalendarManager.m
#import "CalendarManager.h"
#import <React/RCTConvert.h>
static NSString * const kTestNotificationName = @"kTestNotificationName";
static NSString * const TestEventName = @"TestEventName"; // Since this class is implemented, so @implementationCalendarManager { bool hasListeners; // oc class initialization method - (instanceType)init {if( self = [super init] ){
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(sendEventToJS) userInfo:nil repeats:YES];
}
return self;
}
// To export a module named CalendarManager
RCT_EXPORT_MODULE();
// This would name the moduleAwesomeCalendarManager instead // RCT_EXPORT_MODULE(AwesomeCalendarManager); / / in the IOS RCT_EXPORT_METHOD there are a lot of parameters/https://reactnative.cn/docs/native-modules-ios/write their own a timer manually to trigger the event In order to let the IOS to send message to RN RCT_EXPORT_METHOD(addEvent:(NSString *)name details:(NSDictionary *)details) { NSString *location= [RCTConvert NSString:details[@"location"]];
NSDate *time = [RCTConvert NSDate:details[@"time"]]. RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback) {callback(@)"I'm a simple callback."); } // handle asynchronous callback RCT_REMAP_METHOD(asyncFindEvents, findEventsWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve(@"no_events"); } // Multithreading (returns onepromise) RCT_EXPORT_METHOD(doSomethingExpensive:(NSString *)param callback:(RCTResponseSenderBlock)callback) { dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
callback(@[@"1"The @"2"]); }); } // Export constant - (NSDictionary *)constantsToExport {return @{@"firstDayOfTheWeek": @"Monday"}; } - (NSArray<NSString *> *)supportedEvents {return @[@"EventReminder"]; } // Triggered when adding the first listener -(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}]; }} / / this also can send messages - (void) sendEventToJS {[self sendEventWithName: @ "EventReminder" body: @ {@ "name:" @ "2333"}); } @endCopy the code
- Lib index part
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); }});/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = initialization = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
/** When you start the Example project, if you only modify the JS/TS code, it will be automatically synchronized to the example in the development environment. If you modify the Native module, what do you need to do to use the update? Open ios.project under the lib project in Xcode and change 2 in it. After modifying everything, watch and run build */
/** After you start an external project and link past tense and process and principle above, it is recommended to modify everything, watch and run build */
/** How to link directly from external projects to this project module for local development? In fact, we have tried using link, but the effect is not particularly good, so we are currently using the external project package directly add, such as the following external project "react-native-awesome-module": "/Users/wcmismac020/Desktop/origin/react-native-awesome-module" */
export function multiply(a: number, b: number) :Promise<number> {
return AwesomeModule.multiply(a, b);
}
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = initializing UI components = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
RequireNativeComponent Automatically resolves 'RNTMap' to 'RNTMapManager'
const RNTMap = requireNativeComponent('RNTMap');
// Sometimes your native component has some special attributes that you want to export, but you don't want it to be a public interface.
export interfaceInterMapKitVIewProps { zoomEnabled? :boolean;
region: {
// Center point coordinates
latitude: number;
longitude: number;
// The maximum distance between latitude and longitude
latitudeDelta: number;
longitudeDelta: number;
};
onRegionChange: (value: any) = > void;
}
const MapKitVIew: React.FC<InterMapKitVIewProps> = (props) = > {
return (
<RNTMap/ / @ts-ignore
style={{ flex: 1 }}
{. props} / >
);
};
export { MapKitVIew };
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = function module = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
export interface InterCalendarManager {
addEvent: (name: string, options: any) = > void;
findEvents: (cb: (err: any, events: any) = >void) = > void;
asyncFindEvents: () = > Promise<any>;
doSomethingExpensive: (
parmas: any,
cb: (value1: any, value2: any) = >void
) = > void;
firstDayOfTheWeek: any;
calendarManagerEmitter: NativeEventEmitter;
// Listen on events
}
export const CalendarManager =
NativeModules.CalendarManager as InterCalendarManager;
// Automatic manual call
// CalendarManager.addEvent('Birthday Party', {
// location: '4 Privet Drive, Surrey',
// time: new Date().getTime(),
// description: '... ',
// });
// The normal callback function
// CalendarManager.findEvents((error: any, events: any) => {
// if (error) {
// console.error(error);
// } else {
// console.error(events);
/ /}
// });
// Async callback function
// async function updateEvents() {
// try {
// const events = await CalendarManager.findEvents();
// console.log('events', events);
// } catch (e) {
// console.error(e);
/ /}
// }
// updateEvents();
// Async callback functions are multithreaded
// async function updateEventsQue() {
// try {
// const events = await CalendarManager.doSomethingExpensive();
// console.log('events', events);
// } catch (e) {
// console.error(e);
/ /}
// }
// updateEventsQue();
// Get constants
// console.log(CalendarManager.firstDayOfTheWeek);
// Proactively collect events from Native
CalendarManager.calendarManagerEmitter = new NativeEventEmitter(
// @ts-ignore
CalendarManager
);
// const subscription = calendarManagerEmitter.addListener(
// 'EventReminder',
// (reminder) => console.log(reminder.name)
// );
// // Don't forget to unsubscribe, usually implemented in the componentWillUnmount lifecycle method.
// subscription.remove();
Copy the code
3.RNE test
/* eslint-disable @typescript-eslint/no-unused-vars */
// @ts-ignore
import React from 'react';
import { useEffect } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import {
CalendarManager,
MapKitVIew,
multiply,
} from 'react-native-awesome-module';
export default function App() {
// const [res, setResult] = React.useState<number | undefined>();
// React.useEffect(() => {
// multiply(7, 2).then(setResult);
/ /} []);
const addEvent = () = > {
CalendarManager.addEvent('Birthday Party', {
location: '4 Privet Drive, Surrey'.time: new Date().getTime(),
description: '... '}); };const addEventCb = () = > {
console.log(222);
CalendarManager.findEvents((error: any, events: any) = > {
if (error) {
console.error(error);
} else {
console.error(events); }}); };const addEventCbAsync = async() = > {try {
const events = await CalendarManager.asyncFindEvents();
console.log('events', events);
} catch (e) {
console.error(e); }};const addEventCbAsyncQue = async() = > {try {
CalendarManager.doSomethingExpensive(
'ssss'.(value: any, value2: any) = > {
console.log('====> Multithreaded return value 1', value);
console.log('====> Multithreaded return value 2', value2); }); }catch (e) {
console.error(e); }};const getConstants = () = > {
console.log(CalendarManager.firstDayOfTheWeek);
};
useEffect(() = > {
const subscription = CalendarManager.calendarManagerEmitter.addListener(
'EventReminder'.(reminder: any) = > console.log(reminder.name)
);
return () = >{ subscription.remove(); }; } []);// return (
// <>
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// );
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%' }}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1.alignItems: 'center'.justifyContent: 'center',},box: {
width: 60.height: 60.marginVertical: 20,}});Copy the code
reference
The react – native – builder – Bob warehouse
RN Official Documents
Github address of this project
OC document