ios 原生应用集成 react-native

前言

其实官方文档《Integration with Existing Apps》讲得挺详细,但是有点面向 ios 开发,对于 ios 开发的同学应该很容易看懂吧。不熟悉的我摸索了很久,记录一下。

重要!!!

先参考官网《Setting up the development environment》把 react-native 的开发环境搭建起来,因为基本如出一辙。对于 ios 而言, react-native 像是它的一部分子应用,本质上还是原生应用。
因为就是要用到原生的东西,所以不要用 Expo 这种方式。这个是方便开发用的,最后应该要回到原生来。

能成功启动 react-native,想必也搞定了 CocoaPods 这个麻烦的工具,用来下载原生相关依赖的。

创建 ios app

Xcode Version 14.3 (14E222b)

1、创建一个 ios app

2、填写相关信息

Product Name:顾名思义,这个应用的名字,随便填,自己记住。
Team:没有就先跳过,苹果开发要求的,上线要用到。
Organization Identifier:组织标识,公司或者产品网址倒着写,随便填。
Interface:有 SwiftUI 和 Storyboard 可选,选择 Storyboard。
Language:选 Swift 或 Objective-C 都可以。

3、操作 xcode

上图为例

先点击中间的“分屏按钮”,编辑器就会变成两个编辑区,一个打开 ViewController.m 文件,一个打开 Main 文件。虽然显示是 Main 文件,但硬盘上可以看到是叫 Main.storyboard 文件。

然后点击右边的“组件库按钮”,打开组件库,拖拽一个 button 组件到 storyboard 编辑区,也就是生成一个按钮到应用上。

最后点击左边的“运行按钮”,看到弹出来的手机模拟器上有自己加的 button 就算成功了。

ios 集成 react-native

1、创建一个 react-native app

成功启动后。

  • 进入 ios 文件夹,把除 Podfile 文件之外的东西全删了。
  • 然后把刚刚新建的 ios 工程里的所有文件放进这个 react-native 的 ios 文件里,接着把刚仅剩下的 Podfile 文件里,原 react-native 应用的名字全部替换成刚刚拷贝过来的这个 ios 的应用名
  • 包括测试相关的 “xxxxxTests” 也要替换。当然如果创建 ios 工程没有勾上测试要求,就要把相关的 “xxxxxTests” 去掉。

然后在这个 ios 路径执行:

pod install

这就是 CocoaPods 这个工具在安装 react-native 所需要的原生依赖。当然也不一定要新建一份 react-native 工程。说白了这个 ios 文件夹里的东西就是 ios 原生 app 的东西,创建 ios 应用本身是没有 Podfile 这份文件的。可以执行

pod init

来生成 Podfile 文件。但是要安装运行 react-native 相关的依赖,所以最好是从一份 react-native 工程里把 Podfile 内容拷贝过来,然后执行安装。这也是官网推荐的方法。

2、运行集成了 react-native 的 ios

双击 ios 文件夹里的 .xcworkspace 文件,就会用 Xcode 打开这个 ios 工程。接着刚刚的分屏操作,把编辑器分成两个编辑区。然后根据刚刚创建 ios 应用选择的 Swift 或 Objective-C,根据官网 event-handler 给出的事件代码,添加到 ViewController.m 文件里。

注意:

  • 在文件最上面需要添加依赖代码,Swift 添加的依赖是 import React,Objective-C 添加的 #import <React/RCTRootView.h>
  • 然后代码里 moduleName 这个参数的相关值要替换成 react-native 的应用名。也就是要和入口里的注册函数 AppRegistry.registerComponent(appName, () => App); 里的那个 appName 的值要一致。因为这就是告诉 ios app 加载这个名字的组件。

然后在 Xcode storyboard 编辑区,按住鼠标右键或者 ctrl + 鼠标左键,从视图的 button 组件可以拖出一条蓝线,可以直接指向另外编辑区的刚刚新增的事件函数,这样子视图层的这个按钮就会和逻辑层的响应事件关联起来。

