UIScreen.main on iPhone Duo — What to Use Instead

Do not use UIScreen.main on a two-display device; get the screen from the current window scene, take size from the view or scene you own, and drive layout with size classes instead of orientation.

Last updated

Quick answer

Do not use UIScreen.main on iPhone Duo: get UIScreen from the current window scene (view.window?.windowScene?.screen / window?.windowScene?.screen), take size from the view or scene you own, take scale from traitCollection.displayScale, and drive layout with size classes instead of orientation. This is the migration behind the No UIScreen.main checklist item.

Why it breaks

Do not use UIScreen.main on a two-display device: iPhone Duo has an inner display and an outer display, and your app can share a display in Split View, so "the main screen" is not the window you are drawing into and not the size of your scene. Apple has said UIScreen.main is heading for deprecation.

The inner display does not honor supported interface orientations — it rotates regardless of your Info.plist mask. That means interfaceOrientation, UIDevice.current.orientation, and statusBarOrientation will lie about the layout you should use. The same trap applies to userInterfaceIdiom and device-model checks: they describe hardware, not the Compact or Regular width of the current scene.

Existing iPhone apps usually fail here in three places: a launch splash or overlay sized to UIScreen.main.bounds, a landscape branch that never fires on the inner display, and a Compact-only stack that never expands when Duo unfolds. For SwiftUI patterns after the swap, see the SwiftUI guide. For fold chrome, see ReservedRegion. For split / overlay placement, see ArrangementView.

Common mistake

Sizing anything to UIScreen.main.bounds — a full-screen overlay, a snapshot, a collection view layout, a launch animation — is the most common Duo break. That rectangle is the hardware display, not your scene. In Split View it is wider than your window; on the outer display it is the wrong screen entirely.

UIScreen.main replacements

Size layout to the view or scene you own — never UIScreen.main.bounds. When you truly need a UIScreen, take it from the current window scene, as in Apple's Tech Talk Prepare your app for iPhone Duo.

WindowSceneScreen.swift
let screen = window?.windowScene?.screen
ViewWindowSceneScreen.swift
let screen = view.window?.windowScene?.screen
  • Layout size: the view's bounds, the scene's bounds, GeometryReader / containerRelativeFrame, or SwiftUI environment geometry.
  • Scale: traitCollection.displayScale instead of UIScreen.main.scale.
  • A real UIScreen object only when you need that display — for example displayScale on a specific screen, or coordinating with an outer-display scene. Copy the one-liners above; never UIScreen.main.
OverlayBefore.swift
// Before — hardware display, not your window
let width = UIScreen.main.bounds.width
overlay.frame = CGRect(x: 0, y: 0, width: width, height: 44)

let scale = UIScreen.main.scale
snapshot.scale = scale
OverlayAfter.swift
// After — the view you actually own
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    overlay.frame = CGRect(
        x: 0,
        y: 0,
        width: view.bounds.width,
        height: 44
    )
}

let scale = traitCollection.displayScale
snapshot.scale = scale
GeometryAfter.swift
// SwiftUI: measure the container, not the display
GeometryReader { proxy in
    Banner()
        .frame(width: proxy.size.width)
}

Content()
    .containerRelativeFrame(.horizontal) { width, _ in width }

Orientation replacements

On Duo's inner display the system does not honor supported interface orientations — drive layout from size classes, not from Info.plist masks, interfaceOrientation, or UIDevice orientation.

Do not branch on landscape vs portrait, and do not treat .phone as Compact. In UIKit, read traitCollection.horizontalSizeClass and register for trait changes. In SwiftUI, read the environment values Apple cites in the same Tech Talk:

SizeClassEnvironment.swift
@Environment(\.horizontalSizeClass)
private var horizontalSizeClass

@Environment(\.verticalSizeClass)
private var verticalSizeClass
OrientationBefore.swift
// Before — these lie on Duo's inner display
if UIDevice.current.orientation.isLandscape {
    useWideLayout()
}
if UIApplication.shared.statusBarOrientation.isLandscape {
    useWideLayout()
}
if windowScene.interfaceOrientation.isLandscape {
    useWideLayout()
}
if traitCollection.userInterfaceIdiom == .phone {
    useCompactLayout()
}
SizeClassUIKit.swift
// After — available width, not rotation or device identity
func applyLayout(for sizeClass: UIUserInterfaceSizeClass) {
    if sizeClass == .regular {
        useWideLayout()
    } else {
        useCompactLayout()
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    applyLayout(for: traitCollection.horizontalSizeClass)
    registerForTraitChanges(
        [UITraitHorizontalSizeClass.self]
    ) { (view: Self, _) in
        view.applyLayout(for: view.traitCollection.horizontalSizeClass)
    }
}
SizeClassSwiftUI.swift
var body: some View {
    if horizontalSizeClass == .regular {
        TwoColumnLayout()
    } else {
        SingleColumnLayout()
    }
}

Apple recommendation

Branch on size class, not device identity. Compact vs Regular describes the space you were given after a fold, an unfold, or a Split View resize. A model string, an idiom check, or an orientation lock does not.

Common mistake

Don't treat userInterfaceIdiom or a device-model check as "iPhone vs Duo." A Duo scene can be Compact (folded, or a narrow Split View pane) or Regular (unfolded, or a wide pane). Branch on horizontalSizeClass and verticalSizeClass.

Grep checklist

Search the project — including storyboards, launch screens, and third-party UI kits — for every call site, then re-test in the Duo Simulator. The Xcode testing guide covers Device Hub poses; the compatibility checklist is the shipping gate for this item.

  • UIScreen.main — including .bounds, .scale, .nativeBounds, and coordinate-space helpers
  • UIDevice.current.orientation
  • statusBarOrientation
  • interfaceOrientation

After the grep is clean, fold, unfold, rotate the inner display, and resize Split View. If a view still assumes a full-screen rectangle, it was reading the wrong bounds even if it never named UIScreen.

Frequently asked questions

What should I use instead of UIScreen.main on iPhone Duo?

Get UIScreen from the window scene (`view.window?.windowScene?.screen` / `window?.windowScene?.screen`), size from view or scene bounds, scale from traitCollection.displayScale, and layout from size classes.

Why is UIScreen.main a problem on iPhone Duo?

Duo has two displays, and your app can occupy only part of a display in Split View. "The main screen" is then ambiguous: it is not the window you are drawing into, and it is not the size of your scene. Apple has said UIScreen.main is heading for deprecation. Read the view, the scene, or the window scene's screen instead.

Can I still lock supported interface orientations?

On the inner display, the system does not honor supported interface orientations — the inner display rotates regardless. Info.plist orientation masks, interfaceOrientation, and UIDevice orientation will lie. Drive layout from size classes, not from a lock you cannot enforce.

What replaces UIScreen.main.bounds for layout?

Use the view's bounds, the scene's bounds, GeometryReader, containerRelativeFrame, or SwiftUI environment geometry. Those values are the space your UI actually owns after a fold, an unfold, or a Split View resize.

Is userInterfaceIdiom a safe way to detect iPhone vs Duo?

No. Idiom and device-model checks describe hardware identity, not the width you were given. A Duo scene can be Compact (folded, or a narrow Split View pane) or Regular (unfolded, or a wide pane). Branch on horizontalSizeClass and verticalSizeClass.

When is it valid to touch a UIScreen at all?

When you need a real screen object — for example displayScale on a specific display, or coordinating with an outer-display scene. Get it from the window scene: view.window?.windowScene?.screen. Never take the global UIScreen.main shortcut.