We at Sandstorm really embraced React in lots of projects, ranging from internal tools, our product exply, to customer projects and the Neos CMS React UI. That's why it was very natural for us to use React Native - a nice toolkit to build cross-platform apps. However, there are some problems to solve in order to nicely manage native iOS dependencies. Read on for a description of the issues, and our solutions to them!
Our Problem: The build system for iOS is ... suboptimal (IMHO).
Furthermore, e.g. IntelliJ for Java has the possibility to auto-configure itself based on the maven pom.xml or build.gradle file – thus there is a human-readable, minimal and expressive dependency listing and build description. For XCode, on the other hand, you have to work with extremely verbose xcodeproject files, which have to be checked into Git, and are generally very fragile to handle in my experience in React Native projects. Just having a single setting "wrong" of the 100s of settings in XCode will then lead to very strange build issues.
To me, this is extremely fragile, and when skimming GitHub, you see the same issues appearing again and again on many React Native libraries.
Note: I am sure the XCode build system also has its benefits, but to me personally, it definitely lacks the conciseness and expressiveness of e.g. a gradle-based build; and a standardized way to manage dependencies. So be aware, I am no "native" ObjectiveC/Swift developer, so some details might be inaccurate.
- We'd like a standardized way to install native dependencies.
- We'd like to adjust the XCode project as minimally as possible.
- We'd like reproducible builds with little moving parts.
CocoaPods to the rescue!
We've checked the two community dependency managers for ObjectiveC: CocoaPods and Carthage. We settled on CocoaPods after trying out both. Some people see the holistic approach of CocoaPods as a little dangerous; but for us, it worked quite well now with almost no problems.
The basic principle of CocoaPods is depicted in the following diagram: It creates a "Pods" project, which exposes a static library libPods.YourReactNativeProject.a, which contains the native dependencies. Furthermore, your own project (which by default has e.g. references to React Native core), is modified to contain this single dependency. Everything which is managed by Cocoapods is shown in orange in the diagram below.
The problem now is that the native libraries (e.g. react-native-svg) naturally depend on React Native core again - thus we need to ensure that they reference the same React Native library which you link to from the outer project.
Note: For the build and deployment to work properly, you need to keep a direct dependency from your project to React Native; and you need to prevent dependencies from your libraries to React Native. Otherwise, developing and testing will still work, but things will fail when building for the device.
Step by Step Tutorial to managing React Native Dependencies with CocoaPods
The following steps should help you to get started with cocoapods for React Native quickly, and serve as a reference for our learnings.
1. Installing cocoapods, create a basic Podfile
First, install cocoapods using sudo gem install cocoapods.
Then, in the ios folder, create a Podfile with the following contents:
The above Podfile does not contain any foreign dependencies yet; however it sets up the dependency to React which we'll need as soon as we add the first library.
Note: This podfile is basically taken from the official docs; though they do not mention that this can be a good way to manage foreign dependencies. Furthermore, it has been adjusted by our real-world learnings.
2. Execute pod install
By running pod install, an ios/Pods directory will be created, containing an XCode project with all foreign dependencies. Furthermore, the already-existing xcodeproj will be modified to contain the dependency to the pod-library. This will be the only modification to the main xcodeproj - no need to manually modify it anymore!
After this, commit your results and ensure that react-native run-ios as well as the Run action through XCode still work.
Note: We are following the recommendation of cocoapods, and are checking in the full ios/Pods folder into Git.
To install react-native-svg, first install it using yarn add react-native-svg.
Note: We always use yarn as dependency manager, as it ensures that the dependencies get checked out in a reproducible way.
Now, it's time to include it in the Podfile. To do that, we have to inspect the package in node_modules/react-native-svg - and if we are lucky (like in the case of this package), we are finding a *.podspec file; in this case it is named RNSVG.podspec. Remember the name, it will be used in the next step.
Now, add the following line to the ios/Podfile:
You see we are referencing the podfile in the NPM package (which is installed through yarn).
Now, you should run pod install again (in the ios folder). You should not see changes to the main XCode project, only the Pods xcode project will change. You should verify that the dependency you just added actually ends up as target in xcode - as shown in the following image:
If your just-added library does not appear as target of the Pods project, the Podfile of the library is wrong – we usually patch the Podfile then (as we will show in the next example).
In order to add react-native-app-auth, you need to add the following two lines to the Podfile:
When running pod install, it will fail because the homepage is not set in that podfile. When you fix this, you'll see that the dependency does not appear as Target in the Pods project of XCode (see screenshot above to remember where to look). After investigation, it turned out that the path to the sources in the Podfile was not correct - after we fixed this and ran pod install again, the problem disappeared.
To sum up, we are patching the package (which is a pragmatic way to solve the problem; but if you know a better way let us know). For that, we are using the patch-package npm package to remember the patches in our project. In a nutshell, you need the following patch:
The patch changes the following things:
- Manually fix the reference to AppDelegate.h. If anybody knows a better way to do this, let us know!
- Add the homepage.
- Fix the source files path - the source files are directly next to RNAppAuth.podspec, without a nested directory. I am unsure how this Podfile could ever work :-)
Now, you again need to run pod install.
Installing react-native-vector-icons works just as usual: Install it using yarn add react-native-vector-icons and then add the following line to the Podfile:
Then, remember to run pod install. You then still need to edit the Info.plist file of your project to include the needed fonts in the application bundle, as explained here in the official docs.
Installing react-native-i18n works by adding it using yarn add react-native-i18n, and then add the following line to the Podfile:
Then, remember to run pod install.
Installing react-native-navigation works by adding it using yarn add react-native-navigation ^2.0.0, and then add the following line to the Podfile:
Then, remember to run pod install. You now need to adjust your AppDelegate.m as described in step 3 of the official docs.
Example: React ART
React ART is a little special, as it is already packaged with the React Native source code; it is just not linked explicitely. To link it (and to have it work in all cases), I needed to manually link it as described in step 1 and 2 of the React Native docs. Furthermore, I added it as subspec in the Podfile:
Now, remember to run pod install.
This is so far the only library where a manual adjustment of the main XCode project is needed.
- This has been tested with react 16.3.1, react-native 0.55.4, cocoapods 1.5.3, XCode 9.4, and Mac OS 10.13.4 (High Sierra).
As soon as you use react-native link, react-native run-ios will break with build errors! If that happens, the following has helped for us:
Go to [YourReactApp] -> select the main target and copy it; name it "..._XCode"
In the original target, go to "Build Phases" and "Link Binary with Libraries", and remove everything except libPods-...... This should fix react-native run-ios.
When you want to run/debug through XCode, use the copied XCode target.
Summary: Never use react-native link for modifying iOS targets!
- We are not manually modifying the xcodeproj which was generated by react-native init. We never use react-native link.
- We're using cocoapods to add native dependencies to the project. We version the full Pods folder in Git.
- If we need to patch the sources, we do so by using patch-package. It would be nicer to get rid of this, but we won't be dogmatic about this.