Apple announced iPhone Duo on September 9 — the first foldable iPhone, a 5.4-inch outer display and a 7.6-inch inner one joined by a hinge you can leave at any angle. It ships October 23.
When I went looking for developer documentation, most of it wasn't there. The Human Interface Guidelines page landed a day after the announcement. Preparing your app for iPhone Duo and Xcode 27.1 are, as I write this, still listed as coming.
What did exist was six Tech Talk videos. So I watched them — or rather, I read them, which turned out to be the more interesting problem.
Reading a video
Apple's tech talk pages have a transcript panel, but it's populated by JavaScript. Fetch the HTML and you get an empty <section id="transcript-content">.
The video itself, though, is served over HLS. And the HLS master playlist declares a subtitle rendition:
#EXT-X-MEDIA:TYPE=SUBTITLES,...,URI="subtitles/en/prog_index.m3u8"
That's a WebVTT track. Fetch the master playlist, find the subtitle rendition, walk its segment list, strip the cue timings, and you have the exact text of the talk — no video download, no speech-to-text, no transcription errors. Six talks in about forty seconds.
This matters more than it sounds. I was about to write API names into a reference other people would rely on. A summarizer's paraphrase of AVCaptureDeviceDirectionCoordinator would have been worse than useless. Exact text or nothing.
The one idea
Here's the framing that makes everything else fall out:
iPhone Duo is not a new idiom. It's a wider continuum of sizes.
Every bug in a foldable port comes from an app asserting something fixed — this idiom, this orientation, this screen, this width, this symmetric inset. Every fix replaces the assertion with a question about the space available right now.
If you catch yourself writing a branch that means "if iPhone Duo", stop. Write it against size class or available space instead, and it'll also be right on iPad, in Split View, under iPhone Mirroring, and on whatever ships next.
The whole size-class model is two rows:
| Display | Horizontal | Vertical |
|---|---|---|
| Outer | compact | regular (portrait) / compact (landscape) |
| Inner | regular | regular |
That's it. You don't design a layout per pose.
Four things that will bite an existing app
1. Your orientation lock stops holding
The inner display doesn't honor supported interface orientations. It rotates regardless of what your app declared.
If any layout decision keys off interfaceOrientation, it's now a bug. Same for userInterfaceIdiom. Branch on size class.
UIRequiresFullScreen behaves similarly — still honored, but your app still resizes when the device opens and closes.
2. Safe areas are asymmetric
This is the one I'd bet money on breaking apps quietly.
Because the outer display is wider and shorter than a normal iPhone screen, the system moves toolbars, the tab bar and navigation controls to the side — preserving vertical space and putting controls nearer your thumb. Which means left and right safe area insets now differ, and which side depends on the pose and on which half of a Split View you occupy.
So this:
// Correct on every current iPhone. Wrong here.
let width = view.bounds.width - view.safeAreaInsets.left * 2
...needs to become this:
let width = view.bounds.inset(by: view.safeAreaInsets).width
It doesn't crash. It just lays out wrong, on one side, in some poses. Grep your codebase for * 2 near an inset.
3. UIScreen.main is ambiguous
On a two-display device, which one is "main"? It's slated for deprecation. Prefer no screen reference at all — use the environment, the trait collection, or the scene's bounds:
// Before
let scale = UIScreen.main.scale
// After
let scale = traitCollection.displayScale
If you genuinely need the screen, reach it through the window scene rather than the global.
4. "Front camera" no longer means "pointing at you"
iPhone Duo has two front cameras — one on the outer display, one under the inner display. Both report position .front.
But the displays can face opposite directions. You can be looking at the inner display while streaming the outer front camera, which is pointing away from you. Close the device and that same camera swings around to face you. Flip the device while open and a rear camera becomes the selfie camera.
AVCaptureDevice.position describes where a camera sits on the hardware. It no longer tells you where it points relative to the person looking at your UI.
The fix is AVCaptureDeviceDirectionCoordinator (in AVKit), which reports facing relative to a given view — so it knows which display your UI is on. Build it from your view, the device types to monitor, and a change handler.
One nice detail: because it's tied to a view, it's main-actor isolated, so instead of handing you an AVCaptureDevice it gives you an AVCaptureDeviceDescriptor — a sendable stand-in you pass to your camera actor and rehydrate there.
There's also a virtual front camera that switches between the two physical ones automatically. Convenient, but it exposes only what both share: 1080p, 60fps, and no depth.
What you get for free
A lot, if you use system components:
-
NavigationSplitView/UISplitViewController— collapse to a single stack when closed, tile or overlay when open. -
TabView/UITabBarController— adapt across poses; you can opt into a sidebar on the inner display. - Sheets, alerts, menus, popovers — reposition themselves around the fold.
That last point deserves emphasis. System components carry fold avoidance: when the device is partially folded, the inner display curves through the middle, and controls landing in that curve get genuinely harder to tap. The system nudges interactive elements aside automatically. You inherit that by using standard components — and you owe yourself an implementation if you don't.
Scrollable content is exempt. It's fine for a feed to pass under the curve.
The genuinely new parts
Reserved regions. Three of them: the outer camera (always present, expands into the Dynamic Island for Live Activities), the inner camera (only while active), and the folding region (only while folded). You query them from a GeometryProxy in SwiftUI or from UIView in UIKit, as either .division regions (the fold, which splits the display) or .occlusion regions (cameras, which cover part of it).
Regions can be active or inactive — the fold has zero width when flat — and you can opt into seeing inactive ones. That's useful for stable decisions, like always preferring an even column count on a device that has a fold, so your grid doesn't reshuffle every time someone bends it.
Arrangement views. A layout container between navigation containers and content containers, arranging exactly two views by rule. Two styles: split (divides the area — for main/detail, where neither view may be obscured) and overlay (stacks them, moving side by side when folded — for foreground over background, where partial obscuring is fine).
The mapping is pleasantly direct: if you'd reach for an HStack or VStack, you want split. If you'd reach for a ZStack, you want overlay.
Vertical bars. Toolbars and tab bars on the side get their own set of considerations — fixed width and flexible height means symbol-only items work far better than text, so give every item both a title and an image and let the system choose. Overflow happens sooner, so set visibility priorities.
If you're not on Swift
Worth knowing before you go looking:
Flutter's MediaQuery.displayFeatures is documented as populated only on Android. It returns an empty list on iPhone Duo no matter how folded the device is. The dual_screen package is Android-only in practice and hasn't been published in about three years.
React Native has no fold API at all.
Neither is fatal, because the two things most likely to break — resizing and asymmetric safe areas — are solvable in pure Dart/JS today (LayoutBuilder and MediaQuery.paddingOf; useWindowDimensions and useSafeAreaInsets). It's the fold itself and the hinge that need a platform channel or native module.
I filed a proposal on flutter/flutter about mapping the iOS reserved regions onto DisplayFeature, including a genuine API gap: iOS can express "this device has a fold, currently flat" and Flutter's model can't — an inactive feature simply isn't in the list, so a flat iPhone Duo and an iPhone 18 look identical.
The caveat I'd rather state than bury
I could not verify these API signatures against the SDK. Xcode 27.1 hadn't shipped when I wrote this — my machine has iOS 26.5, where none of these symbols exist. Everything here comes from Apple's tech talks and the HIG.
So treat the names as the shape of the API and confirm against the headers before you rely on them. I'd rather say that plainly than have you paste something that doesn't compile.
The write-up
I packaged all of this as nine structured skills — so Claude Code, Cursor and similar tools can use it directly for a device with no training data behind it — but it reads fine as a straight reference:
https://github.com/mirzaaghazadeh/iphone-duo-skills
Covering fold-aware layout, vertical bars, hinge and scenes, the cameras, design review, games, and the Flutter and React Native ports. The transcript-fetching script is in there too, under scripts/.
If you find something I got wrong — especially once Xcode 27.1 lands and the signatures can actually be checked — open an issue. I'd genuinely like to know.
Top comments (0)