Какие встроенные механизмы iOS обеспечивают проброс событий жизненного цикла дочерним UIViewController
Что проверяют
Понимаете ли вы контейнерный API UIKit: проброс событий дочерним контроллерам встроен и работает сам, если связь родителя с ребёнком оформлена правильно. Следом спрашивают, как вручную управлять появлением ребёнка, например при своей анимации переключения.
Как отвечать
Механизм называется view controller containment. Добавление ребёнка: addChild(child), затем view.addSubview(child.view), затем child.didMove(toParent: self). addChild сам вызывает willMove(toParent:), а didMove вы вызываете вручную. Удаление в обратном порядке: child.willMove(toParent: nil), child.view.removeFromSuperview(), child.removeFromParent(), и последний сам вызовет didMove(toParent: nil).
Что после этого UIKit делает за вас:
- события появления:
viewWillAppear,viewDidAppear,viewWillDisappear,viewDidDisappearприходят детям вслед за родителем; - повороты и смена размера:
viewWillTransition(to:with:)доходит до детей; - traits:
traitCollectionнаследуется по иерархии, с iOS 17 на изменения подписываются черезregisterForTraitChanges, аtraitCollectionDidChangeпомечен устаревшим; - статус-бар и системные элементы: родитель отдаёт решение ребёнку через
childForStatusBarStyleиchildForHomeIndicatorAutoHidden.
Ручной режим: переопределите shouldAutomaticallyForwardAppearanceMethods, вернув false, и вызывайте у ребёнка beginAppearanceTransition(_:animated:) и endAppearanceTransition(). Для замены одного ребёнка другим с анимацией есть transition(from:to:duration:options:animations:completion:).
Где спотыкаются
Вызывают child.viewWillAppear(animated) руками из родителя, как советуют старые статьи. При правильном addChild ребёнок получает событие дважды, а если addChild не было, ручной вызов маскирует настоящую ошибку.
Добавляют child.view как сабвью без addChild. Вью на экране есть, а событий появления, поворотов и traits контроллер не получает.
Забывают didMove(toParent:) или в ручном режиме не закрывают каждый beginAppearanceTransition вызовом endAppearanceTransition. Тогда viewDidAppear не приходит, и логика, завязанная на него, молча не срабатывает.