此时再鼠标右键或者 ctrl + 鼠标左键单击 storyboard 上 button 组件,可以看出已经和相应事件绑定了起来。

在 react-native 工程根目录,执行 react-native start 也就是 yarn start 启动 react-native 服务器,会以服务器的方式提供 jsbundle 文件,也就是事件代码里的 8081 index.bundle?platform=ios 地址,可以把这个地址丢到浏览器查看,能看到 js 文件内容,就是生成成功。除去资源文件,其实最终 react-native 产出就是这份 js 文件,然后交到原生端去解析,变成原生组件。

好了,响应代码加了,适配了,绑定了,点击“运行按钮”也就是那个三角形按钮。模拟器跑起来,然后点击一开始就添加的按钮,就能看到 react-native 的界面弹出来了。成功!

3、集成的展示方式

(1) 抽屉式展示改成嵌入式展示

上面代码是类似弹出一个抽屉一样的交互,可以注释掉相关代码,改成以下代码,变成嵌入效果,同时设置嵌入框大小。
CGRectMake 四个参数分别是:x y width height

  • Objective-C:
// UIViewController *vc = [[UIViewController alloc] init];
// vc.view = rootView;
// [self presentViewController:vc animated:YES completion:nil];
rootView.frame = CGRectMake(0, 0, 200, 200);
[self.view addSubview:rootView];
  • Swift:
// let vc = UIViewController()
// vc.view = rootView
// self.present(vc, animated: true, completion: nil)
rootView.frame = CGRectMake(50, 200, 300, 400);
view.addSubview(rootView)

(2) 点击触发加载改成自动加载

当然不是一定要有个按钮触发,只是方便调试而已。成功之后,可以把相关的 react-native 加载代码放到 viewDidLoad 这个生命周期里,让它自动展示出来。

- (void)viewDidLoad {
  [super viewDidLoad];
  // react-native 加载代码
}

异常情况

1

如果报错 [RNIntegrade Swift.AppDelegate window]: unrecognized selector sent to instance xxxxxx",参考 issue 或者 stackoverflow 给的解决方案。大概意思就是新版本 window 这个对象什么的没有注入要主动声明啥的,可能是可能不是,不太懂。

  • Objective-C,在 AppDelegate.h 文件添加相关代码:
// #import <UIKit/UIKit.h>
// @interface AppDelegate : UIResponder <UIApplicationDelegate>
// @end
// 替换成下面这样
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
@property (nonatomic, strong) UIWindow *window;
@end
  • Swift,在 AppDelegate.swift 文件添加相关代码:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window:UIWindow? // 添加这一句,问号要带上
    ...
}

2

在语言选择 Swift 时,如果在 bundleURL: jsCodeLocation, 这一行报出下列的错误:Value of optional type 'URL?' must be unwrapped to a value of type 'URL'。改动两行代码:

let jsCodeLocation = NSURL(string: "http://localhost:8081/index.bundle?platform=ios");
let rootView = RCTRootView(
  bundleURL: jsCodeLocation! as URL,
  ...,
)

3

上面的修改集成展示方式,可能会报错:UIViewControllerBasedStatusBarAppearance key in the info.plist is set to no

在 Xcode 最左边的文件目录选择 info 文件,如图如提示设置参数。当然只是解决了这个报错,实际会有啥影响就不懂了。

剩余问题

在一开始的 Interface 选择的时候,选择 Storyboard。其实一开始我选择的是 SwiftUI,然后看网上的教程和官网的示例都是 Storyboard 的,就是关键的那一步事件绑定怎么也看不懂按不出来怎么操作。SwiftUI 里的事件绑定方式都不一样,而且工程目录也不一样,都找不到对应文件。尝试了很多次都不行,后面换成 Storyboard 才成功。

这个对熟悉 ios 开发的同学来看应该就很简单吧。后面有知道怎么弄了再更新。

其它

关于 react-native 开发相关,可以参考《react-native ios 流水账》。
与 android 的集成,可以参考《android 原生应用集成 react-native》。