.onDisappear: принцип работы и применение в SwiftUI

Автор: IT Sectr Опубликовано: 2026-06-26 Время чтения: 10 мин

.onDisappear — модификатор SwiftUI, который выполняет замыкание при удалении View из иерархии интерфейса. Вызов происходит при закрытии экрана, переключении вкладки, dismiss-модального окна или скролле элемента за пределы видимости. По данным Apple Developer Documentation (2026), onDisappear не гарантирует вызов в сценариях аварийного завершения или при уничтожении приложения system-watchdog-ом. Подробнее о SwiftUI читайте в материале по SwiftUI.

Главное

  • .onDisappear — модификатор SwiftUI для выполнения кода при исчезновении View.
  • Симметричность — onDisappear является парным к onAppear и вызывается в обратном порядке.
  • Очистка ресурсов — основной сценарий: отмена таймеров, закрытие соединений, сохранение состояния.
  • Аналитика — через onDisappear отправляют события screen close, dwell time, session end.
  • Отсутствие гарантии — onDisappear не вызывается при аварийном завершении приложения.

Что такое .onDisappear?

.onDisappear — модификатор View в SwiftUI, который принимает замыкание Void и выполняет его при удалении View из иерархии. В паре с .onAppear он формирует полный цикл жизни экрана: появление — работа — исчезновение. Apple представила onDisappear вместе с выходом SwiftUI в iOS 13 как аналог viewDidDisappear из UIKit.

Синтаксически .onDisappear идентичен onAppear: модифицирует любую View и прикрепляет замыкание, которое вызывается SwiftUI-компоновщиком при удалении представления. В отличие от UIKit, где viewDidDisappear срабатывает только после завершения анимации перехода, onDisappear в SwiftUI может вызываться до завершения анимации — в момент, когда View помечается на удаление.

Синтаксис onDisappear

Базовый синтаксис onDisappear такой же лаконичный, как у onAppear. Модификатор не принимает дополнительных параметров — только замыкание, которое выполняется синхронно в главном потоке.

swift
struct DetailView: View {
    var body: some View {
        Text("Detail Screen")
            .onDisappear {
                print("DetailView disappeared from screen")
            }
    }
}

Как работает .onDisappear

Механизм вызова onDisappear противоположен onAppear: сначала дочерние элементы получают onDisappear, затем родитель. Это правило child-first гарантирует, что ресурсы дочерних элементов освобождаются до освобождения ресурсов родителя. Если дочерний элемент зависит от данных родителя, он должен иметь возможность корректно завершиться без родительского контекста.

SwiftUI вызывает onDisappear в момент, когда View исключается из графа рендеринга. Триггером может быть: pop из NavigationStack, переключение TabView, dismiss модального окна (sheet/fullScreenCover), изменение conditionally-отображаемого View (if/switch). В List и ScrollView onDisappear вызывается при скролле ячейки за пределы prefetch-буфера.

Порядок вызова child-first

Child-first порядок означает, что если в VStack три дочерних View, onDisappear вызовется сначала для каждого ребёнка в обратном порядке, затем для родителя. Это критично для корректной очистки: дочерние таймеры отменяются до того, как родительский ViewModel освободит shared-ресурсы.

swift
struct ParentView: View {
    var body: some View {
        VStack {
            ChildView(id: "A")
            ChildView(id: "B")
        }
        .onDisappear {
            print("Parent onDisappear — last")
        }
    }
}

struct ChildView: View {
    let id: String
    var body: some View {
        Text("Child \(id)")
            .onDisappear {
                print("Child \(id) onDisappear")
            }
    }
}

Вывод в консоль: Child B onDisappear, Child A onDisappear, Parent onDisappear — последний. Обратный порядок по сравнению с onAppear гарантирует корректную цепочку освобождения.

Когда вызывается .onDisappear

