Может ли метод viewDidLayoutSubviews вызываться несколько раз
Что проверяют
Понимаете ли вы цикл раскладки в UIKit и не держите ли в этом методе одноразовую настройку. Следом обычно спрашивают, куда тогда класть код, которому нужны итоговые размеры вью.
Как отвечать
Да, и это нормальное поведение. Метод вызывается после каждого прохода раскладки корневой вью контроллера: viewWillLayoutSubviews(), затем layoutSubviews() у view, затем viewDidLayoutSubviews(). Повторный проход случается из-за:
- показа экрана: при первом появлении метод нередко срабатывает несколько раз подряд, пока устанавливаются safe area и размеры панелей навигации;
- смены размера: поворот, смена size class, ресайз окна в Split View и Stage Manager на iPad;
- ручного запроса:
setNeedsLayout()с последующим проходом илиlayoutIfNeeded(); - изменений иерархии: правка констрейнтов, смена safe area, добавление сабвью;
- прокрутки: если корневая вью это
UITableViewилиUICollectionView, как вUITableViewController, метод срабатывает во время скролла очень часто.
Если ничего не помечено к раскладке, метод не вызывается. Реализация по умолчанию у UIViewController пустая, но super вызывают по привычке, на случай промежуточного базового класса.
Отсюда правило: код внутри должен быть идемпотентным и дешёвым. Подходит пересчёт того, что зависит от итоговых размеров: frame у CAGradientLayer, cornerRadius от высоты кнопки. Для одноразовой настройки, которой нужна геометрия, есть viewIsAppearing(_:): он появился в iOS 17 и доступен начиная с iOS 13. Реакцию на поворот удобнее писать в viewWillTransition(to:with:).
Где спотыкаются
Ставят флаг «уже выполнено» и считают размеры один раз. После поворота или ресайза окна вёрстка остаётся старой.
Меняют констрейнты или frame внутри метода так, что это запускает новый проход. Получается цикл раскладки: интерфейс дёргается, растёт нагрузка на процессор.
Кладут туда тяжёлую работу: reloadData(), запрос в сеть, декодирование картинок. На экране с таблицей это выполняется при каждом скролле и съедает плавность.