React Native’s Native Modules in iOS / Swift

Adonis Gaitatzis
6 min readOct 25, 2021

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:

  1. Create a React Native App
  2. Open the project in Xcode
  3. Write custom modules in Swift if necessary
  4. Expose the Swift modules into a React-Native / Objective-C Bridge
  5. Call the native module methods from React-Native
  6. 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:

React Native project running in 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 &
React Native project open in Xcode

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:

  1. Create a Swift Module
  2. Create an Objective-C bridging header
  3. 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:

Create a new Swift File

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

Create Bridging Header Modal

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)

Create New Objective-C file

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.

--

--

Adonis Gaitatzis

Is a technology and branding nerd who can synthesize any business need into a technology solution. Fun projects have included brain imaging tech & mesh routers.