.onDisappear: zasada działania i zastosowanie w SwiftUI

Autor: IT Sectr Opublikowano: 2026-06-26 Czas czytania: 10 min

.onDisappear — modyfikator SwiftUI, który wykonuje domknięcie przy usuwaniu View z hierarchii interfejsu. Wywołanie następuje przy zamknięciu ekranu, przełączaniu zakładki, dismiss okna modalnego lub przewinięciu elementu poza widoczny obszar. Według Apple Developer Documentation (2026), onDisappear nie gwarantuje wywołania w scenariuszach awaryjnego zakończenia lub przy zniszczeniu aplikacji przez system-watchdog. Więcej o SwiftUI przeczytasz w materiale o SwiftUI.

Najważniejsze

  • .onDisappear — modyfikator SwiftUI do wykonywania kodu przy znikaniu View.
  • Symetryczność — onDisappear jest parzysty do onAppear i wywoływany w odwrotnej kolejności.
  • Czyszczenie zasobów — główny scenariusz: anulowanie timerów, zamykanie połączeń, zapisywanie stanu.
  • Analityka — przez onDisappear wysyła się zdarzenia screen close, dwell time, session end.
  • Brak gwarancji — onDisappear nie jest wywoływany przy awaryjnym zakończeniu aplikacji.

Co to jest .onDisappear?

.onDisappear — modyfikator View w SwiftUI, który przyjmuje domknięcie Void i wykonuje je przy usuwaniu View z hierarchii. W parze z .onAppear tworzy pełny cykl życia ekranu: pojawienie się — działanie — zniknięcie. Apple wprowadziła onDisappear wraz z wydaniem SwiftUI w iOS 13 jako odpowiednik viewDidDisappear z UIKit.

Składniowo .onDisappear jest identyczny z onAppear: modyfikuje dowolny View i dołącza domknięcie, które jest wywoływane przez komponent SwiftUI przy usuwaniu widoku. W przeciwieństwie do UIKit, gdzie viewDidDisappear jest wywoływany dopiero po zakończeniu animacji przejścia, onDisappear w SwiftUI może być wywołany przed zakończeniem animacji — w momencie, gdy View jest oznaczony do usunięcia.

Składnia onDisappear

Podstawowa składnia onDisappear jest równie zwięzła jak onAppear. Modyfikator nie przyjmuje dodatkowych parametrów — tylko domknięcie, które jest wykonywane synchronicznie w głównym wątku.

swift
struct DetailView: View {
    var body: some View {
        Text("Ekran szczegółów")
            .onDisappear {
                print("DetailView zniknął z ekranu")
            }
    }
}

Jak działa .onDisappear

Mechanizm wywołania onDisappear jest przeciwny do onAppear: najpierw elementy potomne otrzymują onDisappear, potem rodzic. Ta zasada child-first gwarantuje, że zasoby elementów potomnych są zwalniane przed zwolnieniem zasobów rodzica. Jeśli element potomny zależy od danych rodzica, musi mieć możliwość poprawnego zakończenia bez kontekstu rodzica.

SwiftUI wywołuje onDisappear w momencie, gdy View jest wykluczany z grafu renderowania. Wyzwalaczem może być: pop z NavigationStack, przełączenie TabView, dismiss okna modalnego (sheet/fullScreenCover), zmiana warunkowo wyświetlanego View (if/switch). W List i ScrollView onDisappear jest wywoływany przy przewijaniu komórki poza prefetch-bufor.

Kolejność wywołania child-first

Kolejność child-first oznacza, że jeśli w VStack są trzy potomne View, onDisappear zostanie wywołany najpierw dla każdego dziecka w odwrotnej kolejności, potem dla rodzica. Jest to krytyczne dla poprawnego czyszczenia: potomne timery są anulowane zanim rodzicielski ViewModel zwolni współdzielone zasoby.

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

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

Wynik w konsoli: Child B onDisappear, Child A onDisappear, Parent onDisappear — ostatni. Odwrotna kolejność w porównaniu z onAppear gwarantuje poprawny łańcuch zwalniania.

Kiedy wywoływany jest .onDisappear