Сценарии вызова onDisappear зависят от типа контейнера. В NavigationStack onDisappear срабатывает при pop-to-root, обычном pop-back или снятии экрана с помощью свайпа (interactivePopGestureRecognizer). В TabView переключение вкладки вызывает onDisappear для скрываемой и onAppear для отображаемой — оба модификатора срабатывают практически одновременно.

В Sheet и fullScreenCover onDisappear вызывается при программном dismiss (через @Environment(\.dismiss)) или жесте свайпа вниз. Важная особенность: если sheet был открыт, но пользователь переключил приложение, onDisappear НЕ вызывается до фактического закрытия.

СценарийonDisappear вызываетсяПримечание
Pop в NavigationStackДаСразу после анимации
Переключение вкладки TabViewДаТекущая вкладка
Dismiss sheetДаДо завершения анимации
Скролл в ListДаЯчейка вышла из prefetch-зоны
Сворачивание приложенияНетНет гарантии вызова
Crash/watchdog killНетНе вызывается

Примеры .onDisappear

Основные сценарии onDisappear — это очистка ресурсов, сохранение состояния и трекинг. В отличие от onAppear, задачи onDisappear выполняются на выходе и не требуют проверки на дублирование, так как View исчезает однократно.

Сохранение состояния и отмена таймеров

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

swift
struct FormView: View {
    @State private var draftText = ""
    @State private var timer: Timer?
    
    var body: some View {
        TextField("Enter text", text: $draftText)
            .onAppear {
                timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
                    saveDraft()
                }
            }
            .onDisappear {
                timer?.invalidate()
                timer = nil
                saveDraft()
            }
    }
    
    private func saveDraft() {
        UserDefaults.standard.set(draftText, forKey: "draft")
    }
}

Отмена таймера в onDisappear предотвращает выполнение кода после того, как экран уже закрыт. Без отмены таймер может попытаться обновить @State, который уже не принадлежит актуальному View, вызывая runtime warning.

Трекинг времени на экране

Длительность пребывания на экране — классический трекинг-сценарий. Засекаем время в onAppear, вычисляем разницу в onDisappear и отправляем аналитическое событие с продолжительностью сессии.

swift
struct TrackedView: View {
    @State private var appearTime: Date?
    
    var body: some View {
        Text("Tracked Screen")
            .onAppear {
                appearTime = Date()
                Analytics.shared.logEvent("screen_view", params: ["screen": "TrackedView"])
            }
            .onDisappear {
                if let start = appearTime {
                    let duration = Date().timeIntervalSince(start)
                    Analytics.shared.logEvent("screen_close", params: [
                        "screen": "TrackedView",
                        "duration_ms": Int(duration * 1000)
                    ])
                }
            }
    }
}

.onDisappear vs .onAppear — отличия

Ключевое отличие onDisappear от onAppear — порядок вызова и гарантии срабатывания. onAppear вызывается при добавлении View в иерархию и имеет свойство повторного вызова при пересоздании. onDisappear вызывается при удалении и гарантированно срабатывает только при штатном закрытии, но не при аварийных сценариях.

По данным WWDC 2024, Apple рекомендует рассматривать onDisappear как точку очистки, а не как точку фиксации данных. Критически важные данные (платежи, регистрации) должны сохраняться в реальном времени, а не в момент исчезновения View, так как onDisappear не гарантирует вызов при сворачивании приложения.

Характеристика.onAppear.onDisappear
Момент вызоваДобавление View в иерархиюУдаление View из иерархии
ПорядокParent-firstChild-first
ГарантияВысокаяНет при аварийных завершениях
Основная задачаИнициализацияОчистка
Повторный вызовПри пересоздании ViewОднократно за исчезновение

Рекомендация: используйте onDisappear только для некритичной очистки и трекинга. Для сохранения данных используйте scenePhase или applicationWillTerminate уведомления в AppDelegate.

Типичные сценарии применения

