iphone-duo-react-native について
iPhone Duo向けにアプリを調整するReact Native開発者向けのガイドです。ネイティブコードなしでのサイズ変更やセーフエリアの処理から、Swiftで固有情報を取得する方法まで説明します。
主な機能
- 01左右差のあるインセットを辺ごとに処理
- 02ウィンドウサイズ変更に応じたレイアウト更新
- 03幅のブレークポイントによるレイアウト切り替え
- 04ネイティブコードで折り目領域とヒンジ角度を取得
活用シーン
- 01折りたたみやSplit View時の余白問題の修正
- 02内側ディスプレイ向けの2カラムレイアウト作成
- 03操作に必要な場合のデバイス固有情報の追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mirzaaghazadeh/ iphone-duo-skills --skill iphone-duo-react-nativeインストール後、Claude Code で/iphone-duo-react-nativeを入力して読み込まれたか確認します。
詳細
iPhone Duo での React Native
React Native には折りたたみやヒンジに関する API がありません。useDisplayFeatures もヒンジイベントもなく、デバイスが折りたたまれていることを JavaScript に知らせるものは何もありません。この記事の執筆時点では、iPhone Duo に対応した React Native や Expo のリリースはありません。
幸い、最もよく問題が起きる 2 つの要素、つまりサイズ変更とセーフエリアは、おそらくすでにインストール済みのライブラリで解決できます。次の 2 段階で進めてください。
Tier 1 — 今すぐ使える、ネイティブコード不要
エッジごとに処理するセーフエリアのインセット
ここは React Native アプリが iPhone Duo で問題を起こす部分です。その理由を率直に説明すると、このデバイスのセーフエリアは左右非対称です。 iOS はディスプレイの片側にコントロールを配置するため、left と right のインセットは異なります。どちら側になるかは、端末の姿勢と、アプリが Split View のどちら側に表示されるかによって変わります。
react-native-safe-area-context の useSafeAreaInsets() を使ってください。独立した数値として { top, right, bottom, left } を返し、インセットが変わると更新されます。
const insets = useSafeAreaInsets();
// each edge, independently
paddingLeft: insets.left,
paddingRight: insets.right,
避けるべきことは 3 つあります。
react-nativeコアのSafeAreaView— 非推奨であり、ノッチのケースしか適切に処理できません。コンテキストライブラリのフックか、そのSafeAreaViewを使ってください。- 左右対称の計算 —
width - insets.left * 2のような式は、インセットが異なる時点で誤りになります。このデバイスでは、ほとんどの場合にインセットが異なります。 - インセットを一度だけ読み取ること — アプリの実行中に変化するため、キャッシュするのではなく、レンダーごとにフック経由で読み取ってください。
React Native 0.81+ でエッジツーエッジが有効になっている場合、コンテンツはすでにシステム UI の下まで広がっています。そのため、インセットを正しく処理することは見た目だけでなく、機能上不可欠です。
レンダーのたびにサイズ変更に対応する
iPhone Duo では、開く、閉じる、折りたたむ、回転する、Split View に入るといった操作で、ウィンドウのサイズが頻繁に変わります。
useWindowDimensions() を使ってください。これはフックなので、変更時に再レンダーされます。Dimensions.get('window') はスナップショットを返すだけで、気づかないうちに古い値になります。モジュールスコープや定数に Dimensions.get(...) がある場合、このデバイスでは不具合になります。
コンポーネント単位のサイズ調整には、ウィンドウの寸法から算出するよりも実際に測定した領域を得られる onLayout のほうが確実です。
デバイスではなく幅に応じて分岐する
iOS はサイズクラスで判定します。外側のディスプレイはコンパクト幅、内側のディスプレイはレギュラー幅です。React Native にはこれに相当するものがないため、useWindowDimensions() から幅のブレークポイントを使ってください。
Platform.OS、react-native-device-info から取得したデバイスモデル、または向きによる分岐は避けてください。内側のディスプレイはサポート対象の向きに従わないため、向きから分かることは思ったより少ないです。
内側のディスプレイに適したレイアウトを用意する
スマートフォン向けのレイアウトを 7.6 インチに引き伸ばすと、未完成な印象になります。手軽にできるのは、幅が広いときのブレークポイント以上で 2 列またはマスター・ディテールのレイアウトを使うことです。また、React Navigation を使う場合は、幅が広いときにはドロワーまたはレールスタイルのナビゲーターを、幅が狭いときにはタブを使ってください。
Tier 2 — ネイティブモジュールが必要
iPhone Duo 固有の機能には、Swift とのブリッジが必要です。次の順で対応するのがおすすめです。
| ネイティブ API(iOS 27.1) | JS に公開する理由 |
|---|---|
UIView の reservedRegions(kind:) | タッチ領域を折り目から離し、ディスプレイ下カメラを避けるため |
UIHingeInteraction | エフェクトに使うヒンジ角度の連続的な値を取得するため |
traitCollection のサイズクラス | ブレークポイントの推測ではなく、iOS が実際に使うシグナルを取得するため |
モジュールの構成
TurboModule(新アーキテクチャ)またはレガシーのネイティブモジュールを作成し、継続的に変化する値には
NativeEventEmitter を使用します。ヒンジ角度はリアルタイムのストリームです。イベントとして送信し、JavaScript からポーリングしないでください。
React のコンテンツをマウントしているビューから予約領域を取得してください。これらの API は UIView に紐づいており、領域はビューを基準に報告されるためです。領域はプレーンなオブジェクト({ x, y, width, height, kind, active })として送信し、JavaScript からそのままレイアウトに利用できるようにします。
必ず厳格にガードしてください。同じ JavaScript バンドルがすべての iPhone で実行されるため、モジュールは折りたたみ式デバイス以外や iOS 27.1 未満では、例外を投げるのではなく「領域なし、ヒンジなし」を返す必要があります。Swift で利用可能性を確認し、決して決めつけないでください。
Expo を使用している場合は、設定プラグインとローカルのネイティブモジュールが必要です。Expo Go では動作しないため、開発ビルドが必要です。
次に Tier 1 に取り込む
領域が JavaScript に届けば、既存のレイアウトで扱える単なる数値です。分割領域の周囲に配置する絶対配置の
View を使えばコントロールを折り目から離せます。遮蔽領域から導出した
marginRight を使えばコンテンツをカメラに重ねずに済みます。
作業の順序
Dimensions.get()をuseWindowDimensions()に置き換えます。- コアの
SafeAreaViewをuseSafeAreaInsets()に置き換え、すべてのインセット式で対称性を確認します。 - プラットフォームや向きによる分岐を幅のブレークポイントに置き換えます。
- 内側のディスプレイのサイズを活かせるよう、幅広レイアウトの経路を追加します。
- その後にのみ、折りたたみへの対応が本当にアプリを改善する場合に限り、ネイティブモジュールを作成します。
手順 1~4 は、タブレットや折りたたみ式 Android でも役立つ通常の React Native 開発です。手順 5 のみが iPhone Duo 固有の作業で、テストには Xcode 27.1 シミュレーターが必要です。
ネイティブ API の根拠については、
../iphone-duo-adaptive-layout/SKILL.md と ../iphone-duo-readiness/SKILL.md を参照してください。
../iphone-duo-design-review/SKILL.md のデザインガイダンスはフレームワークに依存しないため、React Native アプリにもそのまま適用できます。