.onDisappear: принцип роботи та застосування в SwiftUI

Автор: IT Sectr Опубліковано: 2026-06-26 Час читання: 10 хв

.onDisappear — модифікатор SwiftUI, який виконує замикання при видаленні View з ієрархії інтерфейсу. Виклик відбувається при закритті екрана, перемиканні вкладки, закритті модального вікна або прокручуванні елемента за межі видимості. За даними 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 зник з екрана")
            }
    }
}

Як працює .onDisappear

Механізм виклику onDisappear протилежний onAppear: спочатку дочірні елементи отримують onDisappear, потім батько. Це правило child-first гарантує, що ресурси дочірніх елементів звільняються до звільнення ресурсів батька. Якщо дочірній елемент залежить від даних батька, він повинен мати можливість коректно завершитися без батьківського контексту.

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

Порядок виклику child-first

Child-first порядок означає, що якщо у VStack три дочірніх View, onDisappear викличеться спочатку для кожної дитини у зворотному порядку, потім для батька. Це критично для коректного очищення: дочірні таймери скасовуються до того, як батьківський ViewModel звільнить спільні ресурси.

swift
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

Сценарії виклику 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

Основні сценарії 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 панелі або закритті модального вікна. На 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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