Какой метод UIViewController, кроме viewWillLayoutSubviews, гарантированно предоставляет окончательные размеры view для позиционирования элементов?

Ответ

Основной метод — viewDidLayoutSubviews().

Почему он предпочтительнее viewWillLayoutSubviews?

  • В viewDidLayoutSubviews() гарантировано, что все вычисления Auto Layout завершены и все сабвью имеют свои окончательные (final) размеры (bounds, frame).
  • viewWillLayoutSubviews() вызывается перед вычислением лэйаута. Размеры вью и её сабвью здесь могут быть неактуальными или промежуточными.

Пример позиционирования:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // Безопасно используем окончательные размеры
    let side = min(view.bounds.width, view.bounds.height) * 0.8
    centeredView.frame = CGRect(
        x: (view.bounds.width - side) / 2,
        y: (view.bounds.height - side) / 2,
        width: side,
        height: side
    )
}

Альтернативы для кастомных UIView:

  • layoutSubviews() — аналог для UIView. Здесь также можно позиционировать сабвью, опираясь на окончательный bounds.
  • Использование Auto Layout констрейнтов — наиболее декларативный и правильный способ, который не требует ручного вызова этих методов.

Ответ 18+ 🔞

Давай разжую, как это работает, а то некоторые до сих пор пытаются в viewWillLayoutSubviews размеры ловить, а потом удивляются, почему у них всё пляшет, как дервиш на раскалённой сковороде.

Смотри, есть два главных метода, где можно с лэйаутом работать: viewWillLayoutSubviews и viewDidLayoutSubviews. Разница между ними — как между «сейчас начнём готовить» и «блюдо уже на столе, можно жрать».

viewWillLayoutSubviews() — это как шеф-повар только зашёл на кухню и крикнул: «Так, щас будем готовить!». Инструменты на месте? Не факт. Продукты нарезаны? Хуй там. Автолэйаут только собирается свои вычисления делать, все размеры (bounds, frame) — это прошлогодний снег, на который опираться нельзя. Если ты тут начнёшь свою вьюху позиционировать, она встанет, а потом система придет и скажет: «А теперь по-настоящему!» — и всё съедет. Короче, тут делать нечего, кроме как подготовить какие-нибудь начальные данные, если очень надо.

А вот viewDidLayoutSubviews() — это уже готовый обед, можно садиться и есть. В этот момент система уже всё посчитала, все констрейнты отработали, и каждый сабвью получил свои финальные, блядь, размеры. Твоя вьюха знает, сколько места у неё есть, и ты можешь безопасно, с чистой совестью, расставлять в ней что угодно, опираясь на view.bounds.

Вот, смотри, как это выглядит на практике. Допустим, тебе надо в центр экрана впихнуть квадрат, который занимает 80% от меньшей стороны:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // Теперь view.bounds — это святая истина, а не гадание на кофейной гуще
    let side = min(view.bounds.width, view.bounds.height) * 0.8
    centeredView.frame = CGRect(
        x: (view.bounds.width - side) / 2,
        y: (view.bounds.height - side) / 2,
        width: side,
        height: side
    )
}

Всё. Красиво, предсказуемо, и не будет дергаться при каждом повороте экрана.

А что насчёт кастомных UIView? Там своя кухня. Аналог viewDidLayoutSubviews для обычной вьюхи — это метод layoutSubviews(). Туда же приходит сигнал, что твоя вьюха получила свои окончательные размеры, и можно уже внутри неё сабвьюшки по bounds расставлять.

Но слушай сюда, самый правильный путь, чтобы не ебаться с этими ручными рамками — это Auto Layout констрейнты. Это когда ты не пишешь frame = ..., а говоришь системе: «Хочу, чтобы эта кнопка была по центру, а эта картинка — на 20 пунктов ниже». Система сама всё посчитает в viewDidLayoutSubviews и расставит как надо. Это декларативно, надёжно и не требует вызова супер-методов вручную. Но если уж взялся за ручное управление, то помни — твой домен это viewDidLayoutSubviews, а не эта предварительная болтовня в viewWillLayoutSubviews.