まえがき
React Nativeではモーダル機能は多様なライブラリで提供されている。
本記事ではreact-navigationのモーダルに注目して調査した結果についてまとめる。
React Navigationとは
詳しくはHello React Navigation | React Navigationから。
リンク先の一部文章を直訳したのが以下文。
React ナビゲーションのネイティブスタックナビゲーターは、アプリが画面間を切り替えてナビゲーション履歴を管理する方法を提供します。
画面遷移と、その履歴を管理する方法を提供していると理解できる。また、画面遷移時のアニメーションや、ロジックを実装できたりと多様な機能を持っている。
環境
"react": "18.0.0",
"react-native": "0.69.5",
"@react-navigation/native": "~6.0.6",
"@react-navigation/stack": "~6.3.0",
StackNavigatorを使ってモーダルを実装する
StackNavigatorとは
Stack Navigator | React Navigation
モーダルを実装する
StackNavigatorのScreenにoptionsというプロパティがある。画面をモーダルとして実装するには下記コード例のようにoptionsの中でpresentation: 'modal'
を指定する。
const nav = createStackNavigator();
export const ModalStackNav: React.FC = () => {
return (
<nav.Navigator>
<nav.Screen
name="ExampleModal"
component={ExampleModalScreen}
options={{
presentation: 'modal'
}}
/>
</nav.Navigator>
);
};
後述の「スタイルを整える」でモーダルのサイズを調整できるが、背景に遷移前の画面は見えない。
背景に遷移前の画面を表示させるために
presentation: ‘transparentModal’
を使用する。
transparentModalはmodalと似ているが、いくつか違う点がある。
ドキュメントに記載されている点のうち以下分を引用する。
Sets background color of the screen to transparent, so previous screen is visible
直訳によると「画面の背景色を透明に設定して、前の画面が表示されるようにします」とのこと。
これにより、背景に遷移前の画面を透明で表示できる。
しかしモーダルの大きさを調整していないので、これだけでは遷移前の画面が見えない。
スタイルを整える
モーダルのスタイルを指定し、大きさや四隅の輪郭の丸み等を調整するため、cardStyle
にてCSSを指定する。また、cardOverlayEnabled: true
を指定するとモーダルの外の背景(オーバーレイ)を半透明で表示できる。
headerShown: false
はモーダルにヘッダーが必要ない場合は指定する。
const nav = createStackNavigator();
export const ModalStackNav: React.FC = () => {
return (
<nav.Navigator>
<nav.Screen
name="ExampleModal"
component={ExampleModalScreen}
options={{
cardOverlayEnabled: true,
cardStyle: {
marginTop: 100,
marginBottom: 100,
marginRight: 20,
marginLeft:20,
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10
},
headerShown: false,
presentation: 'transparentModal',
}}
/>
</nav.Navigator>
);
};
背景をタップして遷移前の画面に戻る
StackNavigatorでモーダルを実現する場合、背景をタップして遷移前の画面に戻るためには、onPress
したらgoBack
するようにコンポーネントを指定する。指定先のoption
として、cardOverlay
がある。
cardOverlay
を使用する場合はcardOverlayEnabled: true
を必ず指定する(ドキュメント)。
実装例を以下に示す。試行錯誤があった関係から適宜コメントを入れている。
※goBackの中身は趣旨から外れるため省略している。
const nav = createStackNavigator();
export const ModalStackNav: React.FC = () => {
return (
<nav.Navigator>
<nav.Screen
name="ExampleModal"
component={ExampleModalScreen}
options={{
headerShown: false,
presentation: 'transparentModal',
cardOverlayEnabled: true,
cardOverlay: () =>
<>
{/*
背景を透明にする。
PressableでonPressを指定すればgoBackできそうだが、下記報告と同じような状況(2022.12.25時点確認)の場合onPressが効かない。
https://github.com/react-navigation/react-navigation/issues/10114
zIndexでモーダルの上に表示すればonPressが効くが、モーダルが透明の背景に覆われてしまう。
*/}
<View
style={[StyleSheet.absoluteFillObject, {
backgroundColor: 'rgba(0,0,0,0.7)',
},]}
/>
{/*
画面全体をタップ可能なコンポーネントで覆い、goBackできるようにする。
背景色を指定するコンポーネントと別にPressableを配置することで上記問題を解決する。
*/}
<Pressable
style={[StyleSheet.absoluteFillObject,{
zIndex: 1,
}]}
onPress={goBack}
/>
{/*
画面全体を覆うコンポーネント上に配置し、モーダルの大きさと一致させ、モーダル上でタップしてもgoBackしないようにする。
cardStyleでzIndexを指定してもcardOverlayで指定しているコンポーネントより上に表示されないことへの対処。
*/}
<View style={{
marginTop:100,
marginBottom: 100,
marginRight: 20,
marginLeft: 20,
height: '80%',
width: '90%',
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
zIndex: 2,
}}/>
</>,
cardStyle: {
marginTop: 100,
marginBottom: 100,
marginRight: 20,
marginLeft:20,
height: '80%',
width: '90%',
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
},
}}
/>
</nav.Navigator>
);
};
ドキュメントでは背景をタップした場合はクローズしたり、ダイアログのアニメーションをカスタマイズしたい場合はuseCardAnimation
を使用すると案内されている。実際、StackNavigatorを使って実装しようとすると上記のように複雑になってしまうため、ドキュメントに従って実装した方がよさそうだ。
StackNavigatorを使ってボトムシートを実装する
ボトムシートとは、画面の下辺からスライドアップして内容を表示する要素である。これをreact-navigationのモーダルで実現してみる。
gestureEnabled: true
を指定するとモーダルを指で動かすことで閉じる動作ができる。
...TransitionPresets.ModalPresentationIOS
を指定するとボトムシートと背景の見た目をより想定通りに実現できる。「IOS」と名前に入っているがandroidでも動作する。
const nav = createStackNavigator();
export const ModalStackNav: React.FC = () => {
return (
<nav.Navigator>
<nav.Screen
name="BottomSheet"
component={BottomSheetScreen}
options={{
cardStyle: {
top: 100,
borderTopLeftRadius: 10,
borderTopRightRadius: 10
},
headerShown: false,
presentation: 'transparentModal',
gestureEnabled: true,
...TransitionPresets.ModalPresentationIOS,
}}
/>
</nav.Navigator>
);
};
あとがき
ここまでreact-navigationのモーダルの実装方法について調べてきた。
感想としては背景に遷移前の画面を表示するまでは容易に実装できたが、背景をタップしてクローズしたり、なんらかのアニメーションを加えたりしたい場合は実装難度が上がるため、より簡潔に実装するために実現方法の選択肢の幅を広く持っておく必要があると感じた。
React Nativeでは様々なライブラリがモーダル機能を提供しているので、仕様に合わせてどれが最適な実装方法かを吟味して選定する必要があると考える。
ちなみに、ここまで紹介したもの以外にもStackNavigatorのスクリーンオプションズにはmodalと名のつくpresentationの種類がある。興味がある場合は以下リンクから参照できる。
Native Stack Navigator | React Navigation