Goal

Cheat Sheet

  • Getting Started

    • iPhone Duo is out to customers on Oct 23, 2026.
    • Apps look different depending on the SDK they’re built against: pre-iOS 27 apps run letterboxed at phone width, iOS 27 apps fill the outer display as a single column, and iOS 27.1 apps use the full two-column width.
    • iPhone Duo simulator is only available in Xcode 27.1 beta for now.
    • If you want to physically run your app on iPhone Duo, you can attend the Workshop: Optimize your app for iPhone Duo at your nearest Apple Store location.
  • Layout & Resizing Fundamentals

    • Align foreground content to the safe area, and let background content go edge-to-edge.
    • The iOS 26 Concentricity APIs (ConcentricRectangle, UICornerConfiguration) let your UI match the device’s actual corner radius.
  • Bars & Navigation

    • If you are using the native iOS components like tab bar and navigation, most of the work is already done for you.
    • If you use UITabBar, UIToolbar, or UINavigationBar as standalone views, they remain horizontally oriented and do not receive iPhone Duo’s automatic vertical-axis bar adaptation. To get that adaptation, use the appropriate system container—such as UINavigationController, UITabBarController, or UISplitViewController—and configure its managed navigation or toolbar items.
    • iPhone Duo turns Horizontal bars to Vertical. Vertical bars have a fixed width and flexible item height, making them better suited to symbol-only items.
    • Use AxisBehavior (.verticalPreferred / .horizontalOnly) to override how the system decides whether a toolbar item goes on the horizontal or vertical axis.
    • The ellipsis (…) is reserved for the system overflow menu — consolidate your own overflow actions into it using ToolbarOverflowMenu (SwiftUI) or additionalOverflowItems (UIKit) instead of building a custom one.
    • Disable the vertical bar for bottom-heavy, single-item, or control-heavy apps (like Calculator) using the toolbarVerticalBehavior and preferredVerticalBarBehavior (not yet in Apple’s API reference — see the Raise the bar with iPhone Duo Tech Talk) APIs.
    • On the inner display, you can present a tab bar as a sidebar via .defaultTabBarPlacement(.sidebar) in SwiftUI or sidebar.preferredPlacement in UIKit.
  • Poses & Arrangement Views

    • Design around two size classes — compact and regular — instead of building a custom layout for each of the 6 device poses.
    • ArrangementView is a new API in iOS 27 for iPhone Duo and it works on a regular iPhone as well.
  • Reserved Regions & Hinge Response

  • Multiwindow & Multidisplay

    • iPhone Duo is the first iPhone to support multiple instances of your app’s UI.
    • New windows can only be created on the inner display — not the outer display.
    • If your app is running on the inner display, it moves to the outer display automatically when you unfold the device. If you’re using split-view multitasking, the system promotes whichever app you’re interacting with to the outer display. For the reverse (outer to inner), recently used apps return to where they were; but if it’s been a while, the app that was on the outer display opens full-screen on the inner display.
    • iPhone Duo doesn’t support iPad-style Stage Manager multitasking.
    • Scene accessories let your app show content on multiple displays at the same time; CameraCaptureAccessory pairs additional UI on the outer display with your app’s main camera UI on the inner display.
  • Camera

    • iPhone Duo is the first iPhone to support two front cameras.
    • Use builtInOuterUltraWideCamera and builtInInnerUltraWideCamera with the new AVCaptureDeviceDirectionCoordinator API (none of these three are in Apple’s API reference yet — see the Build a great camera experience for iPhone Duo Tech Talk) to manage the transition between the outer and inner cameras.
  • Migrating an Existing App

    • Scene lifecycle (not app lifecycle) is required when linking against iOS 27.
    • Avoid referencing main screen (i.e., UIScreen.main), it will be deprecated soon. Prefer the environment, trait collection, or the scene’s bounds, and access screens dynamically from the window scene (i.e., window?.windowScene?.screen).
    • Replace screen-scale references with the trait collection’s displayScale, and use registerForTraitChanges where automatic trait tracking isn’t available.
    • User interface idiom and interface orientation are no longer meaningful for layout decisions — even on iPad or in iPhone Mirroring, your app still runs under the phone idiom, so use size classes instead.
    • Xcode 27 introduces a new app modernization agent skill: it automatically converts main screen calls to traitCollection or scene bounds checks, replaces interface orientation checks with size class checks, and converts your app to use scene lifecycle. Export it for use in other tools with xcrun agent skills export.
  • Mobile Web (not covered in the Deep Dive below)

    • For a mobile web app, you can’t specifically check if it’s a Duo, but there is a standard web API to check if the device has a fold and is folded.
    • For the mobile web app experience, you can test your website in Safari on the iPhone Duo simulator.
    • For Safari updates on iPhone Duo, they will be available when the new WebKit SDK is out.

Deep Dive

Getting Started

Design Specs

  • Dimensions: 7.6 (Inner) * 5.4 (Outer)

    Inner display diagonal of 7.6 inches shown as a wide rectangle, and outer display diagonal of 5.4 inches shown as a narrower rectangle

  • Designing for iPhone Duo tab bar and tool bar:

    Outer display diagram labeling the Dynamic Island, status bar, toolbar, and tab bar stacked vertically along the right edge

  • Controls share this vertical space with dynamic elements like live activities and the status bar.

    Mail app on the outer display with live activity, status bar, and app controls stacked vertically along the right edge

  • Build your layouts with Layout Margin and Horizontal Safe Area Inset:

    Mail app on the outer display with the layout margin highlighted on the left and the horizontal safe area inset highlighted on the right

  • Designing for Hinge and Reserved Regions:

    Outer display outline with the camera region highlighted near the top-right corner

    Inner display outline with the folding region highlighted down the center and the camera region highlighted near the top-right

SDK & Simulator Setup

  • Your app runs on iPhone Duo even without recompiling, and here is how your app is going to look depending on the SDK it’s built for:

    An app built with the pre-iOS 27.0 SDK shown letterboxed at phone width on both a regular iPhone and the iPhone Duo outer display

    An app built with the iOS 27.0 SDK filling the full width on a regular iPhone, with a single column shown on the narrower iPhone Duo outer display

    An app built with the iOS 27.1 SDK filling the full width with a two-column grid on both a regular iPhone and the iPhone Duo outer display

  • You need to download Xcode 27.1 to run your app in the iPhone Duo simulator using DeviceHub. On-screen control buttons let you open, close, rotate, and fold the device to check your layout in every pose.

    Xcode Simulator running an app on iPhone Duo, with on-screen controls at the bottom for opening, closing, rotating, and folding the device

Layout & Resizing Fundamentals

  • You can add horizontal or vertical layout by using the size classes below — horizontalSizeClass/verticalSizeClass in SwiftUI, or UITraitCollection.horizontalSizeClass/verticalSizeClass in UIKit:

    
    	// SwiftUI
    	@Environment(\.horizontalSizeClass)
    	private var horizontalSizeClass
    
    	@Environment(\.verticalSizeClass)
    	private var verticalSizeClass
    
    	// UIKit
    	traitCollection.horizontalSizeClass
    	traitCollection.verticalSizeClass
    
  • On iPhone Duo, there are several size class combinations as shown below:

    Outer display size class combinations: compact width by regular height in portrait, and compact width by compact height in landscape

    Inner display size class combinations: regular width by regular height in both landscape and portrait

  • Outer display honors supported interface orientations, and inner display doesn’t.

  • To make sure your UI fits perfectly on screen, use the iOS 26 Concentricity APIs, ConcentricRectangle and UICornerConfiguration, to fit the screen’s corners.

    // SwiftUI
    ConcentricRectangle()
        .fill(Color.green)
        .padding(8.0)
        .ignoresSafeArea()
    
    // UIKit
    // UICornerConfiguration
    
  • For SwiftUI, your content is placed in the safe area by default. Here is how to position your app to respect and ignore safe area with ignoresSafeArea(_:edges:):

    
    // UIKit
    foreground.frame = view.bounds.inset(by: view.safeAreaInsets)
    
    // SwiftUI
    .ignoresSafeArea()
    
    // UIKit
    backgroundView.frame = view.bounds
    

