Building iPhone Duo Layouts with ArrangementView


Greetings, traveler!

SwiftUI has several ways to build adaptive layouts. You can switch between HStack and VStack, use ViewThatFits, work with size classes, or implement a custom Layout.

ArrangementView solves a slightly different problem. Instead of describing exactly where two views should appear, you describe their relationship and let the system decide how to present them in the current environment.

The API is available with the system provided arrangements in iOS 27.1.

What ArrangementView does

ArrangementView manages two pieces of content: a primary view and a secondary view.

A basic example looks like this:

NavigationStack {
    ArrangementView {
        PlayerView()
    } secondary: {
        UpNextView()
    }
}

The container can change the position, size, and visibility of these views depending on factors such as the available geometry, size classes, aspect ratio, and active division regions.

This matters on devices where the usable display can change significantly. Apple introduced the API while discussing layouts for iPhone Duo, where a hinge can divide the display into separate usable regions.

You normally do not need to inspect that geometry yourself when ArrangementView can handle it.

Using the split arrangement

The default arrangement is .split.

ArrangementView {
    PlayerView()
} secondary: {
    TranscriptView()
}
.arrangementViewStyle(.split)

A split arrangement divides the available area between both views.

When the container is wider than it is tall, SwiftUI prefers a horizontal split. When it is taller than it is wide, SwiftUI can use a vertical split.

You describe the relationship once and leave the exact placement to the system.

You can also restrict the split to a particular axis:

ArrangementView {
    PlayerView()
} secondary: {
    TranscriptView()
}
.arrangementViewStyle(
    .split.axes(.horizontal)

)

There is an important consequence to this restriction. If the arrangement cannot reasonably split along its preferred axis, the system may show only one of the views instead of forcing both into an unsuitable layout.

Using the overlay arrangement

The second system provided style is .overlay.

ArrangementView {
    ContentView()
} secondary: {
    ControlsView()
}
.arrangementViewStyle(.overlay)

The overlay arrangement is intended for content with a foreground and background relationship.

Instead of always reserving separate space for both views, the arrangement can place one above the other. In another device configuration, it can move them into separate regions.

A media interface is a good example. A compact player might appear above a list in one configuration but receive its own region when more appropriate space becomes available.

Responding to an overlay arrangement

A child view can determine whether it currently appears above another view through the overlayArrangementZIndex environment value.

struct ControlsView: View { @Environment(\.overlayArrangementZIndex)
    private var zIndex
    var body: some View {
        Controls(
            compact: zIndex > 0
        )
    }
}

This lets the content adapt to its placement.

For example, a secondary panel could show a compact version while it overlays the primary content:

var mode: Mode {
    zIndex > 0 ? .collapsed : .expanded
}

When the arrangement moves that panel into its own region, the same view can switch to an expanded representation.

The layout decision stays with the system while the view still controls how its content looks in each state.

Split or overlay?

Apple suggests choosing the arrangement based on the relationship your interface already has.

Use .split when both pieces of content should remain independently visible. A main view with a transcript, inspector, detail panel, or related list fits this model.

Use .overlay when one view can reasonably appear above another.

A useful approximation is to look at the layout you would otherwise use. Interfaces based on HStack or VStack are candidates for .split. Interfaces based on ZStack are candidates for .overlay.

ArrangementView versus Layout

ArrangementView does not replace SwiftUI’s Layout protocol.

A custom Layout gives you direct control over the geometry of its subviews. Your implementation measures them and decides exactly where to place them.

struct CustomLayout: Layout {
    // Measure and place subviews manually.
}

With ArrangementView, you do not calculate those positions. You declare that two pieces of content have a particular relationship, the system chooses the presentation.

This makes ArrangementView useful at a higher level of the view hierarchy. It is closer to an adaptive presentation container than a replacement for HStack, VStack, or a custom Layout.

ArrangementView versus AnyLayout

AnyLayout can also switch between layouts while preserving the identity of their subviews.

For example:

let layout = isHorizontal

    ? AnyLayout(HStackLayout())

    : AnyLayout(VStackLayout())

layout {

    PrimaryView()

    SecondaryView()

}

The important difference is who makes the decision.

With AnyLayout, your code determines when to use the horizontal or vertical layout.

With ArrangementView, you provide the semantic relationship between the two views and allow the system to decide how that relationship should appear.

That distinction becomes more useful as device geometry becomes less predictable.

Working with reserved regions

iPhone Duo introduces reserved regions that represent hardware areas affecting usable content.

SwiftUI can query them through GeometryProxy:

GeometryReader { proxy in

    let regions = proxy.reservedRegions(

        kind: .division

    )

}

A division region separates usable areas, such as the region around a hinge.

You can also query occlusion regions:

let regions = proxy.reservedRegions(

    kind: .occlusion

)

Those describe hardware that covers part of the display.

For manually positioned controls, these APIs give you the information needed to adjust your layout.

For common two region layouts, ArrangementView can account for relevant layout conditions without requiring you to manually calculate the position of each view.

Where ArrangementView belongs

Apple recommends placing ArrangementView below the navigation container.

NavigationStack {

    ArrangementView {

        PrimaryView()

    } secondary: {

        SecondaryView()

    }

}

ArrangementView does not provide navigation infrastructure itself.

Because of that, putting a navigation container such as NavigationSplitView inside an arrangement is generally the wrong hierarchy.

Apple also advises against placing ArrangementView inside List or ScrollView because of the way scrollable containers participate in layout.

Think of it as a container for major regions of an interface rather than something you add around individual rows or small components.

When to use ArrangementView

ArrangementView makes sense when your screen naturally contains two related areas and their placement can change with the environment.

A player and its queue, an editor and inspector, a document and related controls, or a main view with supporting information are good candidates.

If you already know the exact layout you want and only need to position a set of smaller views, stacks or the Layout protocol remain more appropriate.

The useful part of ArrangementView is that you stop encoding every possible physical configuration yourself.

Instead of saying:

Place A to the left of B.

you describe:

A is primary.

B is secondary.

Their relationship is split.

SwiftUI decides how to express that relationship for the current geometry.

That is especially useful on iPhone Duo, but the underlying idea is broader: adaptive layout code can describe the role of content instead of hardcoding every possible arrangement.