.onDisappear — модификатор SwiftUI, который выполняет замыкание при удалении View из иерархии интерфейса. Вызов происходит при закрытии экрана, переключении вкладки, dismiss-модального окна или скролле элемента за пределы видимости. По данным Apple Developer Documentation (2026), onDisappear не гарантирует вызов в сценариях аварийного завершения или при уничтожении приложения system-watchdog-ом. Подробнее о SwiftUI читайте в материале по SwiftUI.
Главное
.onDisappear — модификатор View в SwiftUI, который принимает замыкание Void и выполняет его при удалении View из иерархии. В паре с .onAppear он формирует полный цикл жизни экрана: появление — работа — исчезновение. Apple представила onDisappear вместе с выходом SwiftUI в iOS 13 как аналог viewDidDisappear из UIKit.
Синтаксически .onDisappear идентичен onAppear: модифицирует любую View и прикрепляет замыкание, которое вызывается SwiftUI-компоновщиком при удалении представления. В отличие от UIKit, где viewDidDisappear срабатывает только после завершения анимации перехода, onDisappear в SwiftUI может вызываться до завершения анимации — в момент, когда View помечается на удаление.
Базовый синтаксис onDisappear такой же лаконичный, как у onAppear. Модификатор не принимает дополнительных параметров — только замыкание, которое выполняется синхронно в главном потоке.
struct DetailView: View {
var body: some View {
Text("Detail Screen")
.onDisappear {
print("DetailView disappeared from screen")
}
}
}
Механизм вызова onDisappear противоположен onAppear: сначала дочерние элементы получают onDisappear, затем родитель. Это правило child-first гарантирует, что ресурсы дочерних элементов освобождаются до освобождения ресурсов родителя. Если дочерний элемент зависит от данных родителя, он должен иметь возможность корректно завершиться без родительского контекста.
SwiftUI вызывает onDisappear в момент, когда View исключается из графа рендеринга. Триггером может быть: pop из NavigationStack, переключение TabView, dismiss модального окна (sheet/fullScreenCover), изменение conditionally-отображаемого View (if/switch). В List и ScrollView onDisappear вызывается при скролле ячейки за пределы prefetch-буфера.
Child-first порядок означает, что если в VStack три дочерних View, onDisappear вызовется сначала для каждого ребёнка в обратном порядке, затем для родителя. Это критично для корректной очистки: дочерние таймеры отменяются до того, как родительский ViewModel освободит shared-ресурсы.
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 зависят от типа контейнера. В 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 — это очистка ресурсов, сохранение состояния и трекинг. В отличие от onAppear, задачи onDisappear выполняются на выходе и не требуют проверки на дублирование, так как View исчезает однократно.
Сохранение состояния в onDisappear особенно полезно для форм, где данные должны быть сохранены при уходе с экрана. Таймеры и Combine-подписки отменяются в onDisappear, чтобы избежать утечек при возврате на экран.
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 и отправляем аналитическое событие с продолжительностью сессии.
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 от onAppear — порядок вызова и гарантии срабатывания. onAppear вызывается при добавлении View в иерархию и имеет свойство повторного вызова при пересоздании. onDisappear вызывается при удалении и гарантированно срабатывает только при штатном закрытии, но не при аварийных сценариях.
По данным WWDC 2024, Apple рекомендует рассматривать onDisappear как точку очистки, а не как точку фиксации данных. Критически важные данные (платежи, регистрации) должны сохраняться в реальном времени, а не в момент исчезновения View, так как onDisappear не гарантирует вызов при сворачивании приложения.
| Характеристика | .onAppear | .onDisappear |
|---|---|---|
| Момент вызова | Добавление View в иерархию | Удаление View из иерархии |
| Порядок | Parent-first | Child-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 отключает сокет, а onAppear переподключает его, что даёт значительную экономию трафика для приложений с несколькими экранами.
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 не гарантирует вызов при аварийном завершении приложения (crash, watchdog kill), сворачивании приложения без закрытия экрана или системном сценарии, при котором приложение уничтожается в фоне. Для критически важных данных используйте scenePhase или applicationWillTerminate.
.onDisappear срабатывает при удалении View из иерархии — локальный колбэк для конкретного экрана. .scenePhase (через @Environment(\.scenePhase)) срабатывает при изменении состояния всего приложения: active, inactive, background. Для трекинга времени на экране используйте onAppear+onDisappear, для сохранения глобального состояния — scenePhase.
Причина — быстрое переключение между вкладками или повторный push/pop одного экрана. SwiftUI может создать новый экземпляр View, удалить старый, создать снова — каждый раз вызывая onDisappear и onAppear. Проверьте, не используете ли вы .id(), .equatable() или не пересоздаёте ли View в родительском теле.
Да, .onDisappear полностью поддерживается на macOS (10.15+) с тем же поведением: вызывается при закрытии окна, удалении split view панели или dismiss модального окна. На macOS onDisappear также вызывается при скрытии окна (hide), а не только при закрытии, что важно учитывать для macOS-приложений.
Сохраните ссылку на URLSessionTask в @State и вызовите task.cancel() в onDisappear. Альтернативно используйте .task модификатор, который отменяет async-операцию автоматически при исчезновении View. .task является предпочтительным для всех async-операций, включая URLSession.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также