Bars & Navigation

Use System Containers

  • Rebuild your app against the latest SDKs, then use bars provided by navigation containers.

  • In SwiftUI, pair the toolbar modifier with NavigationStack or NavigationSplitView. In UIKit, prefer UINavigationController and UITabBarController over custom UIToolbar, UINavigationBar, or UITabBar instances, whose content isn’t considered.

    // SwiftUI
    var body: some View {
        NavigationStack {
            ContentView()
                .toolbar {
                    ToolbarItem(placement: .bottomBar) {
                        ...
                    }
                }
        }
    }
    
    // UIKit
    let viewController = UIViewController()
    viewController.toolbarItems = [...]
    let navigationController = UINavigationController(rootViewController: viewController)
    

Where Bar Items End Up

  • Tool bar buttons are moved to the top and bottom of the vertical space depending on where they were positioned before:

    Toolbar back and navigation buttons positioned at the top of the outer display, compared between a regular iPhone and iPhone Duo

    Bottom toolbar buttons for delete, move, and reply shown moving to the vertical edge on the outer display

  • Tab bar buttons are moved to the bottom:

    Fitness+ tab bar buttons moving from the bottom edge on a regular iPhone to the vertical right edge on the outer display

  • Tool bar buttons with text should stay on top:

    The text-only Select button remaining at the top toolbar on both a regular iPhone and the outer display

Organize & Order Bar Items

  • iPhone Duo allows navigation, toolbar, and tab bar controls to coexist in a shared region.

  • Reserve the top for primary navigation like back or close, followed by prominent actions such as done. If you are using a navigation controller, the back button is added automatically.

  • Use the cancellation action placement in SwiftUI, or a leading item with leftItemsSupplementBackButton set to false in UIKit.

    // SwiftUI
    .toolbar {
        ToolbarItem(placement: .cancellationAction) {
            ...
        }
    }
    
    // UIKit
    navigationItem.leftItemsSupplementBackButton = false
    navigationItem.leadingItemGroups = [UIBarButtonItemGroup(...)]
    
  • Place prominent actions with topBarPinnedTrailing or pinnedTrailingGroup.

    // SwiftUI
    .toolbar {
        ToolbarItem(placement: .topBarPinnedTrailing) {
            ...
        }
    }
    
    // UIKit
    navigationItem.pinnedTrailingGroup = UIBarButtonItemGroup(...)
    

Icon vs. Text-Only Items & AxisBehavior

  • iPhone Duo turns Horizontal bars to Vertical. Vertical bars have a fixed width and flexible item height, making them better suited to symbol-only items.

  • You place actions on the horizontal bar like before, and the system decides if your content is better suited on the horizontal or vertical axis. Items with an icon (i.e., back and share button) in the example below change to vertical, and the text-only items like the Edit button stay on the horizontal axis.

    Back and share icon buttons moving to the vertical toolbar on the outer display, while the text-only Title and Edit items stay on the horizontal bar

  • In general, you want to provide all your content with icons and then the system will decide where to place them based on content type.

  • Even though your content is an image, it’s still important to provide a title and the system will use it in overflow menus and expanded forms.

  • By default, SwiftUI and UIKit infer where to place the item based on its content, like if it’s an item that’s text only or a symbol in text. If you need to adjust this default behavior, there’s a new AxisBehavior API.

    // SwiftUI
    var body: some View {
        ContentView()
            .toolbar {
                ToolbarItem {
                    ProfileView()
                }
                .axisBehavior(.verticalPreferred)
            }
    }
    
    // UIKit
    let item = UIBarButtonItem(customView: ProfileView())
    item.axisBehavior = .verticalPreferred
    
  • If you’re creating a custom item that transitions between a symbol and a text, use the horizontal-only axis behavior:

    // SwiftUI
    var body: some View {
        ContentView()
            .toolbar {
                ToolbarItem {
                    SelectOrDoneButton()
                }
                .axisBehavior(.horizontalOnly)
            }
    }
    
    // UIKit
    item.axisBehavior = .horizontalOnly
    

