Vending pure-Swift views in React Native

It’s an island that cannot be found, except by those who already know where it is..

JP
6 min readMar 29, 2016

On the surface, React Native is a pretty amazing technology. It takes a state of the art web paradigm and applies the same ideas to native mobile apps.

But the trouble with any budding tech is that eventually, you’ll need to plug in some legacy bits—something which confused the hell out of me for a long while.

In my case, I’m porting an existing Swift app to React Native. All of the existing Views and Controllers are written purely in Swift (with no Objective C at all in what I’m now referring to as the “legacy” version of the app).

One option would’ve been to back-port all of them to Objective C (more on this in a bit) but in the interest of keeping them as close to the originals as possible, I decided to import them without touching them at all.

What follows is a guide and code sample illustrating how to consume a pure-Swift view in a React Native app. As always, if you’d prefer just to skip to the code, it is available on GitHub here.

Init

To get started, make sure you’ve installed the react-native-cli package from NPM. Then open a Terminal and type in

react-native init MySwiftDemo

The CLI seems to have gotten much quicker recently. Fun fact: v2.0 is out now so if you haven’t updated in a while go get it first!

Also relatively new is the ability to jump straight into running your app by typing

react-native run-ios

This will open new Terminal processes for both xcodebuild and the React Packager as well as the Simulator, but it won’t open Xcode itself. Neat!

As the screenshot above mentions, you can also immediately launch the app on an emulated Android device, but you need to have it running first. I’m going to focus on iOS for now.

Adding a Swift View

Let’s go ahead and open Xcode up so we can add a new Swift file called SampleView.swift

When you get asked if you want a bridging header, you need to click on Create. Don’t worry about it for now, we’ll come back to it later.

Ok let’s fill in SampleView. Don’t worry we’re keeping it really simple. Here’s the file in it’s entirety:

import UIKitclass SampleView: UIView {  override init(frame: CGRect) {
super.init(frame: frame)
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100,
height: 50))
label.text = “This is Swift”
self.addSubview(label)
}
required init?(coder aDecoder: NSCoder) {
fatalError(“init(coder:) has not been implemented”)
}
}

Alright so we’ve got an example of a pure-Swift view. When this works, we’ll see the label “This is Swift” in our React Native app.

The next step is to add another Swift file — call this one SampleViewManager.swift

import Foundation@objc(SampleViewManager)
class SampleViewManager : RCTViewManager {
override func view() -> UIView! {
return SampleView();
}
}

Again, keeping things as simple as possible, but there are two key bits here:

  • We’re subclassing RCTViewManager
  • and exposing this class to Objective C classes in our app using @ objc

Those are important because the next step is to export this to React, which we’ll need one final new file to do.

There’s probably a slightly neater way to do this where you don’t have to have a SampleViewManager class at all, but my goal here was to consume a completely untouched Swift UIView, and without subclassing RCTViewManager in a separate Manager I would have had to modify the originals.

Adding an Objective C Module

Before we can leave Xcode for the wonderful world of JavaScript, there’s one more file we need to create. This time, create a Cocoa Touch class called SampleViewModule.

Make sure you choose Language > Objective-C

In the interest of keeping this demo as simple as possible, the first thing I’m going to do is delete the header. Separating your interface from your implementation has never made a lot of sense to me, and it keeps the numbers of files down anyway.

Assuming you deleted your SampleViewModule.h file as soon as it was created, open up the remaining SampleViewModule.m file and paste the following in:

#import “RCTBridgeModule.h”
#import “RCTViewManager.h”
@interface RCT_EXTERN_MODULE(SampleViewManager, RCTViewManager)
@end

Because we subclassed RCTViewManager and exposed our SampleViewManager to Objective C classes, we can use RCT_EXTERN_MODULE to export this. We don’t need to worry about an @ implementation here — there isn’t one!

Finally add this line to your Bridging Header to expose RCTViewManager to your Swift Manager class.

#import “RCTViewManager.h”

The Problem with Macros

Seems pretty sweet right? Well not quite. When you’re working with Objective C views, you can use the

RCT_EXPORT_MODULE

macro to export your view.

Note that here we’re using the similar-looking but not quite equal

RCT_EXTERN_MODULE

The reason for this is Swift doesn’t support macros (RCT_EXPORT_MODULE is a macro…), so RCT_EXTERN_MODULE was created specifically to support Swift. Sort of. (see https://github.com/facebook/react-native/pull/982)

If you’re a newcomer and don’t want to spend hours researching bridging languages, the TL:DR version is that the RCT_EXPORT_MODULE line you can use in Objective C will automatically grant access to any methods or properties on that module. It even sends events back and forth from JavaScript, all by including that one macro.

Swift doesn’t have something this elegant. RCT_EXTERN_MODULE on its own will let us render the native views, but if you need access to method or properties on your Swift View you’ll need to mark those separately.

C’est la vie.

Importing our SampleView into JavaScript

While it may not be as awesome as the Objective C equivalent, RCT_EXTERN_MODULE means that our SampleViewManager is now available in Javascript.

import { requireNativeComponent }from ‘react-native’
const SampleViewNative = requireNativeComponent(‘SampleView’, SampleView)

It’s easy to get confused here. So:-

  • SampleViewNative — is the JavaScript variable name of your React component. It can be called anything you want.
  • ‘SampleView’ — needs to match whatever your Swift Manager file was called. React automatically resolves this to ‘SampleViewManager’ here, so if your Swift file was called MyAwesomeManager just put ‘MyAwesome’ here.
  • SampleView — yeah I probably could have called this something better. This should be the name of the React component your are currently editing. So the same as your module.exports line at the bottom of the file.

The very last bit is to require our new React component in our index.ios.js file

const SampleView = require(‘./SampleView.ios.js’);
A pure-Swift view in React Native!

So that’s one way of rendering a pure Swift UIView in React Native. There are probably simpler ways of doing it, and back-porting to Objective C so you can use RCT_EXPORT_MODULE might be an option.

But the cool thing about this is I can take the same UIViews and use exactly the same code in either the shipping version of the app and my React Native version.

If you made it this far thanks for reading. If you have any questions or comments please leave them below, or you can get me on Twitter @jpdriver.

Peace out.

--

--