Ответ
Оба метода вызываются в процессе лайаута (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() // Если появляется заново