◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-04-12中級

Rorkアプリのレイアウト崩れ・レスポンシブ対応トラブルシューティング

Rorkで作ったアプリのレイアウト崩れを実例コードで直します。SafeAreaViewの落とし穴、flex:1が効かない原因、固定px指定による破綻、スクロールできない問題、テキストの切れや重なりまで、画面サイズ差に耐えるレスポンシブ対応を実践的に解説します。

Rork585レイアウト3レスポンシブデザイントラブルシューティング85React Native239SafeArea3Flexbox

iPhone 16 Pro Maxで完璧に見えたUIが、iPhone SE3で操作不能になる

Rorkでアプリを作っていて、プレビューでは美しく仕上がったのに実機テストで崩壊する——この経験がある開発者は多いはずです。特に多いのが、大画面のiPhoneで確認して満足した後、小さな画面のデバイスやAndroidで開いた瞬間にボタンが画面外に飛び出す、テキストが重なる、スクロールできないといった症状です。

Rorkが生成するReact Nativeのコードは、基本的にFlexboxベースのレイアウトシステムを使っています。Webブラウザ上のCSSに似ているようで微妙に違うため、Web開発経験者ほど落とし穴にはまりやすい。ここでは私がRorkアプリの開発で実際に遭遇したレイアウト崩れのパターンと、それぞれの修正方法をコード付きで解説します。

SafeAreaViewの罠:ノッチとDynamic Islandに潜む3つの落とし穴

Rorkが生成するコードで最も多いレイアウト問題は、SafeArea周りです。iPhoneのノッチやDynamic Island、Androidのステータスバーとの干渉で、コンテンツが隠れたりパディングが二重にかかったりします。

落とし穴1:SafeAreaViewの二重ラップ

Rorkが生成したコードをカスタマイズする過程で、SafeAreaViewが入れ子になるケースがあります。親コンポーネントと子コンポーネントの両方がSafeAreaViewを使うと、余白が倍になります。

// ❌ 二重SafeAreaView — 余白が2倍になる
const App = () => (
  <SafeAreaView style={{ flex: 1 }}>
    <NavigationContainer>
      <Stack.Screen component={HomeScreen} />
    </NavigationContainer>
  </SafeAreaView>
);
 
const HomeScreen = () => (
  <SafeAreaView style={{ flex: 1 }}> {/* ここで余白がさらに追加される */}
    <Text>コンテンツ</Text>
  </SafeAreaView>
);

修正方法は、SafeAreaViewを最上位の1箇所だけに限定し、子コンポーネントでは通常のViewを使うことです。

// ✅ SafeAreaViewは最上位のみ
const App = () => (
  <SafeAreaView style={{ flex: 1 }}>
    <NavigationContainer>
      <Stack.Screen component={HomeScreen} />
    </NavigationContainer>
  </SafeAreaView>
);
 
const HomeScreen = () => (
  <View style={{ flex: 1 }}> {/* 通常のViewを使う */}
    <Text>コンテンツ</Text>
  </View>
);

落とし穴2:Androidのステータスバー問題

React NativeのSafeAreaViewは、iOSではノッチやホームインジケーターに対応するが、Androidではステータスバーの領域を考慮しない。Rorkで生成したアプリをAndroidで見ると、コンテンツがステータスバーの裏に潜り込む。

// ✅ クロスプラットフォーム対応のSafeArea処理
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
 
// react-native-safe-area-contextを使う方法(推奨)
const Screen = ({ children }) => (
  <SafeAreaView style={styles.container}>
    {children}
  </SafeAreaView>
);
 
// もしくはPlatform判定でAndroidのみ手動パディング
const ScreenManual = ({ children }) => (
  <View style={[
    styles.container,
    Platform.OS === 'android' && {
      paddingTop: StatusBar.currentHeight || 24
    }
  ]}>
    {children}
  </View>
);
 
const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#fff' }
});

ポイントは、React Native本体のSafeAreaViewではなく、react-native-safe-area-contextライブラリのものを使うこと。後者はiOSとAndroidの両方で安全領域を正しく計算します。Rorkのプロジェクトには通常すでにインストールされているが、インポート元が違う点に注意しよう。

落とし穴3:Dynamic IslandデバイスでのSafeArea上端の値

iPhone 14 Pro以降のDynamic Island搭載機では、SafeAreaの上端インセットが従来のノッチ機より大きい(59pt vs 47pt)。固定値でパディングを設定していると、Dynamic Islandモデルでコンテンツが被る。

// ❌ 固定値は特定デバイスでしか機能しない
const Header = () => (
  <View style={{ paddingTop: 47 }}>
    <Text>ヘッダー</Text>
  </View>
);
 
// ✅ useSafeAreaInsetsで動的に取得
import { useSafeAreaInsets } from 'react-native-safe-area-context';
 
const Header = () => {
  const insets = useSafeAreaInsets();
  return (
    <View style={{ paddingTop: insets.top }}>
      <Text>ヘッダー</Text>
    </View>
  );
};

Flexboxの「flex: 1」が効かない——親のheight指定を忘れていないか

