.onDisappear: принцип рада и примена у SwiftUI

Аутор: IT Sectr Објављено: 2026-06-26 Време читања: 10 мин

.onDisappear — модификатор SwiftUI који извршава затварање при уклањању View из хијерархије интерфејса. Позив се дешава при затварању екрана, пребацивању картице, dismiss-у модалног прозора или скроловању елемента ван видљивог подручја. Према 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("Детаљни екран")
            .onDisappear {
                print("DetailView је нестао са екрана")
            }
    }
}

Како ради .onDisappear

Механизам позива onDisappear-а је супротан onAppear-у: прво подређени елементи добијају onDisappear, затим родитељ. Ово правило child-first гарантује да се ресурси подређених елемената ослобађају пре ослобађања ресурса родитеља. Ако подређени елемент зависи од података родитеља, мора имати могућност да се правилно заврши без родитељског контекста.

SwiftUI позива onDisappear у тренутку када се View искључује из графа рендеровања. Окидач може бити: pop из NavigationStack-а, пребацивање TabView-а, dismiss модалног прозора (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("Дете \(id)")
            .onDisappear {
                print("Дете \(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 се позива при програмском dismiss-у (путем @Environment(\.dismiss)) или гестом превлачења надоле. Важна карактеристика: ако је sheet отворен, али је корисник пребацио апликацију, onDisappear се НЕ позива до стварног затварања.

СценаријonDisappear се позиваНапомена
Pop у NavigationStackДаОдмах после анимације
Пребацивање картице TabViewДаТренутна картица
Dismiss 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 упозорење.

Праћење времена на екрану

Дужина боравка на екрану — класичан сценариј праћења. Бележимо време у onAppear-у, израчунавамо разлику у onDisappear-у и шаљемо аналитички догађај са трајањем сесије.

swift
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 vs .onAppear — разлике

Кључна разлика onDisappear-а од onAppear-а — редослед позива и гаранције активирања. onAppear се позива при додавању View-а у хијерархију и има својство поновног позивања при рекреирању. onDisappear се позива при уклањању и гарантовано ради само при нормалном затварању, али не у хитним сценаријима.

Према WWDC 2024, Apple препоручује да се onDisappear посматра као тачка чишћења, а не као тачка чувања података. Критично важни подаци (плаћања, регистрације) треба да се чувају у реалном времену, а не у тренутку нестанка View-а, јер onDisappear не гарантује позив при скривању апликације.

Карактеристика.onAppear.onDisappear
Тренутак позиваДодавање View у хијерархијуУклањање View из хијерархије
РедоследParent-firstChild-first
ГаранцијаВисокаНема при хитним завршецима
Основни задатакИницијализацијаЧишћење
Поновни позивПри рекреирању ViewЈеднократно по нестанку

Препорука: користите onDisappear само за неcritično чишћење и праћење. За чување података користите 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

WebSocket — типичан ресурс који треба затворити при напуштању екрана. У примеру испод onDisappear искључује socket, а 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 који ствара додатно оптерећење. Решење — коришћење кашњења или провера да ли је socket потребан на следећем екрану.

Често постављана питања

Може ли .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 панела или dismiss-у модалног прозора. На 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. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође