原生 iOS 项目集成 React Native

创建一个 React Native 项目并写一个纯的 React Native 应用可以参考官方指南

Android 项目集成 React Native 可以参考 原生 Android 项目集成 React Native

本文主要介绍原生 iOS 项目集成 React Native 并用于部分页面开发的流程。开发环境为 macOS 10.12、Xcode 8.0、React Native 0.35.0。而官方给出的 植入原生 iOS 应用指南 只对应到 0.28 版本。最新版(当前为 0.35)的集成方案稍微有些变动。

0. 安装 CocoaPods

iOS 开发者可以跳过这一步。

0.0 使用 rvm 安装/更新 ruby 环境

安装 cocoapods 对 ruby 版本有要求

1
2
3
4
5
6
7
$ curl -L https://get.rvm.io | bash -s stable
$ source ~/.rvm/scripts/rvm
// 查看远程 ruby 版本
$ rvm list known
// 查看本地 ruby 版本
$ rvm list
$ rvm install 2.3.0

0.1 使用 gem 安装 cocoapods

如要修改 gem 的镜像地址

1
2
3
4
5
6
$ gem sources -l
// 删除已有的源地址
$ gem sources -r https://rubygems.org/
// 添加需要的源地址
$ gem sources -a https://ruby.taobao.org/
$ gem sources -l

安装 cocoapods

1
2
3
$ gem install cocospods
$ cd ~/.cocoapods/repos/
$ git clone https://github.com/CocoaPods/Specs.git master

顺利的话安装 ruby 和 cocoapods 两步都会成功,如果失败了可以针对具体问题去网上搜索相关教程或解决方案。这里不详述。

1. 创建/修改 iOS 项目

集成 React Native 要求 iOS 系统版本不小于 7.0。

2. 添加 package.json

在 iOS 项目根目录新建文件 package.json,内容如下(参考 react-native init 生成的 package.json 文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "react-native-sample",
"version": "0.0.1",
"description": "sample of react native embedding ios",
"main": "index.ios.js",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"author": "danke77",
"license": "ISC",
"dependencies": {
"react": "^15.3.2",
"react-native": "^0.35.0"
},
"devDependencies": {
}
}

执行 npm install 就可以安装 dependencies 下的 npm 组件了。

这个时候在 iOS 项目根目录就生成了 node_modules/ 文件夹,里面就是一些用到的组件。

执行 react-native upgrade 可以更新已有组件。

3. 添加 index.ios.js

在 iOS 项目根目录创建目录 js/,js 相关的代码就放在这个文件夹下。

js/ 下添加 App.js,内容如下

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
import React, { Component } from 'react'
import { View, Text, StyleSheet } from 'react-native'
export default class extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.text}>
Hello React Native!
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ffffff'
},
text: {
fontSize: 20,
color: '#333333'
}
})

在 iOS 项目根目录新建文件 index.ios.js,内容如下

1
2
3
4
import { AppRegistry } from 'react-native'
import App from './js/App'
AppRegistry.registerComponent('navigation', () => App)

这里的 navigation 一般会根据模块功能命名,后面还会用到。

当然也可以把 App.js 的内容写在 index.ios.js 里,但这样写更清晰一些,尤其是项目大了文件多的情况。

4. 用 cocoapods 集成 React Native

在 iOS 项目根目录的 Podfile 文件(没有则创建)添加 React Native 相关内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
target 'HelloReactNative' do
platform :ios, '7.0'
source 'https://github.com/CocoaPods/Specs.git'
# 这里的 :path 内容取决于 node_modules/ 实际所在的位置
pod 'React', :path => ‘./node_modules/react-native', :subspecs => [
'Core',
'CSSLayout',
'RCTText',
'RCTImage',
'RCTNetwork',
'RCTWebSocket', # needed for debugging
# Add any other subspecs you want to use in your project
]

要在这里添加项目需要的依赖,如要使用 React Native 的 Text 则必须要添加 RCTText 依赖(pod 'React/RCTText')。

然后在根目录执行 pod install

如果报错说找不到 CSSLayout,则需要在 node_modules/react-native/React.podspec 里添加

1
2
3
4
s.subspec 'CSSLayout' do |ss|
ss.source_files = "React/CSSLayout/**/*.{c,h}"
ss.header_mappings_dir = "React"
end

