Beautiful HD Wallpapers を最新の iPhone Air で開いたとき、ホーム画面設定プレビューの壁紙が微妙にはみ出していることに気づきました。「また解像度が増えたか」と思いながらシミュレーターを立ち上げると、いくつかの画面でレイアウトがずれていました。
2013年から個人で iOS アプリ開発を続けてきて、累計5,000万DLを超えるアプリ群のうち4本を今もメンテナンスしています——Beautiful HD Wallpapers、Ukiyo-e Wallpapers、Relaxing Healing、Law of Attraction Everyday。毎年 Apple が新しい iPhone を出すたびに解像度対応が必要になりますが、2026年は iPhone Air という新フォームファクターが加わったことで、対応する機種の組み合わせが一気に増えました。
Rork で iOS アプリを開発していると、AI がレイアウトのコードを生成してくれます。ただ「新しい解像度で動かしたら崩れた」という問題は、結局自分で手直しするしかない部分です。4本のアプリを同時に対応した実体験をもとに、Rork プロジェクトでの解像度管理パターンを整理します。
2026年の iPhone 解像度マトリクスを整理する
対応を始める前に、現時点で流通している主要な iPhone の解像度(ポイント単位)を把握しておく必要があります。
混乱しやすいのが「ピクセル」と「ポイント」の違いです。iOS の UI レイアウトはポイント(pt)で計算されます。Retina ディスプレイでは 1pt = 2px または 3px になりますが、Rork(React Native)でのレイアウトコードで扱うのはすべてポイントです。デザイナーが Figma で作業するときも、基本的にはポイント単位で考えると混乱が減ります。
2026年時点での主要 iPhone のポイント解像度をまとめると、以下のようになります。
iPhone Air(2026) : 幅 420pt × 高さ 912pt(3x Retina)
iPhone 17 Pro : 幅 402pt × 高さ 874pt(3x Retina)
iPhone 17 Pro Max / 16 Pro Max : 幅 440pt × 高さ 956pt(3x Retina)
iPhone 17 / 16 : 幅 393pt × 高さ 852pt(3x Retina)
iPhone 15 / 旧世代標準モデル : 幅 393pt × 高さ 852pt(3x Retina)
iPhone 15 Plus : 幅 430pt × 高さ 932pt(3x Retina)
iPhone Air の幅 420pt に注目してください。従来の Plus/Max 系(430pt)よりも狭く、標準モデル(393pt)よりも広い、これまでなかった中間サイズです。width > 400 ? 'large' : 'standard' のようなブレークポイント設計を使っていた場合、iPhone Air は 'large' に分類されますが実際は Pro Max よりずっと狭い。この不整合がレイアウト崩れの根本原因になります。
Apple が新しいフォームファクターを追加する理由の一つは、ユーザー層の多様化への対応です。iPhone Air は薄くて軽いことを特徴にしており、より大きな画面を求めながらも Pro Max の重さを避けたいユーザー向けに設計されています。開発者の立場からは、またひとつ「対応すべきサイズ」が増えた、という現実があります。しかし見方を変えると、iPhone Air ユーザーはアプリの見た目に敏感な層である可能性が高く、解像度対応のクオリティがそのまま評価につながりやすいとも言えます。
高さ方向の差異も重要です。iPhone 17 Pro(874pt)は標準モデル(852pt)より 22pt 高く、Pro Max(956pt)はさらに 82pt 高い。フルスクリーン表示前提のアプリでは、この差がそのまま画像の見切れや余白として現れます。壁紙アプリを作っていると、この 22pt がとても大きく感じられます。
Rork プロジェクトで解像度情報を取得する
Rork は内部的に React Native(Expo)を使っているため、解像度の取得には React Native 標準の API を利用します。最も基本的な方法は useWindowDimensions フックです。
import { useWindowDimensions } from 'react-native' ;
const WallpaperViewer : React . FC = () => {
const { width , height } = useWindowDimensions ();
return (
< View style = {{ width, height, position : 'absolute' , top : 0 , left : 0 }} >
{ /* フルスクリーンコンテンツ */ }
</ View >
);
};
useWindowDimensions はアプリが回転したときに値が変わります。壁紙アプリのようにポートレート固定で動作するアプリでは問題になりませんが、横向き表示もサポートする場合は注意が必要です。起動時の一度きりの取得で十分であれば、Dimensions.get('window') を使う方が余分な再レンダリングを避けられます。
import { Dimensions } from 'react-native' ;
// アプリ起動時に一度だけ取得する(ポートレート固定アプリ向け)
const SCREEN_WIDTH = Dimensions. get ( 'window' ).width;
const SCREEN_HEIGHT = Dimensions. get ( 'window' ).height;
私が4本のアプリで採用しているのは後者の方法です。壁紙アプリの性質上、ポートレート固定で動作するため、起動時の取得で十分対応できています。また、モジュールレベルで定数化することで、コンポーネント内でのフック呼び出しが不要になり、コードがシンプルになります。
デバイス別レイアウト定数を一箇所で管理する
最初は各画面コンポーネントに if (width === 420) のような分岐を直接書いていました。しかしそれでは変更箇所が散らばってしまいます。私が最終的に採用したのは、デバイス別の定数を DeviceConfig.ts として一元管理する方法です。
以前、複数の画面に三項演算子が29箇所以上散らばっていた状態から、このファイルに集約したことで、次の機種対応は1ファイルの変更で完結するようになりました。
// src/config/DeviceConfig.ts
import { Dimensions } from 'react-native' ;
const { width , height } = Dimensions. get ( 'window' );
type DeviceProfile = {
wallpaperWidth : number ;
wallpaperHeight : number ;
safeAreaTop : number ;
safeAreaBottom : number ;
thumbnailHeight : number ;
gridColumns : number ;
};
const DEVICE_PROFILES : Record < string , DeviceProfile > = {
'iphone-air' : {
wallpaperWidth: 420 ,
wallpaperHeight: 912 ,
safeAreaTop: 59 ,
safeAreaBottom: 34 ,
thumbnailHeight: 220 ,
gridColumns: 3 ,
},
'iphone-17-pro' : {
wallpaperWidth: 402 ,
wallpaperHeight: 874 ,
safeAreaTop: 59 ,
safeAreaBottom: 34 ,
thumbnailHeight: 210 ,
gridColumns: 3 ,
},
'iphone-pro-max' : {
wallpaperWidth: 440 ,
wallpaperHeight: 956 ,
safeAreaTop: 59 ,
safeAreaBottom: 34 ,
thumbnailHeight: 230 ,
gridColumns: 3 ,
},
'iphone-standard' : {
wallpaperWidth: 393 ,
wallpaperHeight: 852 ,
safeAreaTop: 59 ,
safeAreaBottom: 34 ,
thumbnailHeight: 200 ,
gridColumns: 3 ,
},
'default' : {
wallpaperWidth: width,
wallpaperHeight: height,
safeAreaTop: 44 ,
safeAreaBottom: 34 ,
thumbnailHeight: 200 ,
gridColumns: 3 ,
},
};
function detectDevice () : string {
if (width === 420 ) return 'iphone-air' ;
if (width === 402 ) return 'iphone-17-pro' ;
if (width >= 430 ) return 'iphone-pro-max' ;
if (width === 393 ) return 'iphone-standard' ;
return 'default' ;
}
export const DEVICE = DEVICE_PROFILES [ detectDevice ()];
export const SCREEN_WIDTH = width;
export const SCREEN_HEIGHT = height;
DEVICE オブジェクトをアプリ全体で参照すれば、解像度関連の分岐を各コンポーネントに書く必要がなくなります。型定義(DeviceProfile)を明示しておくことで、新しいデバイスを追加したとき、プロファイルに必要なフィールドが欠けていると TypeScript がエラーを出してくれます。これが地味に助かります。
壁紙アプリ特有のフルスクリーン表示問題
壁紙アプリを作っていると、「画像をいかにきれいにフルスクリーン表示するか」は最重要課題の一つです。iPhone Air の幅 420pt という中途半端な数値がここで問題になります。
従来の壁紙画像は多くの場合、標準解像度(393pt × 852pt 相当)または Plus/Max 系(440pt × 956pt 相当)向けに最適化されていました。iPhone Air(420pt × 912pt)はこのどちらでもありません。フルスクリーン表示での resizeMode の選択が重要になります。
// ❌ アスペクト比が崩れる
< Image
source = {{ uri : wallpaperUrl }}
style = {{ width : SCREEN_WIDTH , height : SCREEN_HEIGHT }}
resizeMode = "stretch"
/>
// ✅ 壁紙アプリで推奨:全面を覆う
< Image
source = {{ uri : wallpaperUrl }}
style = {{
position : 'absolute' ,
top : 0 ,
left : 0 ,
width : SCREEN_WIDTH ,
height : SCREEN_HEIGHT ,
}}
resizeMode = "cover"
/>
resizeMode="cover" は画面全体を覆うように画像を拡大・縮小しますが、画像の端が少し切れることがあります。壁紙アプリとしては通常これが望ましい挙動です。iPhone Air の場合、縦横比(420:912 ≈ 0.46)が標準機(393:852 ≈ 0.46)とほぼ同じため、cover モードでの見た目の差は最小限です。
一方、グリッド一覧のサムネイル表示は要注意です。幅に依存する計算をしていると、デバイスごとにサムネイルの高さや余白がずれます。
// サムネイルサイズの動的計算(DeviceConfig を活用)
import { DEVICE, SCREEN_WIDTH } from '../config/DeviceConfig' ;
const GUTTER = 8 ;
const THUMBNAIL_WIDTH = ( SCREEN_WIDTH - GUTTER * ( DEVICE .gridColumns + 1 )) / DEVICE .gridColumns;
const THUMBNAIL_HEIGHT = DEVICE .thumbnailHeight;
Beautiful HD Wallpapers では、グリッドのサムネイルに整数ピクセルを使うことで、画像の僅かな滲みを防いでいます。React Native では Math.floor() か PixelRatio.roundToNearestPixel() でポイントを整数化しておくのが安全です。
Safe Area と Dynamic Island の実装
2022年以降の iPhone Pro シリーズに搭載された Dynamic Island は、従来の notch とは Safe Area のインセット値が変わります。iPhone Air にも Dynamic Island が搭載されているため、safeAreaTop の値が機種ごとに異なる可能性があります。
react-native-safe-area-context の useSafeAreaInsets が最も確実な取得方法です。
import { useSafeAreaInsets } from 'react-native-safe-area-context' ;
const HomeScreen : React . FC = () => {
const insets = useSafeAreaInsets ();
return (
< View
style = {{
flex : 1 ,
paddingTop : insets.top,
paddingBottom : insets.bottom,
}}
>
{ /* コンテンツ */ }
</ View >
);
};
注意点は、SafeAreaProvider でアプリのルートをラップしないと動作しないことです。Rork が生成するコードでは、このプロバイダーが欠けていることがあります。
// App.tsx のルートコンポーネントを必ずラップする
import { SafeAreaProvider } from 'react-native-safe-area-context' ;
export default function App () {
return (
< SafeAreaProvider >
{ /* ナビゲーション・画面コンポーネント */ }
</ SafeAreaProvider >
);
}
iPhone Air での実機テストで確認したところ、insets.top は 59pt でした。iPhone 17 Pro でも同じ値です。この確認はデバッグビルドで console.log を仕込んでおくのが手軽です。
if (__DEV__) {
console. log ( 'Safe Area insets:' , JSON . stringify (insets));
// 期待値: {"top": 59, "bottom": 34, "left": 0, "right": 0}
}
壁紙の詳細表示画面など「Safe Area も含めてフルスクリーン表示したい」ケースもあります。その場合は insets.top や insets.bottom を考慮せずに表示し、代わりにホームインジケーターや Dynamic Island が表示されてもコンテンツが隠れても問題ないデザインにするのが最もシンプルです。
FlatList と ScrollView のパフォーマンスへの影響
スクロールリストやグリッド表示はほぼすべてのアプリに存在します。解像度対応の文脈でよく見落とされるのが、スクロールパフォーマンスへの影響です。iPhone Air の幅 420pt は中途半端な値のため、FlatList でグリッドを組んでいると、各行のレイアウト計算が既存デバイスと異なるパスを通ることがあります。
特に numColumns と columnWrapperStyle を組み合わせて使っている場合、各列の幅が小数点以下を含む値になると React Native の内部でレイアウトが再計算を繰り返すことがあります。
// ❌ 小数点が出ることがある(iPhone Air で 132.67pt など)
const columnWidth = SCREEN_WIDTH / 3 ;
// ✅ 整数に丸めて安定させる
const columnWidth = Math. floor ( SCREEN_WIDTH / 3 );
const totalGridWidth = columnWidth * 3 ;
const horizontalPadding = ( SCREEN_WIDTH - totalGridWidth) / 2 ;
Beautiful HD Wallpapers では、グリッド表示のパフォーマンスを保つために getItemLayout も実装しています。これにより、スクロール位置のジャンプや、大量の壁紙画像リストでのちらつきが大幅に改善されました。
< FlatList
data = {wallpapers}
numColumns = { 3 }
keyExtractor = {(item) => item.id}
getItemLayout = {(_, index) => ({
length : THUMBNAIL_HEIGHT + 4 , // 高さ + マージン
offset : ( THUMBNAIL_HEIGHT + 4 ) * Math. floor (index / 3 ),
index,
})}
renderItem = {({ item }) => <WallpaperThumbnail item = {item} /> }
/>
getItemLayout を実装するには、全アイテムの高さが一定である必要があります。デバイスごとに THUMBNAIL_HEIGHT を変えている場合は、DeviceConfig.ts から取得した値で計算するようにしてください。
Launch Screen と Splash Screen の対応
新機種のシミュレーターを使って初めて起動したとき、スプラッシュ画面が崩れていることがあります。resizeMode: "contain" を設定していると、iPhone Air の縦横比に合わせた不要な余白が目立つことがあります。
Rork(Expo)の app.json では以下のように設定します。
{
"expo" : {
"splash" : {
"image" : "./assets/splash.png" ,
"resizeMode" : "cover" ,
"backgroundColor" : "#000000"
},
"ios" : {
"supportsTablet" : false
}
}
}
resizeMode: "cover" にすることで、どの解像度でも画面全体を覆う表示になります。ロゴや重要な視覚要素は画像の中央付近に配置するデザインが必要ですが、一度そのテンプレートを作ってしまえば、以後の機種追加でも対応不要になります。
私は2025年に4本のアプリ全てのスプラッシュを cover モードに切り替えました。それ以来、新機種が追加されてもスプラッシュ画面の再設計が必要になったことはありません。小さな事前投資が、毎年の更新コストを削減しています。
App Store スクリーンショットの更新と効率化
新機種が追加されるたびに App Store のスクリーンショット要件が変わります。2026年現在、App Store Connect では以下の主要サイズへの対応が求められます。
6.9インチ(iPhone 17 Pro Max / 16 Pro Max) : 1320px × 2868px
6.7インチ(iPhone Air) : 1260px × 2736px
6.3インチ(iPhone 17 Pro) : 1206px × 2622px
6.1インチ(iPhone 17 / 16) : 1179px × 2556px
4本のアプリで各アプリ5〜10枚 × 4サイズ = 最大40〜80枚の更新が必要になります。手動で全て再作成するのは非現実的です。
私が採用しているのは Figma を使ったマスタースクリーンショット管理です。最大サイズ(1320 × 2868px / Pro Max)でマスターを作成し、各機種サイズのフレームをコンポーネントリンクで展開します。アプリの UI に変更があった場合も、マスターコンポーネントを更新するだけで全サイズに反映されます。
Rork プロジェクトで Fastlane が直接使えない場合の代替として、EAS Build でビルドしたアプリを各機種のシミュレーターで起動し、スクリーンショットを取る方法もあります。ただし自動化の仕組みをゼロから構築するコストを考えると、Figma ベースの管理の方が個人開発規模では現実的です。
テスト戦略と機種確認の落とし穴
新解像度のシミュレーターで動作確認するときに注意したいのが、Safe Area の値が実機と異なる場合があるという点です。特に Dynamic Island の挙動は実機でしか確認できないことが多いです。
シミュレーターで確認できること・できないことを整理しておくと、テストの優先順位が立てやすくなります。シミュレーターで確認できるのは、レイアウトの大まかなずれ、画像の切れ方、フォントサイズの表示、グリッドの行列数などです。一方、実機が必要な確認は、Dynamic Island との実際のレイアウト干渉、ホームインジケーター周辺の余白の実際の見た目、高負荷時のスクロールパフォーマンス、壁紙設定機能の実際の動作などです。
実機を持っていない機種の確認は、TestFlight に登録したベータテスターに依頼する方法もあります。Beautiful HD Wallpapers では日本語圏以外のユーザーも多く、英語・韓国語・スペイン語のコミュニティからフィードバックをもらえることがあります。4本のアプリを運営していて感じるのは、このような国際的なユーザーとの接点が、新機種への対応の早さに直結するということです。
4本同時対応のワークフロー
4本のアプリのコードベースで DeviceConfig.ts を同期するための簡単なシェルスクリプトを用意しています。
#!/bin/bash
# sync_device_config.sh — 4つのアプリ間で定数ファイルを同期する
SOURCE = "./src/config/DeviceConfig.ts"
TARGETS = (
"../beautiful-wallpapers/src/config/DeviceConfig.ts"
"../ukiyoe-wallpapers/src/config/DeviceConfig.ts"
"../relaxing-healing/src/config/DeviceConfig.ts"
"../law-of-attraction/src/config/DeviceConfig.ts"
)
for target in "${ TARGETS [ @ ]}" ; do
cp " $SOURCE " " $target "
echo "✅ Synced: $target "
done
このスクリプトを新解像度対応のたびに実行するだけで、4本のアプリに同じ変更が反映されます。Rork Max を使って複数アプリを管理している方には、同様のアプローチが効果的だと思います。
個人開発を12年続けてきた中で見えてきたのは、複数のアプリを運営するときの鍵は「変更の影響範囲をできるだけ小さくすること」だということです。今回のような解像度定数の一元管理も、その考え方の延長です。新機種が出るたびに修正コストを最小化できる構造を作っておくことが、長期的な個人開発の持続可能性につながっていくと感じています。
なぜ毎年この作業が必要になるのか
iPhone の解像度対応を繰り返しながら、毎年同じことをやっているという感覚があります。2013年に個人でアプリ開発を始めた頃は、対応すべき解像度は iPhone 5 の縦長画面(4インチ)と iPhone 4S(3.5インチ)の2種類だけでした。それが今では6〜7種類を同時に管理する状況になっています。
この繰り返しをネガティブに感じたこともあります。しかし最近は少し見方が変わってきました。宮大工だった私の祖父が社寺建築の修復に従事していたという話を、子どもの頃から聞かされていました。職人の仕事は「完成してから終わり」ではなく、建物が存在し続ける限り手入れが続くものだという感覚を持っています。アプリの運用も同じで、リリースしたら終わりではなく、使い続けてもらうために定期的にメンテナンスするのが当然という考え方です。
Rork を使ってアプリを作ると、最初のバージョンを素早く作れます。しかしそのアプリを長く運営していくためには、解像度対応のような「地味だが確実に必要な作業」をどう効率化するかが長期的な差になります。コードを書いた本人が「どこを変えれば何が変わるか」を素早く判断できる構造にしておくことが、個人開発の継続に直結します。
Beautiful HD Wallpapers は2013年に最初のバージョンをリリースし、12年以上継続してきました。その間に iPhone 4、5、6、X、11、12、13、14、15、16、Air——と次々と新解像度に対応してきました。毎回何かが崩れ、毎回直してきた経験から言えるのは、「崩れにくい構造」を最初から意識して作ることの価値は、最初は実感しづらいが数年後に大きく返ってくるということです。
既存コードの解像度分岐を見つけ出す
Rork でプロジェクトを作り込んでいると、どこに解像度依存のコードが潜んでいるか見えにくくなります。新機種対応の作業を始める前に、影響範囲を把握する点が肝心です。
解像度に関連するコードを探すための主なキーワードは4種類あります。最初は Dimensions 関連で、Dimensions.get('window').width や useWindowDimensions を使っている箇所が解像度に直接依存しています。次に Platform.select や Platform.OS === 'ios' の中に暗黙的な解像度仮定が埋まっているケースです。3番目は StyleSheet.create の中の固定値で、width: 393 や height: 120 のように特定の機種を想定して書かれたマジックナンバーです。最後は paddingTop や marginBottom に設定された固定値で、Safe Area の代わりに決め打ちされているパターンです。
以下のコマンドで一通り洗い出せます。
grep -rn "Dimensions.get\|useWindowDimensions\|width: [0-9]\{3\}\|height: [0-9]\{3\}" \
src/ --include= "*.tsx" --include= "*.ts" | grep -v "node_modules"
出力が5件以上あれば DeviceConfig.ts への集約を検討してください。私が最初にこの調査をしたとき、4本のアプリ合計で70件以上ヒットしました。集約後は15件以下になりました(import 文と型定義のみ)。
日本語ユーザーへの特有の考慮点
Beautiful HD Wallpapers や Ukiyo-e Wallpapers では日本語ユーザーが多く、App Store のレビューを読んでいると「新しい iPhone に変えたら壁紙がおかしい」というフィードバックが他地域に比べて多い傾向があります。日本では iPhone の普及率が高く、毎年新機種に買い替えるユーザーが多いため、解像度対応の速さがレビュー評価に直結しやすいです。
Ukiyo-e Wallpapers のように日本文化をテーマにしたアプリでは、縦横比へのこだわりが視覚的品質に大きく影響します。縦長の版画を iPhone Air の横幅が広くなった画面に表示するとき、resizeMode の選択だけでは対応しきれないケースがあります。画像ごとに最適な表示方法をユーザーが選べるオプションを提供することが、日本のアート系アプリでは特に重要だと感じています。浮世絵のような縦長構図と、横長のパノラマ壁紙では求められる表示ロジックが根本的に異なります。そのどちらにも対応できる設計にしておくと、ユーザーの多様な好みに応えられます。
Relaxing Healing のような音楽・ヒーリング系アプリでは、没入感のある画面全体を使ったビジュアルが重要です。iPhone Air の横幅が広くなったことで、背景画像が横長に感じられる場合があります。背景素材はどの比率でも破綻しないデザインで一本化しておくことで、機種別のアセット管理が不要になります。
PixelRatio を使った高解像度対応
もう一つ見落とされがちな観点として、3x Retina 画面での実ピクセル数があります。iPhone Air では 1pt = 3px なので、420pt × 912pt の論理解像度は実際の画面ピクセルでは 1260px × 2736px になります。画像素材をこの実ピクセルサイズで用意しているかどうかが、ズームしたときの鮮明さに影響します。
PixelRatio を使うと、論理サイズから実ピクセルサイズを計算できます。
import { PixelRatio } from 'react-native' ;
const logicalWidth = SCREEN_WIDTH ; // iPhone Air: 420pt
const pixelWidth = PixelRatio. getPixelSizeForLayoutSize (logicalWidth);
// → 1260px (420 × 3)
// CDN から解像度に最適な画像を取得する例
const imageUri = `https://cdn.example.com/wallpaper_${ pixelWidth }w.jpg` ;
Beautiful HD Wallpapers では CDN から解像度別の画像を動的に取得しています。iPhone Air(1260px幅)と Pro Max(1320px幅)で別ファイルを配信することで、不必要に大きな画像をダウンロードせずに済み、表示速度が改善されます。
全体を振り返ってと次のアクション
今回の対応で最も効果があったのは、DeviceConfig.ts のような定数管理ファイルを作成したことでした。コード全体に散らばっていた Dimensions.get('window').width への直接参照を、このファイルからの参照に置き換えるだけで、今後の新機種対応は1ファイルの変更で完結するようになります。
12年間の個人開発で繰り返し実感してきたのは、「今の作業が楽になる構造」よりも「次の作業が楽になる構造」を選ぶことの重要性です。解像度定数の一元管理は、作る当初はひと手間かかります。しかし iPhone の新機種が出るたびに、そのひと手間が確実に返ってきます。個人開発でアプリを長期間運営するということは、毎年こうした地味なメンテナンスを積み重ねることです。その積み重ねを少しでも軽くしておくことが、創作に使える時間を守ることにつながります。
まず試してほしいのは、既存プロジェクトで Dimensions.get('window') を使っている箇所を grep で探し出すことです。
grep -rn "Dimensions.get\|SCREEN_WIDTH\|screenWidth" src/ --include= "*.tsx" --include= "*.ts"
この出力が5件以上あれば、一元管理ファイルへの移行を検討する価値があります。同じ課題に取り組んでいる方の参考になれば幸いです。