Migrating Your React Native iOS Project to Use CocoaPods

React Native 0.60 introduced a number of changes, one of which integrating CocoaPods by default.

This tutorial will help you migrate your project to use pods. I’ll also guide you into setting up an environment that is team-friendly by using a Ruby version manager and the Bundler package manager to install your pods.

You can use this tutorial for any version of React Native that you are using. However, if you are on React Native 0.60 or higher, I will show you how to set up autolinking as well (no more react-native link <library-name> commands!). 🔗

Image for post
Image for post

What is CocoaPods and Why Do I Need Them? 📦

According to cocoapods.org:

Basically, it’s npm or yarn, but for macOS and iOS modules.

It’s not absolutely necessary to migrate for your 0.60 project to work. Manual BUT support for xcodeproject files is removed in 0.61. Completing this migration will let you use the new xcodeworkspace model. And migration is easy! 🎉

Step 1: Set Up a Ruby Environment 💎

CocoaPods is built with Ruby and can be installed via the RubyGems package manager. So we will need Ruby to start the process.

Good News: Ruby comes pre-installed on a Mac!

Bad News: It’s probably not what we should use.

Why?

  1. You will need to use sudo to install Ruby packages (also knowns as “gems”). You start installing things at a system level. It can mess with other programs. It can mess with other users. Be smart. Don’t use sudo. 🙅🏻‍♀️
  2. Different machines come with different versions of Ruby installed. We should be consistent in how we install our dependencies.

Instead, let’s install Ruby via a version manager called rbenv. It’s like nvm, but for Ruby.

Before we install rbenv, find out which version of Ruby you would like to work with. Head over to ruby-lang.org where you can out what the latest stable version is.

Next, in the root of your project, create a .ruby-version file and type that number in.

// .ruby-version
2.7.1

This will be your project’s required version of Ruby.

For the purposes of this tutorial, I will be using version 2.7.1. You can use whichever version you please.

Step 2: Install rbenv ⬇️

rbenv can be installed via Homebrew. We will use this to install our desired version of Ruby.

To install rbenv:

$ brew install rbenv

Then, run:

rbenv init

…and follow the instructions to finish the installation. You will probably be asked to update your .bash_profile or .zshrc with a new PATH variable.

When done, restart Terminal to apply your changes.

Step 3: Install Ruby ⬇️

Use rbenv to install your desired version of Ruby:

$ rbenv install 2.7.1

Once installed, you’ll need to boot it up:

$ rbenv global 2.7.1

This command sets your global version of Ruby.

🙋🏿‍♂️ Question: “Will my global version of Ruby apply to all projects on my machine?”

👩🏻‍🏫 Answer: “Nope. If you have a .ruby-version file in a project, that specified local version will be used instead.”

Next, run the following command to update your shims and allow your version of Ruby to be used properly on your system:

$ rbenv rehash

You’re done! You now have a sudo-less, version-specified copy of Ruby installed on your machine.

Step 4: Create a Gemfile 💎

All set with Ruby. Now it’s time to install CocoaPods.

First, check which version of CocoaPods you wish to install. I went with the latest release listed.

Next, create a file called Gemfile in the root of your directory, and add the following, including the version of CocoaPods you wish to install:

# Gemfile
source "https://rubygems.org"

gem "cocoapods", '1.9.1'

Step 5: Install Bundler 📦

To install the version of CocoaPods listed in your Gemfile, we will use a tool called Bundler. Bundler will help us install gems and will produce a lock file (similar to package-lock.json or yarn.lock), that will keep our dependencies in check.

To install Bundler, run:

$ gem install bundler

Step 6: Install CocoaPods 🍎

Finally! To install CocoaPods, run the following in the root of your project:

bundle install

This creates a Gemfile.lock as well. Please always commit your lock files!

Step 7: Create a Podfile 🍎

We will need to create a Podfile that Xcode will use to install the necessary iOS dependencies.

cd into tour ios directory, and create a new file called Podfile

The contents of this file will be specific to the version of React Native that you are using. This tutorial shows a migration to CocoaPods on a React Native 0.60.6 project. Other versions of React Native will have different dependencies listed in their Podfiles.

So, if you are upgrading to a different version of React Native, I suggest you start a new temporary project in the version of React Native you wish to use (e.g. npx react-native init YourProjectName --version X.XX.X), and copy the contents of the Podfile in the new project, instead of what I provide below.

In a React Native 0.60.6 project, add the following to your Podfile and change all instances of <target-name> to the target name of your app.

🙋🏾‍♀️ Question: “How do I find the target name?”

👩🏻‍🏫 Answer: “Open up your project in Xcode. Select the project in the top left corner and look at the list of TARGETS. Here’s a screenshot:”

Xcode Target List
Xcode Target List

My 0.60.6 Podfile:

# ios/Podfile

platform :ios, '9.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

target '<target-name>' do
# Pods for <target-name>
pod 'React', :path => '../node_modules/react-native/'
pod 'React-Core', :path => '../node_modules/react-native/React'
pod 'React-DevSupport', :path => '../node_modules/react-native/React'
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
pod 'React-RCTWebSocket', :path => '../node_modules/react-native/Libraries/WebSocket'

pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'

pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'

target '<target-name>Tests' do
inherit! :search_paths
# Pods for testing
end

use_native_modules!
end

target '<target-name>-tvOS' do
# Pods for <target-name>-tvOS

target '<target-name>-tvOSTests' do
inherit! :search_paths
# Pods for testing
end

end

Step 8: Install Pods 📦

