はじめに
React Nativeアプリでスワイプジェスチャーを実装する際、アプリがクラッシュしたり、ステートが正しく更新されないといった問題に直面することがあります。本記事では、スワイプジェスチャーを用いて画像を左右に切り替える際の課題とその解決策について解説します。
問題の背景
スワイプジェスチャーを実装する際、以下のような課題が発生しました。
Reactの状態更新の非同期性
- 状態(state)が更新される前に古い値を参照してしまう。
- 状態が適切に更新されず、スワイプが正しく機能しない。
UIスレッドとJSスレッドの分離
- React Native Gesture HandlerとReanimatedを組み合わせた際、JSスレッドで状態更新を行おうとするとエラーが発生する。
スワイプ可能範囲の制限
- 最初の画像では右スワイプを無効化、最後の画像では左スワイプを無効化する必要があった。
解決策
1. 状態更新の適切な管理
Reactの状態(state)は非同期で更新されるため、最新の値を確実に取得するにはuseRefを利用して状態を保持するのが有効です。以下のコードは、この問題を解決した例です。
import { Gesture } from "react-native-gesture-handler";
import { useRef } from "react";
import { runOnJS } from "react-native-reanimated";
const currentPageRef = useRef(0); // ページ番号を保持
const swipeGesture = Gesture.Pan()
.onEnd((event) => {
console.log("Gesture ended, translationX:", event.translationX, currentPageRef.current);
// 右スワイプ (前の画像へ移動)
if (event.translationX > 100 && currentPageRef.current > 0) {
console.log("Swiped Right@@@", currentPageRef.current);
currentPageRef.current = Math.max(currentPageRef.current - 1, 0); // 0以下にならないよう制御
runOnJS(setCurrentPage)(currentPageRef.current); // Reactの状態を更新
}
// 左スワイプ (次の画像へ移動)
else if (event.translationX < -100 && currentPageRef.current < tutorialImages.length - 1) {
console.log("Swiped Left@@@", currentPageRef.current);
currentPageRef.current = Math.min(currentPageRef.current + 1, tutorialImages.length - 1); // 範囲外にならないよう制御
runOnJS(setCurrentPage)(currentPageRef.current); // Reactの状態を更新
}
});
2. UIスレッドとJSスレッドの分離
React Native Gesture Handlerでジェスチャーイベントを処理する場合、UIスレッドとJSスレッド間の通信が必要です。状態を直接UIスレッドから更新することはできないため、ReanimatedのrunOnJSを利用してJSスレッドでReactの状態を更新します。
runOnJS(setCurrentPage)(newPage); // newPageをJSスレッドで更新
実装結果
上記の解決策を適用した結果、スワイプジェスチャーの問題を解決することができました。スワイプ時に画像が適切に切り替わり、最初と最後の画像でスワイプが無効になることも確認できました。
まとめ
本記事では、React Nativeアプリでスワイプジェスチャーを実装する際の課題とその解決策について解説しました。特に、以下のポイントが重要です。
- 状態管理にはuseRefを活用し、非同期性の影響を受けにくくする。
- UIスレッドとJSスレッド間の通信にはrunOnJSを利用する。
このアプローチは、他のReact Nativeプロジェクトでも再利用可能な汎用的な方法です。この記事が、同様の課題を抱えるエンジニアの参考になれば幸いです。