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 1
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
Step 2
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
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
- https://shift.infinite.red/native-modules-for-react-native-android-ac05dbda800d#.luaf32pbp
- http://facebook.github.io/react-native/docs/native-modules-android.html
- http://facebook.github.io/react-native/docs/native-modules-ios.html
- https://www.reddit.com/r/reactnative/comments/49dr4n/get_device_volume/
- http://stackoverflow.com/questions/7255006/get-system-volume-ios
- http://stackoverflow.com/questions/2924676/how-do-i-get-the-current-volume-amplitude-in-an-android-mediaplayer
- https://github.com/weflex/react-native-wechat/issues/33
- https://developer.apple.com/reference/avfoundation/avaudiosession/1616533-outputvolume?language=objc
- https://developer.apple.com/reference/mediaplayer/mpvolumeview?language=objc
- https://github.com/facebook/react-native/releases/tag/v0.40.0
- https://developer.apple.com/reference/mediaplayer/mpvolumeview?language=objc
- https://github.com/IFours/react-native-volume-slider