- Published on
- 8 min read Intermediate
> iPhone Duo Split View: Is Your Window Left or Right?
On iPhone Duo's inner display, Split View puts two windows side by side with the fold running between them. From inside your app, each window is just a rectangle. In my testing it was 469 by 669 points on either side, and nothing in SwiftUI says whether you're the one on the left or the one on the right.
I wanted to know whether the new reservedRegions(kind:options:layoutDirectionBehavior:) API could answer that, so I built a small scratch app with Xcode 27.1 beta and ran it on the iPhone Duo simulator in every configuration I could get it into. Each window displayed and logged what it could see about its own geometry. The short answer is yes, the fold tells you, but not in the way I first assumed.
The Obvious Approaches Don't Work
My first idea was to ask UIKit where the window sits on the display by converting its bounds into the screen's coordinate space. For the right-hand window in Split View, that conversion returned an origin of zero, the same as the left-hand window. The coordinate space you get is relative to your scene, not to the physical panel, so it can't tell the two halves apart.
The second idea was the toolbarVerticalEdge environment value. In Split View it did flip, reporting leading for the left window and trailing for the right one, which matches the HIG's rule that each app keeps its controls on its outer edge. The trouble is that a single full-screen window on the inner display also reports trailing, and so does the outer display. Trailing can mean "right half" or "the only window", so it isn't enough on its own.
What the Fold Region Looks Like
The fold is a reserved region of kind .division, and the system reports it to every view it intersects. Here's what the app measured with the hinge at 127 degrees, set with the hinge slider in Device Hub, where the fold is active:
| Configuration | Window size | Fold region frame |
|---|---|---|
| Outer display (closed) | 466 × 678 | none |
| Inner display, one window | 951 × 669 | x: 455.5, width: 40 |
| Split View, left window | 469 × 669 | x: 455.5, width: 13.5 |
| Split View, right window | 469 × 669 | x: 0, width: 13.5 |
The full-screen row explains the rest. The fold itself came back as a line in the exact center of the 951-point display, and the 40-point frame is that line plus the region's margins, 20 points on each side. In Split View, the two windows leave a 13-point gap for the divider between them, so each window receives 20 minus 6.5, or 13.5 points of the strip.
That's the detail that matters. A reserved region's frame is clipped to your view, so in Split View the fold shows up as a sliver pressed against the edge you share with the other window. Which edge it touches tells you which side you're on. A strip in the middle of your bounds means you span the fold, and no strip at all means there's no fold, as on the outer display.
Classifying the Window
That rule is easiest to express as a plain function over two rectangles, which also makes it easy to test against measured values:
import CoreGraphics
enum FoldSide {
case leftOfFold, rightOfFold, aboveFold, belowFold, acrossFold, noFold
init(foldFrame: CGRect?, in bounds: CGRect, tolerance: CGFloat = 0.5) {
guard let fold = foldFrame else {
self = .noFold
return
}
if fold.height >= fold.width {
let touchesLeft = fold.minX <= bounds.minX + tolerance
let touchesRight = fold.maxX >= bounds.maxX - tolerance
switch (touchesLeft, touchesRight) {
case (false, true): self = .leftOfFold
case (true, false): self = .rightOfFold
default: self = .acrossFold
}
} else {
let touchesTop = fold.minY <= bounds.minY + tolerance
let touchesBottom = fold.maxY >= bounds.maxY - tolerance
switch (touchesTop, touchesBottom) {
case (false, true): self = .aboveFold
case (true, false): self = .belowFold
default: self = .acrossFold
}
}
}
}
A vertical strip that touches only your right edge means the fold is to your right, so you're the left window. Touching only your left edge makes you the right window. Touching neither, or both, means the fold runs through you. The horizontal branch covers a fold that runs across the display rather than down it.
Feeding it from SwiftUI takes one query on a GeometryProxy:
import SwiftUI
extension GeometryProxy {
func foldSide(_ layoutDirectionBehavior: LayoutDirectionBehavior = .fixed) -> FoldSide {
let fold = reservedRegions(
kind: .division,
options: .includeInactive,
layoutDirectionBehavior: layoutDirectionBehavior
).first
return FoldSide(foldFrame: fold?.frame, in: CGRect(origin: .zero, size: size))
}
}
Two choices in there are deliberate. Apple's Preparing your app for iPhone Duo guide describes the fold as inactive when the device is fully open, so the query asks for inactive regions too, and the answer shouldn't change when someone flattens the device. The layout direction defaults to .fixed, which reports physical positions. The fold doesn't move when someone switches to Arabic or Hebrew, and "is this the left window" is a physical question.
Using the Answer in a Layout
If the answer drives leading and trailing alignment, pass .mirrors instead. Mirrored frames flip with a right-to-left layout, so "left of the fold" reads as "the fold is on my trailing side", and alignments built on that stay correct in every language. Here, a tool palette sits on the window's outer edge, away from the seam:
struct SketchWindow: View {
var body: some View {
GeometryReader { proxy in
let side = proxy.foldSide(.mirrors)
SketchCanvas()
.overlay(alignment: side == .leftOfFold ? .bottomLeading : .bottomTrailing) {
ToolPalette()
.padding()
}
}
}
}
The view you query has to reach the edge your window shares with its neighbor, since the region is clipped to that view. A container that fills the window, like this GeometryReader, does that.
Testing It in the Simulator
The iPhone Duo simulator ships with Xcode 27.1 beta, and the only way I found to change its pose was the hinge slider in Device Hub. Neither simctl nor devicectl can set the hinge, but devicectl can read it, which is handy for confirming the state you're testing:
xcrun devicectl device motion hinge-angle --device <simulator-udid>
xcrun simctl io <simulator-udid> enumerate
xcrun simctl io <simulator-udid> screenshot --display=<inner-display-uuid> inner.png
The simulator exposes the outer and inner panels as separate displays. The enumerate command lists both with their sizes, and a plain screenshot captures the outer panel, so pass the inner display's UUID to capture that one instead.
Split View itself had to be set up by hand with the system's multitasking controls. Calling openWindow from SwiftUI did open a second window, but it took over the whole inner display rather than landing beside the first one. I also had each window write its measurements to a JSON file in its Documents directory, which xcrun simctl get_app_container then made easy to read without squinting at screenshots.
Limits Worth Knowing
All of these measurements came from the simulator with the hinge at 127 degrees, where the fold is active. I didn't capture the fully open pose, so the .includeInactive option is there on the strength of Apple's documentation rather than a measurement.
The approach also depends on your window touching the fold. The even split I tested met at the seam, but a window that doesn't intersect the fold receives no division region and classifies as noFold, the same as the outer display. If you need to tell those apart, check the hinge status with onHingeChange, since the outer display is the closed pose. And I couldn't get the simulator's inner display into its tall orientation, so the horizontal branch is written from the same logic rather than tested.
Sample Project
Want to see this code in action? Check out the complete sample project on GitHub:
The repository includes a working Xcode project with all the examples from this article, plus unit tests you can run to verify the behavior.
Wrapping Up
On iPhone Duo, the fold is the one piece of shared context both windows can see. Query the .division region, remember that its frame is clipped to your view, and check which edge the sliver touches. Use .fixed when you need a physical answer and .mirrors when the answer feeds layout, and you'll know which half of the device your window is on. For more on reserved regions in general, see Keeping Content Clear of the Fold.
// Continue_Learning
ReservedRegion: Keep Content Clear of the iPhone Duo Fold
iPhone Duo reserves parts of its displays for the fold and the front cameras. Learn which regions exist, when system views handle them for you, and how to query ReservedRegion in SwiftUI and UIKit to move custom content out of the way.
Building Fold-Aware Layouts with ArrangementView in SwiftUI
ArrangementView is a new iOS 27.1 container that lays out a primary and secondary view side by side or layered, and rearranges them around the iPhone Duo fold. Here's how the split and overlay styles work, how to size them, and when not to use one.
How to Get Your App Ready for iPhone Duo
iPhone Duo ships on October 23 with a folding inner display, a compact outer display, and toolbars that move to the side. Here's what to check in your app first, and where the new iOS 27.1 APIs fit in.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.