開發(fā)語言:ReactNative 0.59.5 Swift 5
開發(fā)環(huán)境:VSCode Xcode 10.2
1、項目目錄
參考文章:集成到現(xiàn)有原生應用
首先,我們按照建立一下目錄結(jié)構(gòu),其中:
Code目錄放置所有公用的ReactNative腳本,包,以及相關(guān)配置。
IOS目錄放置原IOS項目。
Code (根目錄)
--IOS (一級目錄)
2、開發(fā)環(huán)境準備
2.1、package.json配置
在Code目錄下創(chuàng)建package.json文件,編輯文件輸入以下內(nèi)容。
{
"name": "AppName",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "yarn react-native start"
}
}
2.2、React和React Native模塊安裝
在Code目錄下使用控制臺執(zhí)行以下語句來安裝React Native。
yarn add react-native
- 注意,執(zhí)行完以上命令后,可能會出現(xiàn)以下提示內(nèi)容,表示我們需要安裝指定版本的React(此例子中需要安裝版本為16.8.3的React)。
warning " > react-native@0.59.5" has unmet peer dependency "react@16.8.3".
在Code目錄下使用控制臺執(zhí)行以下語句來安裝指定版本的React
yarn add react@16.8.3
3、安裝CocoaPods
3.1、安裝CocoaPods(如果已安裝過可跳過此步驟)
控制臺執(zhí)行以下命令
brew install cocoapods
3.2、創(chuàng)建podfile(如果IOS原有項目已配置過podfile可跳過此步驟)
在IOS根目錄下使用控制臺執(zhí)行以下語句來創(chuàng)建podfile文件
pod init
3.3、使用podfile引用React Native
打開podfile,將與React Native有關(guān)的pod項目加入到podfile中。(下面例子中的新增部分)
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
#新增部分
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target 'ReactNativeDemo' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for ReactNativeDemo
#引用需要的第三方庫,例如
pod 'SnapKit', '~>4.2.0'
#新增部分
#引用React,注意path路徑應該指向根目錄code中的node_modules,subspecs中為原生app解析rn控件需要的庫,
#例如,我們在rn中使用text,則需要在subspecs中引用RCTText
# Pods for RNDemo
pod 'React', :path => '../../node_modules/react-native', :subspecs => [
'Core',
'CxxBridge', # 如果RN版本 >= 0.47則加入此行
'DevSupport', # 如果RN版本 >= 0.43,則需要加入此行才能開啟開發(fā)者菜單
'RCTImage',
'RCTText',
'RCTNetwork',
'RCTWebSocket', # 調(diào)試功能需要此模塊
'RCTAnimation', # FlatList和原生動畫功能需要此模塊
'RCTActionSheet',
'RCTGeolocation',
'RCTPushNotification',
'RCTSettings',
'RCTVibration',
'RCTLinkingIOS'
]
#新增部分
# 如果你的RN版本 >= 0.42.0,則加入下面這行,注意path路徑
pod "yoga", :path => "../../node_modules/react-native/ReactCommon/yoga"
#新增部分
# 如果RN版本 >= 0.45則加入下面三個第三方編譯依賴,注意podspec路徑
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'
end
保存文件后,在IOS根目錄下使用控制臺執(zhí)行以下語句來安裝pod包。
pod install
4、腳本創(chuàng)建
在Code根目錄下創(chuàng)建Scripts文件夾,用于存放React Native的腳本文件
Code (根目錄)
--Scripts(一級目錄,用于存放所有React Native的腳本)
然后我們可以在Scripts目錄下開始寫ReactNative的腳本了。
首先我們創(chuàng)建一個FrameText.js,然后寫入如下內(nèi)容:
import React, { Component } from 'react'
import { View, Text } from 'react-native'
export default class FrameText extends Component {
render() {
return (
<View style={{
width:200,
height:100,
backgroundColor: '#CDDAF5'
}}>
<Text>{"我來自ReactNative,我是FrameText"}</Text>
</View>
);
}
}
// 整體js模塊的名稱
export { FrameText }
在FrameText中,我們創(chuàng)建了一個簡單的組件,供其他腳本使用。
然后我們再創(chuàng)建一個index.js,然后寫入如下內(nèi)容
import {AppRegistry} from 'react-native'
import {FrameText} from 'FrameText'
// 整體js模塊的名稱
AppRegistry.registerComponent('Component-1', () => FrameText);
在index中,我們注冊了FrameText組件,供app使用,我們可以在index.js注冊很多不同的組件,app可以通過我們注冊的名字(本例中為Component-1)來創(chuàng)建這些組件,下面我們來看看怎么在app內(nèi)使用他們。
5、IOS項目修改
本例中期望在app的一個controller內(nèi),同時使用原生語言與ReactNative腳本分別顯示2個View,現(xiàn)在我們來看看是如果在原生app中加載ReactNative的View。
5.1、創(chuàng)建 MyReactNativeBridge
新建一個swift文件,創(chuàng)建MyReactNativeBridge類,顧名思義,此類負責橋接原生app與ReactNative,注意,一個app中最好只創(chuàng)建一個橋接實例,所以使用單利模式創(chuàng)建。
import Foundation
import React
class MyReactNativeBridge {
static let sharedInstance = MyReactNativeBridge()
public let bridge : RCTBridge
public func initBridge() {
}
private init() {
#if DEBUG
//在debug使用虛擬服務器實時更新腳本,注意url中的/Scripts/index路徑對應的是根目錄中index.js的相對路徑
let jsCodeLocation = URL(string: "http://127.0.0.1:8081/Scripts/index.bundle?platform=ios")
#else
//在release使用jsbundle包中的腳本
let jsCodeLocation = URL(string: "bundle/index.ios.jsbundle")
#endif
bridge = RCTBridge.init(bundleURL: jsCodeLocation,
moduleProvider: nil, launchOptions: nil)
#if DEBUG
//僅在debug時顯示加載進度條
bridge.module(for: RCTDevLoadingView.self)
#endif
}
}
- 注意,在debug和release模式下,我們使用不同的js包,具體內(nèi)容請參考代碼中的注釋。
5.2、創(chuàng)建并使用RCTRootView
RCTRootView為ReactNative腳本描述的View,我們可以通過MyReactNativeBridge來創(chuàng)建RCTRootView,然后就可以像使用其他UIView一樣使用RCTRootView了。
import UIKit
import React
import SnapKit
class ViewController: UIViewController, RCTRootViewDelegate {
var rnView : RCTRootView?
@IBOutlet weak var rnRoot: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
rnView = RCTRootView.init(bridge: MyReactNativeBridge.sharedInstance.bridge,
moduleName: "Component-1",
initialProperties: nil)!
rnView?.delegate = self
rnView?.sizeFlexibility = RCTRootViewSizeFlexibility.widthAndHeight
rnRoot.addSubview(rnView!)
}
func rootViewDidChangeIntrinsicSize(_ rootView: RCTRootView!) {
rnView?.snp.removeConstraints()
rnView?.snp.makeConstraints({ (make) in
make.top.equalTo(0)
make.left.equalTo(0)
make.width.equalTo(rootView.intrinsicContentSize.width)//200
make.height.equalTo(rootView.intrinsicContentSize.height)//100
})
}
}
- 注意我們實現(xiàn)了RCTRootViewDelegate代理,這并不是必須的,在實現(xiàn)了此代理后,我們可以指定sizeFlexibility的模式(默認為none),ReactNative會修改組件的尺寸為js指定的實際大小(此例為200*100),然后通過rootViewDidChangeIntrinsicSize回調(diào)通知原生app已經(jīng)完成尺寸修改,所以我們在此回調(diào)中添加約束。
現(xiàn)在我們運行xcode模擬器,并且命令開啟ReactNative服務器(yarn start),就可以看到上面例子中的畫面了。
6、原生與ReactNative通信
6.1、原生向ReactNative傳遞數(shù)據(jù)
注意我們在創(chuàng)建RCTRoot時的語句的第三個參數(shù)initialProperties,代表由原生向ReactNative傳遞的參數(shù)。
rnView = RCTRootView.init(bridge: MyReactNativeBridge.sharedInstance.bridge,
moduleName: "Component-1",
initialProperties: nil)!
現(xiàn)在修改創(chuàng)建語句和腳本
- Swift代碼:通過initialProperties向JS傳遞參數(shù)
class ViewController: UIViewController, RCTRootViewDelegate {
var mockData:Dictionary<String, Any> = ["content":"初始化"]
...
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
rnView = RCTRootView.init(bridge: MyReactNativeBridge.sharedInstance.bridge,
moduleName: "Component-1",
initialProperties: mockData)!
}
}
- JS腳本:通過this.props["content"]使用原生App傳遞的參數(shù)(也可以使用this.props.content)
...
export default class FrameText extends Component {
render() {
return (
<View style={{
width:200,
height:100,
backgroundColor: '#CDDAF5'
}}>
<Text>{this.props["content"]}</Text>
</View>
);
}
}
...
重新編譯代碼后,再次運行App,可以看到界面變成如下的樣子
RCTRootView還有一個屬性appProperties,我們可以通過修改這個屬性來對已經(jīng)創(chuàng)建的View傳遞參數(shù)。
- Swift代碼:通過appProperties屬性向已創(chuàng)建的View傳遞參數(shù)
func rootViewDidChangeIntrinsicSize(_ rootView: RCTRootView!) {
rnView?.snp.removeConstraints()
rnView?.snp.makeConstraints({ (make) in
make.top.equalTo(0)
make.left.equalTo(0)
make.width.equalTo(rootView.intrinsicContentSize.width)//200
make.height.equalTo(rootView.intrinsicContentSize.height)//100
})
mockData["content"] = #"重加載"#
rnView!.appProperties = mockData
}
重新編譯代碼后,再次運行App,可以看到界面變成如下的樣子
6.2、ReactNative向原生傳遞數(shù)據(jù)
ReactNative定義了很多宏來處理這一需求,但我們無法在Swift中使用宏,需要使用oc與Swift混編
首先我們創(chuàng)建一個MyReactNativeBridge.swift文件
import Foundation
import React
@objc(MyReactNativeCommunication)
class MyReactNativeCommunication : NSObject {
//此方法處理ReactNative警告
@objc
static func requiresMainQueueSetup() -> Bool {
return true
}
//此方法供ReactNative調(diào)用,向原生app傳遞信息
@objc(test:)
func test(_ str :String) -> Void {
print(str)
}
}
然后在創(chuàng)建一個MyReactNativeBridge.m文件,同時系統(tǒng)會提示是否創(chuàng)建OC-Swift橋接文件,選是
MyReactNativeBridge.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(MyReactNativeCommunication, NSObject)
//此處的test對應MyReactNativeCommunication的text方法
//通過RCT_EXTERN_METHOD導出方法供ReactNative調(diào)用
RCT_EXTERN_METHOD(test:(NSString *)str)
@end
然后在橋接文件中引入如下頭文件
#import <React/RCTBridgeModule.h>
最后修改JS代碼,調(diào)用test方法
import { NativeModules } from 'react-native'
export default class FrameText extends Component {
render() {
NativeModules.MyReactNativeCommunication.test("我回來啦");
...
}
}
重新編譯代碼后,再次運行App,可以看到xcode的輸出 “我回來啦”