Badges, Spacing & Overflow

  • Minimize title-only items or custom views that show both text and an image so more content can go vertical. Adopting the badge API added in iOS 26 will get you a system-standard badge appearance on all devices.

    // SwiftUI
    var body: some View {
        ContentView()
            .toolbar {
                ToolbarItem(...) {
                    InboxButton()
                        .badge(7)
                }
            }
    }
    
    // UIKit
    let item = UIBarButtonItem(...)
    item.badge = .count(7)
    
  • Accessory bars should remain attached to the keyboard rather than moving to the vertical axis.

  • For custom views in particular, you might want to make changes when a vertical bar is present. To know that, read the toolbarVerticalEdge environment property or trait (not yet in Apple’s API reference — see the Raise the bar with iPhone Duo Tech Talk):

    // SwiftUI
    struct ContentView: View {
        @Environment(\.toolbarVerticalEdge) var edge
    
        var body: some View {
            switch edge {
                ...
            }
        }
    }
    
    // UIKit
    switch traitCollection.verticalBarEdge {
        ...
    }
    
  • By default, flexible spacers are zero size in the vertical axis. But fixed spacers continue to respect their minimum size. Your app shouldn’t be creating additional spacing, regardless of whether a bar is horizontal or vertical.

  • Use the toolbarCompressionBehavior API (not yet in Apple’s API reference — see the Raise the bar with iPhone Duo Tech Talk) to configure your app’s preference for each view.

    // SwiftUI
    var body: some View {
        TabView {
            Tab("Recents", systemImage: "clock") {
                ContentView()
                    .toolbarVerticalCompressionBehavior(.prefersToolbarItems)
            }
        }
    }
    
    // UIKit
    navigationItem.verticalBarCompressionBehavior = .prefersBarItems
    
  • If your app already has its own overflow, consolidate its actions into a single system-managed menu. You can do that using ToolbarOverflowMenu in SwiftUI or additionalOverflowItems in UIKit.

    // SwiftUI
    var body: some View {
        ContentView()
            .toolbar {
                ToolbarOverflowMenu {
                    Button("Scan") { ... }
                    Button("Connect") { ... }
                }
            }
    }
    
    // UIKit
    navigationItem.additionalOverflowItems = UIDeferredMenuElement({ provider in
        provider(self.persistentOverflowItems())
    })
    
  • Toolbar visibility priority gives you fine-grained control over which ones remain visible. By default, items overflow from bottom to top, but you can also assign each item a high, low, or custom priority to control the order in which they collapse. Use visibilityPriority APIs in SwiftUI and UIKit to configure this priority.

    
    // SwiftUI
    var body: some View {
        ContentView()
            .toolbar {
                ToolbarItem {
                    Button(...) { ... }
                }
                .visibilityPriority(.high)
            }
    }
    
    // UIKit
    let item = UIBarButtonItem(...)
    item.visibilityPriority = .high
    

Disabling the Vertical Bar

  • Most apps are good candidates for a vertical bar. However, if your app is a single-page app with a bottom-heavy layout like Calculator or if a sheet is control-heavy with only one item, consider disabling a vertical bar so it doesn’t reduce available space. Use the toolbarVerticalBehavior and preferredVerticalBarBehavior (not yet in Apple’s API reference — see the Raise the bar with iPhone Duo Tech Talk) APIs to disable this.

    // SwiftUI
    var body: some View {
        NavigationStack {
            ContentView()
                .toolbarVerticalBehavior(.disabled)
        }
    }
    
    // UIKit
    class MyViewController: UIViewController {
        override var preferredVerticalBarBehavior: UIVerticalBarBehavior {
            .disabled
        }
    }
    
  • New in iOS 27, iPhone apps can also opt into sidebars by setting the tab bar controller’s sidebar.preferredPlacement to .sidebar:

    // SwiftUI
    TabView { … }
        .defaultTabBarPlacement(.sidebar)
    
    // UIKit
    tabBarController.sidebar.preferredPlacement = .sidebar
    
  • The system determines if there is enough space to show a sidebar, for example when the horizontal size class is regular.

  • To determine if the tab bar’s sidebar representation can be shown, use the sidebar’s isAvailable (tabBarController.sidebar.isAvailable) property.

