Когда я впервые увидел API ArrangementView, в нём было что-то слегка неудобное, хотя я не сразу понял, что именно. Только когда я действительно попробовал его в Xcode 27.1 beta, это ощущение стало более конкретным. После того как я подробнее изучил материалы Apple и снова рассмотрел API в контексте iPhone Duo, я понял, что дискомфорт возникал по двум причинам. Во-первых, я просто не до конца понимал, для каких задач предназначен этот API; когда это стало ясно, часть вопросов исчезла. Но вторая причина была связана уже с самим API — и она осталась даже после того, как я понял его замысел.
Чтобы правильно использовать ArrangementView, полезно сделать ещё один шаг назад, прежде чем использовать его: как именно связаны эти два фрагмента содержимого? Какие решения можно доверить контейнеру, а какие компромиссы всё ещё должен определять сам продукт? Сначала подумайте — потом компонуйте.
Необычный контейнер
В отличие от обычных контейнеров компоновки, ArrangementView предназначен не просто для размещения произвольной пары представлений. Вместо этого он описывает отношение между двумя представлениями с помощью набора правил компоновки, а затем объединяет эти правила с доступным пространством, соотношением сторон, классами размеров и областями разделения устройства, чтобы определить, какое из представлений будет показано и где оно окажется. Apple называет такое отображение входных условий в итоговую компоновку arrangement (договоренность).
Чтобы пользоваться им правильно, нужно смотреть на API сразу с нескольких сторон:
- Объявляйте отношения, а не геометрию. В этом смысле он немного похож на
NavigationSplitView. Разработчик указывает, какое представление основное, а какое дополнительное, и выбирает split или overlay; фактическую геометрию контейнер определяет сам, исходя из окружения. Чтобы заранее понимать результат, нужно сначала разобраться в его правилах компоновки и fallback-поведении. - Видимость должна быть частью проектирования содержимого. В режиме split, если контейнер не может разделить доступное пространство по указанной оси, он может оставить только основное представление. Это уже не просто компромисс компоновки, а компромисс в информации и взаимодействии. Дополнительное представление может сохранить своё состояние и восстановить его при повторном появлении, но разработчику всё равно нужно продумать, как пользователь сможет получить доступ к содержащейся в нём информации, пока оно скрыто.
- Конфигурация задаёт предпочтения, а не окончательный результат.
splitArrangementLayoutRatioиsplitArrangementFixedLayoutSizeвыражают предпочтения или ограничения размера, аoverlayArrangementEdgeвлияет на позиционирование, когда overlay переходит в режим бок о бок. Эти параметры распределены по дочерним представлениям, и не все они применимы к каждому возможному результату. Поэтому разработчику нужно понимать не только сами настройки, но и то, как контейнер в итоге их интерпретирует.
С некоторых точек зрения ArrangementView ближе к контейнеру вроде NavigationSplitView, где адаптивное поведение уже встроено. Разница в том, что у NavigationSplitView есть ясная навигационная семантика, явное состояние видимости и хорошо определённое поведение при схлопывании содержимого — например, в компактной ширине он превращается в одноколоночную стековую навигацию. ArrangementView, напротив, должен описывать более широкий спектр отношений между содержимым, из-за чего его семантическую основу сложнее определить.
Неоднозначное понятие primary и secondary
Компромиссы, которые принимает ArrangementView, происходят на уровне представлений. Когда одно из них временно исчезает, вопрос уже не только в его размере, но и в том, остаются ли доступными его информация и взаимодействия.
В split-компоновке смысл primary довольно очевиден: если ограничения не позволяют сохранить разделение, именно primary-представление получает приоритет и остаётся видимым.
Но в overlay тот же термин получает другую роль: когда контейнер использует многослойную компоновку, primary-представление оказывается поверх secondary. Это отличается от привычной семантики модификатора overlay. В случае обычного модификатора поверх исходного представления добавляется новый слой, а исходное остаётся снизу. В overlay внутри ArrangementView, напротив, primary означает именно верхний слой.
Из этого следует, что представление, которое логично оставить видимым как primary в split, вовсе не обязательно должно быть верхним представлением в overlay.
Собственный пример Apple хорошо показывает эту разницу. При переходе от split к overlay компания меняет местами роли primary и secondary у PlayerView и UpNextView.
Проблема не в том, что какое-то из этих правил нелогично. Проблема в том, что два разных набора правил используют одну и ту же терминологию primary/secondary, хотя смысл этих ролей приходится определять по-разному в зависимости от текущего типа компоновки. Primary и secondary выглядят так, будто задают единое отношение между содержимым, но на деле их смысл всё ещё зависит от того, какой arrangement используется прямо сейчас.
Один контейнер, две логики
Объединить split и overlay в одном контейнере — не совсем нелогичное решение. Когда overlay сталкивается с активной областью разделения устройства, он и сам может перейти в режим бок о бок, визуально становясь очень похожим на split.
Но похожая геометрия не означает одинакового отношения между содержимым.
Особенно хорошо это видно при переключении между режимами. Если продукт должен переходить между split и overlay, разработчику всё равно приходится решать, когда должен происходить переход, нужно ли менять primary и secondary местами и как при этом сохранять состояние содержимого.
В SwiftUI .split и .overlay — разные конкретные типы. Для переключения между ними нужен if else, а значит, меняется и идентичность представления. Даже если завернуть выбор в собственный ArrangementViewStyle и выбирать режим по значению внутри makeBody, по сути это всё равно ещё один слой вокруг if else; добавленная анимация даст переход между состояниями, но не непрерывное преобразование одного в другое.
Я также протестировал updateArrangement(_:animated: true) в UIKit. Хотя экземпляр контейнера остаётся тем же, в Xcode 27.1 beta результат всё равно выглядит как жёсткое переключение.
По крайней мере сейчас непрерывного перехода между split и overlay нет. Их объединили внутри одного контейнера, но от этого они не стали единым непрерывным состоянием компоновки. На практике ArrangementView ощущается скорее как объединённые в одном контейнере ArrangementSplitView и ArrangementOverlayView.
Это также объясняет, почему API поначалу может показаться сложным. На поверхности кажется, что мы просто выбираем разные arrangements внутри одного контейнера. На практике же приходится иметь дело с двумя разными наборами логики содержимого, а также с конфигурацией и значениями окружения, которые применяются только к соответствующим режимам.
Нет единого источника истины
Когда контейнер сам отвечает за определение итоговой компоновки, возникает естественный вопрос: могут ли его дочерние представления узнать, какое решение он в итоге принял?
Вокруг ArrangementView Apple предоставляет такие значения, как overlayArrangementZIndex, splitArrangementAxis и reservedRegions для получения зарезервированных областей устройства. Каждое из них полезно, но в основном они раскрывают отдельные кусочки информации, участвующие в процессе компоновки, а не полный итоговый результат контейнера.
Разработчик может узнать ось split, порядок слоёв overlay и зарезервированные области устройства. Но гораздо сложнее напрямую ответить на более общий вопрос: какой именно arrangement пользователь видит сейчас?
Например, значение overlayArrangementZIndex равное 0 само по себе не говорит, находится ли представление в нижнем слое или overlay уже перешёл в режим бок о бок. Аналогично, splitArrangementAxis описывает ось разделения, но не полное состояние видимости. Разумеется, разработчик может вывести итог из дополнительного контекста, однако тогда приложению приходится самостоятельно собирать воедино несколько разрозненных сигналов.
Я бы предпочёл видеть централизованное состояние, уже разрешённое самим контейнером — например: split с указанием его оси, overlay с информацией о слоях или состояние, в котором сейчас отображается только primary-представление.
Важно не то, обязательно ли это должен быть ещё один enum. Главное в том, что сейчас у ArrangementView нет централизованного интерфейса, который описывал бы итоговый результат. Он передаёт дочерним представлениям отдельные фрагменты информации, влияющие на компоновку, но напрямую не сообщает им, какое решение в итоге принял контейнер.
В простых сценариях это легко не заметить. Но как только дочерним представлениям нужно адаптировать своё содержимое к фактическому arrangement, приложению приходится заново реконструировать решение, которое контейнер уже принял.
ArrangementView очень хорошо реагирует на пространственные особенности устройства, но не объединяет в одном месте решения уровня сценария, которые могут понадобиться продукту. Контейнер берёт на себя позиционирование, а приложение всё ещё должно самостоятельно хранить состояние содержимого и семантику каждого режима. Как универсальный контейнер это может ощущаться излишне сложно; как специализированный — недостаточно централизованно.
Его сильная сторона — одновременно и его граница
Если воспринимать ArrangementView как универсальный контейнер, описанные выше издержки быстро становятся заметнее. Но стоит поместить его в контекст iPhone Duo, как некоторые на первый взгляд странные правила сразу получают вполне конкретный смысл.
Складная конструкция создаёт новые доступные области, и двум связанным представлениям может понадобиться обходить область разделения, одновременно перераспределяя доступное пространство. ArrangementView управляет набором правил, которые было бы неудобно напрямую выражать через обычные стековые контейнеры, и в таком сценарии он ощущается удивительно естественно.
Это также объясняет ту часть моего первоначального дискомфорта, которая возникла из-за неправильного понимания роли API. Некоторые проблемы вовсе не были тем, что API не сумел решить; просто я ожидал от него слишком многого.
Но даже если мы нашли его идеальную область применения, это не означает, что он автоматически должен становиться первым выбором.
Apple по-прежнему рекомендует начинать с самого высокоуровневого доступного уровня абстракции: сначала стандартные адаптивные компоненты, затем ArrangementView, затем зарезервированные области, а данные о шарнире использовать только тогда, когда они действительно необходимы.
На мой взгляд, ArrangementView стоит рассматривать, когда интерфейсу действительно нужен собственный split или overlay. Перед его использованием я бы задал несколько более конкретных вопросов:
- Есть ли у двух представлений стабильное отношение «основное содержимое / детали» или «передний план / фон»?
- Когда пространства становится мало, какое представление может исчезнуть? Когда они перекрываются, какое должно находиться сверху? Можно ли выразить оба ответа одной и той же семантикой primary/secondary?
- Нужны ли дочерним представлениям лишь небольшие изменения в зависимости от итоговой компоновки, или большей части содержимого приходится самостоятельно определять и реконструировать текущий режим?
- Действительно ли системная обработка областей, связанных со сгибом устройства, устраняет решения по компоновке, которые в противном случае пришлось бы поддерживать самому приложению?
Если ответы очевидны — особенно когда интерфейс уже похож на один из двух типов отношений, которые демонстрирует Apple, — ArrangementView может оказаться очень удобным инструментом. Если же ответы неоднозначны или само переключение между этими отношениями является важной частью логики продукта, я бы скорее начал с HStack, VStack, ZStack, стандартных навигационных контейнеров или собственной компоновки.
Это вовсе не означает, что вы отказываетесь от какого-то «более продвинутого» подхода. Вы просто оставляете решение там, где лучше всего понимают само содержимое.
ArrangementView кажется неудобным потому, что он упаковывает очень специфический класс проблем устройства в то, что выглядит как универсальный контейнер для двух представлений. Но в этом же заключается и его элегантность: когда отношение между содержимым действительно соответствует правилам устройства, сложность, которую иначе пришлось бы реализовывать в приложении, можно передать системе.
Подумайте на шаг вперед перед тем, как использовать его. Когда отношение между представлениями станет ясным — тогда и компонуйте. Сначала подумайте — потом компонуйте.

