The Duo's two displays map cleanly onto SwiftUI's adaptive APIs: the outer display is compact × regular, the inner display is regular × regular. If your app already respects size classes, you're most of the way there. These snippets cover the rest.
The cheapest pose check you can write. Outer display reports compact width; inner display reports regular width (iPad idiom).
struct ContentView: View {
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
if hSizeClass == .regular {
DuoOpenView() // inner display — two-column layout
} else {
DuoClosedView() // outer display — single column
}
}
}
Opening or closing the Duo resizes your window while the app runs. Anything that snapshots the size class once will go stale mid-fold.
// ❌ Don't — goes stale when the user folds the device
@State private var isPad = UIDevice.current.userInterfaceIdiom == .pad
// ✅ Do — re-reads the live environment value every render
@Environment(\.horizontalSizeClass) private var hSizeClass
@Environment(\.verticalSizeClass) private var vSizeClass
NavigationSplitView collapses to a stack on compact width automatically — two columns open, one column closed, zero pose-specific code.
NavigationSplitView {
SidebarView()
} detail: {
DetailView()
}
// Open (regular width): side-by-side. Closed (compact): stacked. Done.
The inner display folds at its horizontal center (~334 pt from either edge). Keep primary tap targets away from the middle strip — top and bottom are safe.
VStack(spacing: 0) {
ToolbarView() // top — safe
Spacer()
MainContent()
Spacer()
TabBarView() // bottom — safe
}
// Avoid: a lone button centered exactly on the fold line.
// If content must span the fold, make it non-interactive (dividers, backgrounds).
When you need points instead of size classes: outer is 466 pt wide, inner is 669 pt. A 550 pt threshold separates them with room to spare.
GeometryReader { geo in
AdaptiveView(isOpen: geo.size.width > 550)
}
// 466 pt (closed) < 550 < 669 pt (open)
Pin previews to the exact Duo geometry so canvas and simulator agree. (The beta's Previews canvas also has a Display group for this.)
#Preview("Duo — Closed", traits: .fixedLayout(width: 466, height: 678)) {
ContentView()
}
#Preview("Duo — Open", traits: .fixedLayout(width: 669, height: 951)) {
ContentView()
}
When the pose flips, SwiftUI re-renders with the new size class. Wrap the switch in an animation and state survives the fold instead of snapping.
struct ContentView: View {
@Environment(\.horizontalSizeClass) private var hSizeClass
var body: some View {
Group {
if hSizeClass == .regular {
DuoOpenView()
} else {
DuoClosedView()
}
}
.animation(.smooth, value: hSizeClass)
}
}
Exact points, pixels and scale factors: device specs. Convert them: pt/px converter. Everything to verify before launch: the adaptation checklist. Frame a screenshot for your pitch deck: the mockup generator.