React Native’s Native Modules in iOS / Swift
In this article we will look at how to call native Swift modules from React Native
React Native was developed to support Objective-C native modules, but not Swift, so there’s a little trickery involved.
There are a few reasons why you might want to use native modules:
- The OS performs certain tasks faster React Native does.
- Sometimes there’s a feature supported by the OS that isn’t supported by React Native
There’s a tool called react-native-create-module that claims to make this process easier, but I wasn’t able to get it to work.
The Procedure
The basic procedure is pretty simple:
- Create a React Native App
- Open the project in Xcode
- Write custom modules in Swift if necessary
- Expose the Swift modules into a React-Native / Objective-C Bridge
- Call the native module methods from React-Native
- Compile from Xcode
1. Create a React Native App
We can use a standard process to create a react-native app. For this example, we will name it RnNativeSwiftModule
.
$ npx react-native init RnNativeSwiftModule
$ cd RnNativeSwiftModule
You may want to compile it now just to make sure it compiles and builds all the linking modules and stuff:
$ npx react-native run-ios
This will take a few minutes and will bring up a nice little React Native app in your iOS Simulator:
2. Open the Project in Xcode
Working? Great, now open the Xcode project folder. Be sure to open the <project-name>.xcworkspace
file:
$ open ios/RnNativeSwiftModule.xcworkspace &
3. Write Custom Modules in Swift
You could talk to existing modules, but it’s just as likely that you need to put a layer in-between that handles some business logic.
Here you will need to do three things:
- Create a Swift Module
- Create an Objective-C bridging header
- Program the Module
3.1 Create Swift Module
Start by creating a new Swift file (Ctrl + n). For this example, I’m calling it CustomMethods
:
3.2: Create an Objective-C Bridging Header
Click “Next.” Xcode will ask if you want to create an Objective-C bridging header. That’s exactly what we want, so yeah, click “Create Bridging Header”
You’ll end up with two new files:
CustomMethods.swift
<project-name>-Bridging-Header.h
Open the <project-name>-Bridging-Header.h
and make sure it imports React/RCTBridgeModule.h
:
#import <React/RCTBridgeModule.h>
3.3 Program the Module
Let’s start writing some methods. For the purpose of learning how this works, let’s create these methods:
- A simple function
- A simple function that returns a value
- A simple function with a parameter that returns a value
- A simple asynchronous function that resolves (asynchronously returns a value)
- A simple asynchronous function that rejects (asynchronously throws an error)
This will all go in your CustomMethods.swift
:
As the module and it’s methods must all bridge into Objective-C, they must have the @objc
decorator, for example:
@objc(ModelName) class ModelName: NSObject {
@objc public func functionName() {}
}
Additionally, if React Native should know that this module should be initialized in the main thread, the requiresMainQueueSetup()
method must return true
. You can return false
if the module can be started on a background thread.
@objc(ModelName) class ModelName: NSObject {
@objc static func requiresMainQueueSetup() -> Bool { return true }
}
Returning Values
Returning values is something interesting. It appears that Swift methods must return values as an array from a callback:
@objc public func methodName(_ callback: RCTResponseSenderBlock) {
callback(["return data, e.g. 1 or true or '1' or 1.0"])
}
Handling Promises
To handle a Promise, you’ll have to pass the a RTCPromiseResolveBlock
and RCTPromiseRejectBlock
into the function parameters, similar to how the callbacks were handled. Then you use resolve
to resolve the promise for the Javascript.
@objc public func resolvePromise(
_ resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) -> Void {
resolve("CustomMethods.resolvePromise()")
}
Let’s just create some method types that return their method names to React Native.
import Foundation
@objc(CustomMethods) class CustomMethods: NSObject {
@objc static func requiresMainQueueSetup() -> Bool { return true } @objc public func simpleMethod() { /* do something */ } @objc public func simpleMethodReturns(
_ callback: RCTResponseSenderBlock
) {
callback(["CustomMethods.simpleMethodReturns()"])
} @objc public func simpleMethodWithParams(
_ param: String,
callback: RCTResponseSenderBlock
) {
callback(["CustomMethods.simpleMethodWithParams('\(param)')"])
} @objc public func throwError() throws {
throw createError(message: "CustomMethods.throwError()")
} @objc public func resolvePromise(
_ resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) -> Void {
resolve("CustomMethods.resolvePromise()")
} @objc public func rejectPromise(
_ resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) -> Void {
reject("0", "CustomMethods.rejectPromise()", nil)
}
}
4. Expose the Swift modules into a React-Native / Objective-C Bridge
Next you must create a new Objective-C file (Ctrl + n)
Name this file the same as the Swift file from earlier. In this case, CustomMethods
.
Open this file, CustomMethods.m
to define which classes and methods will be exposed to React Native.
For this to work, you must import “React/RCTBridgeModule.h”
:
#import "React/RCTBridgeModule.h"
We can export classes using the following pattern:
@interface RCT_EXTERN_MODULE(ClassName, NSObject)
RCT_EXTERN_METHOD(classMethod1)
RCT_EXTERN_METHOD(classMethod2WithParams: (DataType) paramName)
@end
In the case of the CustomMethods
module, our CustomMethods.m
will look like this:
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"@interface RCT_EXTERN_MODULE(CustomMethods, NSObject)
RCT_EXTERN_METHOD(simpleMethod)
RCT_EXTERN_METHOD(simpleMethodReturns:
(RCTResponseSenderBlock) callback
)
RCT_EXTERN_METHOD(simpleMethodWithParams:
(NSString *) param
callback: (RCTResponseSenderBlock)callback
)
RCT_EXTERN_METHOD(
resolvePromise: (RCTPromiseResolveBlock) resolve
rejecter: (RCTPromiseRejectBlock) reject
)
RCT_EXTERN_METHOD(rejectPromise:
(RCTPromiseResolveBlock) resolve
rejecter: (RCTPromiseRejectBlock) reject
)
@end
5. Call the native module methods from React-Native
We can call the native module from any Component or View, but to keep things simple for this tutorial, we will just call them from App.js
.
React needs to import the native module before calling it.
First you must import the NativeModules
from react-native
library, and then you can import your module by name from the NativeModules
import.
import { NativeModules } from "react-native"
const { CustomMethods } = NativeModules
The next step is to define how to interact with these native modules, which can be done by calling <NativeClassName>.<methodName>()
. Typically you’ll want to retrieve data and do something with it. To keep things simple, we will just use console.log()
or alert()
to print the Native method’s return values:
const App: () => React$Node = () => {
// ...
const nativeSimpleMethod = () => {
CustomMethods.simpleMethod()
}
const nativeSimpleMethodReturns = () => {
CustomMethods.simpleMethodReturns(result => {
alert(result)
})
}
const nativeSimpleMethodWithParams = () => {
CustomMethods.simpleMethodWithParams(
'example',
result => {
alert(result)
}
)
}
const nativeResolvePromise = async () => {
const result = await CustomMethods.resolvePromise()
alert(result)
}
const nativeRejectPromise = async () => {
try {
await CustomMethods.rejectPromise()
} catch (err) {
alert(err)
}
}
// ...
}
Finally, let’s call these React Native methods when the user clicks some buttons:
import { Button } from `react-native`
// ...
const App: () => React$Node = () => {
// ...
<Button
onPress={() => nativeSimpleMethod() }
title="Simple Method"
/>
<Button
onPress={() => nativeSimpleMethodReturns() }
title="Simple Method Returns"
/>
<Button
onPress={() => nativeSimpleMethodWithParams() }
title="Simple Method With Params"
/>
<Button
onPress={() => nativeResolvePromise() }
title="Reject Promise"
/>
<Button
onPress={() => nativeRejectPromise() }
title="Reject Promise"
/>
// ...
}
6. Compile from Xcode
After this process, I was unable to compile from $ npx react-native run-ios
. I received a linker error , essentially saying that ReactNativeBridge.o
was not found for the x86_64 architecture.
However, I was able to compile just fine from Xcode:
Further Reading
The React Native documentation has a whole section on Native Modules.
TeaBreak has a a fabulous tutorial about how to return various types of data from Swift Methods in Swift in React Native — The Ultimate Guide Part 1: Modules.