Ответ
Система автоматически вызывает 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 или другие методы жизненного цикла.
Ответ 18+ 🔞
А, ну вот, опять про этот ваш layoutSubviews()! Ну, слушай, давай разберемся, как эта штука работает, а то у некоторых, блядь, волосы дыбом встают, когда её трогать надо.
Представь себе, что твой UIView — это такой начальник цеха, ёпта. А все его подвьюхи — это работяги, станки там, ящики. Так вот, layoutSubviews() — это когда начальник орет: «А ну-ка, все на свои места, блядь, расходись!». Он расставляет всех этих работяг по новым координатам.
А когда этот начальник начинает орать? Ну, вот основные поводы, блядь:
- Сам цех размер поменял. Повернули телефон — цех скособочило. Или ты в коде
frameему новый прописал. Короче, стены поехали — всем внутри надо перегруппироваться. Автоматом орет. - Нового работягу привели или старого уволили.
addSubview(_:)илиremoveFromSuperview()вызвал — начальник сразу: «Так, щас всех заново по паспортам построю!». - Ты ему вежливо намекнул, что пора. Это когда ты зовешь
setNeedsLayout(). Ты типа: «Шеф, тут перестановочка назрела». Он тебе: «Окей, отмечусь, но сделаю это не сейчас, а когда у меня следующее свободное окошко в графике (в следующем цикле run loop)». Не сразу, но обязательно. - Ты ему приказал, чтоб сию секунду. А вот это уже
layoutIfNeeded(). Ты такой: «Шеф, всё, блядь, немедленно, я жду!». И если до этого былsetNeedsLayout(), то он сразу, синхронно, без разговоров всех строит. Без этого анимации с констрейнтами делать — вообще пипец. - У тебя скроллвьюха, и ты её листаешь. Ну, это ж понятно, блядь: контент-то едет, bounds меняется — начальник постоянно орет, всех переставляет.
- Безопасная зона подъехала или съехала. Навбар скрылся или таб-бар появился —
safeAreaInsetsизменились. Опять же, стены виртуальные поехали — всем внутри «разбежались!».
Ну и смотри, как на практике это выглядит, чтоб не быть, прости господи, распиздяем:
class CustomView: UIView {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(label) // Привел работягу в цех
}
override func layoutSubviews() {
super.layoutSubviews() // ЭТО ВАЖНО, БЛЯДЬ! Сначала дай предку поругаться!
// А вот тут уже сам ругайся и расставляй.
// Смотри, цех (bounds) уже новый, под него и расчитывай.
label.frame = bounds.insetBy(dx: 16, dy: 8) // «Становись вот сюда, отступы соблюдай!»
}
}
// Использование:
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
view.setNeedsLayout() // «Шеф, тебе надо перестановку сделать» (запланирует)
view.layoutIfNeeded() // «Шеф, давай быстрее, я жду!» (вызовет сразу, если надо)
// А вот тут начальник сам заорет, потому что цех расширили:
view.frame.size = CGSize(width: 300, height: 150)
И главное, запомни, ёпта: В layoutSubviews() ты только расставляешь то, что уже есть. Не вздумай там новых работяг нанимать (addSubview) или старых увольнять! Для этого есть init или, на худой конец, другие методы. Здесь ты просто всех по новым меркам строишь. Всё, рот меня чих-пых, понятно?