Apple’s six iPhone Duo Tech Talks contain a lot of API surface, and the natural reaction is to start adopting the new things. That is the wrong order. Most of what makes a SwiftUI app look wrong on iPhone Duo is old code making assumptions that were safe until 9 September 2026 — and until those are gone, the new APIs are built on sand.
Here is the order that actually works.
First: understand what you already have
Your existing app runs on iPhone Duo today, without changes. Apple defined three tiers of behaviour, and which one you get depends entirely on the SDK you build against:
| Built against | Inner display behaviour |
|---|---|
| Not the iOS 27 SDK | Familiar size and aspect ratio |
| iOS 27 SDK | Extends left of the status bar area |
| iOS 27.1 SDK | Extends to the screen edge; bar buttons lay out vertically |
Tier one is not broken. It is an app that does not use the display. That is a competitive problem, not a support ticket — which matters when you are deciding how much time to spend.
The catch is that moving to tier three makes you responsible for the geometry you just claimed: the hinge, the asymmetric safe areas, the vertical bars. Rebuild against 27.1 and stop there and you can genuinely end up looking worse than before.
Second: delete the assumptions
Four assumptions were true on every iPhone before this one.
“The screen is about 400 points wide.” The inner display is 626 × 890 points. Not 20% wider — over 50%.
// Before
content.frame(width: 390)
// After
content
.frame(maxWidth: .infinity)
.frame(maxWidth: 700) // cap for readability, not for a device
“Portrait means tall.” The inner display does not honor supported interface orientations. If you locked to portrait, you are still going to be laid out in landscape.
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
if horizontalSizeClass == .regular { WideLayout() } else { NarrowLayout() }
}
The inner display is regular in both dimensions, and Apple notes it leaves room for sidebars. The outer display is compact-width, regular-height. If you have an iPad layout, the inner display usually wants it.
“Left and right insets match.” They do not, on this device. This is the one that produces subtly off-centre layouts that nobody can quite explain:
// Wrong
let width = bounds.width - insets.left * 2
// Right
let width = bounds.inset(by: insets).width
“Idiom tells me the screen size.” iPhone Duo reports .phone with a regular-width display. Every if idiom == .pad gate now hides your good layout on the device that most needs it.
Fix these four and most apps are already respectable. Everything below is upside.
Third: adopt arrangements
ArrangementView is the API worth real investment. It takes a primary and secondary view and places them across every pose, using size classes, aspect ratio, and the active division regions as input.
var body: some View {
NavigationStack {
ArrangementView {
PlayerView()
} secondary: {
UpNextView()
}
.arrangementViewStyle(.split)
}
}
Split splits horizontally when the view is wider than tall and vertically when taller. Pin the axis when only one makes sense:
.arrangementViewStyle(.split.axes(.horizontal))
Overlay is for a foreground/background relationship rather than a peer one. Its secondary view should respond to its own depth:
struct UpNextView: View {
@Environment(\.overlayArrangementZIndex) private var zIndex: Int
var minimization: UpNextMinimization {
zIndex > 0 ? .collapsed : .expanded
}
}
Apple describes a third pattern, displacement — moving elements as space changes — and warns against it for continuously scrolling content. Shifting a feed under the reader’s thumb is disorienting. One column that changes width beats a column that moves.
Why this beats writing it yourself
The obvious alternative is a GeometryReader with your own breakpoints. It works, until you count the states: two displays, four-plus poses, arbitrary Split View widths, multiple scenes, and a hinge that appears and disappears. ArrangementView handles that matrix, and will handle whatever ships next.
Fourth: handle the fold
ReservedRegion reports where the display is interrupted. Two kinds, and the difference matters:
GeometryReader { proxy in
let division = proxy.reservedRegions(kind: .division) // the hinge
let occlusion = proxy.reservedRegions(kind: .occlusion) // the inner camera
}
A division region divides an area in two. An occlusion region covers part of one. The hinge is the first; the under-display FaceTime camera is the second.
The behavioural detail that catches people: a division region is active only while the device is folded, and has zero width when flat. Your layout must handle regions appearing and disappearing, not just moving. To lay out ahead of a fold and avoid a jump:
let regions = proxy.reservedRegions(kind: .division, options: .includeInactive)
Use arrangements where they fit and query regions directly only when you need finer control — a canvas, a game board, a photo editor.
Fifth: the vertical bar
Under the 27.1 SDK, navigation, toolbar, and tab bar controls share a single vertical region on the inner display in landscape. Standard containers do this for free. What needs your attention is content.
A column has far less room for a label than a row. Apple’s guidance is to prefer symbol-only items and minimise text-only and custom dual-content views. Order matters too — navigation at the top, prominent actions at the bottom:
.toolbar {
ToolbarItem(placement: .cancellationAction) { CloseButton() }
ToolbarItem(placement: .topBarPinnedTrailing) { ShareButton() }
}
Say which of your actions must survive:
ToolbarItem { ComposeButton() }.visibilityPriority(.high)
Reserve .high for the few actions users reach for constantly — marking everything high is the same as marking nothing.
Some items read badly rotated, and axisBehavior handles those:
.axisBehavior(.horizontalOnly)
You can opt a screen out entirely. Apple names calculators and minimal sheets as good candidates:
.toolbarVerticalBehavior(.disabled)
Do that per screen, deliberately. Doing it globally to avoid the work will look exactly like what it is.
Sixth: the finishing touches
Let background art bleed and keep interactive content inside the safe area:
BackgroundArtwork().ignoresSafeArea()
Follow the screen’s rounded corners without hard-coding a radius:
ConcentricRectangle()
.fill(.green)
.padding(8.0)
.ignoresSafeArea()
And one modifier that is close to free value:
TabView { … }.defaultTabBarPlacement(.sidebar)
A five-item tab bar stretched across 626 points looks lost. A sidebar looks designed.
On transitions
A question worth addressing directly, since it is what people search for: there is no special “fold transition” API to adopt. The fold is not an animation you drive — it is a size and trait change, and SwiftUI already animates those.
What makes a fold feel good is that your layout is a pure function of the size it is given. When that holds, SwiftUI interpolates between the two states on its own and the result looks intentional. When it does not — when layout is computed imperatively in a callback, or cached, or driven by hinge angle — you get a jump or a jitter that no animation modifier will fix.
Which is why hinge angle is explicitly not for layout. Apple’s line is that hinge data is for interactions and effects only:
GuitarView(pitchBend: pitchBend)
.onHingeChange { _, context in
if let hinge = context.hinge, hinge.status == .partiallyOpen {
pitchBend = calculatePitchBend(angle: hinge.angle)
} else {
pitchBend = 0
}
}
Angle is continuous and noisy; layout driven by it shivers. Division regions are discrete and stable, which is what layout wants. Use angle for what it is uniquely good at: a continuous input to an effect.
Verifying
Xcode 27.1 ships an iPhone Duo simulator in Device Hub with on-screen controls to open, close, rotate, and fold. Walk every pose, and pay attention to the transitions — most bugs surface while folding rather than after.
Snapshot tests at 626 × 890 and 466 × 678 catch regressions cheaply once the layout is right. They will not catch division regions, so keep a manual pass for those.
The order, condensed
- Rebuild against the iOS 27.1 SDK
- Run the App Resizability skill, review every change
- Delete the four assumptions: fixed widths, orientation, symmetric insets, idiom
- Adopt
ArrangementViewwhere you have a primary/secondary relationship - Query reserved regions only where arrangements are not enough
- Audit toolbar content for the vertical bar
- Concentric corners, sidebar tab bar, background bleed
Steps 1 and 3 are most of the visible improvement. The rest is what separates an app that works from one that looks like it was designed for this device.