Сценарий 1: YouTube-подобная загрузка. На экране деталей видео onDisappear сохраняет позицию воспроизведения в UserDefaults. При повторном открытии onAppear восстанавливает позицию из UserDefaults, давая эффект непрерывного просмотра.

Сценарий 2: отмена Combine-подписки. Если ViewModel использует Combine-паблишеры, onDisappear отменяет подписку через cancellable?.cancel(). Это предотвращает обновление UI после ухода с экрана, что особенно важно для списков с пагинацией и поисковых запросов.

Сценарий 3: закрытие WebSocket. В приложениях с real-time соединениями (мессенджеры, ленты котировок) onDisappear закрывает WebSocket-соединение для экономии батареи и трафика. Повторное открытие выполняется в onAppear при возврате на экран.

Закрытие WebSocket в onDisappear

WebSocket — типичный ресурс, который нужно закрывать при уходе с экрана. В примере ниже onDisappear отключает сокет, а onAppear переподключает его, что даёт значительную экономию трафика для приложений с несколькими экранами.

swift
struct ChatView: View {
    @StateObject private var socket = WebSocketManager()
    
    var body: some View {
        ChatListView(messages: socket.messages)
            .onAppear {
                socket.connect()
            }
            .onDisappear {
                socket.disconnect()
            }
    }
}

Важно: при переключении между вкладками TabView onDisappear текущей и onAppear следующей вкладки срабатывают почти одновременно. Для WebSocket это может привести к циклу disconnect-connect, который создаёт лишнюю нагрузку. Решение — использовать задержку или проверку, нужен ли сокет на следующем экране.

Часто задаваемые вопросы

Может ли .onDisappear не вызваться?

Да, .onDisappear не гарантирует вызов при аварийном завершении приложения (crash, watchdog kill), сворачивании приложения без закрытия экрана или системном сценарии, при котором приложение уничтожается в фоне. Для критически важных данных используйте scenePhase или applicationWillTerminate.

Как отличить .onDisappear от .scenePhase?

.onDisappear срабатывает при удалении View из иерархии — локальный колбэк для конкретного экрана. .scenePhase (через @Environment(\.scenePhase)) срабатывает при изменении состояния всего приложения: active, inactive, background. Для трекинга времени на экране используйте onAppear+onDisappear, для сохранения глобального состояния — scenePhase.

Почему .onDisappear вызывается несколько раз подряд?

Причина — быстрое переключение между вкладками или повторный push/pop одного экрана. SwiftUI может создать новый экземпляр View, удалить старый, создать снова — каждый раз вызывая onDisappear и onAppear. Проверьте, не используете ли вы .id(), .equatable() или не пересоздаёте ли View в родительском теле.

Работает ли .onDisappear в SwiftUI на macOS?

Да, .onDisappear полностью поддерживается на macOS (10.15+) с тем же поведением: вызывается при закрытии окна, удалении split view панели или dismiss модального окна. На macOS onDisappear также вызывается при скрытии окна (hide), а не только при закрытии, что важно учитывать для macOS-приложений.

Как отменить URLSessionTask в .onDisappear?

Сохраните ссылку на URLSessionTask в @State и вызовите task.cancel() в onDisappear. Альтернативно используйте .task модификатор, который отменяет async-операцию автоматически при исчезновении View. .task является предпочтительным для всех async-операций, включая URLSession.

Итоги

  • .onDisappear — парный модификатор к onAppear для очистки ресурсов и трекинга при исчезновении View.
  • Child-first порядок — дочерние View получают onDisappear раньше родителя для корректной цепочки освобождения.
  • Нет гарантии вызова — при crash, сворачивании или системном завершении onDisappear может не сработать.
  • Основные сценарии — отмена таймеров, закрытие WebSocket, сохранение состояния, трекинг длительности.
  • Аналитика — onDisappear отправляет screen_close события с вычислением dwell time через Date().timeIntervalSince.
  • Для async операций — используйте .task вместо onDisappear с Task {}, так как .task автоматически отменяет запросы.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также