Rorkが生成するレイアウトコードはflex: 1を多用します。Flexboxは親要素から子要素へと空間を分配する仕組みなので、親のサイズが確定していないと子のflex: 1は効果がない。

典型的な症状は「ScrollViewの中身が見えない」です。

// ❌ ScrollViewの親にflex: 1がない → 高さ0になる
const Screen = () => (
  <View> {/* heightもflexもない */}
    <ScrollView>
      <View style={{ height: 2000 }}>
        <Text>長いコンテンツ</Text>
      </View>
    </ScrollView>
  </View>
);
 
// ✅ 親チェーン全体にflex: 1を貫通させる
const Screen = () => (
  <View style={{ flex: 1 }}> {/* これが必須 */}
    <ScrollView style={{ flex: 1 }}>
      <View style={{ height: 2000 }}>
        <Text>長いコンテンツ</Text>
      </View>
    </ScrollView>
  </View>
);

このルールは「flex: 1のチェーン」と覚えるとよい。ルート(SafeAreaView)から対象のコンポーネントまで、途中の全てのViewにflex: 1が設定されていなければならありません。1箇所でも切れると、そこから下は高さが0として扱われます。

デバッグのコツは、問題のある画面でReact Native Debuggerの「Inspect Element」を開き、各Viewの計算後のheightを確認することです。height: 0のViewがあれば、そこがチェーンの切断点になっています。

固定px指定で壊れる——画面サイズ対応の実践パターン

Rorkでアプリを作ると、AIがwidth: 375のような固定値を生成することがあります。開発中のプレビューでは完璧に見えるが、画面幅が異なるデバイスでは当然崩れます。

パーセンテージの代わりにFlexを使う

React NativeではCSSのwidth: 50%も使えるが、より柔軟なのはFlexboxの比率指定です。

// ❌ 固定幅 — iPhone SE3で画面外にはみ出す
const Card = () => (
  <View style={{ width: 350, padding: 16 }}>
    <Text>カード内容</Text>
  </View>
);
 
// ✅ Flex比率 + 最大幅で制御
const Card = () => (
  <View style={{
    flex: 1,
    maxWidth: 500,
    padding: 16,
    marginHorizontal: 16
  }}>
    <Text>カード内容</Text>
  </View>
);

Dimensionsで画面サイズを取得する

2カラムレイアウトや画像のアスペクト比維持には、画面の実サイズが必要な場面があります。

import { Dimensions, useWindowDimensions } from 'react-native';
 
// ❌ Dimensions.get は初回レンダリング時の値を返す(回転に追従しない)
const { width } = Dimensions.get('window');
 
// ✅ useWindowDimensions は画面回転・分割表示に追従する
const GridItem = () => {
  const { width } = useWindowDimensions();
  const numColumns = width > 768 ? 3 : 2;
  const itemWidth = (width - 48) / numColumns; // 48 = 余白合計
 
  return (
    <View style={{ width: itemWidth, height: itemWidth, margin: 8 }}>
      <Text>アイテム</Text>
    </View>
  );
};

Dimensions.get('window')はアプリ起動時のスナップショットしか返さありません。iPadの画面分割やAndroidのマルチウィンドウで表示領域が変わっても更新されません。useWindowDimensionsフックを使えば、サイズ変更のたびに再レンダリングが走り、常に正しい値が得られます。

ScrollViewとFlatListの落とし穴——「スクロールできない」を解決する

ScrollView内にFlatListを入れてはいけない

Rorkが複雑な画面を生成すると、ScrollViewの中にFlatListが配置されることがあります。この組み合わせはスクロール競合を起こし、パフォーマンス警告が出るだけでなく、FlatListの仮想化(画面外の要素を破棄する最適化)が無効になります。

// ❌ ScrollView内のFlatList — 仮想化が無効になり全データをレンダリングする
const Screen = () => (
  <ScrollView>
    <Text style={styles.header}>ヘッダー</Text>
    <FlatList
      data={items}         // 1000件あると1000件全てレンダリングされる
      renderItem={renderItem}
    />
  </ScrollView>
);
 
// ✅ FlatListのListHeaderComponentを使う
const Screen = () => (
  <FlatList
    data={items}
    renderItem={renderItem}
    ListHeaderComponent={
      <Text style={styles.header}>ヘッダー</Text>
    }
    ListFooterComponent={
      <View style={{ height: 40 }} /> // 下部の余白
    }
  />
);

FlatListのListHeaderComponentとListFooterComponentを活用すれば、ヘッダーやフッターをリストと一緒にスクロールさせつつ、仮想化の恩恵も受けられます。

contentContainerStyleを忘れていないか

ScrollViewのスタイリングでよくある間違いが、styleとcontentContainerStyleの混同です。

// ❌ style に padding を指定 → スクロールバーの位置がずれる
<ScrollView style={{ padding: 16 }}>
  <Text>内容</Text>
</ScrollView>
 
// ✅ contentContainerStyle でコンテンツ領域にパディング
<ScrollView contentContainerStyle={{ padding: 16 }}>
  <Text>内容</Text>