Prominent Tab Bar Minimization

  • UITabBarController also lets you customize the prominent tab. The prominent tab is always visible, even when the tab bar collapses during scrolling. In iOS 27, you have the option to make any tab prominent by setting the prominentTabIdentifier.

    // Set the prominent tab
    let tabs = [
        // ...
    ]
    let tabBarController = UITabBarController(tabs: tabs)
    tabBarController.prominentTabIdentifier = "cart"
    
  • Navigation bars can interactively slide away as people scroll. This provides more room on the screen for your app’s content.

  • You can force this behavior, one way or the other, by setting the barMinimizationBehavior property (not yet in Apple’s API reference — see the Modernize your UIKit app WWDC talk) on your navigation item to .always or .never. If you handle safe area avoidance yourself, set barMinimizationSafeAreaAdjustment to .never so bar minimization doesn’t update insets automatically.

    
    // Customize bar minimization behavior
    override init(
        nibName nibNameOrNil: String?,
        bundle nibBundleOrNil: Bundle?
    ) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    
        navigationItem.navigationBarMinimization.minimizationBehavior = .onScrollDown
        navigationItem.navigationBarMinimization.safeAreaAdjustment = .disabled
    }
    

Poses & Arrangement Views

The 6 Poses & 2 Size Classes

  • Your app is going to have 6 brand new configurations to support.

    Six icons illustrating device poses: closed (phone), tent, flat landscape (tablet), partially folded (book), flat portrait, and laptop-like open angle

    The Mail app shown in six iPhone Duo poses: closed phone, tent, unfolded tablet landscape and portrait, partially folded book, and laptop-like open angle

  • Don’t design a custom layout for each pose, instead focus on two size classes: compact and regular.

  • We are going to use the size classes to support the orientations: Compact width * Compact height, Compact width * Regular height, Regular width * Compact height, Regular width * Regular height.

Adapting to the Inner Display

  • Here are the best options for adapting your app to the inner display:
    • 1.) Split View

      Music app shown split between now-playing details on the left and the full track list on the right on the inner display

    • 2.) Optimize your content for inner display

      Music app content reflowing to use the extra width on the inner display, showing more of the track list without a split layout

    • 3.) Present tab bar as a sidebar in the inner display, and this is a good option for information-dense apps like the example below from Health:

      Health app tab bar presented as a sidebar of categories on the inner display, compared to the bottom tab bar on a regular iPhone

Sheets Across Poses

  • Here is how sheets adapt in iPhone Duo depending on the pose. Control the placement with View.presentationPlacement(_:) in SwiftUI or UISheetPresentationController.preferredPlacement in UIKit (neither is in Apple’s API reference yet — see the Strike a pose with adaptive layouts on iPhone Duo Tech Talk).

    The List Info sheet presented across iPhone Duo poses, from a compact sheet on the outer display to a full two-pane layout on the inner display

Arrangement Views

  • A new layout container is introduced and it sits between navigation and content containers, arranging two views according to a set of rules called an arrangement:

    Diagram grouping SwiftUI containers into Navigation Containers (NavigationStack, NavigationSplitView, TabView), the new Layout Containers, and Content Containers (List, ScrollView)

  • This function from inputs to outputs is called an arrangement. In iOS 27.1, you can use system-provided arrangements in your own app.

    Diagram of an arrangement taking size class, aspect ratio, and reserved region as inputs to produce visibility and frame outputs

  • An ArrangementView has different styles:

