.onDisappear — модифікатор SwiftUI, який виконує замикання при видаленні View з ієрархії інтерфейсу. Виклик відбувається при закритті екрана, перемиканні вкладки, закритті модального вікна або прокручуванні елемента за межі видимості. За даними 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 зник з екрана")
}
}
}
Механізм виклику onDisappear протилежний onAppear: спочатку дочірні елементи отримують onDisappear, потім батько. Це правило child-first гарантує, що ресурси дочірніх елементів звільняються до звільнення ресурсів батька. Якщо дочірній елемент залежить від даних батька, він повинен мати можливість коректно завершитися без батьківського контексту.
SwiftUI викликає onDisappear у момент, коли View виключається з графа рендерингу. Тригером може бути: pop з NavigationStack, перемикання TabView, закриття модального вікна (sheet/fullScreenCover), зміна умовно-відображуваної View (if/switch). У List і ScrollView onDisappear викликається при прокручуванні комірки за межі prefetch-буфера.
Child-first порядок означає, що якщо у VStack три дочірніх View, onDisappear викличеться спочатку для кожної дитини у зворотному порядку, потім для батька. Це критично для коректного очищення: дочірні таймери скасовуються до того, як батьківський ViewModel звільнить спільні ресурси.
struct ParentView: View {
var body: some View {
VStack {
ChildView(id: "A")
ChildView(id: "B")
}
.onDisappear {
print("Parent onDisappear — останній")
}
}
}
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 викликається при програмному закритті (через @Environment(\.dismiss)) або жесті свайпу вниз. Важлива особливість: якщо sheet було відкрито, але користувач перемкнув додаток, onDisappear НЕ викликається до фактичного закриття.
| Сценарій | onDisappear викликається | Примітка |
|---|---|---|
| Pop у NavigationStack | Так | Відразу після анімації |
| Перемикання вкладки TabView | Так | Поточна вкладка |
| Закриття 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 панелі або закритті модального вікна. На macOS onDisappear також викликається при приховуванні вікна (hide), а не тільки при закритті, що важливо враховувати для macOS-додатків.
Збережіть посилання на URLSessionTask у @State і викличте task.cancel() в onDisappear. Альтернативно використовуйте .task модифікатор, який скасовує async-операцію автоматично при зникненні View. .task є кращим для всіх async-операцій, включаючи URLSession.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також