RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-04-29中級

Rork アプリに Bottom Sheet を組み込む実践ガイド — @gorhom/bottom-sheet で iOS と Android の操作感を揃える

Rork が生成する Modal は便利ですが、複数のスナップポイントや慣性スクロールが必要になった瞬間に物足りなくなります。@gorhom/bottom-sheet を後から差し込む実装手順、キーボード対応、iOS/Android の差分対処までを実例で解説します。

Rork515Bottom Sheet@gorhom/bottom-sheetReact Native209UI 実装Reanimated10Gesture Handler2

Rork で生成したアプリを少し触っていくと、ある瞬間に「標準の Modal ではどうしても物足りない」場面が出てきます。地図の下からスッと現れる詳細パネル、商品一覧の上に重ねるフィルター、入力フォームを途中までだけ表示するシート — どれも単純な全画面モーダルでは表現できません。

私自身、自分のアプリに「2段階で開くシート」を入れたかったのですが、Rork が出してくれた Modal ベースのコードでは慣性スクロールが効かず、Android で背景が真っ黒に光る問題に半日悩みました。結論としては @gorhom/bottom-sheet を後から差し込むのが一番素直で、Rork の出力をほとんど壊さずに導入できます。ここではその手順とつまずきやすいポイントをまとめておきます。

なぜ Rork が出す Modal だけでは足りなくなるのか

Rork は React Native 標準の Modal を使った実装をよく出してきます。確認ダイアログや軽い入力パネル程度なら十分機能しますが、次のような要件が重なると一気に苦しくなります。

  • シートの「半開き」「全開き」など複数のスナップポイントを切り替えたい
  • 中身に長いリストがあり、シート内で慣性スクロールさせたい
  • ドラッグの途中で戻したり、しきい値で自動的に開閉させたい
  • iOS 風の角丸・グレースケールの背景フェードを揃えたい

Modal でこれらを再現しようとすると、自前で PanResponder を書き、リスト内のスクロールと外側のドラッグを切り分け、Animated.Value を慎重に扱う必要があります。書けないことはないのですが、保守を考えると専用ライブラリに任せたほうが現実的です。

@gorhom/bottom-sheet を Rork プロジェクトに追加する

Rork は内部的に Expo + React Native のセットアップを使っています。@gorhom/bottom-sheet は Reanimated と Gesture Handler を前提にしているため、まずこの2つが入っているかを確認してから本体を追加します。

# Rork プロジェクトのルートで実行
npx expo install react-native-reanimated react-native-gesture-handler
npm install @gorhom/bottom-sheet

Reanimated は Babel プラグインの登録が必須です。babel.config.js にすでに設定が入っていることが多いですが、もし無ければ末尾に追記します。プラグインの順番が変わるとビルドが通らなくなることがあるので、必ず plugins 配列の最後に置いてください。

// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      // ...他のプラグイン...
      'react-native-reanimated/plugin', // ← 必ず最後
    ],
  };
};

Gesture Handler はアプリのエントリポイントで GestureHandlerRootView がルートを覆っている必要があります。Rork が出した _layout.tsx の最外側に1行足すだけで済むことがほとんどです。

// app/_layout.tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Slot } from 'expo-router';
 
export default function RootLayout() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Slot />
    </GestureHandlerRootView>
  );
}

ここまでで、Rork の生成コードはほとんど触っていません。後付けで Bottom Sheet を入れたい場合の最小限の準備はこれで完了です。

最小構成のボトムシートを動かす

まずは「半分まで開く / 全画面まで開く」の2スナップポイントだけを持つシートを作ります。useRef で参照を取り、ボタンから expand() / close() を呼べる形にしておくと、後から AI 生成された画面に組み込みやすくなります。

import React, { useCallback, useMemo, useRef } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';
 
