Почему запуск анимаций интерфейса предпочтительнее в `viewDidAppear`?

Ответ

Метод viewDidAppear(_:) — наиболее надежное место для старта анимаций, связанных с layout, по двум основным причинам:

1. Гарантированная готовность layout: К этому моменту представление уже добавлено в иерархию окон, и система отработала все вычисления размеров и позиций (layoutSubviews, viewDidLayoutSubviews). В viewDidLoad или viewWillAppear geometry view может быть еще нулевой или неактуальной.

2. Контроль частоты выполнения: viewDidAppear вызывается один раз при каждом полном появлении экрана на переднем плане. Это предотвращает случайный повторный запуск анимации при возврате с модального экрана или из навигационного стека (в отличие от viewWillAppear, который может вызываться чаще).

Практический пример:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // К этому моменту self.view.frame и bounds — окончательные.
    UIView.animate(withDuration: 0.5, delay: 0.3, options: .curveEaseOut) {
        self.welcomeLabel.alpha = 1.0
        self.welcomeLabel.center.y -= 20
    }
}

Исключение: Для анимаций, не зависящих от layout (например, fade-in самого view), можно использовать viewWillAppear, установив начальное состояние, а старт — в viewDidAppear.

Ответ 18+ 🔞

Да ты посмотри, какая история, блядь! С этим viewDidAppear — это ж, ёпта, святое место для анимаций, если они у тебя с размерами и позициями завязаны. Почему? А вот, блядь, почему, слушай сюда.

Во-первых, к этому моменту вся эта хуйня с лэйаутом уже окончательно устаканилась. Система уже всё расставила, расчертила, layoutSubviews отработала, viewDidLayoutSubviews прозвучал как колокол. В viewDidLoad у тебя фреймы могут быть нулевые, в viewWillAppear — ещё плавать, как говно в проруби. А тут — раз! — и всё на своих местах, можно стартовать.

А во-вторых, это ж, блядь, контроль частоты! viewDidAppear — он как серьёзный дядька: вызывается один раз, когда экран полностью выехал на передний план. А его брат-близнец, viewWillAppear, — тот ещё шутник, может и лишний раз дернуть, если ты с модального экрана вернулся или по навигации туда-сюда шастаешь. Чтобы анимация у тебя не пошла второй раз нахуй просто так, вот и юзай viewDidAppear.

Ну, смотри, как на практике выглядит, блядь:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    // Тут уже всё, пизда, готово. self.view.frame — не нулевой, bounds — твёрдый.
    UIView.animate(withDuration: 0.5, delay: 0.3, options: .curveEaseOut) {
        self.welcomeLabel.alpha = 1.0
        self.welcomeLabel.center.y -= 20
    }
}

Хотя, конечно, есть нюанс, ёпта. Если твоя анимация вообще похуй на лэйаут — ну, там, просто вьюшку из прозрачного в непрозрачное превратить — то можно начальное состояние в viewWillAppear выставить, а стартануть уже в viewDidAppear. Но это так, для особо хитрожопых случаев.