Split Arrangement

  • Split arrangement: positions content side by side, shows the horizontal or vertical split depending on device rotation position, if you only want to split horizontally, you can specify axes (not yet in Apple’s API reference — see the Strike a pose with adaptive layouts on iPhone Duo Tech Talk) on split arrangement style. If the arrangement cannot be split it chooses primary view.

    A podcast app’s player and queue list shown side by side in a split arrangement on the outer display

    // SwiftUI
    var body: some View {
      NavigationStack {
        ArrangementView {
          PlayerView()
        } secondary: {
          UpNextView()
        }
        .arrangementViewStyle(
          .split.axes(.horizontal))
      }
    }
    
    // UIKit
    let arrangementVC = UIArrangementViewController()
    let navController = UINavigationController(rootViewController: arrangementVC)
    
    let playerVC = PlayerViewController()
    arrangementVC.setViewController(playerVC, for: .primary)
    
    let upNextVC = UpNextViewController()
    arrangementVC.setViewController(upNextVC, for: .secondary)
    
    arrangementVC.updateArrangement(.split.axes(.horizontal))
    

Overlay Arrangement

  • Overlay arrangement: positions content above or below each other.

    The same podcast app in an overlay arrangement, with the player filling the screen and the queue collapsed into a small floating card

    // SwiftUI
    var body: some View {
      NavigationStack {
        ArrangementView {
          UpNextView()
        } secondary: {
          PlayerView()
        }
        .arrangementViewStyle(.overlay)
      }
    }
    
  • If you fold the device, the overlay arrangement puts the primary and secondary arrangements side by side, which gives the UpNextView a lot more breathing room. We can query the overlay arrangement’s overlayArrangementZIndex property (not yet in Apple’s API reference — see the Strike a pose with adaptive layouts on iPhone Duo Tech Talk), and the UpNextView can fold or unfold based on that zIndex property:

    
    // SwiftUI
    enum UpNextMinimization {
      case collapsed; case expanded
    }
    
    struct UpNextView: View {
      @Environment(\.overlayArrangementZIndex)
      private var zIndex: Int
    
      var body: some View {
        UpNextList(minimization: minimization)
      }
    
      var minimization: UpNextMinimization {
        zIndex > 0 ? .collapsed : .expanded
      }
    }
    
    // UIKit
    let arrangementVC = UIArrangementViewController()
    
    // ...
    
    let primaryState = arrangementVC.state(for: .primary)
    myModel.minimization = (primaryState?.zIndex ?? 0) > 0
      ? .collapsed : .expanded
    

Choosing Between Arrangements

  • First, be sure to follow your existing app patterns.
  • If you’re already implementing a split-like layout in parts of your app using components like an HStack or VStack, consider using the split arrangement.
  • If you’re already implementing an overlay-like layout in parts of your app using components like a ZStack, consider using the overlay arrangement.
  • If you don’t have an existing pattern to lean on, consider an overlay arrangement when there’s a clear foreground/background relationship between your views.
  • Consider a split arrangement when there’s more of a main-detail relationship between your content.

When Not to Use Arrangement Views

  • ArrangementViews don’t provide navigation infrastructure to your app. So avoid putting navigation containers like NavigationSplitViews inside an ArrangementView. And due to the nature of views like List and ScrollView, avoid putting ArrangementViews inside these scrollable containers.

Reserved Regions & Hinge Response

Querying Reserved Regions

  • Query reserved regions with reservedRegions(kind:options:layoutDirectionBehavior:) in SwiftUI / UIKit:

    
    // SwiftUI
    GeometryReader { proxy in
      let regions = proxy.reservedRegions(
        kind: .division)
    }
    // UIKit
    let regions = view.reservedRegions(
      kind: .division)
    
    // Query the frame to incorporate it into your own layout
    let frames = regions.map(\.frame)
    
  • You can include the inactive region using the .includeInactive option:

    // SwiftUI
    GeometryReader { proxy in
      let regions = proxy.reservedRegions(
        kind: .division, options: .includeInactive)
    
      let frames = regions.map(\.frame)
      // ...
    }
    
  • In iPhone Duo, the FaceTime camera is represented by an occlusion region. You query it by passing the .occlusion type. This region is active when the camera is active and inactive when the camera is inactive:

    // SwiftUI
    GeometryReader { proxy in
      let regions = proxy.reservedRegions(
        kind: .occlusion)
    
      let frames = regions.map(\.frame)
      // ...
    }
    

