How to Create a React Native iOS Native Module

The official React Native docs on writing a native module are very useful, so make sure you give them a read:
http://facebook.github.io/react-native/docs/native-modules-ios.html

Why?

I wanted to create an iOS React Native module that could set and get the system-wide volume, but I couldn't find a prebuilt component that did this, so I decided to make my own.

Spoiler: Turns out you can't set the volume programmatically on iOS, so all this module will ever do is get the system-wide volume. I hope you still find this tutorial useful for learning how to build a native module 😎

How?

First I found a snippet of Objective C code on a Stack Overflow thread that could get the current system-wide volume from an iOS device.

float volume = [AVAudioSession sharedInstance].outputVolume;  

The above snippet uses the iOS AVAudioSession API

Step One

Create the native files for your custom module in Xcode

Your Xcode project file will be located at my-project/ios/my-project.xcodeproj

Right click on project folder in Xcode → New File → Next → Enter custom module name → Next

alt alt alt

Step Two

Add code to your newly created native module

RCTBridgeModule is what we use in native land to talk with React Native. RCT is short for React and you can include it in your Objective C code by importing it into your header-file (.h) like so:

OutputVolume.h

#import "React/RCTBridgeModule.h"

@interface OutputVolume : NSObject <RCTBridgeModule>
@end

Now we can take the StackOverflow snippet we found, put it in our implementation-file (.m), and expose it to React Native:

OutputVolume.m

#import "OutputVolume.h"=
#import "React/RCTLog.h"
#import <AVFoundation/AVAudioSession.h>

@implementation OutputVolume

// This RCT (React) "macro" exposes the current module to JavaScript
RCT_EXPORT_MODULE();

// We must explicitly expose methods otherwise JavaScript can't access anything
RCT_EXPORT_METHOD(get)  
{
  float volume = [AVAudioSession sharedInstance].outputVolume;
  RCTLogInfo(@"The system volume level is %f", volume);
}

@end

index.ios.js

Now from your JavaScript code you can call your new methods like this:

import { NativeModules } from 'react-native';  
var OutputVolume = NativeModules.OutputVolume;  
OutputVolume.get();  

Error

I ran into an error 'RCTBridgeModule.h' file not found

https://github.com/weflex/react-native-wechat/issues/33

To fix this error we have to show Xcode where the RCT modules live.

Change RCTWebChat.xcodeproj → Build Settings → Search Paths → Header Search Paths to:

$(SRCROOT)/../../React
$(SRCROOT)/../../react-native/React and
$(SRCROOT)/../../react-native/Libraries

alt

After you have this setup you should see a log in Xcode when you call your new method from JavaScript.

Step 3

Sending data back

Logging data is cool but usually you want to get some data back out of native land and into JavaScript. See the modified function below to see how you can pass a value back to JavaScript using a promise.

OutputVolume.m

#import "OutputVolume.h"
#import "React/RCTLog.h"
#import <AVFoundation/AVAudioSession.h>

@implementation OutputVolume

RCT_EXPORT_MODULE();

// We can send back a promise to our JavaScript environment :)
RCT_REMAP_METHOD(get,  
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
  float volume = [AVAudioSession sharedInstance].outputVolume;
  NSString* volumeString = [NSString stringWithFormat:@"%f", volume];

  if (volumeString) {
    resolve(volumeString);
  } else {
    reject(@"get_error", @"Error getting system volume", nil);
  }

}

@end

index.ios.js

import { NativeModules } from 'react-native';  
const OutputVolume = NativeModules.OutputVolume;  
OutputVolume.get().then(volume => alert(volume));  

Resources