cd into your ios directory. Now run:

$ bundle exec pod install

In your ios directory, this command creates:

  • A Podfile.lock file
  • A <app-name>.xcworkspace directory
  • A Pods directory

‼️ From this point on, always use your new .xcworkspace directory when opening the project in Xcode. Do not use the old .xcodeproj. ‼️

The script you just ran will be the new script you will need to use every time you want to install a new pod. Often times, you’ll see the documentation of a library instruct you to run a pod install command. Don’t do this. Run bundle exec pod install instead.

🙋🏼‍♀️ Question: “Why do I need to use the bundle exec prefix?”

👩🏻‍🏫 Answer: “Not using bundle exec will skip the version of CocoaPods you are requiring with your Gemfile. Your Gemfile is there to prevent problems. Let’s not ignore it.”

Do yourself a favor and create a script in your package.jsonto help you out:

// package.json

{
...
"scripts": {
"podInstall": "cd ios && bundle exec pod install",
}
}

Now every time you add a new dependency with native iOS modules, just run npm run podInstall or yarn run podInstall to install the pod.

Step 9: Ignore your Pods directory 🙅🏻‍♀️

Pods are essentially node_modules. Don’t commit them. They can be easily reinstalled.

Update your .gitignore to ignore these files:

// .gitignore

# CocoaPods
/ios/Pods/

Step 10: Update Your Dependencies ⬆️ (with Autolinking!)

For those using React Native 0.60 or higher

If you are using React Native 0.60 or higher, the coolest part of the Podfile is that use_native_modules! function at the end. This is the autolinking bit. Once you install your library via npm or yarn, you are done with the iOS install. This works for almost all libraries. Almost all…

Go through the installation instructions of each dependency you have and see if there are any additional podspecs that you need to add. (You can also just try to run your project and see if anything breaks and take it from there. 🤠)

Some libraries will still require you to adjust your Podfile. Here is an example of something I had to do:

# Example v0.60 Podfile using libraries that still require some podspecs. 
# Do not add to your project.

...
# Patch for react-native-push-notification install error
pod 'React-RCTPushNotification', :path => '../node_modules/react-native/Libraries/PushNotificationIOS'

# Patch for react-native-image-crop-picker dark mode bugs
pod 'QBImagePickerController', :path => '../node_modules/react-native-image-crop-picker/ios/QBImagePicker/QBImagePickerController.podspec'
...

With any adjustments you make, make sure the use_native_modules! function lives at the end of your file.

For those using React Native < 0.60

If you are using a version of React Native lower than 0.60, you will need to find the installation instructions for each dependency you are using, and manually add any necessary podspecs to your Podfile.

For example, if you are on React Native 0.59 and are using the react-native-maps library, you would need to add something like the following:

# Example v0.59 Podfile adjustments if using react-native-maps 
# Do not add to your project.

...
# react-native-maps dependencies
pod 'react-native-maps', path: rn_maps_path
pod 'react-native-google-maps', path: rn_maps_path
pod 'GoogleMaps'
pod 'Google-Maps-iOS-Utils'
end

post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == 'react-native-google-maps'
target.build_configurations.each do |config|
config.build_settings['CLANG_ENABLE_MODULES'] = 'No'
end
end
end
end

Step 11: Install Your New Pods ⬇️

If you have updated any libraries to newer versions, or have made changes to your Podfile you will need to run bundle exec pod install once again in your iOS directory.

Or, if you have added it in Step 8, run your podInstall script instead.

Changing the Podfile is just like changing a package.json file. It’s a set of instructions indicating which dependencies should be installed.

Step 12: Delete Your Old Links 🧹

The only thing I like more than writing code is deleting code. 😍

Your old react-native link commands have created a lot of unnecessary links that need to be removed.

Open up your new <app-name>.xcworkspace directory in Xcode. Pick your Target and under the General tab you will see a “Frameworks, Libraries, and embedded Content” section. Delete everything except for the JavaScriptCore.framework.

Example Xcode Screenshot Before Removing Links
Example Xcode Screenshot Before Removing Links
Example Xcode Screenshot Before After Removing Links
Example Xcode Screenshot Before After Removing Links

You will see a heck of a lot of code deleted from your old project.pbxproj file. Doesn’t that feel great?!

Step 13: Build and Test! 🧪

Go ahead and run your project! Make sure you give your app a full test. If any errors come up, they are very likely dependency errors, so head over to those repos and see if there are any additional steps that have been missed.

Congrats on your migration! 🎉 🎉 🎉

Metro Bundler Won’t Start?

When I first upgraded to 0.60, I had an issue where Metro Bundler would not automatically start when running my app.

I had to add a Run Script, which can be found under the Build Phases tab in your Target.

New Run Script Section
New Run Script Section

Here is the script:

export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}"
echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${SRCROOT}/../node_modules/react-native/scripts/.packager.env"
if [ -z "${RCT_NO_LAUNCH_PACKAGER+xxx}" ] ; then
if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then
if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then
echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly"
exit 2
fi
else
open "$SRCROOT/../node_modules/react-native/scripts/launchPackager.command" || echo "Can't start packager automatically"
fi
fi

Once this script was in, I no longer had to manually launch the bundler with npx react-native start.

Migrating to CocoaPods Checklist
Migrating to CocoaPods Checklist

👋 Hi! I’m Juliette. I work at Eventric as a Software Developer. Come follow me on Twitter at @Juliette.

Written by

React & React Native Developer. Building awesome things @Eventric.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store