Size classes inside ArrangementView

⋅ 4 min read ⋅ iOS SwiftUI UIKit

Table of Contents

After I posted my table of size classes in ArrangementView, Chad Etzel noticed something odd. The view at the top of each pane reported the window's size classes, but a view inside it reported different ones.

My table only read the top view, so I tested it again. Chad is right.

What I posted on iOS 27.1

This is the image from my tweet. I read the size classes in the view I passed straight to ArrangementView, on the iOS 27.1 iPhone Duo simulator.

ArrangementView size classes on iPhone Duo, as posted. Folded portrait, stacked: both panes Compact and Regular, 382 by 322 points. Folded landscape: First view only, Compact and Compact, 594 by 432 points. Half-open portrait, stacked: both Regular and Regular, 669 by 373 and 669 by 421 points. Half-open landscape, side by side: both Regular and Regular, 455 by 635 and 371 by 635 points. Open portrait, stacked: both Regular and Regular, 669 by 417 points. Open landscape, side by side: both Regular and Regular, 433 by 635 points.
The size classes I posted, read from the top view of each pane on iOS 27.1.

Here is the weird part. Every pane reports the window's size classes, no matter how small it is.

  • Half-open and side by side, the Secondary view is only 371 points wide, but it still reports a Regular width. An iPhone 17 in portrait is 402 points wide, and it's Compact.
  • Stacked, the panes are only 373 and 421 points tall, but they still report a Regular height.

A view that reads a regular width at 371 points would pick its iPad layout in a space narrower than an iPhone. That didn't look right.

It turns out these values are only what the top view sees, the view I passed straight to ArrangementView. If you add a child view inside it, the child reads the size classes you'd expect: compact along the axis that is split.

Here is the same test, with the size classes read by a child view inside each pane:

Six iPhone Duo screenshots of an ArrangementView with a pink and a blue pane, each pane showing the size classes read by a child view inside it. Folded portrait, stacked: both panes Compact and Compact. Folded landscape, first view only: Compact and Compact. Half-open and open portrait, stacked: both panes Regular and Compact. Half-open and open landscape, side by side: both panes Compact and Regular.
The size classes read by a child view inside each pane, in every pose. Measured on the iOS 27.1 iPhone Duo simulator.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

The top view on iOS 27.1

Here is the part that confused us both. On iOS 27.1, the view you pass straight to ArrangementView reads the window's size classes. Every view below it reads the pane's.

ArrangementView {
PaneView() // iOS 27.1: the window's size classes
} secondary: {
PaneView()
}

struct PaneView: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass // Regular

var body: some View {
ChildView() // reads Compact
}
}

On iOS 27.2, the same PaneView reads the pane's size classes, like everything inside it.

The same app on a half-open iPhone Duo with a pink and a blue pane side by side, on iOS 27.1 and iOS 27.2. Each pane shows the size classes of its top view and of a view inside it. On iOS 27.1, the top view reads Regular and Regular, and the view inside reads Compact and Regular. On iOS 27.2, both read Compact and Regular.
Half-open iPhone Duo, side by side. On iOS 27.1, only the top view of each pane gets the window's size classes. On iOS 27.2, it matches the views inside.

To check that it's about the top view, I wrapped PaneView in one extra VStack on iOS 27.1. Then PaneView was no longer the top view, and it read Compact / Regular like the rest.

That's why my earlier table said the panes keep the window's size classes. I was reading the one view that does, on iOS 27.1.

iOS 27.2

On iOS 27.2, the top view reads the same size classes as the views inside it. Here is the corrected table (Horizontal / Vertical):

Pose Layout Size (points) Each pane
Folded, portrait Stacked 382 × 322 each Compact / Compact
Folded, landscape First view only 594 × 432 Compact / Compact
Half-open, portrait Stacked 669 × 373, 669 × 421 Regular / Compact
Half-open, landscape Side by side 455 × 635, 371 × 635 Compact / Regular
Open, portrait Stacked 669 × 417 each Regular / Compact
Open, landscape Side by side 433 × 635 each Compact / Regular
Six iPhone Duo screenshots on iOS 27.2 of an ArrangementView with a pink and a blue pane, each pane showing the size classes read by its top view. Folded portrait, stacked: both panes Compact and Compact. Folded landscape, first view only: Compact and Compact. Half-open and open portrait, stacked: both panes Regular and Compact. Half-open and open landscape, side by side: both panes Compact and Regular.
The size classes read by the top view of each pane, measured on the iOS 27.2 iPhone Duo simulator. They match what a child view reads on iOS 27.1.

UIKit gets the same values. A UIView, a UIViewController, and a UIHostingController inside a pane all matched the SwiftUI views.

iPad

ArrangementView works on iPad too. Here it is full screen on iPad Pro 13-inch (M5), iOS 27.2:

Orientation Layout Size (points) Each pane
Portrait Stacked 1032 × 662 each Regular / Compact
Landscape Side by side 688 × 980 each Compact / Regular
An iPad Pro 13-inch with a pink and a blue pane in an ArrangementView, each pane showing the size classes of its top view and of a view inside it. Portrait, stacked, 1032 by 662 points each: both read Regular and Compact. Landscape, side by side, 688 by 980 points each: both read Compact and Regular.
iPad Pro 13-inch (M5), full screen, iOS 27.2. The top view and the view inside agree.

These are the same size classes as an unfolded iPhone Duo. Half-open or open, iPhone Duo also gets Regular / Compact when stacked and Compact / Regular side by side. So when it's unfolded, your panes get the same size classes as they would on iPad, even on the biggest iPad.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Summary

  • Inside an ArrangementView, the axis that is split goes compact. Side by side gives a compact width. Stacked gives a compact height.
  • iOS 27.1 seems to have a bug: the view you pass straight to ArrangementView reads the window's size classes, not the pane's.
  • If you need the pane's size classes on iOS 27.1, read them in a view nested inside a container, not in the top view. One extra VStack is enough:
ArrangementView {
VStack {
PaneView() // Now reads the pane's size classes on iOS 27.1 too.
}
} secondary: {
VStack {
PaneView()
}
}
  • iOS 27.2 doesn't have this problem. The top view and the views inside it agree.
  • If you need the window's size classes, read them outside the ArrangementView and pass them in.
struct ContentView: View {
@Environment(\.horizontalSizeClass) private var windowSizeClass

var body: some View {
ArrangementView {
PaneView(windowSizeClass: windowSizeClass)
} secondary: {
PaneView(windowSizeClass: windowSizeClass)
}
}
}

Thanks to Chad Etzel for spotting this.


Read more article about iOS, SwiftUI, UIKit, or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

I also make short videos and visual guides on Patreon. Some things are easier to see than to read, like how an API looks and moves on a real screen. If you like to learn that way, come and watch. Sharing the article is also greatly appreciated.

Become a patron Tweet Share
Previous
How to use layoutMarginsGuide in SwiftUI

iOS 27.1 gives SwiftUI its own version of UIKit's layoutMarginsGuide, contentMargins(for: .container). Here is how to apply the system margins to a view, how to read them from a GeometryProxy, and what to do on older iOS.

← Home