Scenariusze wywołania onDisappear zależą od typu kontenera. W NavigationStack onDisappear jest wyzwalany przy pop-to-root, zwykłym pop-back lub usunięciu ekranu gestem przesunięcia (interactivePopGestureRecognizer). W TabView przełączenie zakładki wywołuje onDisappear dla ukrywanej i onAppear dla wyświetlanej — oba modyfikatory są wyzwalane praktycznie jednocześnie.

W Sheet i fullScreenCover onDisappear jest wywoływany przy programowym dismiss (przez @Environment(\.dismiss)) lub geście przesunięcia w dół. Ważna cecha: jeśli sheet został otwarty, ale użytkownik przełączył aplikację, onDisappear NIE jest wywoływany do faktycznego zamknięcia.

ScenariuszonDisappear wywoływanyUwaga
Pop w NavigationStackTakNatychmiast po animacji
Przełączenie zakładki TabViewTakBieżąca zakładka
Dismiss sheetTakPrzed zakończeniem animacji
Przewinięcie w ListTakKomórka poza prefetch-strefą
Zwijanie aplikacjiNieBrak gwarancji wywołania
Crash/watchdog killNieNie jest wywoływany

Przykłady .onDisappear

Główne scenariusze onDisappear — to czyszczenie zasobów, zapisywanie stanu i śledzenie. W przeciwieństwie do onAppear, zadania onDisappear są wykonywane na wyjściu i nie wymagają sprawdzania duplikacji, ponieważ View znika jednorazowo.

Zapisywanie stanu i anulowanie timerów

Zapisywanie stanu w onDisappear jest szczególnie przydatne dla formularzy, gdzie dane muszą być zapisane przy opuszczeniu ekranu. Timery i subskrypcje Combine są anulowane w onDisappear, aby uniknąć wycieków przy powrocie na ekran.

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")
    }
}

Anulowanie timera w onDisappear zapobiega wykonaniu kodu po zamknięciu ekranu. Bez anulowania timer może próbować zaktualizować @State, który już nie należy do aktualnego View, wywołując runtime warning.

Śledzenie czasu na ekranie

Czas przebywania na ekranie — klasyczny scenariusz śledzenia. Rejestrujemy czas w onAppear, obliczamy różnicę w onDisappear i wysyłamy zdarzenie analityczne z czasem trwania sesji.

swift
struct TrackedView: View {
    @State private var appearTime: Date?
    
    var body: some View {
        Text("Śledzony ekran")
            .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 — różnice

Kluczowa różnica onDisappear od onAppear — kolejność wywołania i gwarancje zadziałania. onAppear jest wywoływany przy dodawaniu View do hierarchii i ma właściwość ponownego wywołania przy odtworzeniu. onDisappear jest wywoływany przy usuwaniu i gwarantowanie działa tylko przy normalnym zamknięciu, ale nie w scenariuszach awaryjnych.

Według WWDC 2024, Apple zaleca traktować onDisappear jako punkt czyszczenia, a nie jako punkt zapisu danych. Krytycznie ważne dane (płatności, rejestracje) powinny być zapisywane w czasie rzeczywistym, a nie w momencie znikania View, ponieważ onDisappear nie gwarantuje wywołania przy zwijaniu aplikacji.

Cecha.onAppear.onDisappear
Moment wywołaniaDodanie View do hierarchiiUsunięcie View z hierarchii
KolejnośćParent-firstChild-first
GwarancjaWysokaBrak przy awaryjnych zakończeniach
Główne zadanieInicjalizacjaCzyszczenie
Ponowne wywołaniePrzy odtworzeniu ViewJednokrotnie na zniknięcie

Zalecenie: używaj onDisappear tylko do niekrytycznego czyszczenia i śledzenia. Do zapisywania danych używaj scenePhase lub powiadomień applicationWillTerminate w AppDelegate.

Typowe scenariusze zastosowania

Scenariusz 1: ładowanie podobne do YouTube. Na ekranie szczegółów wideo onDisappear zapisuje pozycję odtwarzania w UserDefaults. Przy ponownym otwarciu onAppear przywraca pozycję z UserDefaults, dając efekt ciągłego oglądania.

Scenariusz 2: anulowanie subskrypcji Combine. Jeśli ViewModel używa publisherów Combine, onDisappear anuluje subskrypcję przez cancellable?.cancel(). Zapobiega to aktualizacji UI po opuszczeniu ekranu, co jest szczególnie ważne dla list z paginacją i zapytań wyszukiwania.

Scenariusz 3: zamykanie WebSocket. W aplikacjach z połączeniami czasu rzeczywistego (komunikatory, kanały notowań) onDisappear zamyka połączenie WebSocket w celu oszczędzania baterii i transferu. Ponowne otwarcie następuje w onAppear przy powrocie na ekran.

Zamykanie WebSocket w onDisappear

WebSocket — typowy zasób, który należy zamykać przy opuszczeniu ekranu. W poniższym przykładzie onDisappear rozłącza socket, a onAppear ponownie go podłącza, co daje znaczną oszczędność transferu dla aplikacji z wieloma ekranami.

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

Ważne: przy przełączaniu między zakładkami TabView onDisappear bieżącej i onAppear następnej zakładki są wyzwalane prawie jednocześnie. Dla WebSocket może to prowadzić do cyklu disconnect-connect, który generuje dodatkowe obciążenie. Rozwiązaniem jest użycie opóźnienia lub sprawdzenia, czy socket jest potrzebny na następnym ekranie.

Często zadawane pytania

Czy .onDisappear może nie zostać wywołany?

Tak, .onDisappear nie gwarantuje wywołania przy awaryjnym zakończeniu aplikacji (crash, watchdog kill), zwinięciu aplikacji bez zamknięcia ekranu lub scenariuszu systemowym, w którym aplikacja jest niszczona w tle. Dla krytycznie ważnych danych używaj scenePhase lub applicationWillTerminate.

Jak odróżnić .onDisappear od .scenePhase?

.onDisappear jest wyzwalany przy usuwaniu View z hierarchii — lokalny callback dla konkretnego ekranu. .scenePhase (przez @Environment(\.scenePhase)) jest wyzwalany przy zmianie stanu całej aplikacji: active, inactive, background. Do śledzenia czasu na ekranie używaj onAppear+onDisappear, do zapisywania globalnego stanu — scenePhase.

Dlaczego .onDisappear jest wywoływany kilka razy pod rząd?

Przyczyną jest szybkie przełączanie między zakładkami lub wielokrotny push/pop tego samego ekranu. SwiftUI może utworzyć nową instancję View, usunąć starą, utworzyć ponownie — za każdym razem wywołując onDisappear i onAppear. Sprawdź, czy nie używasz .id(), .equatable() lub czy nie odtwarzasz View w ciele rodzica.

Czy .onDisappear działa w SwiftUI na macOS?

Tak, .onDisappear jest w pełni obsługiwany na macOS (10.15+) z tym samym zachowaniem: wywoływany przy zamknięciu okna, usunięciu panelu split view lub dismiss okna modalnego. Na macOS onDisappear jest również wywoływany przy ukryciu okna (hide), a nie tylko przy zamknięciu, co jest ważne dla aplikacji macOS.

Jak anulować URLSessionTask w .onDisappear?

Zapisz referencję do URLSessionTask w @State i wywołaj task.cancel() w onDisappear. Alternatywnie użyj modyfikatora .task, który automatycznie anuluje operację async przy zniknięciu View. .task jest preferowany dla wszystkich operacji async, w tym URLSession.

Podsumowanie

  • .onDisappear — parzysty modyfikator do onAppear do czyszczenia zasobów i śledzenia przy znikaniu View.
  • Kolejność child-first — potomne View otrzymują onDisappear wcześniej niż rodzic dla poprawnego łańcucha zwalniania.
  • Brak gwarancji wywołania — przy crash, zwinięciu lub systemowym zakończeniu onDisappear może nie zadziałać.
  • Główne scenariusze — anulowanie timerów, zamykanie WebSocket, zapisywanie stanu, śledzenie czasu trwania.
  • Analityka — onDisappear wysyła zdarzenia screen_close z obliczeniem dwell time przez Date().timeIntervalSince.
  • Dla operacji async — używaj .task zamiast onDisappear z Task {}, ponieważ .task automatycznie anuluje żądania.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również