export default function PlaceDetailScreen() {
  const sheetRef = useRef<BottomSheet>(null);
 
  // 端末の高さに対するパーセンテージで指定できる
  const snapPoints = useMemo(() => ['25%', '60%', '90%'], []);
 
  const openSheet = useCallback(() => {
    sheetRef.current?.snapToIndex(1); // 60% の位置で開く
  }, []);
 
  return (
    <View style={styles.container}>
      <Button title="詳細を見る" onPress={openSheet} />
 
      <BottomSheet
        ref={sheetRef}
        index={-1} // 初期は閉じておく
        snapPoints={snapPoints}
        enablePanDownToClose
        backgroundStyle={styles.sheetBg}
        handleIndicatorStyle={styles.indicator}
      >
        <BottomSheetView style={styles.sheetBody}>
          <Text style={styles.title}>カフェ・ヒロカワ</Text>
          <Text>営業時間: 10:00 - 19:00</Text>
          <Text>東京都渋谷区...</Text>
        </BottomSheetView>
      </BottomSheet>
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
  sheetBg: { backgroundColor: '#ffffff' },
  indicator: { backgroundColor: '#cccccc', width: 48 },
  sheetBody: { padding: 20 },
  title: { fontSize: 20, fontWeight: '600', marginBottom: 8 },
});

期待する挙動は次の通りです。ボタンを押すとシートが下から60%の位置までスッと現れ、上にドラッグすれば90%、下にドラッグすれば25%もしくは閉じる、の3段階で止まります。enablePanDownToClose を入れておかないと下方向にドラッグして消すことができないので忘れないでください。

スナップポイントとジェスチャの設計で意識すること

スナップポイントは「ピクセル」ではなく「比率」で書くのが基本です。iPhone SE と iPad で見た目を揃えるためには、画面サイズに連動した比率指定のほうが扱いやすいからです。一方で、コンテンツの中身が決まっている場合は 'CONTENT_HEIGHT' を渡して中身の高さに合わせるという選択肢もあります(v4 以降)。

スナップポイントを増やしすぎると、ユーザーがどこで止めればいいか分からなくなります。私は「閉じる + 半開き + 全開き」の3つで止めることが多く、4つ以上はほぼ使いません。地図系アプリで Apple Maps のような挙動を再現したいときだけ、最下段に「ハンドルだけ見える」位置(10%程度)を追加します。

ドラッグ操作を細かく制御したい場合は onChange でインデックスを監視し、特定の位置から先に行ったらフォーカスを当てる、といった実装も可能です。ただし、ジェスチャの結果に副作用を被せると Reanimated のワーレットスレッドで実行されるため、UI スレッドへ戻す処理を runOnJS で囲む必要があります。この辺りの注意点は Rork × Reanimated と Gesture Handler で作る高度なアニメーション にまとめています。

入力フォームを乗せるときのキーボード対応

Bottom Sheet にテキスト入力を置くと、必ず一度はキーボードに隠される事故が起きます。@gorhom/bottom-sheet は専用の BottomSheetTextInput を提供しており、これを使うとフォーカス時に自動でシートを上にずらしてくれます。

import BottomSheet, {
  BottomSheetView,
  BottomSheetTextInput,
} from '@gorhom/bottom-sheet';
 
<BottomSheet
  ref={sheetRef}
  index={-1}
  snapPoints={['40%', '90%']}
  keyboardBehavior="interactive" // iOS で自然に追従
  keyboardBlurBehavior="restore" // 閉じる時に元の位置へ
  android_keyboardInputMode="adjustResize"
>
  <BottomSheetView style={{ padding: 16 }}>
    <BottomSheetTextInput
      placeholder="メモを入力..."
      style={{
        height: 44,
        paddingHorizontal: 12,
        backgroundColor: '#f4f4f4',
        borderRadius: 8,
      }}
    />
  </BottomSheetView>
</BottomSheet>