Responding to the Hinge

  • Your app can respond to hinge using onHingeChange modifier in SwiftUI and UIHingeInteraction (not yet in Apple’s API reference — see the Leverage multiple displays and scenes on iPhone Duo Tech Talk) in UIKit. Both report the high-level hinge status — closed, partially open, and fully open — as well as continuous updates of the hinge angle.

    • A null value for hinge indicates that app is running on a device without a hinge.
    struct InstrumentView: View {
        /// Normalized bend, 0 is no bend, 1 is deepest bend
        @State private var pitchBend: Double = 0
    
        var body: some View {
            GuitarView(pitchBend: pitchBend)
                .onHingeChange { _, context in
    	            // A null hinge means the device doesn't have one
                    if let hinge = context.hinge, hinge.status == .partiallyOpen {
                        pitchBend = calculatePitchBend(angle: hinge.angle)
                    }
                    else {
                        pitchBend = 0
                    }
                }
        }
    
        private func calculatePitchBend(angle: Angle) -> Double { ... }
    }
    

Multiwindow & Multidisplay

Split View Multitasking

  • Designing for iPhone Duo multitasking:

    Two apps side by side in split-view multitasking, each with its own controls along the outer edge of the display

  • All apps support split view multitasking in iPhone Duo. If your app already supports iPad or iPhone mirroring, you are off to a great start.

  • iPhone Duo is the first iPhone to support multiple instances of your app’s UI. If your app supports this on iPad, it will work on iPhone Duo as well. There is one thing to keep in mind. On iPad, new windows can be created at any time. On the outer display of iPhone Duo, new windows cannot be created. That behavior is reserved for the inner display.

  • This dynamic availability is a behavior unique to iPhone Duo. Make sure you handle errors when requesting new scenes. Use UIWindowSceneActivationAction, which automatically hides the button that uses it when new windows aren’t available.

Scene Accessories

  • Scene accessories (not yet in Apple’s API reference — see the Leverage multiple displays and scenes on iPhone Duo Tech Talk) allow your app to show content on multiple displays at the same time.

  • For iPhone and iPad, scene accessories give your app the ability to pair additional content with your app’s main UI, like using your iPhone as a controller for a game on an external display. They are enabled by default, but can be toggled at any time.

  • New for camera apps on iPhone Duo, CameraCaptureAccessory allows you to pair additional UI on the outer display, while your app’s main UI stays on the inner display. This lets you show content to a person while taking their photo or recording them.

    struct CameraRootView: View {
        @State private var model = TeleprompterModel()
    
        var body: some View {
            CameraView(model: model)
                .sceneAccessory {
                    CameraCaptureAccessory(isEnabled: $model.isEnabled) {
                        TeleprompterView(model: model)
                    }
                    .onAvailabilityChange { newValue in
                        model.isAvailable = newValue
                    }
                }
                .toolbar {
                    TeleprompterToggle(isEnabled: $model.isEnabled)
                        .disabled(!model.isAvailable)
                }
        }
    }
    

Camera

  • iPhone Duo is the first iPhone to support two front cameras.
  • Use the discovery session AVCaptureDeviceDiscoverySession to find the front camera.
  • Use the builtInOuterUltraWideCamera to access the front camera on the outer display.
  • Use the builtInInnerUltraWideCamera to access the one on the inner display.
  • Use the new AVCaptureDeviceDirectionCoordinator API (none of the two camera device types or this coordinator are in Apple’s API reference yet — see the Build a great camera experience for iPhone Duo Tech Talk) to manage the transition between the outer and inner cameras.
  • On iPhone Duo, a camera app can use both displays at once.

