Компоновка: стеки и контейнеры

Чтобы расположить элементы на экране, SwiftUI использует стеки — контейнеры, выстраивающие содержимое по вертикали, горизонтали или слоями.
Суть урока: VStack ставит элементы столбиком, HStack — в строку, ZStack — слоями друг на друга. Вкладывая стеки друг в друга, вы собираете любой макет, как из конструктора.

Три базовых стека покрывают почти всю компоновку. VStack (vertical) выстраивает дочерние вью сверху вниз:

VStack(alignment: .leading, spacing: 12) {
    Text("Заголовок").font(.title)
    Text("Подзаголовок").foregroundStyle(.secondary)
}

HStack (horizontal) ставит элементы в ряд, а ZStack накладывает их слоями по глубине — удобно для фона под содержимым:

HStack {
    Image(systemName: "person.circle")
    Text("Профиль")
    Spacer()              // расталкивает по краям
    Text(">")
}

ZStack {
    Color.blue            // нижний слой
    Text("Поверх").foregroundStyle(.white)
}

Spacer — гибкая распорка: она занимает всё свободное место, отодвигая соседей. Параметры alignment и spacing управляют выравниванием и расстоянием. Настоящая мощь — во вложенности: стек внутри стека даёт сложные макеты.

VStack {
    HStack {
        Text("Слева"); Spacer(); Text("Справа")
    }
    HStack {
        ForEach(1...3, id: \.self) { i in
            Text("\(i)").padding().background(.gray.opacity(0.2))
        }
    }
}
VStack                    HStack                 ZStack
  +--------+               +--+--+--+              +--------+
  |   A    |               | A| B| C|              | задний |
  +--------+               +--+--+--+              |[передн]|
  |   B    |               по горизонтали          +--------+
  +--------+                                       слоями
 по вертикали

Попробуй сам ▶ — запусти код прямо в браузере (Pyodide). Здесь нет Swift, но логика та же, что под капотом мобильного кода:

# Стеки как вложенные списки: моделируем структуру макета.
layout = {
    'VStack': [
        {'HStack': ['Слева', 'Spacer', 'Справа']},
        {'HStack': ['1', '2', '3']},
    ]
}

def show(node, depth=0):
    pad = '  ' * depth
    if isinstance(node, dict):
        for name, kids in node.items():
            print(pad + name)
            for k in kids:
                show(k, depth + 1)
    else:
        print(pad + str(node))

show(layout)

Как работает под капотом

Компоновка в SwiftUI — это переговоры между родителем и детьми. Родительский стек предлагает каждому дочернему вью доступное место; вью сообщает, сколько ему нужно; стек расставляет их и решает, кому отдать остаток. Spacer — это вью с минимальным «желаемым» размером, но максимальной гибкостью, поэтому он забирает всё свободное пространство. Понимание этого диалога объясняет, почему элементы иногда сжимаются или растягиваются не так, как вы ожидали.

Частые ошибки

  • Забыть Spacer для выравнивания. Без него элементы центрируются, а не прижимаются к краям.
  • Превышать лимит дочерних вью. Стек принимает до 10 прямых детей; больше — оборачивайте в Group или ForEach.
  • Путать alignment стека и выравнивание текста. Это разные уровни настройки.

Best practices

  • Стройте макеты вложением простых стеков, а не одним гигантским.
  • Используйте Spacer и параметр spacing вместо магических отступов.
  • Применяйте ForEach внутри стеков для повторяющихся элементов.

Итоги. VStack, HStack и ZStack — три оси компоновки: вертикаль, горизонталь и глубина. Spacer управляет свободным местом, а вложенность даёт сколь угодно сложные макеты. Это основа верстки любого экрана в SwiftUI.

Шире контекста

Система компоновки SwiftUI устроена как вежливый диалог между родителем и детьми, и понимание этого диалога избавляет от множества загадок «почему оно так встало». Родитель предлагает доступное место, ребёнок сообщает желаемый размер, родитель расставляет всех и делит остаток. Spacer — крайний случай: он почти не хочет места для себя, но жадно забирает всё свободное. Помимо трёх базовых стеков, в современном SwiftUI есть Grid для табличных макетов, LazyVStack и LazyHStack для длинных списков, которые создают элементы по мере прокрутки, экономя память. Но именно VStack, HStack и ZStack остаются рабочими лошадками, и почти любой экран — это их вложенная комбинация. Научившись мысленно раскладывать готовый дизайн на стеки (что идёт по вертикали, что по горизонтали, что слоями), вы сможете воспроизвести практически любой макет, а отладчик иерархии вью в Xcode поможет понять, как именно распределилось пространство.

Проверьте себя
1. Какой стек располагает элементы слоями друг на друга по глубине?
AVStack
BHStack
CZStack
DGridStack
2. Что делает Spacer внутри HStack?
AДобавляет фиксированный отступ 8 поинтов
BЗанимает всё свободное место, расталкивая соседей
CЦентрирует текст
DСоздаёт новую строку