App Store の審査に出した後、知人から「上のバッテリーアイコンが見えないよ」と連絡が来たことがあります。明るい背景に白いアイコンが乗ってしまっていたのです。Rork が生成したコードでは StatusBar まわりは見落とされやすく、特に画面遷移を挟むと突然色が変わる、Android だけ背景色が反映されない、といった現象が頻繁に起こります。
ここでは私が実際にリリースしたアプリで踏んだトラブルを基に、StatusBar の色やスタイルが思った通りに表示されない問題を切り分け、確実に直す手順を整理しました。expo-status-bar と React Native 標準の StatusBar の使い分けを含め、本番で困らない構成までまとめています。
まず確認すべきこと — どのライブラリを使っているか
Rork で生成された画面コードには、たいてい次の 2 種類のいずれかが含まれています。
import { StatusBar } from 'expo-status-bar'— Expo 推奨。SDK 50 以降ではこちらが標準import { StatusBar } from 'react-native'— React Native 標準。プラットフォーム差が大きい
私は基本的に expo-status-bar を使い、Android で背景色を変えたい時だけ react-native の StatusBar を併用する方針で固めています。expo-status-bar は iOS/Android の差異を吸収してくれる薄いラッパーなので、まずはこちらを使い切ることをおすすめします。
両方のインポートが混在していると、内部で別々のインスタンスを参照することになり、設定が打ち消し合って原因不明の挙動になります。プロジェクト全体で一本化しましょう。
ケース 1: アイコンが背景に溶けて見えない
最も多いのがこのパターンです。明るい背景に白い文字、暗い背景に黒い文字が乗ってしまい、時刻やバッテリーが視認できません。
// app/(tabs)/index.tsx
import { StatusBar } from 'expo-status-bar';
import { View, Text } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
{/* 明るい背景なので、アイコンは黒(dark)で表示 */}
<StatusBar style="dark" />
<Text>こんにちは</Text>
</View>
);
}style プロパティは次の 3 つを取ります。
light— アイコンを白く描画(暗い背景用)dark— アイコンを黒く描画(明るい背景用)auto— システムの配色に追従(多くの場合これで十分)
ホーム画面が dark、設定画面が light のように画面ごとに値が変わる場合は、各 Screen コンポーネント内で StatusBar を宣言してください。_layout.tsx で一度だけ宣言しても、expo-status-bar は最後にマウントされたものを優先するため、画面遷移で揺らぎます。
ケース 2: Android だけ背景色が反映されない
iOS の StatusBar には背景色の概念が無く、画面の View がそのまま透けて見えています。一方 Android はステータスバーが独立した領域を持ち、明示的に色を指定しないとデフォルトの黒のままです。
import { StatusBar } from 'expo-status-bar';
import { Platform, StatusBar as RNStatusBar, View } from 'react-native';
export default function ScreenWithColoredBar() {
return (
<View style={{ flex: 1 }}>
{/* iOS は expo-status-bar に任せる */}
<StatusBar style="light" />
{/* Android のみ背景色を指定 */}
{Platform.OS === 'android' && (
<RNStatusBar backgroundColor="#1E3A8A" barStyle="light-content" />
)}
{/* 画面コンテンツ */}
</View>
);
}「Android で背景色を変えたいから RN 標準を使う」「iOS のスタイル切り替えは expo に任せる」というハイブリッド構成にしておくと、双方の良いところを取れます。Android 15 のエッジ・トゥ・エッジ表示では backgroundColor の挙動が変わる可能性があるため、react-native-edge-to-edge の導入も検討してください。
ケース 3: 画面遷移で色が点滅する
タブ切り替えやスタックナビゲーションの遷移中に、一瞬だけ前画面のスタイルが残る現象です。これは StatusBar の更新タイミングが遷移アニメーションより遅いことが原因で起こります。
Expo Router を使っている場合は、useFocusEffect と組み合わせて、画面がフォーカスされた瞬間に StatusBar を更新します。
import { useFocusEffect } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { useCallback, useState } from 'react';
import { View } from 'react-native';
export default function SettingsScreen() {
const [style, setStyle] = useState<'light' | 'dark'>('dark');
// 画面がフォーカスされる度に StatusBar を再宣言
useFocusEffect(
useCallback(() => {
setStyle('light');
return () => {
// 離脱時は何もしない(次画面が再宣言するため)
};
}, []),
);
return (
<View style={{ flex: 1, backgroundColor: '#0F172A' }}>
<StatusBar style={style} animated />
{/* ... */}
</View>
);
}animated プロパティを付けると、色の切り替えにフェードが入って点滅が緩和されます。逆にチラつきが許容できないアプリ(金融系など)では animated={false} にして、急激に切り替える方が「動作している感」が伝わります。私はコンテンツ系のアプリでは animated を付け、業務系では外しています。
ケース 4: モーダル表示中に色が戻ってしまう
Modal コンポーネントを表示すると、StatusBar のスタイルが OS 標準に戻されてしまうことがあります。これは Modal が独自のウィンドウとしてマウントされ、親ビューの StatusBar 設定が伝播しないためです。
import { Modal, View } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { useState } from 'react';
export default function ScreenWithModal() {
const [open, setOpen] = useState(false);
return (
<View style={{ flex: 1 }}>
<StatusBar style="dark" />
<Modal visible={open} onRequestClose={() => setOpen(false)}>
<View style={{ flex: 1, backgroundColor: '#000' }}>
{/* Modal 内でも StatusBar を再宣言 */}
<StatusBar style="light" />
{/* モーダルのコンテンツ */}
</View>
</Modal>
</View>
);
}ポイントは Modal の中身でも StatusBar を再宣言することです。閉じた後は親側の宣言が再度効くので、明示的に戻す処理は不要です。
iOS の presentationStyle="fullScreen" ではこれで十分ですが、pageSheet や formSheet だと OS 側がスタイルを管理してしまうため、StatusBar の再宣言が無視されます。デザイン優先で表示形式を選ぶのがおすすめです。
ケース 5: SafeAreaView を使っていて余白が崩れる
StatusBar の問題は、SafeAreaView のインセット計算とも絡みます。translucent を有効にしている場合、コンテンツが StatusBar の裏まで広がるため、ヘッダーが切れて見えることがあります。
expo-status-bar の translucent プロパティを使うと、Android のステータスバーが透明になり、その分インセットを react-native-safe-area-context の useSafeAreaInsets から取得して padding を確保する必要が出てきます。
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar';
import { Platform, View } from 'react-native';
export default function TranslucentBar() {
const insets = useSafeAreaInsets();
return (
<View style={{ flex: 1 }}>
<StatusBar style="light" translucent />
<View
style={{
paddingTop: Platform.OS === 'android' ? insets.top : 0,
backgroundColor: '#1E3A8A',
}}
>
{/* ヘッダー */}
</View>
</View>
);
}translucent を使うかどうかは、デザインがエッジ・トゥ・エッジを採用しているかで決まります。最近の Material You デザインでは透過が前提なので、新規アプリならこちらを推奨します。Safe Area の扱いについては Rork で Safe Area・ノッチ表示が崩れる問題の解決ガイド に詳しく書きました。
デバッグの順番
ここまで読んで「自分のはどれだろう」と迷った方のために、私が普段やっている切り分け手順を共有します。
- iOS シミュレータと Android エミュレータの両方で同じ画面を開く
- 両方とも問題があれば、StatusBar の宣言自体が無いか、
autoのまま放置されている可能性が高い - Android だけ問題があれば、
backgroundColorの指定漏れかtranslucentの影響を疑う - iOS だけ問題があれば、
presentationStyleの指定が悪さをしている可能性がある - 画面遷移で起こるなら、画面ごとに StatusBar が宣言されているかを確認する
- プラットフォーム差全般のチェックリストは Rork で iOS と Android の挙動が違う時のチェックリスト も参照ください
iOS シミュレータでは Cmd + Shift + H でホームに戻すと StatusBar の状態がリセットされるので、再現性の確認に使えます。Android エミュレータは「Settings」アプリ内で外観を切り替えると、システムテーマと自分のアプリの相互作用が見やすくなります。
時間を節約してくれる小さな習慣
最後に、私が実際に取り入れて効果のあった「StatusBar 不具合を未然に防ぐ習慣」を 3 つ紹介します。どれもリリース前に取り入れられる軽い工夫です。
ひとつ目は、画面テンプレートに StatusBar を組み込んでおくこと。新しい画面を作る時、最初から <StatusBar style="auto" /> がリターンの先頭に書いてあるスニペットからコピーしています。デザインが固定スタイルを要求した時には auto を dark か light に書き換えるだけ。最初から宣言があれば、StatusBar が抜け落ちたままリリースされる事故は起きません。
ふたつ目は、早い段階で実機で確認すること。シミュレータとエミュレータは大抵正しく描画してくれますが、動画再生・スクリーンショット撮影・回転といった動作を実機で行うと、シミュレータでは出ない不具合が見つかることがあります。私は今、App Store 提出の前に必ず実機の iPhone と廉価な Android 端末で全画面の StatusBar をチェックするようにしています。20 分で終わる作業ですが、レビューで「アイコンが見えません」と書かれるダメージに比べれば誤差です。
みっつ目は、監査を自動化すること。私は Detox か Maestro で全画面を巡回してスクリーンショットを撮るだけの簡易フローを用意しています。サムネイル 40 枚程度を眺めるのは 1 分で済み、コントラスト問題は一目で見つかります。自動化が間に合わないなら、iOS のコントロールセンターを下ろした状態で各画面を手動で確認するだけでも十分多くの問題を拾えます。
複数の Rork プロジェクトで同じバグと戦っているなら、共通コンポーネントに抽出するサインです。私は ThemedStatusBar という薄いラッパーを個人テンプレートリポジトリに持っており、theme プロパティ 1 つを受け取って iOS/Android の分岐を内部で処理しています。20 行ほどのコードですが、何度も元を取ってくれている小さな相棒です。
全体を振り返って
StatusBar は地味ですが、ここがおかしいだけで「作りが雑な印象」が一気に出てしまうので、リリース前に必ず両プラットフォームで確認してください。具体的なアクションとしては、まず使っているライブラリを expo-status-bar に統一し、各画面コンポーネントに 1 行 <StatusBar style="..." /> を入れることから始めるのが効果的です。それだけでケース 1 と 2 の半数は解決します。
スプラッシュ画面から本編に切り替わる瞬間の挙動が気になる場合は、Rork のスプラッシュ画面が固まる・白フラッシュする問題の解決法 もあわせて確認すると、起動時のちらつきまで一気に潰せます。