keyboardBehavior は iOS 限定のオプションで、'interactive' にしておくとキーボードと連動してシートが滑らかに動きます。Android 側は android_keyboardInputMode="adjustResize" を指定し、加えて app.jsonsoftwareKeyboardLayoutMode"resize" に変更してください。ここを "pan" のままにしているとシート全体が押し上げられて見た目が崩れます。

複数の入力欄があるフォームでは、フォーカスの切り替え時に若干の遅延が起きることがあります。これは BottomSheetScrollView を内側に使うことで解消できることが多く、リスト上にフォームを配置するパターンでも自然なスクロールになります。

iOS と Android で挙動が分かれるポイント

実機で触ると、思ったよりも OS 間の差が目立ちます。私が踏みやすかった3点だけ書いておきます。

  • 背景の暗転: iOS は半透明の黒が綺麗にフェードしますが、Android は端末によって BackdropComponent を渡さないと真っ黒になることがあります。@gorhom/bottom-sheet には BottomSheetBackdrop が付属しているので、明示的に渡すのが安全です。
  • ハンドルのドラッグ可能領域: Android は標準だとハンドル部分の判定が小さく、指が滑ります。handleHeight を 28〜32 程度まで広げると操作感が自然になります。
  • 戻るボタンとの連動: Android では物理戻るボタンでシートだけを閉じたいことがあります。useFocusEffectBackHandler を登録し、sheetRef.current?.close() を呼んで true を返すと意図通りに動きます。

これらは「Rork が生成したデフォルトコード」では考慮されないことがほとんどなので、Bottom Sheet を入れた直後にレビュー実機(できれば中価格帯の Android)で必ず確認することをおすすめします。実機特有のクラッシュや表示崩れを早期に拾いたい場合は、Rork で作ったアプリが実機でクラッシュする時の切り分け方 も併せて読んでみてください。

なお、シート内のローディング表示は標準のスピナーよりもスケルトンを使うほうが体感が良くなります。実装パターンは Rork でスケルトンスクリーンを実装してロード待ちを快適にする にまとめてあるので、組み合わせて使うと完成度が上がります。

まずは小さく組み込んでみる

Rork が出してくれる Modal で十分な場面では、無理に Bottom Sheet を導入する必要はありません。ただ「半開き」や「上から重ねるリスト」を作りたいと思った瞬間に、自前実装と既製ライブラリで悩む時間が一気に増えます。そのときの選択肢として @gorhom/bottom-sheet をひとつ持っておくと、Rork の生成コードと無理なく共存できる、というのが3アプリほど触ってみた感想です。

最初の一歩としては、すでにある画面のうち1つだけシート化してみるのが取り組みやすいと思います。設定ボタンを押すと下から出てくるカテゴリ選択、地図の下に出てくる場所詳細など、効果が分かりやすい画面から差し替えてみてください。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-03-30
Rork × React Native Reanimated & Gesture Handler 実践ガイド — 60fps を維持するアニメーション設計と高度なジェスチャーインタラクション
Reanimated 3 と Gesture Handler で 60fps を保つ設計を、壁紙・癒し系アプリの実運用で踏んだ落とし穴とともに整理しました。runOnJS の発火遅延、useDerivedValue キャッシュ、FlashList との併用、cancelAnimation 漏れなど、公式ドキュメント外の知見を実測値付きで共有します。
開発ツール2026-06-21
Rork のお気に入り一覧を長押しドラッグで並べ替える — 再レンダリングを抑えた reorderable list と並び順の永続化
Rork が生成したお気に入り一覧に、長押しドラッグの並べ替えを後から足す実装手順です。再レンダリングを抑える設計、worklet の境界、並び順の永続化、ジャンクやゴーストカードの回避までを実コードでまとめました。
開発ツール2026-04-24
Rork アプリで Reanimated の worklet エラーが出たときに順番に疑う 6 つのポイント
Rork 生成コードに Reanimated を組み込んだ途端に出る worklet 関連のエラーを、babel プラグインの抜け漏れから closure キャプチャまで、現場でぶつかる順に切り分けていきます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →