Adding React Native to a Complex App — Part 4: iOS

This fourth (and final) article in an expert content series takes a deep dive into the technical details involved in adding React Native to an existing iOS app

So, you’re a developer working on adding React Native to an existing complex app, and you’re currently looking at iOS? Previously in our series:

  • In part 1 of this guide, we looked at planning: you should have a clear, considered strategy
  • In part 2 of this guide, we looked under the hood of React Native: hopefully you’ve got a basic understanding of the pieces we’re working with.
  • In part 3 of this guide, we looked in detail at the many pitfalls and potential issues you might encounter on Android.

In this article, we will focus on integrating with an existing iOS app. The good news is, this should be much simpler than on Android: there are fewer moving parts, and those parts tend to be purpose-designed by Apple for iOS development.

The bad news is, when something does go wrong, the debugging information is likely to be less detailed and the steps available are likely to be much less clear. React Native’s documentation is a lot vaguer here as well.

1. Preparation

These steps will assume you’re working on a Mac. While it’s theoretically possible to develop iOS apps on other operating systems, it’s much more complex, and we’re already introducing all the complexity of the existing native app — let’s try to stick to one source of complexity at a time.

1.1 Check your Ruby version

CocoaPods  (which manages iOS dependencies) is built on Ruby, and React Native like to be strict and prescriptive about which version of Ruby it should be running on, to avoid known issues. Different React Native versions have different Ruby requirements. To align with these:

  • If there’s a file .ruby-version in the root of your React Native app folder, use the Ruby version specified there.
  • If not:
    • 1. Go to the Ruby section of the iOS dev environment docs.
    • 2. Switch the version of the docs to match the version of React Native you are using, via the dropdown in the page header.
    • 3. Follow the (updated) docs on how to find the required Ruby version for your React Native version.

1.2 Keep Xcode and Cocoapods up to date…

There’s not much preparation needed except ensuring the two crucial tools are up to date (XCode and Cocoapods):

  • Xcode updates are by default managed through the Mac app store, but you might prefer to have a bit more control by browsing versions on the Apple Developer site (you must be logged in to any Apple Developer account).
  • Cocoapods versions are managed as Ruby Gems. Their documentation covers upgrading to the latest one, but not using an older version; if you need an older version, you must:
    • 1. Remove the current version (else it’ll always use the latest) with sudo gem uninstall cocoapods.
    • 2. Install the specified version with the v flag, like sudo gem install cocoapods -v x.y.z.

1.3 …but if possible, not too up to date

One tip here: if you can, if a new release of XCode is very fresh (less than a month old or so), try to stick to the last-but-one version. It’s quite common for new versions of Xcode to break things in unexpected ways and for the React Native community to need to scramble to catch up. For example, see this where builds broke in XCode 14.3 and React Native needed to rush out patch releases to all three supported versions .

It doesn’t happen with every release, but it’s common enough that it’s worth avoiding if you can. If there’s a brand-new bleeding-edge version as yet untested by the React Native community, consider delaying adding uncertainty to your project until after you’ve resolved the uncertainty and complexity of the native app and you have a baseline configuration that you know works.

1.4 Have working ‘clean’ projects for comparison

It is strongly advisable to set up, build, and keep a working native app instance and a working React Native app  of the appropriate version and dependencies, so you can catch local issues like Ruby or Xcode version issues early before adding complexity, and so you can refer back to complete configurations you know work.

2. Editing the Podfile

The first ‘real’ step in React Native’s guide to integrating an existing iOS app  is editing the project Podfile which manages the dependencies side of the build. You should edit this in XCode, to benefit from sophisticated IDE warnings of errors or issues you may introduce when splicing together two different configurations.

2.1 Finding the Podfile in Xcode

If you are new to iOS development, you might stumble at the first hurdle: where is the Podfile in Xcode? If you open a fresh workspace in Xcode that hasn’t yet been built, the crucial PodFile isn’t actually visible. You need to:

  • Check the app project has a .xcworkspace file, and open that in Xcode. It wraps the app project and its pods.
    • If there isn’t one, your native app probably didn’t use pods: create one by running pod init, then open it.
    • Don’t open the .xcodeproj file: in a pods project. Xcode will try to treat the project as a pods-free one, which won’t work.
  • The Podfile is usually found in Xcode by opening the Pods item from the top level of your project view, where it should be a sibling (not a child) of your app. This might not exist before your first build, however.
    • If it doesn’t exist, before changing anything for React Native, do a quick build with the play button, or run pod install.
    • The Pods item should then appear, with Podfile as the first item under it.

2.2 Adding React Native dependencies

The official React Native docs are, at the time of writing, uncharacteristically vague about what to add to the Podfile.

Rather than trying to guess which of dozens of native dependencies are required or inter-dependent and writing a React Native Podfile from scratch, a simpler way to start is to take the Podfile from a fresh, clean React Native app of the appropriate version from the working React Native app built in step 3.1.4 above.

This is written for a default React Native app of the exact appropriate version and so is likely to be a much more stable starting point than anything hacked together by hand, based on documentation that has a higher risk of being stale than the actual appropriate React Native app template.

2.3 Merging in React Native’s pod template

The bulk of the React Native pod file will be pulling in some meta-dependencies and setting some variables, so it makes sense to put it at the top of the Podfile. Check this for top-level blocks that are duplicated in the native code below (e.g.  target ) and save them for later.

Add some clear comments marking what came from what to help maintainability, for example:

Plain Text
############################
# React Native 0.71.6 Podfile content
############################

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, min_ios_version_supported
prepare_react_native_project!

# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
#
# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
# ```js
# module.exports = {
#   dependencies: {
#     ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
# ```
flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled

linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
  Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
  use_frameworks! :linkage => linkage.to_sym
end

############################
# Native app X.Y.Z Podfile content
############################

# Try using React Native's dynamic minimum version specification over the original
# platform :ios, '12.0'

def some_pods
  pod 'AnExamplePodFromNativeApp'
  pod 'AnotherPodFromNativeApp'
  # ...continues

2.4 Aligning platform iOS versions

Almost certainly, both files will declare platform :ios with a version range, and you’ll need to choose which to use and comment out the other. The best choice will probably need to be whichever is the strictest, which may result in a few Xcode warnings that should give pointers as to obsolete code patterns that need to be updated.

2.5 Merging duplicated blocks

There may be duplicated blocks that require merging by hand, but this will probably be quite straightforward. There will almost certainly be a target block in both, in which React Native applies some of those meta-dependencies, and the content of this can usually just be copied straight into the target block of the native app.

Again, it’s a good idea to comment what’s what. For example:

Plain Text
target 'your-app-name' do
  some_pods

  ############################
  # start of React Native 0.71.6 Podfile content inside native `target`
  ############################
  config = use_native_modules!

  # Flags change depending on the env values.
  flags = get_default_flags()

  use_react_native!(
    :path => config[:reactNativePath],

  # ...continues

2.6 Check for conflicting settings (e.g. use_frameworks)

Once this is done, do a quick skim of the integrated file looking for anything that looks contradictory.

One thing, in particular, to look out for is use_frameworks declarations in a target block, which change the default Pods behaviour from static libraries to static or dynamic frameworks .

This has some implications for React Native:

  • The React Native’s built-in Flipper debugging tool is not compatible with use_frameworks!, so shouldn’t be enabled if the native app requires this. Follow this issue to see if this changes.
  • Some common React Native dependencies require use_frameworks!, like Firebase, which might prove to be an issue if the native app requires static linking or a specific use_frameworks! option.

This is usually a pretty hard requirement and may require rethinking the React Native tools or dependencies you intend to use to meet the use_frameworks! needs of the native app, if it has any.

2.7 Running pod install

Once the Podfiles are merged, pod install should work, giving an updated podfile.lock file with React Native-oriented lines added, and possible errors or warnings about specific issues, which it usually helps you resolve.

3 Adding RCTRootView and building

This next step, wiring in the React Native screens, is hard to give general pointers for because it depends on the specifics of the native app. But the good news is, it should be less fraught than on Android:

  • The RCTRootView and native ViewControllers are simpler and more flexible than Android activities and fragments, and should integrate more easily into most layouts
  • Most relevant build options are controlled via Xcode, and while it can be a bit of a maze to find a particular option, they are at least within one tool.

So it should be possible here to follow the relevant React Native docs guidance  and be guided by Xcode if there are any issues.

3.1 Adding Info.Plist

One common possible error for the first build attempt after wiring in React Native is:

“Cannot code sign because the target does not have an Info.plist file and one is not being generated automatically.>”

This will happen if the native app did not need or use an Info.plist file; React Native assumes one exists.

The setting to auto-generate one managed by Xcode and is not easy to find; also, the exact position in the Xcode UI sometimes changes. At the time of writing, it can be found by selecting the “Pods” top-level item in the Project Navigator left-hand nav tree, selecting “Pods” again in the sub-tree in the main window, then going to “Build Settings”, then “All”, then using the “filter” tool to find “Generate Info.plist file” in the huge long list of options, and set it to “Yes”.

Changing these settings in Xcode writes a line to the appropriate configuration file (e.g.  GENERATE_INFOPLIST_FILE = YES; in the .pbxproj ), then that change can be committed to the repository.

Conclusion: adding React Native to a complex app is challenging, but it brings numerous benefits to you and your organisation

You’ll have seen that integrating React Native into native apps is a significant technical challenge. There are numerous potential pitfalls that require both going deeper under the hood of the native apps than React Native developers normally do, and also handling some technical complexity from React Native that may be a little outside the comfort zones of a native app developer.

While migrating complex  apps to React Native is complex, the rewards of undertaking this journey can be huge for you and your organisation: reducing your time to market, lowering costs, cutting fragmentation, boosting consistency, improving product reliability and, once the technical challenges are resolved, enhancing developer experience. With a solid plan and the right know-how, it can make your organisation more profitable and a better place to work for developers.

If you go down this road, we hope these guides will help — and we hope you have an understanding team with well-managed expectations and a suitably flexible timeframe!

If you need expert support in undertaking the journey of incorporating React Native into your organisation then NearForm can help. Contact us today , we’d love to talk with you about how we can take your organisation to the next level.

Insight, imagination and expertly engineered solutions to accelerate and sustain progress.

Contact