.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("Екран за детайли")
.onDisappear {
print("DetailView изчезна от екрана")
}
}
}
Механизъм на извикване на onDisappear е противоположен на onAppear: първо дъщерните елементи получават onDisappear, после родителят. Това правило child-first гарантира, че ресурсите на дъщерните елементи се освобождават преди освобождаването на ресурсите на родителя. Ако дъщерен елемент зависи от данните на родителя, той трябва да може да завърши правилно без контекста на родителя.
SwiftUI извиква onDisappear в момента, когато View се изключва от графа за рендериране. Задействащ механизъм може да бъде: pop от NavigationStack, превключване на TabView, dismiss на модален прозорец (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("Дете \(id)")
.onDisappear {
print("Дете \(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 предупреждение.
Продължителност на престой на екрана — класически сценарий за проследяване. Записваме времето в onAppear, изчисляваме разликата в onDisappear и изпращаме аналитично събитие с продължителността на сесията.
struct TrackedView: View {
@State private var appearTime: Date?
var body: some View {
Text("Проследяван екран")
.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 publisher-и, onDisappear отменя абонамента чрез cancellable?.cancel(). Това предотвратява актуализиране на UI след напускане на екрана, което е особено важно за списъци с пагинация и заявки за търсене.
Сценарий 3: затваряне на WebSocket. В приложения с real-time връзки (месинджъри, потоци от котировки) onDisappear затваря WebSocket връзката за пестене на батерия и трафик. Повторното отваряне става в onAppear при връщане на екрана.
WebSocket — типичен ресурс, който трябва да се затваря при напускане на екрана. В примера по-долу onDisappear прекъсва socket-а, а 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, който създава допълнително натоварване. Решение — използване на закъснение или проверка дали socket е необходим на следващия екран.
Често задавани въпроси
Да, .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 в body на родителя.
Да, .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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също