Migrating an Existing App

  • When people use iPhone Mirroring on the Mac, they can fully resize the iPhone window, allowing your apps to resize and adapt.
  • If your app is a universal binary, you are off to a great start.
  • Here are the most important steps and common issues you might encounter when making your app more adaptive:
    • Verifying your app is no longer using app lifecycle, and instead has adopted scene lifecycle, the basis for any adaptive app.
    • Making sure your app is not using main screen references, and how to handle user interface idiom and interface orientation checks.

Scene Lifecycle

  • App lifecycle to scene lifecycle (i.e., App Delegate to Scene Delegate):
    • Basis for an adaptive app
    • Require when linking iOS 27

Avoid Main Screen References

  • It is important that you do not reference the main screen in your app. Instead, access the screen dynamically from a window’s window scene.

    // Use local screen references
    // Access the correct screen through a windowScene
    let screen = window?.windowScene?.screen
    
    // Pass in local screen references
    func generateThumbnail(_ image: UIImage, screen: UIScreen) -> UIImage {
        // existing code, replacing main screen with local screen reference
        // ...
    }
    

Screen Geometry & Display Scale

  • Accessing the scale of the screen should be replaced with the trait collection’s displayScale (traitCollection.displayScale).

  • In places where automatic trait tracking is not available, registerForTraitChanges can be used to observe changes.

  • Scenes are not always full-screen.

  • The window scene’s effective geometry provides information on how much space your app has available. If you need to monitor the effective geometry for changes, implement windowScene(_:didUpdateEffectiveGeometry:) in your scene delegate.

    // UIWindowSceneDelegate
    func windowScene(
        _ windowScene: UIWindowScene,
        didUpdateEffectiveGeometry previousEffectiveGeometry: UIWindowScene.Geometry
    ) {
        let geometry = windowScene.effectiveGeometry
        let availableSpace = geometry.coordinateSpace.bounds
        // ...
    }
    
  • In your views and view controllers, instead of referencing the scene bounds, use the available size of your view controller’s view or your view’s superview to determine how much space is available to it.

    // Checking available space
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
    
        let availableSpace = view.bounds.size
        // ...
    }
    
  • For games, resizing can be challenging. Due to this, UIRequiresFullscreen is honored on iPhone in resizable environments starting in iOS 27. Its behavior has also been updated and no longer opts your app fully out of resizing. Instead, it enables discrete resizing that honors your supported interface orientations.

User Interface Idiom & Interface Orientation

  • This trait is no longer meaningful for any kind of layout decision.
  • Your app is expected to use the additional space in a meaningful way, regardless of whether it is running in the phone or pad user interface idiom.
  • When your iPhone app is running on an iPad or in iPhone Mirroring on the Mac, it will be fully resizable, but it will still run under the phone user interface idiom. Stop checking the user interface idiom for any layout decisions in your code.
  • Use size classes instead to handle sizing constraints, such as collapsing menus and updating your app’s layout for the available space.
  • Interface orientation is no longer useful for layout decisions either, and is always portrait in iPhone Mirroring. Use size classes and scene bounds instead.

Agentic Coding

  • New in Xcode 27 is an app modernization skill. It has a deep understanding of the adaptivity tasks, and with the context of your project, it can make a lot of these changes automatically. Use Xcode’s intelligence features and ask an agent to make your app more adaptable.
  • It will automatically convert main screen calls to traitCollection or scene bounds checks, adding invalidation logic if necessary.
  • It will also replace interface orientation checks with size class checks. It will even convert your app to use scene lifecycle.
  • For more complex tasks, it will ask clarifying questions. And for tasks too large to handle in a single session, it will add comments to help you keep track of the remaining work.
  • And to use skills in other tools, you can export the ones Xcode uses with xcrun agent skills export. This will create markdown files that you can then import in your workflows.

References

Design diagrams and screenshots throughout this post are sourced from Apple’s Tech Talks and design references below, unless otherwise noted.