1.Set up the environment

For details, please refer to the official documents. After the environment is ready, the project directory is as follows





  • The original iOS project is placed in the iOS folder in the root directory (no Android, so no Android path).
  • React Native’s iOS entry is index.ios.js
  • Other React Native code is placed in the Component folder
  • Jsbundle main. Jsbundle is a collection of code we wrote in React Native.

2. The entrance

RN entry index. The ios. Js

'use strict'; //Use strict modeimport React, { Component } from 'react';
import {
  AppRegistry,//Used to register the component StyleSheet,//Using the styles Text, View, Image, NavigatorIOS,//Navigation controller TouchableHighlight,//Click on effect NativeModules//Calling native methods}from 'react-native';

import Repayment from './component/repayment';
import SettlementAccountList from './component/SettlementAccountList';

export default class MECRM extends Component {

  _handleNavigationBackRequest() {
    var RNBridge = NativeModules.RNBridge;
    RNBridge.back();
   }

  _settlementAccountList() {
    var status = this.props["status"];
    if (status === 0 || status === 3) {
      this.refs['nav'].push({
        title: 'Payer Information Form',
        component: SettlementAccountList,
        barTintColor: '#7B9DFD',
        tintColor: 'white',
        passProps: {
        }
      })
    }
  }

  render() {
    return (
        <NavigatorIOS
            ref='nav'
            initialRoute={{
                component: Repayment,//The registered component name must be capitalized title:'Request for Refund',
                rightButtonIcon: require('image! contacts'),
                leftButtonTitle: 'return',
                onLeftButtonPress: (a)= > this._handleNavigationBackRequest(),
                onRightButtonPress: (a)= > this._settlementAccountList(),
                passProps: {
                  orderid: this.props["orderid"],
                  status: this.props["status"],
                  price: this.props["price"]
                },
                barTintColor: '#7B9DFD'
            }}
            style={{flex: 1}}
            itemWrapperStyle={styles.itemWrapper}
            tintColor="white"
            titleTextColor ='white'
        />
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '# 333333',
    marginBottom: 5,}}); AppRegistry.registerComponent('RNBackApply'.(a)= > MECRM);Copy the code

Index.ios. js is the entry to RN, and the key is the NavigatorIOS tag:

  • Ref =’nav’, NavigatorIOS object is marked as ‘nav’, similar to iOS development tag, this.refs[‘nav’] NavigatorIOS object. (Fix this pass)
  • InitialRoute Initializes the route, where the initial page is penalty, Then click on the left and right buttons respectively carry out handleNavigationBackRequest (return to the native page), settlementAccountList (jump to refund the account list page)
  • PassProps, pass OrderID, status, and price to your page. (here the parameters are passed from naive to index.ios.js, and index.ios.js to your page.)





Native entry

let jsCodeLocation = URL(string: "http://localhost:8081/index.ios.bundle? platform=ios")
let mockData:NSDictionary = ["orderid": self.orderId,
                             "status" : self.orderDetailModel.status,
                             "price"  : self.orderDetailModel.cost]
let rootView = RCTRootView(bundleURL: jsCodeLocation,
                          moduleName: "RNBackApply",
                   initialProperties: mockData as [NSObject : AnyObject],
                       launchOptions: nil)
let vc = UIViewController()
vc.view = rootView
self.navigationController? .isNavigationBarHidden =true
self.navigationController? .pushViewController(vc, animated:true)Copy the code
  • JsCodeLocation RN execution file path. This path is used during development and needs to be changed to the main.jsbundle path when publishing
  • MockData is data passed from native to RN
  • ModuleName: “RNBackApply” corresponds to registerComponent(‘RNBackApply’, () => MECRM) in index.ios.js

3. Build the page

As mentioned above, the opening page is penalty, so how is penalty implemented as in the picture above? The following is a brief implementation

'use strict';
import React, { Component } from 'react';
import {
   View,
   Text,
   StyleSheet,
   ScrollView,
   TouchableHighlight,// The whole area has a press effect
   AlertIOS,
   TouchableOpacity,// Text has pinch effect
   TextInput,
   Image,
   NativeModules,
   DeviceEventEmitter/ / notice
 } from 'react-native';

import PayTypeChoice from './PayTypeChoice';// Note that the path is based on the current file

export default class repayment extends Component {
  constructor(props) {
    super(props);
    var defaultMoney = (this.props["price"] *0.2<0.01?0.01:this.props["price"]);
    this.state = {events: {
                    info: {
                      id: ' ',
                      orderId: this.props["orderid"].toString(),
                      account: ' ',
                      accountType: 1,
                      accountName: ' ',
                      bankName: ' ',
                      branchName: ' ',
                      money: defaultMoney.toString(),
                      status: ' ',
                      remark: ' ',
                      failReason: ' '}}};this._applySettlementRequest();
    this._accountInfoChoiced();
  }

  _accountInfoChoiced() {
      this.subscription = DeviceEventEmitter.addListener('accountInfoChoiced',(accountInfo) => {
          var newEvents = this.state.events;
          newEvents.info.account = accountInfo.account;
          newEvents.info.accountName = accountInfo.accountName;
          newEvents.info.accountType = accountInfo.accountType;
          newEvents.info.bankName = accountInfo.bankName;
          newEvents.info.branchName = accountInfo.branchName;
          this.setState({events: newEvents});
      })
  }

  _renderRow(title: string, subTitle: string, placeholder: string, onChangeText: Function, maxLength: int) {
    var status = this.props["status"];
    return (
         <View>
             <View style={styles.row}>
               <Text style={styles.rowText}>
               {title}
               </Text>
               {(status === 0 || status === 3)?
                 <TextInput
                   style={styles.rowInputText}
                   autoCapitalize={'none'}
                   maxLength = {maxLength}
                   onChangeText={onChangeText}
                   value={subTitle}
                   placeholder={placeholder}
                   selectionColor='#0064FF'
                   clearButtonMode={'while-editing'}
                   returnKeyType={'done'}
                 />
                 :
                 <TextInput
                   style={styles.rowInputText}
                   autoCapitalize={'none'}
                   onChangeText={onChangeText}
                   value={subTitle}
                   placeholder={placeholder}
                   selectionColor='#0064FF'
                   clearButtonMode={'while-editing'}
                   returnKeyType={'done'}
                   editable={false} /> } </View> <View style={styles.separator} /> </View> ); }... _renderButton(onPress: Function) {var status = this.props["status"];
     var buttonString = 'Application for Refund';
     switch (status) {
       case 0:
         var buttonString = 'Application for Refund';
         break;
       case 1:
         var buttonString = 'Refund processing';
         break;
       case 2:
         var buttonString = 'Paid back';
         break;
       case 3:
         var buttonString = 'Rejected, reapply';
         break;
       case 4:
         var buttonString = 'Pending review';
         break;
     }
     var canPost = false;
     var orderInfo = this.state.events.info;
     if ((status === 0 || status === 3) && orderInfo.accountName.length > 0 && orderInfo.account.length > 0 && orderInfo.money.length > 0 ) {
        if (orderInfo.accountType === 2) {
          if (orderInfo.bankName.length > 0 && orderInfo.branchName.length > 0) {
             canPost = true; }}else {
           canPost = true; }}return (
         <View style={styles.container}>
           <View>
             {canPost?
               <TouchableOpacity style={styles.button} onPress={onPress}>
               <Text style={styles.buttonText}>{buttonString}</Text>
               </TouchableOpacity>
               :
               <View style={styles.disableButton}>
               <Text style={styles.buttonText}>{buttonString}</Text>
               </View>
             }
           </View>
         </View>
     );
  }

  _onButtonPress() {
    var orderInfo = this.state.events.info;
    orderInfo.money = Number(orderInfo.money*100);
    if(isNaN(orderInfo.money)){
      AlertIOS.alert(
        'Please enter the correct amount of refund'.)return;
    }
    var orderPrice = (this.props["price"] *100);
    if (orderInfo.money > orderPrice*0.8) {
      AlertIOS.alert(
        ' '.'The current rebate is more than 80% of the payment amount, do you want to continue? ',
        [
          {text: 'Back to modify'},
          {text: 'Continue to initiate', onPress: () => {
            var RNBridge = NativeModules.RNBridge;
            RNBridge.setSettlement(orderInfo,(error, events) => {
              if (error) {
                // console.error(error);
              } else {
                this._handleNavigationBackRequest(); }})}}])return;
    }
    var RNBridge = NativeModules.RNBridge;
    RNBridge.setSettlement(orderInfo,(error, events) => {
      if (error) {
        // console.error(error);
      } else {
        this._handleNavigationBackRequest(); }})}; _applySettlementRequest() {var status = this.props["status"];
// status Parameter description
// 0 not applied
// 1
// 2 Successful refund
// 3 Refund failed
    if (status === 0) {
      return
    }
    var RNBridge = NativeModules.RNBridge;
    var orderid = this.props["orderid"].toString();
    RNBridge.applySettlement(orderid,(error, events) => {
      if (error) {
        console.error(error);
      } else {
        events.info.money = (events.info.money/100).toString();
        this.setState({events: events});
      }
    })
  }

  render() {
    var orderInfo = this.state.events.info;
    var status = this.props["status"];
    return (
        <ScrollView style={styles.list}>
        <View style={styles.line}/>
        <View style={styles.group}>
        <View>
          {this._renderPayTypeRow(() => {
            this.props.navigator.push({
              title: 'Method of Refund',
              component: PayTypeChoice,
              barTintColor: '#7B9DFD',
              tintColor: 'white',
              passProps: {
                accountType: this.state.events.info.accountType,
                getPayType:(accountType)=>{
                  var newEvents = this.state.events;
                  newEvents.info.accountType = accountType;
                  this.setState({events: newEvents}); }}})})} {this._renderRow('name', orderInfo.accountName, 'Please enter your name', (accountName) => {
            var newEvents = this.state.events;
            newEvents.info.accountName = accountName;
            this.setState({events: newEvents});
          },10)}
          <View>
            {(orderInfo.accountType === 2)?
            <View>
            {this._renderRow('Bank of Deposit', orderInfo.bankName, 'Please enter your bank account', (bankName) => {
              var newEvents = this.state.events;
              newEvents.info.bankName = bankName;
              this.setState({events: newEvents});
            })}
            {this._renderRow('Branch', orderInfo.branchName, 'Please enter the opening branch', (branchName) => {
              var newEvents = this.state.events;
              newEvents.info.branchName = branchName;
              this.setState({events: newEvents});
            })}
            </View>
              :
              null
            }
          </View>

          ......

        </View>
        </View>
        <View style={styles.line}/>
        {this._renderButton(() => {
          this._onButtonPress();
        })}
        </ScrollView>
      );
  }
}

const styles = StyleSheet.create({
    ......
});Copy the code
  • Constructor initializes the data, where the data structure is consistent with the result of the network request.
  • RenderRow and the other renderXXXRow functions that have been omitted simply make the render function less bloated. It is common to return JSX fragments that show different styles depending on the conditions in the build interface, but JSX does not support if.else. :, used many times in the above code.
  • Requirement Function 1: Click return method to jump to the page of return method selection, and then send back the selected method.




    The value is passed to the next page as a function, as follows.

Repayment.js

{this._renderPayTypeRow((a)= > {
      this.props.navigator.push({
          title: 'Method of Refund',
         component: PayTypeChoice,
          barTintColor: '#7B9DFD',
         tintColor: 'white',
          passProps: {
             accountType: this.state.events.info.accountType,
            getPayType:(accountType)= > {
                var newEvents = this.state.events;
                newEvents.info.accountType = accountType;
                this.setState({events: newEvents}); }}})})}Copy the code

PayTypeChoice.js

render() {
  return (
      <ScrollView style={styles.list}>
      <View style={styles.line}/>
      <View style={styles.group}>
      <View>
        {this._renderRow(Alipay.this.state.alipay,() => {
          this.props.getPayType(1);
          this.props.navigator.popToTop()
        })}
        <View style={styles.separator} />
        {this._renderRow('Bank card'.this.state.bankcard,() => {
          this.props.getPayType(2);
          this.props.navigator.popToTop()
        })}
      </View>
      </View>
      <View style={styles.line}/>
      </ScrollView>
    );
}Copy the code

GetPayType in penalty. Js is the function that is passed to the next page and executed when the payback method is selected. In PayTypeChoice. Js, pass the payback method as a parameter when the cell is clicked, such as this.props. GetPayType (1), and set the payback method to Alipay.

  • Requirement Function 2: Click the icon in the upper right corner to jump to the list page of refund accounts, and then bring the selected account information back to fill the page.




    As mentioned earlier, the logic for clicking an icon to jump to is written in the index.ios.js file

     _settlementAccountList() {
       var status = this.props["status"];
       if (status === 0 || status === 3) {
         this.refs['nav'].push({
           title: 'Payer Information Form',
           component: SettlementAccountList,
           barTintColor: '#7B9DFD',
           tintColor: 'white',
           passProps: {
           }
         })
       }
     }Copy the code

SettlementAccountList to pass a page value using a pass-through function, SettlementAccountList must obtain the penalty callback function. It was a hassle, and I tried it and it didn’t work. In this case, notifications are very simple, and using the Notification component DeviceEventEmitter in React Native doesn’t matter.

SettlementAccountList sends a notification when account information is selected

 _onPressCell(rowData: string) {
     this.props.navigator.popToTop(a)DeviceEventEmitter.emit('accountInfoChoiced', rowData);
 }Copy the code

Receive notification in Penalty

 _accountInfoChoiced() {
     this.subscription = DeviceEventEmitter.addListener('accountInfoChoiced',(accountInfo) => {
          var newEvents = this.state.events;
         newEvents.info.account = accountInfo.account;
            newEvents.info.accountName = accountInfo.accountName;
        newEvents.info.accountType = accountInfo.accountType;
         newEvents.info.bankName = accountInfo.bankName;
        newEvents.info.branchName = accountInfo.branchName;
        this.setState({events: newEvents}); })}Copy the code
  • Requirement function 3: When entering the page, pull the return information filled in before, click the back button on the upper left to return to the Native page, and pull the existing information from the return account information page. These three points are called Native methods. Although RN also has network request methods, network requests in APP have public parameters, public authentication methods, error handling, etc., so it is better to choose Native for network requests.

The steps for creating a Native method to be called by RN are pretty clear in the official documentation, and I’ve posted a snippet of code that I wrote here.

RNBridge.m

#import "RNBridge.h"
#import <UIKit/UIKit.h>
#import <MECRM-Swift.h>

@implementation RNBridge
RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(back)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UITabBarController *tabvc = (UITabBarController*) [self getCurrentVC];
        UINavigationController *navi = [tabvc selectedViewController];
        navi.navigationBarHidden = NO;
        [navi popViewControllerAnimated:YES];
    });
}

// Get the rebate information
RCT_EXPORT_METHOD(applySettlement:(NSString *)orderID callback:(RCTResponseSenderBlock)callback)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UITabBarController *tabvc = (UITabBarController*) [self getCurrentVC];
        UINavigationController *navi = [tabvc selectedViewController];
        UIViewController * vc = navi.viewControllers.lastObject;
        [vc startAnimating];
        NSString *path = [NSString stringWithFormat:@"/order/%@/applySettlement",orderID];
        [NetworkTool GET:path parameters:nil successHandler:^(id _Nonnull result) {
            [vc stopAnimating];
            callback(@[[NSNull null], result]);
        } failureHandler:^(NSError * _Nullable error) {
            [vc stopAnimating];
            callback(@[error.localizedDescription, [NSNull null]]);
        }];
    });
}

// Set the rebate information
RCT_EXPORT_METHOD(setSettlement:(NSDictionary *)orderInfo callback:(RCTResponseSenderBlock)callback)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UITabBarController *tabvc = (UITabBarController*) [self getCurrentVC];
        UINavigationController *navi = [tabvc selectedViewController];
        UIViewController * vc = navi.viewControllers.lastObject;
        [vc startAnimating];
        NSString *orderID = orderInfo[@"orderId"];
        NSString *path = [NSString stringWithFormat:@"/order/%@/applySettlement",orderID];
        [NetworkTool POST:path parameters:orderInfo successHandler:^(id _Nonnull result) {
            [vc stopAnimating];
            callback(@[[NSNull null], result]);
        } failureHandler:^(NSError * _Nullable error) {
            [vc stopAnimating];
            callback(@[error.localizedDescription, [NSNull null]]);
        }];
    });
}

