iOS | 原生App中集成React Native组件指南

前言

本文主要介绍如何将ReactNative集成到原生的iOS项目当中, 并在iOS的原生项目当中去调用使用RN组件,把 ReactNative组件集成到iOS应用中有如下几个主要步骤:

  • 配置好 React Native 依赖和项目结构。
  • 了解你要集成的 React Native 组件。
  • 使用 CocoaPods 把这些组件以依赖的形式加入到项目中。
  • 创建 js 文件,编写 React Native 组件的 js 代码。
  • 在应用中添加一个RCTRootView。这个RCTRootView正是用来承载你的 React Native 组件的容器。
  • 启动 React Native 的 Packager 服务,运行应用。
  • 验证这部分组件是否正常工作。

1. 创建iOS原生项目

创建一个名为iOSDemo的iOS原生App项目, 并在项目根目录下创建一个名为ReactNative的空目录文件,用于存放和RN相关的文件, 项目目录结构如下

image.png

2. 创建React-Native项目

创建一个名为rndemo的RN项目,并保证可以顺利的运行起来, 如何搭建RN环境和创建RN项目这里不做过多说明,网上有很多相关文章, 也可以看我之前的相关环境搭建文章, RN项目创建完成后,会自动创建相关的支持文件以及目录,代码结构如下:

image.png

RN运行起来效果如下:


image.png

一会我们就会将该页面集成到iOS的项目当中去.并在iOS端调用打开.

3. 安装 JavaScript 依赖包

在iOS项目的ReactNative文件夹中创建一个名为package.json的空文本文件

image.png

package.json文件中填入以下内容:

{
  "name": "mydemo",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "yarn react-native start"
  },
  "dependencies": {
    "react": "16.9.0",
    "react-native": "0.61.5"
  }
}

注意: reactreact-native版本号需要和RN项目中的版本对应, 可以参考rndemo中的package.json文件.

version字段没有太大意义(除非你要把你的项目发布到 npm 仓库)。scripts中是用于启动 packager 服务的命令。

接下来我们使用 yarnnpm(两者都是 node 的包管理器)来安装JavaScript所依赖模块(其实就是React和ReactNative框架)。请打开一个终端/命令提示行,进入到iOS项目的ReactNative目录中(即包含有 package.json 文件的目录),然后运行下列命令来安装:

$ npm install

执行完成以后, 所有 JavaScript 依赖模块都会被安装到项目根目录下的node_modules/目录中(这个目录我们原则上不复制、不移动、不修改、不上传,随用随装)。

node_modules/目录记录到.gitignore文件中(即不上传到版本控制系统,只保留在本地)。 node_modules/和iOS的'Pods'文件类似

安装完成后, 目录如下:


image.png


4. CocoaPods集成

通过CocoaPods 把 ReactNative 依赖的环境集成到你当前的项目中。

在iOS项目中,创建Podfile文件,如何创建大家都懂, 打开Podfile文件,修改如下:

# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'

target 'iOSDemo' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  pod 'FBLazyVector', :path => "./ReactNative/node_modules/react-native/Libraries/FBLazyVector"
  pod 'FBReactNativeSpec', :path => "./ReactNative/node_modules/react-native/Libraries/FBReactNativeSpec"
  pod 'RCTRequired', :path => "./ReactNative/node_modules/react-native/Libraries/RCTRequired"
  pod 'RCTTypeSafety', :path => "./ReactNative/node_modules/react-native/Libraries/TypeSafety"
  pod 'React', :path => './ReactNative/node_modules/react-native/'
  pod 'React-Core', :path => './ReactNative/node_modules/react-native/'
  pod 'React-CoreModules', :path => './ReactNative/node_modules/react-native/React/CoreModules'
  pod 'React-Core/DevSupport', :path => './ReactNative/node_modules/react-native/'
  pod 'React-RCTActionSheet', :path => './ReactNative/node_modules/react-native/Libraries/ActionSheetIOS'
  pod 'React-RCTAnimation', :path => './ReactNative/node_modules/react-native/Libraries/NativeAnimation'
  pod 'React-RCTBlob', :path => './ReactNative/node_modules/react-native/Libraries/Blob'
  pod 'React-RCTImage', :path => './ReactNative/node_modules/react-native/Libraries/Image'
  pod 'React-RCTLinking', :path => './ReactNative/node_modules/react-native/Libraries/LinkingIOS'
  pod 'React-RCTNetwork', :path => './ReactNative/node_modules/react-native/Libraries/Network'
  pod 'React-RCTSettings', :path => './ReactNative/node_modules/react-native/Libraries/Settings'
  pod 'React-RCTText', :path => './ReactNative/node_modules/react-native/Libraries/Text'
  pod 'React-RCTVibration', :path => './ReactNative/node_modules/react-native/Libraries/Vibration'
  pod 'React-Core/RCTWebSocket', :path => './ReactNative/node_modules/react-native/'

  pod 'React-cxxreact', :path => './ReactNative/node_modules/react-native/ReactCommon/cxxreact'
  pod 'React-jsi', :path => './ReactNative/node_modules/react-native/ReactCommon/jsi'
  pod 'React-jsiexecutor', :path => './ReactNative/node_modules/react-native/ReactCommon/jsiexecutor'
  pod 'React-jsinspector', :path => './ReactNative/node_modules/react-native/ReactCommon/jsinspector'
  pod 'ReactCommon/jscallinvoker', :path => "./ReactNative/node_modules/react-native/ReactCommon"
  pod 'ReactCommon/turbomodule/core', :path => "./ReactNative/node_modules/react-native/ReactCommon"
  pod 'Yoga', :path => './ReactNative/node_modules/react-native/ReactCommon/yoga'

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

