LoginSignup
2
3

More than 3 years have passed since last update.

[ReactNative] ScreenView+Webview refreshControlについての備忘録(Androidのバグ)

Posted at

最近小さめのReactNativeのプロジェクトにアサインされたワタクシでございます。
全くまだ分かってないんですけど、UI部分の改修案件が来たので対応しています。
その際に直面したバグ対応の備忘録を残します。

どのようなバグか

iosでは起きず、Androidのみのバグとなります。

render() {
  <ScrollView
     refreshControl={
       <RefreshControl
         refreshing={ this.state.refreshing }
         style={{ backgroundColor: "#FFFFFF" }}
         onRefresh={() => { this.onRefresh() }}
       />
     }
     contentContainerStyle={{flex: 1}}
     style={{ backgroundColor: "#FFFFFF" }}
   >
            <WebView
              style={{ flex: 1 }}
              onMessage={this.onMessage}
              onNavigationStateChange={this.onNavigationStateChange}
              onLoadStart={this.onLoadStart}
              onLoadEnd={this.onLoadEnd}
              ref={webView => {this.webView = webView}}
              source={{ uri: this.state.url.toString() }}
            />
    </ScrollView>
}

このようなソースがあります。
webviewを表示しており、このwebviewはスクロールが可能になっています。
スクロールをする際にiosでは起きませんが、Androidではスクロールで下までいってしまったあとに上に戻ろうとすると必ずrefreshControlが走り、上にスクロールが出来なくなるバグが起きてました。
アプリの上部に来た時だけ更新してくれればいいのに、常に上部をひっぱると更新されてしまいます。

原因

webview側は問題なくスクロールします。スクロールされているのはScrollViewではなくて、webviewであることがわかり、(ScrollViewにonScrollつけてもイベントが走らなかったため)ScrollViewのscrollYが更新されず0のままでした。
そうすると恐らく、scrollYが0であるとしてAndroidでは常にrefreshControlが走ってしまうのではないか、というところまでたどり着きました。

開発環境

  • react-native: 0.61.5
  • react: 16.9.5
  • react-native-webview: 8.0.3

解決方法

const INJECTED_JS = `
  window.onscroll = function() {
    window.ReactNativeWebView.postMessage(
      JSON.stringify({
        scrollTop: document.documentElement.scrollTop || document.body.scrollTop
      }),
    )
`

export default class WebViewPage extends React.Component {
  state = {
    scrollViewHeight: 0,
    isPullToRefreshEnabled: false,
  }

  onMessage = () => {
    const { data } = e.nativeEvent

    try {
      const { scrollTop } = JSON.parse(data)
      this.setState({ isPullToRefreshEnabled: scrollTop === 0 })
    } catch (err) {
      // ...
    }
  }

  render () {
    const { scrollViewHeight, isPullToRefreshEnabled } = this.state

    return (
      <View style={{flex:1}}>
        <ScrollView
          refreshControl={
            <RefreshControl
              refreshing={ this.state.refreshing }
              enabled={ isPullToRefreshEnabled }
              style={{ backgroundColor: "#FFFFFF" }}
              onRefresh={() => { this.onRefresh() }}
            />
          }
          onLayout={e => this.setState({ scrollViewHeight: e.nativeEvent.layout.height })}
          contentContainerStyle={{flex: 1}}
          style={{ backgroundColor: "#FFFFFF" }}
        >
          <WebView
            style={{ flex: 1 }}
            onMessage={this.onMessage}
            onNavigationStateChange={this.onNavigationStateChange}
            onLoadStart={this.onLoadStart}
            onLoadEnd={this.onLoadEnd}
            ref={webView => {this.webView = webView}}
            source={{ uri: this.state.url.toString() }}
            injectedJavaScript={INJECTED_JS}
          />
        </ScrollView>
      </View>
    )
  }
}

解説

  1. ScrollViewでスクロールイベント取れないならwebviewで取るしかないよなということで injectedJavaScript を導入。injectedJavaScriptはwebview側にjavascriptのコードを渡す属性で、ここでwebviewのスクロール位置を取得して、window.ReactNativeWebView.postMessage でReactNative側にその情報を渡してやります。この時、webview側にもonMessageメソッドを定義して、webview側のスクロール位置情報を受け取ります
  2. enabledでいつrefreshControlが走るか定義しておく(onMessage)
  3. ScrollViewコンポーネントのonLayoutを使って、viewが開いたときの高さを取得して保持しておきます。(初回だけ取得用)
  4. WebviewコンポーネントにinjectedJavaScriptを突っ込みます。

以上です。これで画面の一番上にスクロールした時にだけ上部を引っ張ればrefreshControlが走る設計になったかと思います。

参考文献

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3