</ScrollView>

styleはScrollViewの外枠(固定領域)に適用され、contentContainerStyleはスクロールするコンテンツ全体に適用されます。パディングや中央揃えはcontentContainerStyleに書くのが正解です。

テキストが切れる・重なる——フォントサイズと行間の対処法

日本語テキストはアルファベットに比べて文字あたりの面積が大きく、行間も多めに取らないと読みづらい。Rorkが英語ベースで生成したフォント設定では、日本語が窮屈に見えることが多いです。

// ❌ 英語ベースの行間 — 日本語テキストが詰まって見える
const styles = StyleSheet.create({
  body: {
    fontSize: 14,
    lineHeight: 18,  // 行間が狭すぎる
  }
});
 
// ✅ 日本語に最適化した行間
const styles = StyleSheet.create({
  body: {
    fontSize: 14,
    lineHeight: 24,  // fontSize × 1.7前後が日本語に適する
    letterSpacing: 0.3,
  }
});

また、numberOfLinesを設定せずに長いテキストを表示すると、コンテナからはみ出す。切り捨て表示にするなら明示的に指定します。

// ✅ テキスト切り捨ての正しい実装
<Text
  numberOfLines={2}
  ellipsizeMode="tail"
  style={{ fontSize: 14, lineHeight: 22 }}
>
  長い説明テキストがここに入ります。2行を超える部分は省略記号で表示されます。
</Text>

実践:ブレークポイント対応のレスポンシブレイアウトを組む

最後に、Rorkアプリで複数画面サイズに対応するレスポンシブレイアウトの実装例を示す。カスタムフックを1つ作れば、画面全体で一貫したブレークポイント判定ができます。

// hooks/useResponsive.ts
import { useWindowDimensions } from 'react-native';
 
type Breakpoint = 'compact' | 'medium' | 'expanded';
 
export const useResponsive = () => {
  const { width, height } = useWindowDimensions();
 
  const breakpoint: Breakpoint =
    width >= 840 ? 'expanded' :   // iPad・タブレット
    width >= 600 ? 'medium' :     // 大画面スマホ・横向き
    'compact';                     // 通常のスマホ
 
  const isLandscape = width > height;
 
  return { width, height, breakpoint, isLandscape };
};
 
// 使用例:ProductListScreen.tsx
import { useResponsive } from '../hooks/useResponsive';
 
const ProductListScreen = () => {
  const { breakpoint, width } = useResponsive();
 
  const numColumns = breakpoint === 'expanded' ? 4
    : breakpoint === 'medium' ? 3
    : 2;
 
  const itemWidth = (width - (numColumns + 1) * 12) / numColumns;
 
  return (
    <FlatList
      data={products}
      numColumns={numColumns}
      key={numColumns} // numColumns変更時に再マウントするために必須
      renderItem={({ item }) => (
        <View style={{ width: itemWidth, margin: 6 }}>
          <ProductCard product={item} />
        </View>
      )}
    />
  );
};

FlatListのnumColumnsを動的に変える場合、keyプロパティにカラム数を渡すのがポイントです。これがないとReact Nativeはカラム数の変更を検知できず、レイアウトが壊れます。公式ドキュメントにも記載されているが、実装時に見落としやすい。

このフックを使えば、「compactではシングルカラム、expandedではサイドバー付き」のような分岐もシンプルに書ける。Rorkが生成した画面に後からレスポンシブ対応を足す場合でも、UIロジックをuseResponsiveに集約することで、各画面のコードを最小限の変更で対応できます。

レスポンシブ対応でお困りの方はRork UXデザインパターン完全ガイドも参考になります。iPadレイアウトに特化した話はRorkでiPad対応アプリを作るガイドで詳しく扱っています。また、スタイリング全般についてはNativeWind(Tailwind CSS)スタイリングガイドが実践的です。

シェア

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

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

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

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

関連記事

⬡ 開発ツール2026-05-18
Rork で Linking.canOpenURL が iOS だけ false を返す ─ LSApplicationQueriesSchemes 設定漏れの直し方
TikTok や X、LINE 公式アカウントへ飛ばす実装が iOS の本番ビルドだけで動かない症状を、LSApplicationQueriesSchemes の追加と EAS Build の再生成手順で確実に解消します。
⬡ 開発ツール2026-04-22
Rorkで作ったアプリの日本語入力が『変換中に暴発する』問題を根本解決する
Rorkで生成したアプリの日本語入力で、IME変換中にイベントが暴発する、候補ウィンドウが閉じる問題を根本から解決します。デバウンスでの吸収、Enter確定パターン、multilineでの分離という3つの解決策と、Rorkへ追加指示するプロンプト例を紹介します。
⬡ 開発ツール2026-10-15
EXPO_PUBLIC_ の値が、開発では読めて本番だけ undefined になる書き方を、変換結果で見分けます
API の URL が開発中は読めるのに、ビルドした途端に undefined になる。原因は process.env の書き方でした。babel-preset-expo の変換結果を開発・本番で並べ、置き換わる書き方と残ってしまう書き方を表にしました。