// Refunder information table
RCT_EXPORT_METHOD(getSettlementAccount:(NSInteger)start callback:(RCTResponseSenderBlock)callback)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        UITabBarController *tabvc = (UITabBarController*) [self getCurrentVC];
        UINavigationController *navi = [tabvc selectedViewController];
        UIViewController * vc = navi.viewControllers.lastObject;
        [vc startAnimating];
        NSString *path = [NSString stringWithFormat:@"/order/getSettlementAccount? start=%ld&limit=%d", (long)start,100];
        [NetworkTool GET:path parameters:nil successHandler:^(id _Nonnull result) {
            [vc stopAnimating];
            callback(@[[NSNull null], result]);
        } failureHandler:^(NSError * _Nullable error) {
            [vc stopAnimating];
            callback(@[error.localizedDescription, [NSNull null]]);
        }];
    });
}Copy the code

Call the return method in RN

_handleNavigationBackRequest() {
    var RNBridge = NativeModules.RNBridge;
    RNBridge.back();
}Copy the code

Get the completed rebate information in RN

_applySettlementRequest() {
    var status = this.props["status"];
    // status Parameter description
    // 0 not applied
    // 1
    // 2 Successful refund
    // 3 Refund failed
    if (status === 0) {
      return
    }
    var RNBridge = NativeModules.RNBridge;
    var orderid = this.props["orderid"].toString();
    RNBridge.applySettlement(orderid,(error, events) => {
      if (error) {
        console.error(error);
      } else {
        events.info.money = (events.info.money/100).toString();
        this.setState({events: events}); }})}Copy the code