并重新执行 pod install。如果在 Pods/Development Pods/ 下有 React/,且 React/ 下有 CoreCSSLayoutRCTText 等则说明成功。

.gitignore 中添加

1
2
3
# node.js
# node_modules/
npm-debug.log

5. React Native 相关的 ViewController

创建一个用于容纳 React Native 组件的 ViewController,并添加 RCTRootView,它会把 React Native 组件解析成原生的 UIView。

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
#import "HelloReactViewController.h"
#import <RCTRootView.h>
@interface HelloReactViewController ()
@end
@implementation HelloReactViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
#ifdef DEBUG
NSURL * jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
#else
NSURL * jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"bundle"];
#endif
RCTRootView * rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"navigation"
initialProperties:nil
launchOptions:nil];
self.view = rootView;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

jsCodeLocation 是 React Native 资源加载的路径,可以通过网络加载本地的资源文件(主要用于本地调试),或者将其打包成 js bundle 文件(用于发布正式包)。

moduleName 对应 React Native 组件的入口,必须和前面的 AppRegistry.registerComponent('navigation', () => App) 里的 navigation 对应。

6. 启动服务

debug 模式下需要启动 package server,在 package.json 所在目录(一般为项目根目录)下执行 npm start,它等效于 package.jsonscripts 下的 node node_modules/react-native/local-cli/cli.js start,相当于启动一个本地服务。

Terminal 显示如下表示服务已正常启动

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
> react-native-module@0.0.1 start /Users/danke77/Projects/react-native/HelloReactNative
> node node_modules/react-native/local-cli/cli.js start
Scanning 581 folders for symlinks in /Users/danke77/Projects/react-native/HelloReactNative/node_modules (17ms)
┌────────────────────────────────────────────────────────────────────────────┐
│ Running packager on port 8081. │
│ │
│ Keep this packager running while developing on any JS projects. Feel │
│ free to close this tab and run your own packager instance if you │
│ prefer. │
│ │
│ https://github.com/facebook/react-native │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Looking for JS files in
/Users/danke77/Projects/react-native/HelloReactNative
[2016-10-17 17:06:48] <START> Building Dependency Graph
[2016-10-17 17:06:48] <START> Crawling File System
[Hot Module Replacement] Server listening on /hot
React packager ready.
[2016-10-17 17:06:49] <END> Crawling File System (966ms)
[2016-10-17 17:06:49] <START> Building in-memory fs for JavaScript
[2016-10-17 17:06:49] <END> Building in-memory fs for JavaScript (260ms)
[2016-10-17 17:06:49] <START> Building in-memory fs for Assets
[2016-10-17 17:06:50] <END> Building in-memory fs for Assets (138ms)
[2016-10-17 17:06:50] <START> Building Haste Map
[2016-10-17 17:06:50] <START> Building (deprecated) Asset Map
[2016-10-17 17:06:50] <END> Building (deprecated) Asset Map (104ms)
[2016-10-17 17:06:50] <END> Building Haste Map (428ms)
[2016-10-17 17:06:50] <END> Building Dependency Graph (1825ms)

7. 开发调试

模拟器上可以通过工具栏的 Hardware->Shake Gesture 或快捷键调出开发调试菜单;真机上可以通过摇一摇调出。

8. 发布正式包

React Native 的开发版需要有一个 package server 随时发送更新后的 js bundle 文件。如果要打正式包,需要把 js bundle 文件保存到 iOS 项目的目录下。这样,正式包就不需要 server 支持了,可独立运行。

在根目录下创建 bundle/ 文件夹,执行以下命令将 js bundle 保存到资源目录下

1
$ react-native bundle --platform ios --dev false --entry-file index.ios.js --bundle-output ./bundle/index.ios.bundle --assets-dest ./bundle

bundle/ 下就会生成 index.ios.bundle 文件及 assets/ 文件夹,后者会放 React Native 中用到的资源如图片等。

然后将生成的 bundle/ 文件夹以 Create folder references 的形式导入到工程里,就可以打正式包了。

本文是 慌不要慌 原创,发表于 https://danke77.github.io/,请阅读原文支持原创 https://danke77.github.io/2016/10/19/react-native-embedding-ios/,版权归作者所有,转载请注明出处。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!