Компонент, который мы собираемся создать, доступен как Swift Package.   Утро понедельника, и ваш менеджер проекта дает вам задание: добавить список тегов на страницу подробной информации о продукте. Вы говорите «изи» и через 10 минут выдаете следующее. После проверки, команда QA сообщает о возникающей ошибке при большом количестве тегов. Вы продолжаете и делаете для них горизонтальную прокрутку. Недостатком является то, что вы должны заранее знать высоту вьюшек тегов. Команда дизайнеров просит переносить теги на последующие строки, когда они не соответствуют ширине основного вью. И теперь ваше "изи" превратилось в “хардово". Один из ваших коллег предлагает обернуть пользовательский UICollectionView в UIViewRepresentable. А другой - попробовать новый протокол Layout. Вы решили продолжить с Layout… Протокол имеет 2 требования: sizeThatFits определяет, сколько места требует Вью. placeSubviews управляет размещением сабвью(-шек) внутри доступного пространства. Обратите внимание, что sizeThatFits может вызываться несколько раз в процессе компоновки. Он будет пробовать различные предложения по размерам. На момент написания статьи, в iOS он как обычно просто попытается передать все доступное пространство. В macOS он также попытается предложить размер .zero, чтобы можно было вычислить минимальный размер окна. Таким образом, для поддержки macOS нам потребуется вычислить минимальный размер вью. За минимальный размер мы возьмем максимальный размер сабвью в предложении .zero. Всякий раз, когда предложение меньше минимального размера, мы просто вернем минимальный размер раньше. Чтобы вычислить как размер, так и размещение, нам нужно сначала расположить эти сабвью в строки. Основная идея состоит в том, чтобы перебирать сабвью и увеличивать координату X на ширину сабвью + горизонтальный интервал, если он все еще умещается в ширину контейнера, или в противном случае переходить к следующей строке. Это позволит нам получить смещения X для всех сабвью. Затем мы будем перебирать строки и увеличивать координату Y на максимальную высоту сабвью + интервал по вертикали. Это позволит нам получить смещения Y для всех строк. Как только у нас будет расположение строк, ширина заполнит все доступное пространство. И высота будет равна вертикальному смещению последней строки + ее высота. Сабвью будут размещены на соответствующих им смещениях + точка минимального значения границ. Для каждой строки нам нужно знать индексы наших сабвью, размеры и смещения по оси X. Кроме того, общее смещение строки по Y, ширину и высоту строки. Мы разрешаем переопределение горизонтального и вертикального интервалов или используем системные интервалы, если они равны nil. Прокси LayoutSubview позволяет получить системный интервал для пары сабвью. Протокол Layout имеет опциональный параметр layoutProperties, который позволяет управлять StackOrientation. Это влияет на способ обработки Spacer и Divider. Например, при stackOrientation = .horizontal, Spacer будет расширяться только по горизонтали. Таким образом, это позволит обеспечить разрыв линии (строки) в контейнере. У него есть оговорка, что между разделенными строками будет двойной интервал, а системный интервал по умолчанию будет равен нулю. Мы разрешим контролировать значение выравнивания внутри контейнера. Вот только протокол Layout не предоставляет простой способ реализации различных значений выравнивания базовой линии текста: .leadingFirstTextBaseline, .centerLastTextBaseline и т. д. Остальные значения соответствуют значениям UnitPoint. Нам нужно будет внести поправку в placeSubviews со значением привязки. Мы будем кэшировать минимальный размер контейнера и расположение строк для повышения производительности. Расположение строк зависит как от размера предложения, так и от размера сабвью. Всякий раз, когда они изменяются, расположение строк должно быть пересчитано. После всей этой работы мы можем, наконец, переопределить наш список тегов. Контейнер по своей конструкции не поддерживает сабвью, которые бесконечно увеличиваются по вертикальной оси. Как бы вы вообще определили высоту в этом случае? Мы написали наш контейнер универсальным способом, который может обрабатывать самые разные сабвью. Это определенно было непросто, и теперь мы можем оценить простоту использования стандартных HStack и VStack. Полный код смотрите на https://github.com/ksemianov/WrappingHStack.