В каком методе жизненного цикла UIViewController можно получить корректные данные о frame экрана?

Ответ

viewDidLayoutSubviews() — это наиболее подходящий метод для получения окончательных размеров и позиций view.

Почему? Этот метод вызывается после того, как система завершила расчет layout для контроллера и всех его subviews. Значения frame и bounds здесь гарантированно актуальны.

Пример:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // Теперь frame корневой view корректен
    print("Main view frame: (view.frame)")
    // Можно безопасно позиционировать subviews
    customSubview.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 100)
}

Важные нюансы:

  • viewDidLoad()не подходит, так как view загружена в память, но её geometry (размеры и положение) ещё не рассчитана относительно родительского контекста (окна, safe area).
  • viewWillAppear(_:) — тоже может давать некорректные значения, так как final layout ещё не выполнен.
  • viewDidAppear(_:) — даст правильные значения, но вызывается позже, после появления view на экране, что может быть слишком поздно для начальной настройки UI.
  • Метод viewDidLayoutSubviews() может вызываться несколько раз (поворот, изменение constraints, вызов setNeedsLayout()). Для однократной настройки используйте флаг:
    private var isFirstLayout = true
    override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if isFirstLayout {
        isFirstLayout = false
        // Настройка, выполняемая один раз
    }
    }

Ответ 18+ 🔞

А, ну вот, опять про эти ваши вьюхи и фреймы, блядь! Слушай, тут один чувак вечно в viewDidLoad лезет, как будто там уже всё нарисовано, а потом охуевает, что у него ноль на ноль. Так вот, запомни раз и навсегда, ёпта!

viewDidLayoutSubviews() — это, сука, святое место, где можно наконец-то получить настоящие, живые размеры. Почему? Да потому что система тут уже всю эту хуйню с авто-лейаутом, констрейнтами и safe area переварила и выдала итоговый результат. Фреймы здесь — не пустые обещания, а реальные цифры.

Смотри, как это работает:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    // Вот теперь frame корневой вьюхи — это правда, а не пиздёж
    print("Main view frame: (view.frame)")
    // И можно уже без бздёта расставлять свои сабвьюхи
    customSubview.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 100)
}

А теперь про основные грабли, об которые все ебут лбы:

  • viewDidLoad()полная хуйня для размеров! Вьюха в памяти есть, да, но её геометрия — это как чертёж на салфетке. Она ещё не знает, в каком окне её повесят и какие отступы safe area сожрут.
  • viewWillAppear(_:) — тоже, блядь, не айс. Финальный лейаут ещё не просчитан, можно получить левые цифры.
  • viewDidAppear(_:) — ну, тут-то значения будут верные, но это ж как приехать на вокзал, когда поезд уже ушёл, ёпта! Вьюха уже на экране торчит, а ты только собрался её верстать. Поздно, пироги съедены.
  • И главный подвох — viewDidLayoutSubviews() может дергаться дохуя раз: повернул экран, констрейнты поменял, setNeedsLayout() крикнул. Чтобы не настраивать интерфейс каждый раз, как идиот, используй флаг, ядрёна вошь:
    private var isFirstLayout = true
    override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if isFirstLayout {
        isFirstLayout = false
        // И вот тут делай свою магию один-единственный раз
    }
    }

Вот и вся наука. Не усложняй, блядь.