4. The debugging

In the simulator Command +D call up the menu of RN, click Debug JS Master.





Add a debugger in front of the code to be debugged, for example

_onButtonPress() {
    debugger
      var orderInfo = this.state.events.info;
       orderInfo.money = Number(orderInfo.money*100);
       if(isNaN(orderInfo.money)){
           AlertIOS.alert(
            'Please enter the correct amount of refund'.)return; }... };Copy the code

ლ(°◕ ‘◕ლ)

5. Launch and hot update

You need to change the jsCodeLocation in your code when you go live

//let jsCodeLocation = URL(string: "http://localhost:8081/index.ios.bundle? platform=ios")
let jsCodeLocation = Bundle.main.url(forResource: "main", withExtension: "jsbundle")Copy the code

The main.jsbundle needs to be generated manually as follows:

1Run NPM start in the React Native project root directory2Run the curl command to generate main.jsbundle

curl http://localhost:8081/index.ios.bundle -o main.jsbundleCopy the code

So when YOU go to RN, there’s no longer a message at the top. If you can’t find the main.jsbundle file, remember to drag the main.jsbundle file into the iOS project to reference it.

Open the main.jsbundle file and you’ll find that it contains all the js files you wrote. So all of your RN logic is actually in here. So hot updates to RN make a lot of sense, just update this file. Whether you implement it yourself or choose a third-party hot update scheme, you’re just updating the main.jsbundle file in various fancy ways.

