Ответ
Оба метода вызываются в процессе лайаута (layout) представления, но в разные моменты цикла обновления.
viewWillLayoutSubviews() — вызывается перед тем, как система обновит геометрию (frame/bounds) всех subviews. Подходит для подготовки к изменениям.
viewDidLayoutSubviews() — вызывается после того, как система обновила геометрию всех subviews. Идеален для финальных корректировок, основанных на итоговых размерах.
Ключевые отличия:
| Аспект | viewWillLayoutSubviews() |
viewDidLayoutSubviews() |
|---|---|---|
| Время вызова | Перед вычислением layout | После вычисления layout |
| Состояние bounds | Текущее (старое) значение | Обновлённое (новое) значение |
| Типичное использование | Сохранение состояния, подготовка | Финальные корректировки UI |
| Количество вызовов | Многократно при изменениях | Многократно при изменениях |
| Вызов super | Обязателен | Обязателен |
Практические примеры:
class CustomViewController: UIViewController {
@IBOutlet weak var headerView: UIView!
@IBOutlet weak var scrollView: UIScrollView!
private var previousScrollOffset: CGPoint = .zero
// 1. viewWillLayoutSubviews - подготовка
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Сохраняем текущее состояние ДО изменений
previousScrollOffset = scrollView.contentOffset
// Можно принудительно обновить constraints
view.setNeedsUpdateConstraints()
}
// 2. viewDidLayoutSubviews - финальная настройка
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Все subviews имеют актуальные frame/bounds
let safeAreaTop = view.safeAreaInsets.top
// Корректируем UI на основе итоговых размеров
headerView.layer.cornerRadius = headerView.bounds.height / 2
// Восстанавливаем scroll position после rotation
scrollView.contentOffset = previousScrollOffset
// Обновляем custom layout
updateCustomLayout()
}
private func updateCustomLayout() {
// Работа с актуальными geometry
let headerHeight = headerView.bounds.height
// ... custom layout logic
}
}
Когда что использовать:
Используйте viewWillLayoutSubviews для:
- Сохранения текущего состояния UI (позиция скролла, выделение)
- Подготовки данных для нового layout
- Принудительного обновления constraints (
setNeedsUpdateConstraints()) - Очистки кэшей, зависящих от размеров
Используйте viewDidLayoutSubviews для:
- Корректировки UI на основе итоговых размеров
- Настройки layer properties (cornerRadius, shadows)
- Обновления custom layout-логики
- Восстановления состояния после изменений (rotation, trait collection)
- Вызова методов, требующих актуальных geometry
Важные предупреждения:
- Оба метода вызываются многократно — при rotation, изменении constraints, вызове
setNeedsLayout(), изменении safe area. Избегайте тяжёлых операций. - Всегда вызывайте
super— UIKit полагается на реализацию в родительских классах. - Не изменяйте constraints в
viewDidLayoutSubviewsбез необходимости — это может вызвать бесконечный цикл layout. Лучше делать это вviewWillLayoutSubviewsилиupdateViewConstraints(). - Для однократной настройки используйте
viewDidLoad()илиviewWillAppear().
Типичный сценарий при rotation:
1. viewWillTransition(to:with:) // Начало rotation
2. viewWillLayoutSubviews() // Перед изменением layout
3. traitCollectionDidChange() // Если изменились traits
4. viewDidLayoutSubviews() // После изменения layout
5. viewWillAppear() // Если появляется заново Ответ 18+ 🔞
Давай разберём эту парочку, а то как-то всё сухо и академично. Смотри, есть два метода, которые вызываются, когда система пересчитывает, где и какого размера должны быть все твои вьюхи на экране. Это как будто тебе говорят: "Слушай, щас всё переставим, готовься!" и потом "Всё, переставили, проверяй!".
viewWillLayoutSubviews() — это как крик: "Внимание, щас начнём двигать всё нахуй!". Вызывается до того, как система посчитает новые frame и bounds для всех subviews. В этот момент у тебя ещё старые, текущие размеры.
viewDidLayoutSubviews() — это отмашка: "Всё, блядь, расставили!". Вызывается после того, как система всё посчитала и применила. Теперь у всех твоих вьюх — свежие, актуальные размеры. Идеально, чтобы подправить что-то в последний момент.
Короче, в чём разница:
| Штука | viewWillLayoutSubviews() |
viewDidLayoutSubviews() |
|---|---|---|
| Когда зовут | Перед тем как всё посчитать | После того как всё посчитали и применили |
| Какие bounds | Старые, ещё не поменялись | Новые, уже всё обновилось |
| Зачем обычно | Сохранить что было, подготовиться | Подправить интерфейс под итоговые размеры |
| Сколько раз дергают | Много, при любом чихе | Много, при любом чихе |
| Super вызывать? | ОБЯЗАТЕЛЬНО, ёпта! | ОБЯЗАТЕЛЬНО, ёпта! |
Пример из жизни, чтобы не быть мудаком:
class CustomViewController: UIViewController {
@IBOutlet weak var headerView: UIView!
@IBOutlet weak var scrollView: UIScrollView!
private var previousScrollOffset: CGPoint = .zero
// 1. viewWillLayoutSubviews - подготовка, типа "щас всё полетит"
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews() // Не забудь, а то сломается!
// Сохраняем, где юзер скроллил, ДО того как всё перевернётся
previousScrollOffset = scrollView.contentOffset
// Можно сказать системе: "эй, пересчитай констрейнты, я тут новые приготовил"
view.setNeedsUpdateConstraints()
}
// 2. viewDidLayoutSubviews - всё, отбой, можно править
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews() // Опять не забудь!
// Теперь все subviews имеют свои настоящие, новые размеры
let safeAreaTop = view.safeAreaInsets.top
// Делаем шапку круглой, зная её итоговую высоту
headerView.layer.cornerRadius = headerView.bounds.height / 2
// Возвращаем скролл на то же место, где он был до поворота
scrollView.contentOffset = previousScrollOffset
// Запускаем свою кастомную логику расстановки
updateCustomLayout()
}
private func updateCustomLayout() {
// Тут уже можно спокойно работать с финальными размерами
let headerHeight = headerView.bounds.height
// ... дальше твоя магия
}
}
Когда что юзать, чтобы не вышло пиздеца:
Юзай viewWillLayoutSubviews для:
- Сохранения того, что было (позиция скролла, какая ячейка выделена).
- Подготовки данных для нового лейаута.
- Чтобы крикнуть системе: "пересчитай констрейнты!".
- Почистить какой-нибудь кэш, который зависит от размеров.
Юзай viewDidLayoutSubviews для:
- Подгонки интерфейса под итоговые размеры (скругления, тени).
- Настройки слоёв (CALayer) — они любят точные размеры.
- Обновления своей кастомной логики расстановки.
- Восстановления состояния после поворота или смены трейтов.
- Вызова методов, которым нужны уже актуальные frame и bounds.
Важные предупреждения, а то накосячишь:
- Оба метода дергают по сто раз — при повороте, изменении констрейнтов, вызове
setNeedsLayout(), смене safe area. Не пихай туда тяжёлые операции, а то всё будет тормозить, как говно в проруби. - Всегда зови
super— UIKit там внутри свою важную хуйню делает. Не позовёшь — получишь неожиданные пиздецы. - Не меняй констрейнты в
viewDidLayoutSubviewsпросто так — можешь попасть в бесконечный цикл пересчёта лейаута. Лучше делай это вviewWillLayoutSubviewsили вupdateViewConstraints(). - Если тебе нужно что-то сделать один раз при запуске — используй
viewDidLoad()илиviewWillAppear(). Эти методы — не для разовой инициализации.
Как это обычно происходит при повороте экрана:
1. viewWillTransition(to:with:) // Начинаем поворачиваться
2. viewWillLayoutSubviews() // Внимание! Сейчас всё поедет!
3. traitCollectionDidChange() // Если поменялись трейты (размер класс)
4. viewDidLayoutSubviews() // Всё, отбой, расставили по-новому
5. viewWillAppear() // Если вьюха снова появляется