end

注意: path => './ReactNative/node_modules/ 中的路径需要和自己实际项目的JS依赖文件目录对应

提示,可以参考rndemo项目中ios部分的Podfile文件, 位置如下, 将里面依赖项拷贝到iOS项目当中,但需要修改路径

image.png

Podfile文件修改完成后, 就可以开始安装 React Native 的 pod 包了, 在终端执行

$ pod install
image.png

看到此界面,表示集成成功.


5. 代码集成

上述我们已经准备好了所有依赖,可以开始在原生iOS应用中把 React Native代码 真正集成进来了, 接下来我们就通过将示例rndemo页面集成到到iOSDemo中来

React Native 组件

1. 创建一个index.js文件

我们在iOS的ReactNative中创建一个空的index.js文件。(注意在 0.49 版本之前是 index.ios.js 文件)

image.png

index.jsReact Native 应用在 iOS 上的入口文件。而且它是不可或缺的!

在新建的index.js中写入以下代码:

import {AppRegistry} from 'react-native';
import App from './App';
AppRegistry.registerComponent("rndemo", () => App);

rndemo是整体 js 模块(即你所有的 js 代码)的名称。你在 iOS 原生代码中添加 React Native 视图时会用到这个名称。

2. 添加你自己的 React Native 代码

将我们需要使用的RN页面代码导入到iOS项目中, rndemo项目中的App.js文件复制到和index.js同目录下,如下图

image.png

App.js即是我们在步骤二看到的RN欢迎页代码

3.掌握核心科技: RCTRootView

现在我们已经在index.js中创建了 React Native 组件,下一步就是把这个组件添加给一个新的或已有的ViewController

1.创建一个按钮,用于跳转RN页面,界面如下

image.png

2. 创建RN视图:
首先导入RCTRootView的头文件。

#import <React/RCTRootView.h>

在iOS项目中创建一个按钮用于跳转RN页面,代码如下:

- (IBAction)RNPageButtonDidClick:(UIButton *)sender {
    NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
    RCTRootView *rootView =
    [[RCTRootView alloc] initWithBundleURL: jsCodeLocation
                                moduleName: @"rndemo"
                         initialProperties: nil
                             launchOptions: nil];
    UIViewController *vc = [[UIViewController alloc] init];
    vc.view = rootView;
    [self.navigationController pushViewController:vc animated:YES];
}

6. 测试集成结果

1. 添加 App Transport Security 例外
Apple 现在默认会阻止读取不安全的 HTTP 链接。所以我们需要把本地运行的 Packager 服务添加到Info.plist的例外中,以便能正常访问 Packager 服务:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

2. 运行 Packager
要运行应用,首先需要启动开发服务器(即 Packager,它负责实时监测 js 文件的变动并实时打包,输出给客户端运行)。具体步骤,进入iOS项目的index.js所在目录:终端运行:

$ npm start

3. 运行应用
如果你使用的是 Xcode,那么照常编译和运行应用即可。如果你没有使用 Xcode(但是你仍然必须安装 Xcode),则可以在命令行中使用以下命令来运行应用:

$ react-native run-ios

模拟器运行后,点击调转RN页面,会调转到RN欢迎页:


image.png

4. 报错问题:
在调转RN页面时候可能会遇到以下错误

image.png

解决办法:
在info.plist中,add row添加View controller-based status bar appearance并设置为NO即可。

总结:

至此,以上就是将RN模块集成到iOS项目的基本步骤,通过以上步骤,可以了解基本的配置以及集成流程, 在我们实际使用时候,需要根据自身项目实际情况来进行引用; 接下来让我们来看下RN和iOS端是如何进行交互的.

参考文档:

https://reactnative.cn/docs/integration-with-existing-apps/

推荐阅读更多精彩内容