# UIKit on iPhone Duo: vertical bars and arrangements
> What changes in a UIKit app on iPhone Duo: the vertical bar region, UIArrangementViewController, reserved regions, and the UIScreen.main deprecation.
Source: https://iphoneduosupport.com/blog/uikit-iphone-duo-vertical-bars/
Published: 2026-09-09
Framework: uikit
Status: Apple-documented — every claim traces to an Apple source.
Sources: https://developer.apple.com/videos/play/tech-talks/111461/ https://developer.apple.com/videos/play/tech-talks/111462/ https://developer.apple.com/videos/play/tech-talks/111463/
Last verified: 2026-09-09
---
UIKit apps have an advantage on iPhone Duo that is easy to miss: if your app already supports iPad, most of the work is done. The inner display is regular-width in both dimensions, which is the same environment your iPad layout already targets.
The apps with real work ahead are iPhone-only ones, and apps that replaced the system bars with their own.
## UIScreen.main stops making sense
Start here, because it is the change with the widest blast radius.
Apple stated in *Prepare your app for iPhone Duo* that main screen references will be deprecated and should be avoided on two-display devices. This is not a style preference. iPhone Duo has two displays with **different pixel densities** — 430 ppi inner, 460 ppi outer — and your app can move between them while running. A global "main screen" cannot answer which display a given view is actually on.
```swift
// Before
let scale = UIScreen.main.scale
let bounds = UIScreen.main.bounds
// After
let scale = traitCollection.displayScale
let screen = view.window?.windowScene?.screen
```
Scale is a trait, so it updates automatically as the view moves between displays. That is the whole point of reading it from the trait collection rather than a global.
For layout, do not reach for the screen at all. `view.bounds` and the safe area describe the space you were given — which in Split View is smaller than the display regardless of which one you are on.
```bash
grep -rn "UIScreen.main" --include="*.swift" .
```
Run that first. It is usually a bigger list than people expect, and much of it is in code nobody has opened in years.
## Safe areas are asymmetric
On every previous iPhone, left and right insets were mirror images. That made a shortcut look safe:
```swift
// Wrong on iPhone Duo
let width = view.bounds.width - view.safeAreaInsets.left * 2
```
The status bar and camera sit asymmetrically on the inner display, and the hinge introduces geometry with no mirror. Inset the rectangle instead of doing arithmetic on one side:
```swift
let width = view.bounds.inset(by: view.safeAreaInsets).width
```
Apple's split is worth stating plainly, because it is what makes an app look finished:
```swift
// Interactive content — inside the safe area
foreground.frame = view.bounds.inset(by: view.safeAreaInsets)
// Background artwork — full bounds
backgroundView.frame = view.bounds
```
Applying one policy to a whole screen is the mistake. A card with a background image wants the image to bleed and the text to stay inset.
## The vertical bar region
This is the most visible change under the iOS 27.1 SDK. On the inner display in landscape, navigation, toolbar, and tab bar controls move into a **single shared vertical region** at the side. Picture your bars rotated ninety degrees into one column.
You get this automatically with `UINavigationController` and `UITabBarController`. **Custom `UIToolbar` content is not considered.** If you replaced the system bars with your own — a common decision in apps with strong visual identity — you are outside the adaptive path and will need to rebuild on the system containers to get back into it.
Two details worth knowing: the bar stays on the same side in right-to-left languages, and in a split view only the detail column participates. Inspectors do not get their own bar.
### Controlling it
Placement, since order matters in a column:
```swift
// Back / close goes to the top
navigationItem.leftBarButtonItem = closeItem // .cancellationAction equivalent
```
Orientation of individual items:
```swift
item.axisBehavior = .verticalPreferred
item.axisBehavior = .horizontalOnly
```
Detecting the vertical case, so custom views can adapt:
```swift
switch traitCollection.verticalBarEdge { … }
```
What compresses first when space runs out:
```swift
navigationItem.verticalBarCompressionBehavior = .prefersBarItems
```
Which actions survive overflow:
```swift
item.visibilityPriority = .high
```
And consolidating your own ellipsis menu into the system one, so users do not see two identical menus stacked:
```swift
navigationItem.additionalOverflowItems = UIDeferredMenuElement({ provider in
provider(self.persistentOverflowItems())
})
```
Badges from iOS 26 carry over, and are useful for keeping a signal on an item that has lost its text label:
```swift
item.badge = .count(7)
```
### Opting out
Some screens are worse for it. Apple names single-page layouts with heavy bottom content — a calculator — and sheets with only a close button:
```swift
override var preferredVerticalBarBehavior: UIVerticalBarBehavior { .disabled }
```
Per screen, on purpose. Not globally.
## Arrangements
`UIArrangementViewController` is the UIKit counterpart to SwiftUI's `ArrangementView`, and it handles the main-and-secondary case across every pose:
```swift
let arrangementVC = UIArrangementViewController()
let navController = UINavigationController(rootViewController: arrangementVC)
arrangementVC.setViewController(PlayerViewController(), for: .primary)
arrangementVC.setViewController(UpNextViewController(), for: .secondary)
```
Style is set the same way, and can be updated at runtime:
```swift
arrangementVC.updateArrangement(.split.axes(.horizontal))
```
For an overlay arrangement, read the z-index so the secondary view can collapse when stacked:
```swift
let primaryState = arrangementVC.state(for: .primary)
myModel.minimization = (primaryState?.zIndex ?? 0) > 0 ? .collapsed : .expanded
```
## Reserved regions
`UIViewReservedRegion` reports where the display is interrupted:
```swift
let regions = view.reservedRegions(kind: .division)
let frames = regions.map(\.frame)
```
Division regions divide — the hinge. Occlusion regions cover — the under-display camera. Division regions are **active only while folded and have zero width when flat**, so handle them appearing and disappearing, not just moving.
Use arrangements first. Query regions directly when you need control an arrangement cannot give you.
## Multiple scenes
iPhone Duo is the first iPhone supporting multiple app instances. Apps already supporting this on iPad get it here.
```xml
UIApplicationSupportsMultipleScenes
```
Two constraints: new windows can only be created on the **inner display**, and Apple is explicit that you **must handle errors** when requesting a scene through `UIWindowSceneActivation`. The action hides itself when windows are unavailable, so a correctly built UI does not offer something that cannot work.
If you enable it, audit anything assuming a single instance — singletons holding view state, global notification handling that presents UI, analytics assuming one session per launch.
## Split View is not optional
All apps participate in side-by-side multitasking on iPhone Duo. An app that has never been resized can now be handed an arbitrary fraction of the display, at a moment the user chooses.
```swift
override func viewWillTransition(
to size: CGSize,
with coordinator: UIViewControllerTransitionCoordinator
) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate { _ in self.relayout(for: size) }
}
```
While you are there, check `UIRequiresFullScreen`. An app still carrying that key is fighting the platform.
One failure mode that is easy to miss: in Split View your app can be **visible but not frontmost**. Timers, video, and animation stopped on `sceneWillResignActive` will look frozen to a user who can still see the window.
## Where to start
1. `grep -rn "UIScreen.main"` and fix every hit
2. `grep -rn "userInterfaceIdiom"` and check each one against size classes instead
3. Rebuild against the iOS 27.1 SDK
4. Run the App Resizability skill in Xcode 27.1 — it now covers this migration
5. Audit bar content for the vertical region
6. Adopt `UIArrangementViewController` where you have a primary/secondary screen
Steps 1 and 2 are unglamorous and account for most of the difference between an app that behaves and one that does not.
---
Published by iPhone Duo Support (https://iphoneduosupport.com). Not affiliated with Apple Inc.