9月9日、Appleは初の折りたたみiPhone「iPhone Duo」を発表しました。ユーザーの話題は価格($1999)と画面下カメラに集中していますが、私たち開発者に向けてAppleが出してきたのは、6本のTech Talks、HIGの新ページ、そして「月内に公開予定」のXcode 27.1 betaです。私は6本すべてを視聴し、ほぼすべてのアプリで必要になる対応をこの記事にまとめました。コードはSwiftとObjective-Cの両方で載せています。新APIのUIKit部分はどちらの言語からも使えますし、世界のlegacyコードベースの数は、信じたい量よりずっと多いからです。
先に正確性についてお断りしておきます。デバイスの発売は10月23日、Xcode 27.1は執筆時点でまだ「coming later this month」の状態です。以下のシグネチャはすべて公式Tech Talks由来ですが、SDKヘッダと照合するまでは「APIのおおよその形」であって、一字一句の保証ではありません。
新しいイディオムではなく、サイズの連続体#
Appleがほぼすべてのセッションで繰り返す最初のポイントはこれです。iPhone Duoは、専用のインターフェースを別途作るべき「新しいデバイスクラス」ではありません。あくまでiPhoneであり、利用可能なサイズの幅が広がっただけです。
具体的にはこうです。外側ディスプレイ(5.4インチ)は普通のiPhoneとして振る舞います:compact幅、おなじみの画面の向き。内側ディスプレイ(7.6インチ)はほぼiPadで、どの向きでもregular × regularです。そして、最も多くのアプリを壊すであろう仕様がこれです。内側ディスプレイは supportedInterfaceOrientationsを無視します。ポートレート固定のアプリでも、内側画面では回転します。これを禁止する手段はありません。
| 構成 | Horizontal | Vertical |
|---|---|---|
| 外側画面・縦向き | compact | regular |
| 外側画面・横向き | compact | compact |
| 内側画面・任意の向き | regular | regular |
ここから導かれる大原則:画面の向きやデバイスイディオム(userInterfaceIdiom、「iPadなら2カラム」)による分岐は、すべて不具合になります。分岐して良いのはsize classesだけです。
// SwiftUI
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
if hSizeClass == .regular {
TwoColumnLayout()
} else {
SingleColumnLayout()
}
}// Objective-C: the same question via trait collection
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
BOOL isWide = self.traitCollection.horizontalSizeClass ==
UIUserInterfaceSizeClassRegular;
[self applyLayoutForWideMode:isWide];
}コードベースに「もしiPhone Duoなら」という意図の分岐があったら、それは立ち止まるべきサインです。その分岐は「今この瞬間、どれだけのスペースがあるか」という問いに書き換えてください。
3つのSDKティア:再ビルドで何が得られるか#
Duo上でのアプリの挙動は、どのSDKでビルドされたかで決まります。ティアは3つです。
- iOS 27より前のSDK。 変更なしで動きますが「レターボックス」表示になります。閉じた状態ではコンテンツがステータスバーとカメラまで届かず、開いた状態では余白付きの従来サイズで表示されます。
- iOS 27 SDK。 内側ディスプレイでコンテンツがステータスバー領域まで広がります。
- iOS 27.1 SDK(Xcode 27.1)。 完全なedge-to-edge。システムバーが自動的に縦型に再配置され、reserved regionsとarrangementsのAPIが解放されます。
27.1での再ビルドは、あらゆる選択肢の中で最も安く、最も効果の大きい変更です。これなしには、それ以外の対応はそもそも始められません。
ハードコードされた前提を狩り出す#
折りたたみ対応のバグは、どれもコード内の何らかの固定された前提が原因です。今すぐgrepしておくべきものを3つ挙げます。
UIScreen.mainは死にました。 画面が2つあるデバイスでは「メインの画面」という概念自体が曖昧で、AppleはこのAPIがdeprecationに向かうと明言しています。置き換えはこうです。
// Before
let scale = UIScreen.main.scale
// After
let scale = traitCollection.displayScale
// If you really need the screen
let screen = view.window?.windowScene?.screen// Objective-C
CGFloat scale = self.traitCollection.displayScale;
UIScreen *screen = self.view.window.windowScene.screen;対称前提のインセット計算。 Duoのsafe areaは日常的に非対称です。縦型バーとDynamic Island(今回から縦型)は片側に寄っており、どちら側かはデバイスの姿勢と、アプリがSplit Viewのどちら半分にいるかで変わります。width - insets.left * 2のような定番の計算は、もう正しく動きません。
// ❌ assumes symmetry
let width = view.bounds.width - view.safeAreaInsets.left * 2
// ✅ treat each edge independently
let width = view.bounds.inset(by: view.safeAreaInsets).width// Objective-C
CGRect contentFrame = UIEdgeInsetsInsetRect(self.view.bounds,
self.view.safeAreaInsets);幅のハードコード。 「デバイス幅 → レイアウト」の対応表、width > 390のようなブレークポイント——ユーザーの手の動きひとつで幅が変わるデバイスでは、これらはすべて崩壊します。
縦型バー:無料で手に入る、ただしシステムコンテナ限定#
Duoの2つのディスプレイはどちらも通常のiPhoneより横長で背が低いため、システムコントロールは側面のエッジに移動します:ステータスバー、Dynamic Island(縦型になりました)、ツールバー、タブバー。アプリはこれを自動的に受け取れます——ただし条件が2つ。iOS 27.1 SDKでのビルドと、バーのシステムコンテナの使用です。
手作業でview階層に追加したカスタムのUIToolbar、UINavigationBar、UITabBarは、縦型レイアウトに一切参加しません。参加するのはUINavigationController、UITabBarController、SwiftUIの.toolbarだけです。自作のボタンパネルを持っているなら、それが最初の移行候補です。
新APIのうち特に有用なのはこれです。
// SwiftUI: pin the primary action, send the rest to overflow
.toolbar {
ToolbarItem(placement: .topBarPinnedTrailing) {
Button("Post", action: post)
}
ToolbarItem(placement: .cancellationAction) {
Button("Close", action: close)
}
}// Objective-C: the same semantics via UINavigationItem
self.navigationItem.pinnedTrailingGroup =
[UIBarButtonItemGroup fixedGroupWithRepresentativeItem:nil
items:@[postButton]];
// Secondary actions go to the vertical bar's overflow menu
self.navigationItem.additionalOverflowItems =
[UIDeferredMenuElement elementWithProvider:^(void (^completion)(NSArray *)) {
completion(@[shareAction, archiveAction]);
}];縦型バーは幅が固定なので、Appleはsymbol-onlyのボタンをはっきり推奨しています。テキストラベルやsegmented controlは収まらないため、そうした要素はnavigation barに残してください。特定の画面で縦型レイアウトを無効化することは可能です(SwiftUIなら.toolbarVerticalBehavior(.disabled)、UIKitならpreferredVerticalBarBehavior)が、それは意識的な決断であって、デフォルトにすべきものではありません。
Split Viewと複数ウィンドウ——ついにiPhoneに#
DuoはSplit Viewを備えた初のiPhoneです。2つのアプリが50/50で並び、しかもすべてのアプリがopt-inなしで対象になります。あなたのアプリはいつでも画面の半分に置かれ得る——固定幅がもう通用しない、もうひとつの理由です。
2つ目の新要素は、同一アプリの複数シーン。これもiPhoneでは初めてです。すでにiPadでマルチウィンドウをサポートしているなら(Info.plistのUIApplicationSupportsMultipleScenes、AppDelegate中心のライフサイクルからシーンへの移行)、作業の大半は済んでいます。Duo専用のInfo.plistキーはありません。
ただし、iPadにはなかった挙動があります。新しいウィンドウは内側ディスプレイでしか作成されません。閉じた状態のデバイスでは新規シーンのリクエストが失敗するため、これをハンドリングする必要があります。
// Objective-C: a scene activation request can now fail
UISceneSessionActivationRequest *request =
[UISceneSessionActivationRequest requestWithRole:UIWindowSceneSessionRoleApplication];
[UIApplication.sharedApplication activateSceneSessionForRequest:request
errorHandler:^(NSError *error) {
// Device is closed — new windows are unavailable
[self showSingleWindowFallback];
}];SwiftUIでは、Appleは標準のUIWindowSceneActivationアフォーダンスを推奨しています。ウィンドウ作成が不可能なときは自動で非表示になり、手動ハンドリングの半分を肩代わりしてくれます。
私のチェックリスト#
これを机上の空論で終わらせないために、Xcode 27.1が届いたら自分のアプリ(まずはMeteoHealth)でやることを書いておきます。
- iOS 27.1 SDKで再ビルドし、Duoシミュレータで開く:Xcode 27.1のDevice Hubは仮想デバイスの開閉と折り曲げを操作できます。姿勢依存のバグはコードを読むだけでは見つかりません。
- 前提のgrep:
UIScreen.main、向きとイディオムによる分岐、対称前提のインセット、幅のハードコード。 - バーの確認:私はSwiftUIとシステムコンテナなので縦型レイアウトは無料で来るはずですが、ボタンの並び順とoverflowの優先度は目視で確認します。
- Split Viewの両シナリオ(アプリが左と右)、両方の半分を試す。
6本のセッションを通しての正直な結論:アプリがすでに自由にリサイズできるなら——size classesを使い、エッジごとのsafe areaを尊重し、システムコンテナの中で生きているなら——iPhone Duoでほとんど何も変わりません。痛みを引き受けるのは、前提をハードコードしたアプリです。その猶予は10月23日に切れます。
シリーズ次回は、ヒンジのAPI——onHingeChangeとUIHingeInteraction——と、Appleが折り曲げ角度からlayoutを組むことを禁じる理由を解説します。



