「ステータスバーに文字が被ってるんですけど…」って話
React Nativeでアプリを作り始めて、最初の画面を実機で確認した瞬間、たぶん多くの人がこう思うはずです。
「あれ?画面の上、文字がステータスバーに被ってない?」
iPhoneのノッチに見出しが食い込んでいたり、画面下のホームインジケーターに大事なボタンが隠れていたり…。Web開発に慣れていると、こういう物理的な「侵食領域」って意識しないので、初見だとびっくりします。
結論から言うと、これは React Native の SafeAreaView というコンポーネントで一発解決できます。
- SafeAreaView=ノッチ・ステータスバー・ホームインジケーターを避けてコンテンツを配置するコンポーネント
- 使い方は
<View>を<SafeAreaView>に置き換えるだけ - ただし、コアの
SafeAreaViewには罠があるので、現代ではreact-native-safe-area-contextが推奨 - Expoには標準で入っているので、最初から正しい方を使える
本記事では、「画面が見切れる/被る」問題に出会った React Native 初心者〜中級者向けに、セーフエリアの考え方と SafeAreaView の正しい使い方を解説します。実装パターン3選とよくあるハマりどころも網羅しているので、これ1本で安全領域の扱いがマスターできます。
そもそも「セーフエリア」って何?
SafeAreaView の話に入る前に、まず「セーフエリア(Safe Area)」という概念を整理しておきましょう。
セーフエリアとは、「コンテンツを配置しても安全な、画面上の領域」のこと。逆に言えば、その外側は「物理的・OS的に何かが被ってしまう領域」です。
スマホの「危険領域」たち
┌─────────────────────────────┐
│ ▓▓▓ ノッチ/Dynamic Island ▓▓▓ │ ← ここに文字を置くと隠れる
├─────────────────────────────┤
│ ステータスバー(時計・通信状態) │ ← OSが描画する領域
├─────────────────────────────┤
│ │
│ │
│ セーフエリア │ ← ここに置けば安全
│ (安全領域) │
│ │
│ │
├─────────────────────────────┤
│ ▓▓▓ ホームインジケーター ▓▓▓ │ ← Androidのナビゲーションバーも
└─────────────────────────────┘具体例:iOSとAndroidそれぞれの危険領域
- iOS(iPhone X以降):上部のノッチ/Dynamic Island、下部のホームインジケーター
- iOS(旧機種):上部のステータスバーのみ
- Android:上部のステータスバー、下部のナビゲーションバー(機種・OS設定で異なる)
- iPad / 大画面端末:基本的にはステータスバー、ホームインジケーター(あれば)
厄介なのは、これらの領域が機種ごと・OSごとに異なること。「iPhone 14 Proでは大丈夫だったのに、SEだと崩れる」「Androidの機種によって挙動が違う」みたいな事故が起きやすい。だからこそ、OSが動的に教えてくれるセーフエリア情報を使ってコンテンツを配置する必要があるんです。
SafeAreaViewの基本
React Native でセーフエリアを扱うための基本コンポーネントが SafeAreaView。使い方はシンプルで、画面のルート要素に置くだけです。
何もしない場合(NG例)
import { View, Text, StyleSheet } from 'react-native'
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>こんにちは!</Text>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
title: { fontSize: 24, padding: 20 },
})これだと、「こんにちは!」がノッチやステータスバーに被って表示されることがあります。Webだったら気にしないところだけど、モバイルでは事故です。
SafeAreaViewで囲む(OK例)
import { SafeAreaView, Text, StyleSheet } from 'react-native'
export default function App() {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>こんにちは!</Text>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
title: { fontSize: 24, padding: 20 },
})これで、自動的にステータスバー/ノッチ/ホームインジケーターを避けてコンテンツが配置されます。たった2行(importと囲み)の変更だけ。Reactコンポーネントの基本を押さえている人なら、すぐ使えます(Reactコンポーネントの基本については React 19の新機能解説 も参考に)。
コアの<SafeAreaView>には罠がある
「これだけで終わりなら記事も短くて済むのに」と思った方、実はここから本題です。
React Native コアに含まれている SafeAreaView には、実は致命的な制約があります。それは…
- iOSでしか効かない:Androidでは無視される
- 動的なノッチ変化に対応していない:画面回転や折りたたみで挙動がおかしくなる
- 細かい制御ができない:「上だけセーフエリア、下は無視」みたいなことが難しい
そして React Native の公式ドキュメントでも、「react-native-safe-area-context の利用を推奨します」と明記されています。つまり、コアの SafeAreaView は実質「過去の遺物」になっているんです。
現在の推奨:react-native-safe-area-context
現代の React Native 開発では、react-native-safe-area-context というサードパーティライブラリを使うのが事実上の標準です。
特徴とメリット
- iOS / Android 両対応:機種・OSバージョンの差を吸収
- 動的なセーフエリア変更に対応:画面回転や折りたたみでもズレない
- useSafeAreaInsets フック:細かい数値を取得して柔軟にレイアウトできる
- Expo SDK に標準同梱:個別インストール不要
- React Navigation などメジャーライブラリと統合済み
セットアップ
Expoを使っている場合は、すでにインストールされているのでスキップでOK。素のReact Nativeなら以下でインストール。
# Expo(インストール不要、念のため確認用)
npx expo install react-native-safe-area-context
# 素のReact Native
npm install react-native-safe-area-context
cd ios && pod install # iOSの場合は追加で必要SafeAreaProviderでアプリ全体を囲む
このライブラリでは、まず SafeAreaProvider でアプリ全体を囲むのが基本。最上位の App.tsx や _layout.tsx でこうします。
// App.tsx
import { SafeAreaProvider } from 'react-native-safe-area-context'
import HomeScreen from './screens/HomeScreen'
export default function App() {
return (
<SafeAreaProvider>
<HomeScreen />
</SafeAreaProvider>
)
}これで配下のコンポーネントがセーフエリア情報にアクセスできるようになります。
画面ごとにSafeAreaViewで囲む
// screens/HomeScreen.tsx
import { Text, StyleSheet } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
export default function HomeScreen() {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>こんにちは!</Text>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
title: { fontSize: 24, padding: 20 },
})importを react-native から react-native-safe-area-context に変えるだけ。これで iOS / Android どちらも適切に対応されます。
実装パターン3選
セーフエリアの扱い方には、用途別に3つのパターンがあります。場面に応じて使い分けましょう。
パターンA:全画面ラップ(基本)
一番シンプルで、ほとんどの画面はこれで足ります。edges プロパティで上下左右どこにセーフエリアを適用するか指定可能。
import { SafeAreaView } from 'react-native-safe-area-context'
// 全方向にセーフエリア(デフォルト)
<SafeAreaView style={{ flex: 1 }}>
<Content />
</SafeAreaView>
// 上下のみ
<SafeAreaView style={{ flex: 1 }} edges={['top', 'bottom']}>
<Content />
</SafeAreaView>
// 上だけ(下は背景色で塗りつぶしたい時など)
<SafeAreaView style={{ flex: 1 }} edges={['top']}>
<Content />
</SafeAreaView>パターンB:ヘッダー・フッターを別の色にする
「ヘッダー部分は青、コンテンツ部分は白」みたいに、セーフエリアの上下で背景色を変えたい場合。普通に SafeAreaView で囲むと全部同じ背景色になってしまうので、こういう時は外側に色付きのViewを置く工夫が必要です。
import { View, StyleSheet } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
export default function HomeScreen() {
return (
// 外側で青背景を確保(ステータスバー領域も青になる)
<View style={{ flex: 1, backgroundColor: '#1976d2' }}>
// SafeAreaViewでセーフエリアの内側だけ色を変える
<SafeAreaView style={styles.content}>
<Text>こんにちは</Text>
</SafeAreaView>
</View>
)
}
const styles = StyleSheet.create({
content: { flex: 1, backgroundColor: '#fff' },
})パターンC:useSafeAreaInsetsで自由レイアウト
「カスタムヘッダーの高さをノッチ分だけ増やしたい」「下部のフローティングボタンをホームインジケーターより上に置きたい」など、細かい数値で制御したい場合は useSafeAreaInsets フックを使います。
import { View, Text, StyleSheet } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
export default function HomeScreen() {
const insets = useSafeAreaInsets()
// insets = { top: 47, bottom: 34, left: 0, right: 0 }(iPhone 14 Proの例)
return (
<View style={{ flex: 1 }}>
<View style={[styles.header, { paddingTop: insets.top + 16 }]}>
<Text style={styles.headerText}>マイアプリ</Text>
</View>
<View style={styles.body}>
<Text>コンテンツ</Text>
</View>
<View style={[styles.fab, { bottom: insets.bottom + 16 }]}>
<Text>+</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
header: {
backgroundColor: '#1976d2',
paddingHorizontal: 16,
paddingBottom: 16,
},
headerText: { color: '#fff', fontSize: 20 },
body: { flex: 1, padding: 16 },
fab: {
position: 'absolute',
right: 16,
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: '#1976d2',
alignItems: 'center',
justifyContent: 'center',
},
})insets.top や insets.bottom を padding や bottom などに足すことで、機種ごとに動的に正しい余白を確保できます。これがいわば、現代のRN開発の本命の使い方です。
よくある失敗とトラブルシュート
SafeAreaView絡みで、初学者がハマりがちなトラブルとその対処法を紹介します。
失敗1:SafeAreaProviderで囲み忘れ
useSafeAreaInsets を使っているのに、ルートで SafeAreaProvider を設定していない場合、 insets が常に { top: 0, bottom: 0, ... } になってしまいます。
対処:App.tsx や _layout.tsx など最上位で SafeAreaProvider でアプリ全体を囲んでいるか確認。React Navigation を使っている場合、Navigatorよりも外側に置く必要があります。
失敗2:間違ったライブラリの SafeAreaView を import している
// ❌ コアの SafeAreaView(古い、Android効かない)
import { SafeAreaView } from 'react-native'
// ✅ react-native-safe-area-context(推奨)
import { SafeAreaView } from 'react-native-safe-area-context'意外とこのimport違いが原因で「SafeAreaView入れたのに効かない!」となるケース、めちゃくちゃ多いです。
失敗3:ScrollView と組み合わせて挙動が変
SafeAreaView の中に ScrollView を入れた時、スクロールの一番下まで行くとホームインジケーターに被って見える…という現象。これは SafeAreaView がスクロール内のコンテンツに対して効かないため。
対処:ScrollView の contentContainerStyle に paddingBottom を設定するか、useSafeAreaInsets で取得した bottom を加える。
import { ScrollView } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
export default function ListScreen() {
const insets = useSafeAreaInsets()
return (
<ScrollView
contentContainerStyle={{
padding: 16,
paddingBottom: insets.bottom + 16, // 下部余白を確保
}}
>
{/* リスト内容 */}
</ScrollView>
)
}失敗4:モーダル表示時にセーフエリアが効かない
React Native の Modal コンポーネントを使うと、内部でセーフエリアが効かない場合があります。これはモーダルが別のビュー階層で表示されるため。
対処:モーダルの中身も SafeAreaView で囲む、または useSafeAreaInsets で個別対応。
失敗5:StatusBar の色が変わらない
「セーフエリアは効いてるのに、ステータスバーの文字が暗くて見えない」みたいな問題。これはセーフエリアとは別、StatusBar コンポーネントで明示的に指定する必要があります。
import { StatusBar } from 'expo-status-bar' // または 'react-native'
<StatusBar style="light" /> // 白文字
<StatusBar style="dark" /> // 黒文字(デフォルト)Expoでの扱い
Expoは React Native の代表的な開発フレームワークで、react-native-safe-area-context が標準で同梱されているのが大きなメリット。初心者は迷わず Expo で始めるのが圧倒的にスムーズです。
Expoでの最小構成
// app/_layout.tsx(Expo Router使用時)
import { Stack } from 'expo-router'
import { SafeAreaProvider } from 'react-native-safe-area-context'
export default function RootLayout() {
return (
<SafeAreaProvider>
<Stack />
</SafeAreaProvider>
)
}
// app/index.tsx(個別画面)
import { Text } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
export default function HomeScreen() {
return (
<SafeAreaView style={{ flex: 1 }}>
<Text>こんにちは</Text>
</SafeAreaView>
)
}Expo Router を使う場合、_layout.tsx で SafeAreaProvider を設置するのが基本パターン。これで配下の全画面で SafeAreaView や useSafeAreaInsets が使えるようになります。
iOS と Android の考え方の違い
同じ React Native コードでも、iOS と Android ではセーフエリアの考え方が微妙に違います。これを知っておくと、両OSで違和感ない UI を設計できます。
| 項目 | iOS | Android |
|---|---|---|
| 上部の障害物 | ノッチ/Dynamic Island/ステータスバー | ステータスバー |
| 下部の障害物 | ホームインジケーター(最近の機種) | ナビゲーションバー(機種・設定で変動) |
| セーフエリアの厳密さ | 厳密(OSが正確な値を提供) | 柔軟(edge-to-edgeなど自由度高い) |
| 機種差の大きさ | 世代で違う(旧iPhone vs Pro Max) | 非常に大きい(メーカー・OS版で多様) |
iOSは「正しいセーフエリア」がOSから提供される文化、Androidは「edge-to-edge(端から端まで)」が前提でより自由、というイメージ。react-native-safe-area-context はこれらの違いをいい感じに吸収してくれるので、基本は useSafeAreaInsets に任せておけば両OSで違和感のないUIになります。
まとめ:SafeAreaViewはモバイルUI設計の前提知識
本記事のポイントを整理します。
- セーフエリア=ノッチ・ステータスバー・ホームインジケーターを避けた安全領域
- コアの
SafeAreaView(react-native)は使うな:iOS専用+古い、現代では非推奨 - 現代の標準は
react-native-safe-area-context:iOS/Android両対応、Expoに標準同梱 - 使い方は3パターン:全画面ラップ/背景色分け/useSafeAreaInsetsでカスタム
- ハマりどころ:SafeAreaProviderの囲み忘れ、importミス、ScrollView、Modal、StatusBar
- iOSとAndroidの考え方の違いを理解すると両OSで違和感のないUI設計ができる
「ステータスバーに文字が被る」って初見だとびっくりするけど、SafeAreaViewのことを知っていれば、現代のスマホアプリの安全領域なんて怖くない。知ってるか知らないかだけの差です。
そして、もっと大事なのは「機種ごとに違う物理的な制約をOSが教えてくれて、フレームワークが吸収してくれる」というモバイル開発の文化を知ること。Webだけやってきた人がモバイルに来ると、こういう「物理的制約」の発想がなくて戸惑うんですが、慣れると逆に「Webって自由だな」と感じるはず。
次にReact Nativeで画面を作るときは、まず最初に SafeAreaView で囲む。これだけで、ユーザーの体験品質が一段階上がります。
React Native・モバイル開発をさらに深める関連記事
SafeAreaView でモバイルUIの基礎を押さえたら、React・フロントエンド全体の理解を深めて、Web/モバイル両方で戦えるエンジニアを目指しましょう。実装力が一段階上がる関連記事を厳選しました。
React・フロントエンドの基礎力を強化
- 初心者エンジニア向け|Reactのバージョンアップ理由とReact 19の新機能をやさしく解説! – React Native の土台である React の最新動向を押さえる入門記事。Server Components や新フックの理解に
- Next.jsとVercelならサーバーいらない?サーバーレスの仕組みを解説 – Web側のフロントエンドを比較対象として理解。Web vs モバイルのフレームワーク思想の違いがクリアになります
API設計・バックエンドとの連携
- 「全部POSTでよくない?」がモヤる人へ|HTTPメソッド使い分けの正論と現場のリアル – モバイルアプリでもAPI通信は必須。バックエンドとの連携設計の判断軸を押さえる必読記事
- 【あるある】モノレポ分離デプロイでフロントのURLにAPI取得してしまうミスと対策 – フロント・バックエンドを横断する開発のあるある。React Native開発時の環境変数管理にも応用できます
設計力・用語の引き出しを増やす
- ハードコーディングって何?良くないって言われるけど何で?|初心者が知るべき問題点と改善方法 – ノッチ高さなどの値を直書きせず、insetsから動的取得する考え方の根底にある設計原則です
- 【初見殺し】エンジニアが読めないIT用語25選|nginx→ンギンクス?の悲劇を防ぐ – SafeArea、Inset、Edge、Notchなどモバイル開発特有の用語に強くなる土台に
