美文网首页RN
ReactNative混合开发-2 IOS混编配置

ReactNative混合开发-2 IOS混编配置

作者: 肆点壹陆 | 来源:发表于2019-05-02 18:48 被阅读1次

    开发语言:ReactNative 0.59.5 Swift 5
    开发环境:VSCode Xcode 10.2

    1、项目目录

    参考文章:集成到现有原生应用

    首先,我们按照建立一下目录结构,其中:
    Code目录放置所有公用的ReactNative脚本,包,以及相关配置。
    IOS目录放置原IOS项目。

    Code (根目录)
    --IOS (一级目录)

    2、开发环境准备

    2.1、package.json配置

    在Code目录下创建package.json文件,编辑文件输入以下内容。

    {
      "name": "AppName",
      "version": "0.0.1",
      "private": true,
      "scripts": {
        "start": "yarn react-native start"
      }
    }
    

    2.2、React和React Native模块安装

    在Code目录下使用控制台执行以下语句来安装React Native。

    yarn add react-native
    
    • 注意,执行完以上命令后,可能会出现以下提示内容,表示我们需要安装指定版本的React(此例子中需要安装版本为16.8.3的React)。

    warning " > react-native@0.59.5" has unmet peer dependency "react@16.8.3".

    在Code目录下使用控制台执行以下语句来安装指定版本的React

    yarn add react@16.8.3
    

    3、安装CocoaPods

    3.1、安装CocoaPods(如果已安装过可跳过此步骤)

    控制台执行以下命令

    brew install cocoapods
    

    3.2、创建podfile(如果IOS原有项目已配置过podfile可跳过此步骤)

    在IOS根目录下使用控制台执行以下语句来创建podfile文件

    pod init
    

    3.3、使用podfile引用React Native

    打开podfile,将与React Native有关的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,则需要加入此行才能开启开发者菜单
        'RCTImage',
        'RCTText',
        'RCTNetwork',
        'RCTWebSocket', # 调试功能需要此模块
        '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根目录下使用控制台执行以下语句来安装pod包。

    pod install
    

    4、脚本创建

    在Code根目录下创建Scprits文件夹,用于存放React Native的脚本文件

    Code (根目录)
    --Scprits(一级目录,用于存放所有React Native的脚本)

    然后我们可以在Scprits目录下开始写ReactNative的脚本了。
    首先我们创建一个FrameText.js,然后写入如下内容:

    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中,我们创建了一个简单的组件,供其他脚本使用。

    然后我们再创建一个index.js,然后写入如下内容

    import {AppRegistry} from 'react-native'
    import {FrameText} from 'FrameText'
    
    
    // 整体js模块的名称
    AppRegistry.registerComponent('Component-1', () => FrameText);
    
    

    在index中,我们注册了FrameText组件,供app使用,我们可以在index.js注册很多不同的组件,app可以通过我们注册的名字(本例中为Component-1)来创建这些组件,下面我们来看看怎么在app内使用他们。

    5、IOS项目修改

    本例中期望在app的一个controller内,同时使用原生语言与ReactNative脚本分别显示2个View,现在我们来看看是如果在原生app中加载ReactNative的View。

    5.1、创建 MyReactNativeBridge

    新建一个swift文件,创建MyReactNativeBridge类,顾名思义,此类负责桥接原生app与ReactNative,注意,一个app中最好只创建一个桥接实例,所以使用单利模式创建。

    import Foundation
    import React
    
    class MyReactNativeBridge {
        
        static let sharedInstance = MyReactNativeBridge()
        
        public let bridge : RCTBridge
        
        public func initBridge() {
            
        }
        
        private init() {
            #if DEBUG
            //在debug使用虚拟服务器实时更新脚本,注意url中的/Scprits/index路径对应的是根目录中index.js的相对路径
            let jsCodeLocation = URL(string: "http://127.0.0.1:8081/Scprits/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包,具体内容请参考代码中的注释。

    5.2、创建并使用RCTRootView

    RCTRootView为ReactNative脚本描述的View,我们可以通过MyReactNativeBridge来创建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
             })
        }
        
    }
    
    
    • 注意我们实现了RCTRootViewDelegate代理,这并不是必须的,在实现了此代理后,我们可以指定sizeFlexibility的模式(默认为none),ReactNative会修改组件的尺寸为js指定的实际大小(此例为200*100),然后通过rootViewDidChangeIntrinsicSize回调通知原生app已经完成尺寸修改,所以我们在此回调中添加约束。

    现在我们运行xcode模拟器,并且命令开启ReactNative服务器(yarn start),就可以看到上面例子中的画面了。

    6、原生与ReactNative通信

    6.1、原生向ReactNative传递数据

    注意我们在创建RCTRoot时的语句的第三个参数initialProperties,代表由原生向ReactNative传递的参数。

    rnView = RCTRootView.init(bridge: MyReactNativeBridge.sharedInstance.bridge,
                                        moduleName: "Component-1",
                                        initialProperties: nil)!
    

    现在修改创建语句和脚本

    • Swift代码:通过initialProperties向JS传递参数
    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传递的参数(也可以使用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,我们可以通过修改这个属性来对已经创建的View传递参数。

    • Swift代码:通过appProperties属性向已创建的View传递参数
    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向原生传递数据

    ReactNative定义了很多宏来处理这一需求,但我们无法在Swift中使用宏,需要使用oc与Swift混编

    首先我们创建一个MyReactNativeBridge.swift文件

    import Foundation
    import React
    
    
    @objc(MyReactNativeCommunication)
    class MyReactNativeCommunication : NSObject  {
        
        
        //此方法处理ReactNative警告
        @objc
        static func requiresMainQueueSetup() -> Bool {
            return true
        }
        
        
        //此方法供ReactNative调用,向原生app传递信息
        @objc(test:)
        func test(_ str :String) -> Void {
           print(str)
        }
        
    }
    
    

    然后在创建一个MyReactNativeBridge.m文件,同时系统会提示是否创建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调用
    RCT_EXTERN_METHOD(test:(NSString *)str)
    
    @end
    
    

    然后在桥接文件中引入如下头文件

    #import <React/RCTBridgeModule.h>
    

    最后修改JS代码,调用test方法

    import { NativeModules } from 'react-native'
    
    
    export default class FrameText extends Component {
    
        render() {
            NativeModules.MyReactNativeCommunication.test("我回来啦");
            ...
        }
    }
    

    重新编译代码后,再次运行App,可以看到xcode的输出 “我回来啦”

    相关文章

      网友评论

        本文标题:ReactNative混合开发-2 IOS混编配置

        本文链接:https://www.haomeiwen.com/subject/hbmbgqtx.html