The SwiftUI APIs for the foldable iPhone Duo, one runnable example at a time.
iOS 27.1 adds a set of SwiftUI APIs for the iPhone Duo: reading the hinge, finding the fold and the camera, arranging views side by side, and controlling the new vertical bar. This project demonstrates each of them in a small example that you can run, read, and copy.
- Each example lives in one file in
iPhoneDuoByExamples/Examples. - Each file begins with a doc comment that explains the API.
// 👇 The API:comments mark the lines that matter.
- Getting Started
- Examples
- API Cheat Sheet
- Good to Know
- Project Structure
- See Also
- Resources
- Contributing
- Author
- License
Requirements: Xcode 27.1 or later, and the iPhone Duo simulator (iOS 27.1) or an iPhone Duo.
git clone https://github.com/artemnovichkov/iPhone-Duo-by-Examples.git
cd iPhone-Duo-by-Examples
open iPhoneDuoByExamples.xcodeprojSelect the iPhone Duo run destination and press ⌘R. Fold and unfold the device in Simulator to watch the examples react.
Tip
To open an example directly, pass its name as a launch argument. For example, -example hingeAngle or -example avoidDivision. The names are the cases of the Example enum.
|
Reads the live angle with |
Records every hinge update and plots it with Swift Charts. Uses |
|
Queries |
A two-page reader that places one page on each side of the division region. When there's no division, it falls back to one page. |
|
Lays out a media player around the active division region: artwork above and controls below in tabletop pose, side by side in book pose. Apple recommends reserved regions, not the hinge angle, for layout. |
Uses the inactive division region to give a grid an even number of columns, with a gutter right over the fold. |
|
|
|
|
A mail-style demo with a tab bar and toolbar items in the vertical bar. Covers |
|
|
Adapts a layout to the space the app has, using size classes and |
@State private var hinge: DeviceHinge?
var body: some View {
content
.onHingeChange { oldContext, newContext in
// `hinge` is nil when the view isn't in a hierarchy that provides hinge updates.
hinge = newContext.hinge
}
}
// hinge.angle -> Angle (180° when the device is flat)
// hinge.status -> .closed, .partiallyOpen, .fullyOpenImportant
Use the hinge for interactions and effects. For layout, use reserved regions and arrangements.
onHingeChange(isEnabled:_:) runs its action with the initial state and again on every change. To pause updates without removing the modifier, pass isEnabled: false.
GeometryReader { proxy in
let folds = proxy.reservedRegions(kind: .division)
let cutouts = proxy.reservedRegions(kind: .occlusion, options: [.includeInactive])
ForEach(folds) { region in
// region.frame — in the proxy's coordinate space, margins included
// region.margins — room to keep clear around the reserved rect
// region.isActive — whether it currently affects your layout
}
}The division region is active only while the device is partially folded. Lay out around active regions. Use inactive ones for high-level decisions, such as an even number of grid columns.
ArrangementView {
Sidebar()
.splitArrangementLayoutRatio(0.4)
} secondary: {
Detail()
}
.arrangementViewStyle(.split.axes([.horizontal, .vertical]))ArrangementView {
FloatingPanel() // floats on top
.overlayArrangementEdge(.leading) // its edge when the layout goes side by side
} secondary: {
Map() // fills the container
}
.arrangementViewStyle(.overlay)
struct PanelContent: View { // a subview of FloatingPanel
@Environment(\.overlayArrangementZIndex) private var zIndex
// zIndex > 0 -> floating over the map: collapse
}Other modifiers: splitArrangementLayoutRatio(minHorizontal:idealHorizontal:…), splitArrangementLayoutSize(minWidth:…), and splitArrangementFixedLayoutSize(horizontal:vertical:). To write your own style, conform to the ArrangementViewStyle protocol.
TabView { … }
.toolbarVerticalBehavior(.disabled) // opt out, e.g. for a video player
NavigationStack { … }
.toolbar {
ToolbarItem(placement: .topBarPinnedTrailing) { … } // never overflows
ToolbarItem(placement: .primaryAction) { … }
.axisBehavior(.verticalPreferred) // or .horizontalOnly
.visibilityPriority(.high)
ToolbarOverflowMenu { … } // straight into the overflow menu
}
.toolbarVerticalCompressionBehavior(.prefersTabBar) // or .prefersToolbarItems
@Environment(\.toolbarVerticalEdge) private var edge // .leading, .trailing, or nilcontent
.contentMargins(for: .container)
GeometryReader { proxy in
let margins = proxy.contentMargins(for: .container)
}These are observations from the iPhone Duo simulator on iOS 27.1. They aren't documented guarantees.
- The division region is active only when the device is partially folded. When flat, it's inactive. Its frame is the same in both states: 40 pt wide, with 20 pt margins on each side of a zero-width fold line.
- Reserved regions arrive after the first layout pass. Read them in the
GeometryReaderbody so the view updates when they arrive. Don't cache them. - Most regions are inactive by default. On a fully open device, the fold is reported as an inactive division region, and the camera is an inactive occlusion region. To see them, pass
.includeInactive. - The status bar area of the vertical bar is an active occlusion region.
- When folded, the outer display has no reserved regions at all, not even inactive ones. The outer display is compact width and regular height, and it also has a vertical bar on the trailing edge.
- In the
.overlaystyle, the primary view floats in the top leading corner, and the secondary view fills the space behind it. When the device is partially folded, the two go side by side. - Read
overlayArrangementZIndexfrom a subview of the primary or secondary content. The root view of the content always reads0. - Arrangements follow the fold. In book pose,
.splitplaces its divider on the fold, even if that overridessplitArrangementLayoutRatio. - In the
.splitstyle, the secondary view can disappear. If both views don't fit along an allowed axis, only the primary view is shown. For example, this happens with.split.axes(.vertical)on a wide screen. splitArrangementAxiswasnilin every configuration tested in the simulator.- Don't depend on the timing of hinge angle updates. Their rate and precision are system policy. If you only need the posture, use
status. - The vertical bar configuration flows up to the window or the nearest presentation. That's why the Vertical Toolbar demo is presented full screen.
iPhoneDuoByExamples
├── App # App entry point
├── Catalog # Example list, info sheet, metadata
├── Components # Shared views and DeviceHinge helpers
├── Examples # One file per example ← start here
└── Resources # Asset catalog
The Xcode project uses Xcode's JSON project format (project.xcproj). It's readable and easy to edit by hand; each source file is listed there with its target membership.
Apps and games built for iPhone Duo, where folding the device is the whole point.
| Accorduon An accordion where the hinge is the bellows. Fold and unfold to play. |
|
| SandValley Pour sand on the screen and fold the device to make it slide into the valley. |
|
| Duogami An origami workshop. Fold the phone to fold the paper, one crease at a time. |
|
| ClawKit A clay claw machine. The cabinet stands above the fold, the controls sit below it. |
|
| DuoBird Flappy Bird played with the hinge. Snap the device open to flap through the pipes. |
|
| DuoCut The fold is a blade. Slide shapes under it and cut them in half. |
- Get Ready for iPhone Duo
- Preparing your app for iPhone Duo
- Designing for iPhone Duo in the Human Interface Guidelines
- Tech Talks:
Found a new API, or a better way to use one? Issues and pull requests are welcome. Please keep each example in a single file and focused on one idea.
Artem Novichkov, https://artemnovichkov.com/
The project is available under the MIT license. See the LICENSE file for more info.











