Copy-paste ready · Xcode 27.1 beta

SwiftUI snippets for the folding iPhone Duo

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.

1. Detect open vs closed with size classes

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
        }
    }
}

2. Never cache the pose — the fold is live

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

3. Master-detail that adapts for free

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.

4. Keep controls off the hinge

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).

5. Width threshold as a backup check

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)

6. Preview both poses in Xcode 27.1

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()
}

7. Animate the fold transition

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)
    }
}

Keep going

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.