React Native 下拉刷新調(diào)用iOS原生控件

一、關(guān)于React Native Listview實現(xiàn)下拉刷新的功能一直是一個痛點,雖然RN提供了一個ReafreshControll控件以實現(xiàn)下拉刷新,但是RefreshControll控件的可定制性并不高,用戶體驗也差。本篇文章將帶領(lǐng)大家在React Native項目中集成iOS中原生的下拉刷新功能。

二、項目demo 地址: PullRefreshDemo
首先,可以看到,我們在ios項目中導(dǎo)入了SVPullRefresh的三方庫,現(xiàn)在我們就要通過RN來調(diào)用這個下拉刷新的組件。

三、可行方案探究。
1)目前,我們知道SVPullRefresh這個組件是提供給UIScrollview和UITableView的一個下拉刷新的組件。在SVPullToRefresh.h可看到它是為UISCrollview提供的一個分類。


image.png

然而在RN項目中,我們使用的列表組件都是ListView,或者FlatList,這兩個組件都是集成自RN重的Scrollview,我們可以猜想,在ios中,它是不是繼承自UIScrollview呢,若答案為YES,那么我們則可以直接在ios源碼中,給UIScrollveiw添加一個下拉刷新的組件,那么每個ListView或者FlatList則具有了該組件(SVPullRefresh)下拉刷新的功能。
2)打開React Native iOS工程->libraries->React->Views->RCTScrollView.h
很遺憾,我們發(fā)現(xiàn)RCTScrollView這個組件繼承自RCTView。?那么是不是這個方案沒法實現(xiàn)呢?
別急我們再看看RCTScrollView.m文件。在320行,我們可以發(fā)現(xiàn)這個RCTScrollview中持有了一個RCTCustomScrollView *_scrollView; 而且這個RCTCustomScrollView是繼承自UIScrollView的。如下圖:

image.png

繼續(xù)往下在這個類(RCTScrollView)的初始化方法中,他是在RCTView中嵌入了一個UIScrollView(也就是RCTCustomScrollView)這個類,如下圖:

image.png

那么印證了我們上面的觀點,我們是不是可以直接在這個_scrollView這個私有UIScrollView上做操作呢?答案是肯定的。

四、開始集成原生下拉刷新功能:
1)首先,將SVPullRefresh文件夾拖入React.xcodeproj這個子工程下面

image.png

完成后如圖:


image.png

2)
1.1如圖:修改 Bundle React Native code and images 為

export NODE_BINARY=node
../node_modules/react-native/packager/react-native-xcode.sh

1.2 。因為這個三方庫需要從bundle路徑加載Plist文件,所以要將plist文件單獨加入工程,該storehouse.plist文件在SVPullRefresh文件下
如圖:


image.png
image.png

3)在RCTScrollView.h頭文件中導(dǎo)入SVPullRefresh

 #import "SVPullToRefresh.h"

接下來,在該文件下為UIScrollView添加幾個屬性,這幾個屬性用于管控下拉刷新的狀態(tài)和用于曝露在RN端調(diào)用的屬性。 62行

#pragma mark --
#pragma mark -- Custom iOS pull Refresh
/// 用戶下拉之后請求數(shù)據(jù)的回調(diào)
@property (nonatomic, copy) RCTDirectEventBlock onRefreshData;
/// 下拉刷新的開關(guān)
@property (nonatomic, assign) BOOL isPullToRefresh;
/// ?是否調(diào)用原生的下拉刷新模塊
@property (nonatomic, assign) BOOL enablePullToRefresh;
/// 控件是否正在刷新狀態(tài)
@property (nonatomic, assign) BOOL isRefreshing;

4)在RCTScrollView.m文件中 899行添加如下代碼:

