ReactNative Native Modules in Swift – Part 2 (ReactNative bridging)

In Part 1, we saw how to integrate ReactNative inside a Swift project using Cocoapods. Now, before starting to create a ReactNative View and integrate it into our project.

First, let’s take a quick tour of how ReactNative works.

I will not dive in depth (first because I don’t have enough expertise so far and second because it could take a complete book to make it) but at least I will reveal a little bit of the magic behind ReactNative from what I understood and read. There’s a very nice talk from Peggy Rayzis about this subject that you can watch here.

ReactNative is based on the Bridging Technique, which means that everything in JS that uses Native components uses a bridge to establish a communication between both. Here’s a quick schema to describe that :

ReactNative Architecture

As you can see, the bridge drives the all communication between JS and Native (the JS Native Engine enables JS to “exist” inside the app but as it’s an internal framework, there’s no need to know anything about it). The bridge part is what makes ReactNative work and it is what is implemented for you when you integrate ReactNative inside your app. A lot of Native Module and Libraries are already bridged with ReactNative Components and thus a lot of people will never have to generate Custom Native Modules, but as this is the purpose of this post, I think this is important to understand this architecture. Another point that should be noticed is that all the communications between ReactNative and Native Modules are asynchronous and MessageQueue will take care of them. So one major consequence about that is that, the more you communicate, the more messages are sent and thus the more MessageQueue suffers (this is one of the main reason for flickering for example, so if you start to develop a lot using ReactNative, you must for sure start to spy MessageQueue. I will not extend on the subject because this is not the purpose of this post but keep that in mind if you need to make a choice about using ReactNative).

This said, let’s create a Native Module and make it communicate with ReactNative.

Step one, we will create a ReactNative View and display it inside the app. For this, create a js file called index.js that contains this :

import React, { Component } from 'react';
import {
  Alert, 
  Platform,
  StyleSheet,
  Text,
  View,
  Button,
  AppRegistry,
  NativeEventEmitter
} from 'react-native';

export default class TestView extends Component<{}> {

  _onPressButton() {
    
  }
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native! Hello world
        </Text>
        <Button onPress={this._onPressButton} title="Press Me" />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
});

AppRegistry.registerComponent('TestView', () => TestView);<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>

and save it at the root of the all project (not inside the ios folder). What this file does is create a new component called TestView and register it as a Component that can be targeted by the native app thru the creation of an RCTRootView that we will see just after. Inside this View there’s a text and a button.

Now let’s integrate this inside a Native View. Go back to XCode and open the file ViewController.swift. Delete everything and copy/paste the code above :

import UIKit
import React

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index", fallbackResource: nil)
        let rootView = RCTRootView(
            bundleURL: jsCodeLocation,
            moduleName: "TestView",
            initialProperties: nil,
            launchOptions: nil
        )
        view = rootView
    }

}

Then compile the project (“Command + B”). This should build correctly, now let’s try to make it run (“Command” + R).

No Bundle…

You should see the screen “No Bundle...”, which is quite normal as we are not running any package server and thus the bundle is never provided. Let’s solve this by launching a server.

Go to your terminal and cd to the root of your ReactNative project (not the /ios folder) and run this :

npm start

(You could also use “yarn start” for those who use yarn).

npm start…

Now the package server is running and the bundle should be delivered to your app. (We are in debug mode so the server will deliver the index.bundle and this will enable Hot Reload and features like this. In production the process is different but “this is not the purpose of this post… 🙂“). Of course, if you run your app from XCode now, you will think that everything is fine and should work. Well, not completely, because Apple has added a security level for apps that try to load things from outside if it’s not an https server and as our package server runs on http then we need to tell the app that we trust this url. To do this add those lines to your info.plist file in the /ios folder :

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>
App is displaying a ReactNative View

After this, if you run your app in XCode, then your simulator should display the nice screen that you see on your right.

So now let’s bridge this ReactNative View with a Native Module.

For this, we first create a swift file in XCode, called MyNativeModule.swift, and copy/paste this code into it :

import Foundation
import React
@objc(MyNativeModule)
class MyNativeModule: NSObject {
    @objc func triggerRequest() -> Void {
        print("trigger Request")
    }
}

There are many things here that must be said about this code. The first one, as ReactNative was done in ObjectiveC and CPP, and not in Swift, then this module, in order to be visible by ObjC must be declared with “@objc” and the methods inside it that should be visible to ObjectiveC also. It must also subclass NSObject.
You can see that there’s also a func presented to ObjC :

    @objc func triggerRequest() -> Void {
        print("trigger Request")
    }

What we’ll try to do now is bridge this method to our ReactNative code. How do we do this.
First we need to stay in XCode and create a MyNativeModule.m file.

  1. Select create new file and choose ObjectiveC file.
  2. If you don’t have any Bridging Header in your project then it should propose you to create one. Select “Create Bridging Header”
  3. Name it “MyNativeModule”
Create ObjectiveC file
Create Bridging Header

Open the MyNativeModule.m and copy/paste this code into it :

#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(MyNativeModule, NSObject)
RCT_EXTERN_METHOD(triggerJSRequest)
@end

What this file does is creating the missing link between your Swift Native Module and the ReactNative Library. This will all happen at runtime. Pay attention, the name of the function, “triggerJSRequest“, must the same as the name used in your Swift file.
Before talking about that, let’s add something in our JS so we can trigger our native module and see that this works :-).
In index.js file replace the _onPressButton method with this :

  _onPressButton() {
    var React = require('react-native');
    React.NativeModules.MyNativeModule.triggerJSRequest();
  }

As you can see here, I used the same names “MyNativeModule”, “triggerJSRequest” as the one used in XCode. If you don’t do this then for sure, this will not work (and this is a major drawback when developing in ReactNative with native modules. One error in the naming and you are ready for a time of painful debugging thanks to runtime… There are ways to associate different names but I will not cover it here.)
Now recompile your app in XCode and tap on the “Press Me” button. If everything went well then you should see “Trigger Request” displayed on your debug console in XCode.

You can find all of this on github : https://github.com/fredfoc/ReactNativePart1and2

In Part3 I’m explaining what’s behind the macros and what can be added in the Swift file to implement the RCTBridgeModule protocol.

2 thoughts on “ReactNative Native Modules in Swift – Part 2 (ReactNative bridging)”

Leave a Reply

Your email address will not be published. Required fields are marked *