When we talk about our React Native setup in abstract, there are two kinds of "now draw The Tick" for iOS developers:
- How do I build this React Native as a CocoaPods setup?
We're going to address the first part in this post. By the end of this post we're going to get an Emission-like
repo set up for an existing OSS Swift iOS app called GitHawk. The aim being to introduce no
UIViewControllers via a CocoaPod which is
consumed by GitHawk.
To do this we're going to use the CocoaPods'
pod lib create template, and React Native's
react-native init to
make a self-contained React Native repo. It will export a JS file, and some native code which Podspec will
reference. This keeps the tooling complexity for iOS and React Native separate. Read on to start digging in.
So, I'm choosing to be annoying here. I will intentionally be adding
$s before all of the commands, this is
specifically to slow you down and make you think about each command. This isn't a quick tutorial you can skim in 2
minutes, running though it properly should take about an hour.
Also, before you get started, it looks like you're using a really small screen, this post expects you would have a terminal around with useful tools for getting stuff done. I'm afraid without that, you're not going to get much out of it. I'd recommend switching to a computer.
What Are We Going To Do?
We will make a React Native components library (GitDawg) for an existing open-source iOS Swift Application (GitHawk). This reflects an existing app with a team who wants to experiment with React Native in a way that doesn't affect the main codebase. In Artsy, GitDawg is Emission, and GitHawk is Eigen.
We will clone and set up GitHawk
We will use
pod lib createto make a library repo called GitDawg
We will use
react-native initto make a React Native environment
We will bundle the React Native code into our Pod's asset folder
We will edit the Podspec for GitDawg, and then the Podfile for the example project to consume it
We will use cocoapods-fix-react-native to hotfix the React Native files
We will expose a UIViewController which corresponds to the default screen from
react-native initin our Pod
We will change the storyboard reference to point to the UIViewController from our Pod, and run the simulator to see our welcome screen.
We will set up GitHawk to consume our new Pod
We will change GitHawk to show our new UIViewController instead of the bookmarks screen
We will edit GitDawg to act more like a development environment
Let's get started by having a working copy of GitHawk. I'll leave the README for GitHawk to do that, but
if you want to be certain you're on the same version as me - I'm working from this commit
Clone a copy of GitHawk, and get it running in your Simulator, should take about 5-10 minutes, you'll need Xcode 9.3. Then we can move on to starting our components repo.
When you're done with GitHawk, go back a folder so that you're ready to create the GitDawg repo:
$ cd ...
We need CocoaPods:
$ gem install cocoapods.
We're going to need node, and a dependency manager. If you run
$ brew install yarn you will get both.
I'm running on node
8.9.x and yarn
1.5.x. Honestly, it shouldn't matter if you're on node 8, or 9. Yarn is
basically CocoaPods for node projects. If you're wondering what the differences are between yarn and NPM,
then TLDR: there used to be some, but now there's few. I stick with yarn because I prefer how the CLI works, and I
can easily read the lockfile it generates.
We need the React Native CLI, so let's install it globally:
$ yarn global add react-native-cli.
Starting with the Pod
We're going to let CocoaPods create the initial folder for our project. Let's set up your Pod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
I'd recommend using only Objective-C at this point, for simplicities sake. Swift is a great language, but I want tooling simplicity. Swift and React Native is [docs] though. We're also not going to write enough native code to warrant the setup for testing. Plus, if we skip native testing then we can run CI on linux - which is basically instant in comparison.
This has made a new library. Let's go into our project's root with
$ cd GitDawg. There shouldn't be too much in
be about the CocoaPod instead of owning the name of the project. Run
$ mv GitDawg Pod to do that.
We want to create our React Native project. I'm hard-coding my versions in these commands to try ensure this post lasts some time, but you never know what amazing changes the future brings. If things are broken, leave a comment at the bottom of this post.
Let’s create a GitDawg React Native project, and then rename the folder to src:
1 2 3 4 5 6 7 8
We don't want all our project files living in a sub-folder though, so let's move a few of them back to the repo's root, then remove some unused files.
1 2 3 4 5
Which should make your app's folder look something like this:
1 2 3 4 5
To ensure everything is still hooked up, let's make sure that all of your tests are working in the new repo.
1 2 3 4 5 6 7 8 9 10 11 12
Native project that exposes a single component which says
"Welcome to React Native!".
However, it's going to take a bit of work before we can see it in action.
It looks like this, when you run it via the sim:
With that done, we can start looking at the native side of our codebase. We let
pod lib create set up an Example
app for us to work with in the repo, which consumes a Podspec in the root. So we're going to take a look at the
Podspec, and update it.
Our goal with the Example app is to set up an app exclusively for developing components in. In Artsy's case, this app handles auth to the Artsy API and has a series of jump-off points for developing a component.
To get started we need to modify the CocoaPod this repo represents:
- Update our Podspec to handle React Native as a dependency, and our assets
- Add support for native compilation via CocoaPods with cocoapods-fix-react-native
- Create a single
UIViewControllersubclass for the Welcome Screen using the bundled React Native JS
We want to have our Podspec re-use the metadata from React Native to set up GitDawg's dependencies. So replace
GitDawg.podspec with this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
This Podspec is probably more complex then you're used to, but it means less config. To validate the Podspec, use
$ pod ipc spec GitDawg.podspec and read the JSON it outputs. With the Podspec set up, it's time to set up the
We'll start with applying the React Native hot-fix plugin, sometimes a version of React Native is released that doesn't support Swift frameworks (as Facebook doesn't use Swift) and so you have to apply some patches to the code. I made a CocoaPods Plugin that handles the hot-fixes for you.
Start by making a
Gemfile in the
1 2 3 4
$ bundle install in the
Example folder, which will set up the ruby dependencies of
cocoapods-fix-react-native for your app. This makes it possible to reference
We want to take the current
Podfile and make sure that every React Native dependency comes from the folder
node_modules/react-native. We can do this using the
:path operator to redeclare where you can find each Pod.
Note: we also extend the amount of subspecs for
'React' in this Podfile via
subspecs: ['DevSupport'] - this
subspec is what provide the hot code reloading and other developer tools. You'll want this, in here, it will mean
that the example app can be used as a dev environment, and your main app will only get a production environment.
Example/Podfile to look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Run the following to set up the React Native dependencies for your project.
We need some native code to represent our Welcome component from the React Native template. Create two new files in
Pod/Classes, then re-create the CocoaPods project for it by
pod installing again.
1 2 3
We're going to make a pretty vanilla
UIViewController, so declare it exists in the interface and then use an
RCTRootView as it's
1 2 3 4
GDWelcomeViewController is going to handle the React bridging, because that is the simplest option for our
Hello World app. We'll be going back to improve this later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
pod lib create template uses storyboards, you will need to open up the example app's storyboard and
change the initial view controller to be a
If you see a white screen on the app launches then this hasn't been done.
Run the app in the simulator, and you should get this screen:
This is the default screen from the React Native template, and it's proof that everything has worked for our dev app.
Let's take a second to re-cover what has happened to get to this point.
We used the
pod lib createtemplate to make a library repo
react-native initto make a React Native environment, which has the settings in the root and the source code inside
We've bundled the React Native code into our CocoaPod's asset folder
We set up the Podspec for GitDawg, and then the Podfile for the example project to consume it
We added cocoapods-fix-react-native to hot-fix the native files
We added a UIViewController for the default screen from
react-native initto our CocoaPod, and ran
bundle exec pod installto update the example project
We changed the storyboard reference to point to the UIViewController from our Pod, and ran the simulator to see our welcome screen
This is a full run-through of how your Pod would look when integrated into your main app's codebase. At this point you have a unique, isolated app which is going to be your development environment. In our case this app is a menu of different root screens and admin flags.
OK, let’s go take this and migrate it into GitHawk. This is our end-goal:
Our setup is going to be different here because we can't rely on React Native coming from the file-system, as we want to make sure our app has no hint of JS tooling. So we will use CocoaPods to handle downloading and setting up our versions of the React Native libraries. As of 0.54.x, that is React and Yoga.
We want to have a local copy of the JSON version of Podspecs for each of these. They can be generated from the
bundle exec pod ipc spec [file.podspec]. Let's generate one for React:
It will output a bunch of JSON to your terminal. This is perfect. Let's move that text to a file on your desktop.
For the yoga podspec, you should just grab our version, it's not worth me explaining all the details why, other than the PR I made to fix a bug isn't shipped in 0.54 so run:
You should now have two JSON files in your Desktop. Grab them, move them into the
Local Pods folder inside
GitHawk. It should already have a few Podspecs.
1 2 3 4
Gemfile to include cocoapods-fix-react-native:
1 2 3 4
bundle install. Next we need to add GitDawg, and our custom Podspecs to the Podfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$ bundle exec pod install. That should grab React Native for you. Unlike inside GitDawg, CocoaPods will
download the source code from the internet.
:podspec only tells CocoaPods where to find the Podspec, but it will
still download code externally.
Open up the Xcode Workspace -
open Freetime.xcworkspace, and we're gonna make the code changes - it's all in one
file. Open the file
RootNavigationManager.swift (it's in
Classes/Systems) and add a new
import at the top for
1 2 3 4
Then add our new view controller by replacing the bookmarks view controller on line 78.
1 2 3 4 5 6 7
That should get you to the same point as we were in the dev app. Now when you run the app, log in and hit the place where the bookmarks icon used to live. Tada.
This is how all our view controllers are exposed and used in production.
We've now got a successful deploy of our React Native Pod into an external app. However, we need to make some changes in GitDawg now to start making it possible to develop efficiently.
We will need to:
Make a singleton to handle setting up React Native between all potential UIViewControllers
Use the React Native Packager to get runtime editing support
Move your terminal back to the GitDawg folder. We're going to make a class that represents our library, GitDawg
Then we need to re-run
$ bundle exec pod install in the
Example folder to get it in Xcode. Open up the Xcode
workspace for GitDawg and let's fill in these files. These files are based on AREmission.h and
AREmission.m. For us, in a production app,
AREmission has a few key responsibilities:
Pass through the non-optional environment variables to expose in JS
Create and retain the React Native bridge
Set up the native modules so that we React Native can communicate with the host app
For this tutorial we don't need all of these responsibilities, but we will handle the second one.
For the header file,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
And for the implementation file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
Then change your
GDWelcomeViewController.m to use the shared
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
We use the UIAppDelegate callback to set up our React Native bridge (you want this ready as fast as possible
normally) so edit
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Because of Apple's HTTP security, you cannot connect to localhost in an app by default. To fix this, open up
GitDawg-info.plist and right-click to add a new row. Paste in
NSAppTransportSecurity as the name, and Xcode will
switch it to "App Transport Security Settings". Hit the
+ and add "Allow arbitrary loads" then set it to true.
From here: run the GitDawg app and you should see a red screen. This will be telling you to start the React Native
Packager. Let's do that. From the root of the GitDwag repo run
$ yarn start. This will start up a server. Once it
says "Metro Bundler Ready." you can go back into your simulator for GitDawg and hit the reload button at the bottom.
So, there's obviously a lot more to learn here. You've successfully set up a Pod that you can deploy to an app. To make a real version you'd need to do a bit more process like creating a repo, and making tags.
We use our root view controller in Emission to trigger loading any of our view controllers, in different states. We also mix that with some admin options, the ability to run someone's PRs and storybooks.
So good luck! Something like this probably easily scripted, but there's a lot of value in understanding how every piece comes together. So let me know if you make something cool - we've been using this structure for 2 years now and I think it's the right way to integrate React Native into an existing complex app. It keeps your JS tooling in a completely different repo from your iOS tooling.