Куда класть элементы в ячейке для правильной работы self-sizing
Что проверяют
Знаете ли вы, что self-sizing в UITableView и UICollectionView работает от Auto Layout внутри contentView, и умеете ли чинить ячейки, которые схлопываются или ругаются на конфликт констрейнтов. Следом часто спрашивают, как обновить высоту ячейки, если контент изменился после показа.
Как отвечать
Короткий ответ: все сабвью добавляются в contentView, а не в саму ячейку, и привязываются констрейнтами к его краям.
Что нужно для расчёта высоты:
- непрерывная вертикальная цепочка: от верхнего края
contentViewчерез все элементы до нижнего должны идти констрейнты. Если нижнего нет, система не может вывести высоту из содержимого; - собственный размер элементов:
UILabelсnumberOfLines = 0растёт по тексту, а уUIImageViewдо загрузки картинки размера нет, поэтому ему нужна фиксированная высота или констрейнт на соотношение сторон; UIStackView: удобен, но и его нужно прикрепить к четырём краямcontentView;- таблица:
rowHeight = UITableView.automaticDimensionиestimatedRowHeight, близкий к реальному; - коллекция: в compositional layout размер элемента задаётся через
.estimated(...), во flow layout черезestimatedItemSize = UICollectionViewFlowLayout.automaticSize, при этом ширину ячейки нужно ограничить.
Если высота меняется у видимой ячейки, например после раскрытия текста, таблице нужно об этом сказать: performBatchUpdates(nil) или пара beginUpdates() и endUpdates(). В iOS 16 появилось свойство selfSizingInvalidation, с ним ячейка может сама запросить пересчёт через invalidateIntrinsicContentSize().
Где спотыкаются
Добавляют элементы прямо в ячейку, минуя contentView. Высота считается по contentView, такие элементы в расчёт не попадают, а в режиме редактирования всё съезжает.
Видят в консоли конфликт с UIView-Encapsulated-Layout-Height и начинают удалять констрейнты. Это временная высота от таблицы на первом проходе, обычно помогает понизить приоритет нижнего констрейнта до 999.
Вызывают layoutIfNeeded() и считают высоту вручную в cellForRowAt или heightForRowAt. При правильных констрейнтах это лишняя работа, которая бьёт по плавности прокрутки.