#pragma mark --
#pragma mark -- Custom iOSPullDownRefresh
- (void)setEnablePullToRefresh:(BOOL)enablePullToRefresh {
  _enablePullToRefresh = enablePullToRefresh;
  __weak typeof(self) weakSelf = self;
  __weak typeof(_onRefreshData) weakRefreshData = _onRefreshData;
  if (enablePullToRefresh) {
    [_scrollView addPullToRefreshWithActionHandler:^{
      weakSelf.isRefreshing = true;
      if (weakRefreshData) {
        weakRefreshData(nil);
      }
      
    }];
  }
}

///  isPullToRefresh屬性的setter方法,當RN調(diào)用該組件的屬性值改變時,會調(diào)用此方法。那么我們用這個setter方法就能管控組件的刷新狀態(tài)
- (void)setIsPullToRefresh:(BOOL)isPullToRefresh {
  _isPullToRefresh = isPullToRefresh;
  if (isPullToRefresh && !self.isRefreshing) {
    [_scrollView triggerPullToRefresh];
    
  } else if (!isPullToRefresh && self.isRefreshing) {
    [_scrollView.pullToRefreshView stopAnimating];
    
  }
}

5)當然我們需要在RCTScrollViewManager.m中暴露出這個組件的屬性,打開RCTScrollViewManager.m 78行添加:

RCT_EXPORT_VIEW_PROPERTY(isPullToRefresh, BOOL)
RCT_EXPORT_VIEW_PROPERTY(enablePullToRefresh, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onRefreshData, RCTDirectEventBlock)

這里我們只需曝露三個屬性,isRefreshing屬性是用于標志控件刷新狀態(tài)而聲明的

  1. 暴露屬性的同時,我們也需要修改React Native node_modules下的ScrollView組件的props
    依次打開文件:node_modules->react-native->libraries->Components->ScrollView->ScrollView.js
    在380行添加如下代碼
    isPullToRefresh: PropTypes.bool,
    currentRefreshingState:PropTypes.bool,
    enablePullToRefresh:PropTypes.bool,
    onRefreshData:PropTypes.func,

控件的屬性申明等已經(jīng)封裝完成,下面我們將在React Native 代碼中調(diào)用這個控件。

五。我們在index.ios.js中調(diào)用該組件:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  ListView
} from 'react-native';

export default class PullRefreshDemo extends Component {
  constructor(props) {
    super(props);
    this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: this.ds.cloneWithRows(['row 1', 'row 2','row 1', 'row 2','row 1', 'row 2','row 1', 'row 2','row 1', 'row 2','row 1', 'row 2','row 1', 'row 2','row 1', 'row 2','row 1', 'row 2','row 1', 'row 2']),
      isPullToRefresh:false,

    }
  }
  render() {
    return (
      <View style={{backgroundColor:'#ebebeb'}}>
      <View style={{height:64,backgroundColor:'yellow',borderBottomWidth:1,borderBottomColor:'black'}}></View>
      <ListView 
        ref={(list)=>{this.list=list}}
        style={{backgroundColor:'#ebebeb'}}
        renderRow={this.renderRow.bind(this)}
        dataSource={this.state.dataSource}
        /*是否加載原生下拉組件*/
        enablePullToRefresh={true}
        /*控制刷新狀態(tài)*/
        isPullToRefresh={this.state.isPullToRefresh}
        /*下拉刷新加載數(shù)據(jù)的回調(diào)函數(shù)*/
        onRefreshData={this.loadData.bind(this)}
      />
      </View>
    );
  }
  renderRow(data){
    return (
      <View style={{marginBottom:10,width:'100%',backgroundColor:'green',height:50,justifyContent:'center',alignItems:'center'}}>
        <Text style={{fontSize:20}}>{data}</Text>
      </View>
    )
  }
  loadData(){
    // 因為isPullToRefresh默認為false,如果不先設(shè)置為true,那么render方法不會刷新
    this.setState({isPullToRefresh:true})

    setTimeout(()=>{
      this.setState({
        isPullToRefresh:false
      })
    },2000)
  }
}


AppRegistry.registerComponent('PullRefreshDemo', () => PullRefreshDemo);

最終效果:

QQ20170717-160254.gif

六、最后,不足之處請大家指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容