В каком методе жизненного цикла UIViewController известны размеры view
Что проверяют
Понимаете ли вы, что жизненный цикл контроллера и цикл разметки это разные вещи. Ошибка здесь даёт классический баг: вычисления по нулевым или неверным размерам.
Как отвечать
Первый момент, когда размеры действительно актуальны, это viewDidLayoutSubviews(): система уже применила ограничения и расставила подвиды, поэтому view.bounds и фреймы дочерних элементов достоверны. Для отдельного вида аналог это layoutSubviews().
Что происходит раньше:
viewDidLoad(): иерархия создана, но разметка ещё не выполнялась, размеры взяты из storyboard или xib и почти наверняка не совпадут с итоговыми;viewWillAppear(_:): экран вот-вот появится, размеры всё ещё не финальные;viewWillLayoutSubviews(): разметка вот-вот начнётся, значения прежние.
Важная особенность: viewDidLayoutSubviews вызывается многократно, при повороте, изменении размера окна, появлении клавиатуры, обновлении ограничений. Поэтому в нём нельзя создавать объекты и подписки, только пересчитывать геометрию, а тяжёлые вещи защищать проверкой изменения размера.
Что обычно делают именно там: скругления и слои CALayer, которые не подчиняются автолейауту, градиенты, вычисление contentInset и размеров ячеек, ручная расстановка без ограничений.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
gradientLayer.frame = headerView.bounds
}Стоит добавить, что для реакции на смену размерного класса есть viewWillTransition(to:with:), а в SwiftUI роль измерителя играет GeometryReader.
Где спотыкаются
Считают геометрию в viewDidLoad и получают нули или размеры из макета.
Забывают вызвать super в переопределении.
Ставят подписки и создают слои в viewDidLayoutSubviews, из-за чего они плодятся при каждом повороте.