Ответ
Система автоматически вызывает layoutSubviews(), когда требуется обновить геометрию (layout) всех подвью (subviews). Это ключевой метод для ручной верстки.
Основные триггеры вызова:
- Изменение bounds (размера) самого view. Например, при повороте устройства или изменении
frame/bounds. - Добавление или удаление подвью с помощью
addSubview(_:)илиremoveFromSuperview(). - Вызов
setNeedsLayout()на view. Этот метод помечает view как нуждающееся в обновлении layout'а, и вызовlayoutSubviews()произойдет в следующем цикле run loop. - Вызов
layoutIfNeeded()на view. Если для view был вызванsetNeedsLayout(), этот метод принудительно вызываетlayoutSubviews()немедленно, синхронно. Используется для анимации изменений layout'а. - Прокрутка UIScrollView (так как меняется bounds скроллящегося контента).
- Изменение
safeAreaInsets(например, появление/скрытие navigation bar).
Практический пример:
class CustomView: UIView {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(label)
}
override func layoutSubviews() {
super.layoutSubviews() // Всегда вызывайте super
// Здесь вычисляем и задаем фреймы подвью на основе текущих bounds
label.frame = bounds.insetBy(dx: 16, dy: 8)
}
}
// Использование:
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
view.setNeedsLayout() // Запланирует вызов layoutSubviews
view.layoutIfNeeded() // Вызовет layoutSubviews немедленно (если нужно)
// Изменение размера вызовет layoutSubviews автоматически
view.frame.size = CGSize(width: 300, height: 150)
Важно: В layoutSubviews() следует только располагать подвью, а не добавлять/удалять их. Для добавления/удаления лучше использовать init или другие методы жизненного цикла.