
What can the APIs added in iOS 27.1 for iPhone Duo do?
This page has been translated by machine translation. View original
Introduction
New APIs for the foldable device iPhoneDuo have been added in iOS 27.1 SDK.
Broadly speaking, they cover the following:
- Arranging or overlapping two Views (
ArrangementView) - Getting the hinge opening state (
onHingeChange) - Getting the position of the fold crease and camera (
reservedRegions) - Adjusting vertically arranged toolbars, etc. (
toolbarVerticalBehaviorand others)
All of these are available for both SwiftUI and UIKit. I looked into them a bit, so here's a summary.
I plan to write separate articles covering finer details and behaviors.
Arranging / Overlapping Two Views
ArrangementView
ArrangementView is a container that can arrange or overlap two Views — primary and secondary — depending on the display size and environment.
ArrangementView {
PrimaryView()
} secondary: {
SecondaryView()
}
.arrangementViewStyle(.split) // or (.overlay)
There are three styles: .automatic, .split, and .overlay. Since the documentation states that the default .automatic resolves to .split, there are effectively two.
.split is a style that places the two screens side by side. For example, it suits combinations like a music playback screen on the left and lyrics display on the right, where you don't want to hide either.
.overlay is a style that layers primary on top of secondary. I couldn't think of many use cases for it. The documentation does mention the ideal use case, but honestly it didn't click for me.
The following is an excerpt from the documentation:
An overlay arrangement layers the primary view on top of the secondary view in z-order. This layout is well-suited for full screen experiences like media players, where playback controls overlay a video surface:
Getting the Hinge Opening State
onHingeChange(isEnabled:_:)
With onHingeChange, you can receive the hinge state and angle. The closure receives both the previous and new values.
.onHingeChange { _, newContext in
guard let hinge = newContext.hinge else { return }
let degrees = hinge.angle.degrees // Angle, use degrees to extract the degree value
let status = hinge.status // .closed / .partiallyOpen / .fullyOpen
}
hinge.status has three values: .closed, .partiallyOpen, and .fullyOpen, and the angle is updated continuously.
I think the use cases are limited, but it's nice that this event is provided in a straightforward way.
Getting the Position of the Fold Crease and Obstructions
reservedRegions(kind:options:layoutDirectionBehavior:)
Using reservedRegions on GeometryProxy, you can get the regions occupied by the fold crease and obstructions like cameras.
GeometryReader { proxy in
// Region of the fold crease
let folds = proxy.reservedRegions(kind: .division)
// Region of obstructions (camera, etc.)
let occlusions = proxy.reservedRegions(kind: .occlusion)
}
There are two kinds of regions:
.division: A place that divides the area in two, like a fold crease. Only active when folded..occlusion: A place obstructed by an element. The official documentation gives Dynamic Island, cameras, and window controls as examples.
I'll cover the options and layoutDirectionBehavior arguments in a separate deep-dive article.
Adjusting Vertically Arranged System Bars
toolbarVerticalBehavior(_:)
On iPhone Duo, the navigation bar, toolbar, and tab bar are displayed vertically side by side along the edge of the screen.
The vertical layout appears on the outer display and when using the inner display in landscape orientation. When using the inner display in portrait orientation, it reverts to the conventional horizontal bars.
toolbarVerticalBehavior(_:) determines whether to use the vertically arranged bars. There are two values:
.automatic: The system decides whether to use the vertical bars.disabled: Uses horizontal bars instead of vertical
TabView {
Tab("Home", systemImage: "house") {
NavigationStack { HomeView() }
}
Tab("Settings", systemImage: "gear") {
NavigationStack { SettingsView() }
}
}
.toolbarVerticalBehavior(.disabled) // Disable vertical bars
toolbarVerticalCompressionBehavior(_:)
toolbarVerticalCompressionBehavior(_:) lets you decide which to prioritize when the tab bar or toolbar items in the vertical layout overflow the available vertical space. The one that is not prioritized will be collapsed into an ellipsis button. There are three values:
.automatic: Leave it up to the system.prefersToolbarItems: Keep toolbar items with priority.prefersTabBar: Keep the tab bar with priority
TabView {
Tab("Recent Items", systemImage: "clock") {
NavigationStack {
RootView()
.toolbar {
Button("Up", systemImage: "chevron.up") {}
Button("Down", systemImage: "chevron.down") {}
}
.toolbarVerticalCompressionBehavior(.prefersToolbarItems) // Keep Toolbar when overflowing
}
}
}
axisBehavior(_:)
axisBehavior(_:) lets you decide, per toolbar item such as ToolbarItem, whether it appears in the vertical or horizontal bar. There are three values:
.automatic: Leave it up to the system.horizontalOnly: Displayed in the horizontal bar.verticalPreferred: Can be displayed in either vertical or horizontal bar, but when both bars are present, vertical is preferred
.toolbar {
ToolbarItem(placement: .primaryAction) {
Toggle(isOn: $isOn) { ... }
}
.axisBehavior(.horizontalOnly) // Displayed in the horizontal bar
}
toolbarVerticalEdge
toolbarVerticalEdge is an Environment that lets you read the position where the vertical bars are placed. The type is HorizontalEdge?.
- The value reflects the system's preferred position (
.leading/.trailing) regardless of whether the vertical bars are currently displayed - Returns nil on devices, size classes, or orientations that don't use vertical bars
The official documentation introduces a use case of aligning a custom palette to the same side as the vertical bar.
struct ContentView: View {
@Environment(\.toolbarVerticalEdge) var toolbarVerticalEdge
var body: some View {
FloatingToolPalette()
.frame(maxWidth: .infinity,
alignment: toolbarVerticalEdge == .trailing
? .trailing : .leading) // Align to trailing if bar is on the trailing side, otherwise align to leading
}
}
The control around the navigation bar, toolbar, and tab bar has become more complex... I feel like verification is needed, such as how the system-default behavior works.
References