6. Some additions and questions (Mumble)

We just talked about push, modal, right? For example, the sniper

<View style={{marginTop: 22}} >
    <Modal
          animationType={"slide"}
         transparent={false}
         visible={this.state.modalVisible}
         onRequestClose={()= > {alert("closed")}}
      >
           <View style={{marginTop: 22}} >
              <View>
              <Text>Hello World!</Text>
                <TouchableHighlight onPress={()= >{ this.setModalVisible(! this.state.modalVisible) }}><Text>Hide Modal</Text>
              </TouchableHighlight>
           </View>
        </View>
     </Modal>
    <TouchableHighlight onPress={()= > {this.setModalVisible(true)}}>
         <Text>Show Modal</Text>
    </TouchableHighlight>
  </View>Copy the code

As a bonus, if the NPM start command doesn’t work, try react-native start

There is one remaining question, when I return the native page, I call the native method to return, can’t RN itself return, I actually try to do this, and I take it for granted. NavigatorIOS ‘pop method does not respond to any native navi. ViewControllers.

(lldb) po navi.viewControllers
<__NSArrayI 0x600000254160>(
<MECRM.MainOrderViewController: 0x7f82a1226ac0>,
<MECRM.OrderDetailViewController: 0x7f829f54e2c0>,
<UIViewController: 0x7f82a1004ed0>
)Copy the code

RN officially offers NavigatorIOS and Navigator, which use the same methods and feel more native with iOS. But they say Navigator is cooler.