Site icon AppTractor

Наконец-то я нашел применение для .fixedSize

Если бы неделю назад мне сказали, что .fixedSize спасёт мне вторую половину рабочего дня, я бы рассмеялся.

Уже одно название словно предупреждает: этот модификатор загонит вас в угол. Кому вообще нужны представления, зафиксированные в статическом размере? Поэтому, как и многие разработчики на SwiftUI, я мысленно отправил его в категорию «модификаторы, которыми я никогда не воспользуюсь».

А затем мне досталась задача по компоновке, которую — вот неожиданность — можно было аккуратно решить только с помощью .fixedSize.

И она преподала мне урок, который стоило усвоить много лет назад: плохих API не существует — бывают лишь интерфейсы, для которых вы ещё не нашли подходящий сценарий. Каждый модификатор SwiftUI появился по определённой причине. Принципиально избегая какого-то из них, вы без всякой пользы сокращаете собственный набор инструментов.

Давайте разберём задачу.

Задача

Требование звучало просто:

Мы хотим, чтобы все элементы в области прокрутки имели такую же высоту, как самый высокий из них.

Представьте горизонтальный ScrollView, внутри которого находится HStack. Каждый элемент представляет собой карточку с заголовком и описанием.

Длина описаний различается: некоторые короткие, другие длинные. Команда дизайнеров не хотела обрезать текст. Нужно было лишь сделать так, чтобы высота всех карточек соответствовала самой высокой карточке. Тогда строка выглядела бы аккуратно и продуманно, а не состояла из элементов разной высоты.

Цель: растянуть короткую карточку до высоты самой высокой независимо от того, какой именно элемент оказался самым высоким.

Звучит элементарно, правда? На самом деле нет — особенно когда вы годами избегали единственного модификатора, способного решить задачу.

Первая попытка и почему она дала обратный результат

Первой мыслью было очевидное решение: заставить каждую карточку заполнить всё доступное пространство по вертикали.

ScrollView(.horizontal) {
    HStack(alignment: .top, spacing: 16) {
        ForEach(items) { item in
            CardView(item: item)
                .frame(maxHeight: .infinity, alignment: .topLeading)
        }
    }
}

Результат?

Все карточки растянулись на всю высоту экрана. В каком-то смысле задача была выполнена.

Вот почему это произошло: у HStack не было собственного ограничения по высоте, поэтому он принял предложенный родительским ScrollView размер — фактически высоту всего экрана. После этого .frame(maxHeight: .infinity) на каждой карточке с готовностью растянул её до размеров огромного контейнера.

Получилась половина решения. Высота карточек действительно стала одинаковой. Оставалось только заставить сам HStack перестать занимать всё доступное пространство и уменьшиться до высоты самой высокой карточки.

Модификатор, которого я избегал

Именно это делает .fixedSize(horizontal: false, vertical: true). Он сообщает представлению:

Игнорируй высоту, предложенную родителем, и определи собственную высоту на основании идеального размера содержимого.

ScrollView(.horizontal) {
    HStack(alignment: .top, spacing: 16) {
        ForEach(items) { item in
            CardView(item: item)
                .frame(maxHeight: .infinity, alignment: .topLeading)
        }
    }
    .fixedSize(horizontal: false, vertical: true) // 👈 the one line that fixes everything
}

В результате все карточки получают высоту самого высокого элемента, а сама строка не становится выше, чем необходимо.

Почему это действительно работает 🤔

Стоит подробнее рассмотреть происходящее при компоновке. Это отличный пример того, как в SwiftUI работает модель «родитель предлагает размер, ребёнок принимает решение».

  1. ScrollView предлагает HStack всю доступную высоту.
  2. .fixedSize(vertical: true) перехватывает это предложение: «Спасибо, но я выберу идеальную высоту на основании содержимого».
  3. Идеальная высота HStack равна высоте самого высокого дочернего элемента.
  4. HStack предлагает эту зафиксированную высоту каждой карточке.
  5. .frame(maxHeight: .infinity) растягивает все карточки до этой высоты.

Таким образом, два модификатора не конфликтуют друг с другом — они работают в паре.

.fixedSize определяет, какой должна быть высота: ровно такой, чтобы вместить самую высокую карточку. .frame(maxHeight: .infinity) определяет, как остальные карточки должны вести себя по отношению к этой высоте: полностью её заполнить.

Уберите любой из них — и всё перестанет работать. Без .fixedSize стек растянется на весь экран. Без .frame(maxHeight: .infinity) короткие карточки останутся короткими, и строка снова будет неровной.

Задача решена ✅

Главный урок

Признаюсь честно: на поиск решения у меня ушло около полутора часов.

Не потому, что само решение сложное — это буквально одна строка. Просто я постоянно обходил .fixedSize стороной и не хотел его пробовать. Мой мозг уже пометил этот модификатор как «опасный» и отказывался пересматривать такую оценку.

Проблема была не в сложной компоновке. Я сам ограничил собственный набор инструментов.

В следующий раз, когда поймаете себя на мысли «я никогда не использую этот модификатор», остановитесь и спросите себя почему. Если ответ звучит как «у него пугающее название» или «я когда-то прочитал сообщение, где советовали его избегать», это не настоящая причина.

Каждый модификатор SwiftUI существует потому, что кто-то в Apple столкнулся с задачей компоновки, которую он решает. Ваша задача — понимать, для какой проблемы предназначен каждый инструмент.

Не ограничивайте возможные решения. Ответ может находиться прямо перед вами — просто под названием, которое вы привыкли игнорировать 😅

Источник

Exit mobile version