От запуска @main
до исчезновения экрана. Восемь стадий, через которые проходит каждый View —
с живой анимацией, состояниями и кодом. Наводите на этап, чтобы рассмотреть его в подробностях.
SwiftUI — это цикл. Сверху вниз идёт описание UI, справа живёт хранилище состояний, снизу приходят пользовательские события, слева — системное окружение и хуки видимости (onAppear / .task / onDisappear).
Любая мутация @State запускает повторный обход body → diff → layout → render — но касается только зависимых узлов.
Описание появится здесь.
У каждого View есть две независимые оси. Одна — время жизни узла в дереве SwiftUI: пока он существует, живут @State, @StateObject, локальный кеш. Вторая — видимость на экране: onAppear/onDisappear/.task. Один узел может появиться-исчезнуть много раз — например, в TabView или при скролле в List.
@State/@StateObject
visibility — каждое появление-исчезновение
onAppear срабатывает на каждом переключении.
@State сохраняется, но если экран ушёл во второй push — он остаётся жив.
@State внутри ячейки на это не рассчитан.
.id(...) осмысленно.
Структура ContentView создаётся часто и почти бесплатно. UI — это то, что SwiftUI рендерит из дерева, которое возвращает body. Не пугайтесь, что init срабатывает много раз — это нормально.
Время жизни узла (где живёт @State) и его видимость (что триггерит onAppear) — это две разные оси. Узел в TabView создаётся один раз, а onAppear на этой вкладке срабатывает при каждом её показе. Поэтому загрузку «один раз на узел» делайте через guard items.isEmpty или флаг hasLoaded, а не «полагаясь на onAppear».
Вопреки интуиции, body вызывается раньше onAppear: parent body → child init → child body → layout → onAppear → .task → render. Поэтому если вы дёргаете данные в .task — body должен сначала уметь нарисовать «пустое» состояние (например, ProgressView), а только потом получить данные.
@State и @StateObject SwiftUI хранит вне структуры View — в storage, привязанном к узлу графа. Поэтому пересоздание View-структуры не теряет значения. @ObservedObject — наоборот: владельцем должен быть кто-то выше по иерархии, иначе на каждом diff parent (родителя) получите новый объект.
SwiftUI не «перерисовывает экран» — он перевычисляет дерево и diff’ит изменения. Если изменилось только одно состояние, тело может пересчитаться, но реальное обновление коснётся лишь затронутых узлов. Это две фазы: layout phase (всё чисто, считаем фреймы и diff) и commit phase (отправляем изменения в render-сервер).
Хук срабатывает каждый раз, когда View появляется в иерархии — включая возвраты после навигации и переключения табов. Для одноразовых задач используйте .task(id:) с привязкой к идентификатору ресурса или флаг hasLoaded внутри View.
Если вы оборачиваете тот же View в новую if-ветку, SwiftUI считает его новым: узел уничтожается и создаётся заново, состояние теряется. Чтобы этого избежать — используйте .id(...) осмысленно или избегайте структурных скачков.
Материал собран из официальной документации Apple, ключевых WWDC-сессий и нескольких давно проверенных независимых источников. Если хотите копнуть глубже — стартуйте с трёх ссылок в первой колонке.
В roadmap есть отдельная глава «SwiftUI internals» с упражнениями и видео-разбором.