去年の9月、正式版が配られた翌日にレビュー欄の空気が変わりました。書かれていたのは「落ちる」ではなく、「上の文字がバーに重なっている」「下のボタンが押しづらい」という見た目の話でした。
こちらのコードは1行も変わっていません。変わったのは、ユーザーの端末のほうでした。
個人開発でアプリを何本か抱えていると、この時期の数日は妙に落ち着きません。私はこの数年、予備の iPhone を1台だけベータ機として切り離して、正式版が届く前に自分のアプリを一通り触るようにしています。Mac も Xcode も使わない、地味な手順です。
9月までに残っている時間を、正しく数える
iOS 27 の開発者ベータは、8月24日に7回目が配布されました。6回目が8月17日でしたので、おおよそ週次のペースです。正式リリースは9月と案内されています。
ここで押さえておきたいのは、修正が届く順番のほうです。
正式版が公開されると、ユーザーの端末は数日のうちに新しい OS へ入れ替わっていきます。一方でこちらの修正版は、書き直して、ビルドして、審査を通ってから届きます。つまりユーザーの環境が先に変わり、こちらの対応は必ず後追いになります。この時間差を短くする方法は、事前に見ておくこと以外にありません。
もうひとつ、順番の制約があります。ベータ SDK でビルドしたアプリは TestFlight には配れても審査には出せない、という制限です。詳しくはベータ SDK でビルドしたアプリは、TestFlight には配れても審査には出せませんにまとめてあります。
ですので「iOS 27 で確認する → 直す → 出す」という流れのうち、最後の一手だけは正式版が出るまで閉じられません。今の時期にできるのは、確認と修正の準備までです。それでも、9月に慌てて原因を探すのとは、負荷がまるで違います。
予備機を1台、ベータ機として切り離す
まず、メイン端末には入れないでください。
ベータから元のバージョンへ戻すには、端末を消去して復元する必要があります。そして新しい iOS で取ったバックアップは、古い iOS へは戻せません。メイン端末をベータにすると、正式版が出るまで実質的に戻れない状態を抱えることになります。連絡や決済に使っている端末で背負う種類のリスクではありません。
予備機の準備は3手で終わります。
- 予備の iPhone に Apple ID でサインインし、バックアップを取る
- 設定 → 一般 → ソフトウェアアップデート → ベータ版アップデート から iOS 27 を選ぶ
- App Store から、自分の公開中のアプリを普通にインストールする
3 が要点です。開発ビルドでも、Rork Companion のプレビューでもなく、いまユーザーが手にしているのとまったく同じ配信中の版を入れます。
開発ビルドで代用すると、バンドルされているアセットも、有効になっている設定も、少しずつ違う状態を見ることになります。「手元では出なかったのにレビューには書かれている」の多くは、この差から来ます。私はここで一度失敗してから、必ずストア版を入れる手順に変えました。
引き出しの古い端末で構いません。SIM も不要です。Wi-Fi につながって、App Store にサインインできれば足ります。
触る順番は、崩れやすい順に決めておく
端末が用意できたら、闇雲に触らずに順番を決めます。上から順に、影響範囲が広くて修正コストが低いものを並べています。
| 順 | 見る場所 | 実際に起きやすいこと | 直す層 |
|---|---|---|---|
| 1 | 上端と下端のセーフエリア | 見出しがステータスバーに重なる/下端のボタンがホームインジケータに潜る | JS |
| 2 | 権限ダイアログ | 説明文の改行位置が変わる、発火のタイミングがずれる | ネイティブ設定 |
| 3 | 写真ピッカーと共有シート | 選択後にアプリへ戻ってこない、シートの高さが変わる | JS+ライブラリ |
| 4 | 通知の見え方 | タイトルが途中で切れる、画像が出ない | サーバー/JS |
| 5 | WebView と外部ブラウザ遷移 | 戻るジェスチャーと画面内スワイプの競合 | JS |
| 6 | 文字サイズを最大にした状態 | ボタンのラベルが2行になって枠から出る | JS |
上の3つで、体感としては見つかる不具合の大半を拾えます。逆に下へ行くほど、遭遇率は下がる代わりに、気づかれないまま放置されやすくなります。
順番を先に決めておく理由は、時間が限られているからというより、同じ観点で全アプリを横断できるようにするためです。壁紙アプリを複数本運用していると、1本ずつ丁寧に触る余裕はありません。「全部の1番を見る、次に全部の2番を見る」と横に流したほうが、判断がぶれずに済みます。
JS 側で先に潰しておけるもの
ベータ機を待たずに、今すぐ手を入れられる箇所が3つあります。いずれも Rork が生成したコードでよく見かける書き方の、少し先の話です。
下端は SafeAreaView ではなく insets で受ける
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import { View, Pressable, Text, StyleSheet } from 'react-native';
function Footer() {
const insets = useSafeAreaInsets();
return (
<View style={[styles.footer, { paddingBottom: insets.bottom + 12 }]}>
<Pressable style={styles.button}>
<Text style={styles.label}>保存</Text>
</Pressable>
</View>
);
}
export default function App() {
return (
<SafeAreaProvider>
<Footer />
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
footer: { paddingHorizontal: 16, paddingTop: 12 },
button: { minHeight: 44, alignItems: 'center', justifyContent: 'center' },
label: { fontSize: 17 },
});SafeAreaView は、端末から返る安全領域をそのまま padding として当てます。手軽ですが、下端に自分の余白をもう少し足したくなったときに、二重の余白か不足のどちらかへ振れます。
useSafeAreaInsets は同じ値を数値で返します。数値であれば insets.bottom + 12 のように自分の設計と足し合わせられます。OS 側でホームインジケータの扱いが変わっても、変わるのは insets.bottom の値だけで、レイアウトの式はそのまま生き残ります。ここを数値で持っておくかどうかが、毎年9月の作業量を決めています。
文字サイズの上限は、枠が固定の要素にだけ置く
<Text
style={{ fontSize: 15 }}
maxFontSizeMultiplier={1.4}
numberOfLines={1}
ellipsizeMode="tail"
>
設定を保存
</Text>allowFontScaling={false} で全体の拡大を止めてしまうのは避けたいところです。文字を大きくして読んでいる方の設定を、こちらの都合で無効化することになります。
その代わり、ボタンやタブのラベルのように枠の高さが決まっている要素だけ、倍率の上限を置きます。本文は拡大されるままにしておいて、崩れると操作できなくなる箇所だけを守る、という線引きです。
バージョン分岐は、プラットフォームごとに意味が違う
import { Platform } from 'react-native';
// iOS の Platform.Version は "27.0" のような文字列
// Android の Platform.Version は API レベルを表す数値(36 など)
const iosMajor =
Platform.OS === 'ios' ? parseInt(String(Platform.Version), 10) : 0;
export const isIOS27OrLater = iosMajor >= 27;Platform.Version は、iOS では OS のバージョン文字列、Android では API レベルの数値を返します。返ってくるものの意味そのものが違います。
Platform.Version >= 27 とだけ書いた分岐は、iOS では「iOS 27 以降」を、Android では「API レベル 27 以降」つまり Android 8.1 以降を指します。動いてしまうぶん、後から読んで気づきにくい書き方です。上のように Platform.OS で先に分けて、名前で意味を固定しておくほうが安全です。
なお、新しい OS が出たからといって、いきなり分岐を足す必要はありません。まずは分岐なしで触ってみて、実際に挙動が変わっていた箇所にだけ入れる。先回りで書いた分岐は、たいてい要らなかったと後で分かります。
見つけた後、どこまで直せて、どこから待つか
不具合を見つけたら、まず依存関係のずれを確認します。
npx expo install --check
npx expo-doctorexpo install --check は、インストール済みのパッケージが今の SDK と噛み合っているかを見て、ずれていれば正しいバージョンを提示します。expo-doctor は設定ファイルやネイティブ側の食い違いを含めて点検します。OS の変化を疑う前に、この2つで足元を固めておくと切り分けが速くなります。
そのうえで、修正の届け方は2つに分かれます。
- JS レイヤーの修正(レイアウト、文字サイズ、条件分岐、文言)は EAS Update で配れます。審査を待たずに手元から届けられる範囲です
- ネイティブ設定の修正(権限の説明文、Info.plist、ネイティブモジュールの追加)はビルドと審査が要ります。ここは正式版のリリース後になります
この線引きが分かっていると、9月にやることの順番が決まります。まず JS で直せるものを全部直して配り、ネイティブ側の修正は1本にまとめて審査へ出す。慌てて2回3回と審査に出すと、そのたびに数日を失います。
今日やることは1つだけ
引き出しから古い iPhone を1台選んで、バックアップを取っておく。それだけで今日は十分です。
ベータを入れるのはその後でよく、触るのは週末でも間に合います。準備さえ済ませておけば、9月に正式版が降ってきた日、レビュー欄を開く前に自分の目で確かめられます。あの数日の落ち着かなさが、少しだけ小さくなります。
なお、レイアウトが切れる症状そのものへの対処はRork で作ったアプリがノッチや下部インジケータで切れる時の直し方にまとめてあります。ベータ機で崩れを見つけたら、そちらも併せてご覧ください。
毎年この時期は同じことを繰り返していますが、それでも毎回どこかで新しい発見があります。お読みいただきありがとうございました。