В каком методе UIViewController доступны реальные размеры view для позиционирования subviews?

Ответ

Для позиционирования subviews на основе реальных размеров родительской view используйте viewDidLayoutSubviews().

Почему? В этом методе система уже применила все constraints, рассчитала frames с учётом safe area, размеров экрана и ориентации. Любые вычисления geometry здесь будут точными.

Пример настройки subview:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // Безопасно используем bounds корневой view
    let safeBounds = view.bounds.inset(by: view.safeAreaInsets)

    headerView.frame = CGRect(
        x: 0,
        y: safeBounds.origin.y,
        width: safeBounds.width,
        height: 44
    )

    // Или ручная настройка autolayout constraints
    customConstraint.constant = safeBounds.width * 0.8
}

Критические правила:

  1. Не вызывайте layoutIfNeeded() внутри viewDidLayoutSubviews() — это может привести к рекурсивному циклу layout.
  2. Метод вызывается многократно. Используйте флаги для одноразовой инициализации.
  3. Для динамической адаптивной вёрстки предпочтительнее использовать Auto Layout constraints. Ручной расчет frame в viewDidLayoutSubviews — это "запасной" вариант для сложных или производительных сценариев.

Альтернатива для Auto Layout: Переопределите updateViewConstraints() для динамического обновления constraints в ответ на изменение размеров.

Ответ 18+ 🔞

Эй, слушай, вот смотри, как тут у нас с этими вьюхами на iOS. Ты ж знаешь, эту дичь, когда надо что-то поставить по размеру экрана, а оно плывёт, как говно в проруби.

Так вот, блядь, есть у нас такой метод, viewDidLayoutSubviews(). Вот в него и лезь, когда тебе надо наконец-то узнать, где у родительской вьюхи реальные границы, а не те, что она себе в viewDidLoad нафантазировала.

А че он такой особенный, этот метод? А потому что, ёпта, система к этому моменту уже всю свою магию с констрейнтами, сейф-эрией и ориентацией завершила. Фреймы посчитаны, всё расставлено. Тут уже можно брать линейку и чертить без страха, что через секунду всё съедет нахуй.

Смотри, как этим пользоваться, чтоб не обосраться:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // Берём bounds, но отступаем от краёв, где эти чёртовы выемки и бамперы
    let safeBounds = view.bounds.inset(by: view.safeAreaInsets)

    // И вот теперь, сука, можно точно поставить хедер
    headerView.frame = CGRect(
        x: 0,
        y: safeBounds.origin.y,
        width: safeBounds.width,
        height: 44
    )

    // Или, если ты констрейнтами балуешься, их тут же подкрутить
    customConstraint.constant = safeBounds.width * 0.8
}

А теперь, блядь, главное, чтоб не вышло как всегда:

  1. Ни в коем случае, мать твою, не вызывай layoutIfNeeded() внутри этого метода! Это как подлить бензину в костёр — пойдёт бесконечная рекурсия, и приложение просто накроется медным тазом. Система и так всё сама пересчитала.
  2. Этот метод вызывается дохуя раз. Повернул телефон — вызвался. Скрыл клавиатуру — вызвался. Поэтому если тебе надо что-то сделать один раз (типа первоначальной расстановки), ставь флаг, а то заебёшься.
  3. Вообще-то, если ты не еблан, для адаптивки лучше использовать Auto Layout. Ручной расчёт фреймов в viewDidLayoutSubviews — это как молотком гвозди забивать: мощно, но если промажешь, будет пиздец. Оставляй это для каких-то совсем уж ебнутых анимаций или супер-оптимизаций.

Ну а если ты всё же фанат констрейнтов? Тогда тебе в updateViewConstraints(). Туда же, блядь, заходи, когда размеры поменялись, и подкручивай свои констрейнты динамически. Чтоб всё красиво и по фен-шую.