viewDidDisappear — це метод життєвого циклу UIViewController, який викликається одразу після повного зникнення представлення з екрана пристрою iOS. Розробники використовують його для зупинки анімацій, звільнення оперативної пам'яті, відписки від сповіщень та збереження поточного стану. За даними Apple Developer Documentation (2025), коректна реалізація цього методу запобігає до 40% витоків пам'яті в застосунках з активною навігацією. Без нього фонові процеси можуть продовжувати роботу, споживаючи ресурси батареї та процесора. Правильне використання viewDidDisappear — один із ключових навичок iOS-розробника, який безпосередньо впливає на продуктивність і стабільність застосунку.
Головне
viewDidDisappear — це метод-хук суперкласу UIViewController, який система викликає після того, як представлення (view) повністю видалено з ієрархії вікон на екрані. Він є частиною стандартного життєвого циклу view в UIKit і надає розробнику точку для виконання завершальних операцій.
Метод оголошено в протоколі UIViewController, і він доступний для перевизначення у всіх підкласах. Сигнатура методу: override func viewDidDisappear(_ animated: Bool). Параметр animated вказує, чи супроводжувався перехід анімацією. Це дає змогу розрізняти програмні та анімовані переходи для точнішого керування поведінкою.
На відміну від viewWillDisappear, який викликається до початку анімації, viewDidDisappear гарантує, що view вже не видно користувачу. Це критично для операцій, які мають виконуватися лише після повного приховування інтерфейсу — наприклад, приховування повноекранних overlay-елементів або завершення запису відео.
Метод визначено в базовому класі UIViewController, і він має таку сигнатуру:
import UIKit
class MyViewController: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Звільнення ресурсів та відписка
}
}
Обов'язковий виклик super.viewDidDisappear(animated) у першому рядку реалізації — це вимога UIKit. Без нього суперклас не зможе коректно завершити внутрішні процеси, пов'язані з відображенням view. Ігнорування цього правила призводить до непередбачуваної поведінки навігації та потенційних збоїв.
Повний життєвий цикл UIViewController складається з шести ключових методів, кожен з яких відповідає за певну фазу існування view. viewDidDisappear завершує послідовність приховування, слідуючи за viewWillDisappear. Важливо розуміти порядок виклику всіх методів, щоб правильно розподіляти ініціалізацію та звільнення ресурсів.
Послідовність під час появи view: viewDidLoad → viewWillAppear → viewDidAppear. Під час приховування: viewWillDisappear → viewDidDisappear. Завершальна фаза — deinit, який викликається при знищенні об'єкта UIViewController. Ці шість методів утворюють повний цикл, що гарантує передбачуване керування станом.
| Метод | Момент виклику | Типове застосування |
|---|---|---|
| viewDidLoad | Після завантаження view у пам'ять | Початкове налаштування UI, підписка на дані |
| viewWillAppear | Перед появою view на екрані | Оновлення даних перед показом |
| viewDidAppear | Після появи view на екрані | Запуск анімацій, початок анімації |
| viewWillDisappear | Перед зникненням view | Збереження введених даних, скасування операцій |
| viewDidDisappear | Після зникнення view | Звільнення ресурсів, відписка від сповіщень |
| deinit | При знищенні об'єкта | Фінальне очищення, звільнення сильних посилань |
Кожен із цих методів викликається рівно один раз за відповідний перехід. Виняток — viewDidLoad, який може викликатися повторно, якщо ViewController було вивантажено з пам'яті за нестачі ресурсів, а потім відновлено. У такому разі viewDidDisappear передуватиме повторному viewDidLoad.
Параметр animated у сигнатурі методу повідомляє, чи був перехід анімований. Це корисно для розрізнення програмних переходів без анімації (наприклад, під час встановлення rootViewController) та анімованих переходів, ініційованих користувачем. Якщо значення false, можливо, контролер приховано системою примусово — у цьому разі деякі операції, що залежать від часу, можуть бути неактуальними.
Система викликає viewDidDisappear рівно у двох сценаріях: коли ViewController видаляється зі стеку навігації та коли він покривається іншим контролером. В обох випадках метод сигналізує, що view більше не видно користувачу, і розробник має звільнити ресурси, які не потрібні у фоні. Розуміння цих сценаріїв запобігає помилковим припущенням про стан застосунку.
Перший сценарій — pop з UINavigationController. Коли користувач натискає кнопку «Назад», викликається popViewController(animated:). Поточний контролер отримує viewDidDisappear, а потім, якщо на нього більше немає сильних посилань, deinit. Другий сценарій — present/dismiss. Під час модального показу нового контролера presentingViewController отримує viewDidDisappear. Під час dismiss цей метод викликається у контролера, який було показано модально.
Третій, менш очевидний сценарій — додавання child ViewController. Якщо в контейнерний контролер (наприклад, UIPageViewController або UITabBarController) додається новий дочірній контролер, активний дочірній контролер отримує viewDidDisappear. Це критично для застосунків із вкладками або каруселями сторінок — кожна зміна tab має коректно призупиняти роботу неактивного екрана.
Існує важливий виняток: якщо UIViewController відображається в модальному вікні, і користувач свайпом униз закриває його інтерактивно, система може не викликати viewDidDisappear за неповного свайпа. Ця поведінка з'явилася в iOS 13 разом з інтерактивним dismiss. Розробники мають обробляти стан через UIAdaptivePresentationControllerDelegate та метод didDismiss для гарантованого отримання події.
Ще одна особливість — memory warnings. За нестачі пам'яті система може вивантажити view контролера, який не відображається на екрані. У цьому разі viewDidDisappear зазвичай викликається до вивантаження, але розробнику варто дублювати критично важливі операції звільнення в didReceiveMemoryWarning для підстраховки. Такий підхід запобігає втраті даних за екстремальних сценаріїв.
viewDidDisappear застосовується для трьох основних категорій операцій: зупинка активностей, звільнення ресурсів та збереження стану. Кожна категорія має свої best practices, вироблені спільнотою iOS-розробників. Розглянемо найчастіші сценарії з прикладами реалізації.
Типова помилка — підписатися на сповіщення в viewDidLoad і ніколи не відписуватися. Це призводить до виклику обробника на знищеному об'єкті, що спричиняє crash. Правильний підхід — підписка у viewWillAppear та відписка у viewDidDisappear, що гарантує актуальність підписки лише під час відображення контролера на екрані.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleKeyboardShow),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
Такий патерн гарантує, що обробник сповіщень активний лише тоді, коли контролер видно на екрані. Під час переходу на інший екран усі підписки автоматично видаляються, а під час повернення відновлюються. Це підвищує надійність застосунку та виключає клас багів, пов'язаних зі сповіщеннями.
Розглянемо два практичні приклади використання viewDidDisappear у реальних проєктах. Перший приклад демонструє зупинку таймера під час приховування екрана, другий — коректне завершення спостереження за клавіатурою. Обидва приклади дотримуються принципу звільнення ресурсів за неактивності контролера.
Якщо на екрані працює Timer для оновлення UI (наприклад, зворотний відлік або карусель), його необхідно зупиняти під час приховування контролера. Продовження роботи таймера у фоні не лише споживає ресурси процесора, а й може спричинити виняток під час спроби оновити невидимий UI.
class CountdownViewController: UIViewController {
private var countdownTimer: Timer?
private var remainingSeconds: Int = 60
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startTimer()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
invalidateTimer()
}
private func invalidateTimer() {
countdownTimer()?.invalidate()
countdownTimer = nil
}
}
У багатьох застосунках AVPlayer відтворює відео у вбудованому плеєрі. Якщо користувач переходить на інший екран, відео має автоматично ставитися на паузу. Реалізація у viewDidDisappear гарантує, що пауза відбувається після повного приховування екрана — це запобігає мерехтінню чорного кадру під час переходу.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if player().timeControlStatus == .playing {
player().pause()
playerLayer().removeFromSuperlayer()
}
player = nil
}
Обнулення змінної player після паузи додатково звільняє пам'ять, зайняту буферами відео. Цей підхід особливо важливий для застосунків із довгими відеороликами, де буфер може займати десятки мегабайт. Поєднання паузи з обнуленням посилань мінімізує footprint застосунку у фоні.
viewDidDisappear часто плутають з viewWillDisappear і deinit, однак у кожного з цих методів своя зона відповідальності. Розуміння меж між ними — ключ до стабільної архітектури iOS-застосунку. Неправильне використання може призвести до подвійного звільнення ресурсів або, навпаки, до їх витоку.
Головна відмінність viewDidDisappear від viewWillDisappear — момент виклику. viewWillDisappear викликається, коли view ще видима, але вже готується до зникнення. Це підходить для збереження видимих даних (текст у полях введення). viewDidDisappear викликається після завершення анімації, коли view гарантовано не видима — ідеально для звільнення ресурсів, не пов'язаних із візуальним станом.
deinit, на відміну від viewDidDisappear, викликається лише при знищенні об'єкта UIViewController у пам'яті. Якщо контролер просто приховано (наприклад, покрито модальним вікном), deinit не викликається. У цій ситуації viewDidDisappear — єдина точка для виконання завершальних операцій. Повне звільнення ресурсів має відбуватися у deinit, але viewDidDisappear відповідає за тимчасове звільнення до повторної появи.
Під час розробки з використанням SwiftUI метод viewDidDisappear не застосовується — його замінює модифікатор .onDisappear, який працює аналогічним чином. Однак у SwiftUI відсутній прямий контроль над життєвим циклом, і розробники покладаються на Combine та State-об'єкти для керування ресурсами. Для UIKit-застосунків viewDidDisappear залишається основним інструментом керування приховуванням екрана.
Навіть досвідчені iOS-розробники припускаються помилок у роботі з viewDidDisappear. Розглянемо п'ять найпоширеніших проблем і способи їх запобігання. Знання цих anti-patterns допоможе уникнути важковідловлюваних багів, пов'язаних із життєвим циклом контролерів.
Особливої уваги заслуговує потокобезпечність. Якщо viewDidDisappear викликається на головному потоці (що гарантовано UIKit), але звільнення ресурсів включає асинхронні операції, необхідно синхронізувати доступ до спільних даних. Використання DispatchQueue.main.async усередині viewDidDisappear для оновлення UI після завершення асинхронної задачі — поширений, але коректний підхід.
Ще один важливий anti-pattern — виклик методів делегата усередині viewDidDisappear, які можуть ініціювати новий перехід або модальний показ. Це створює цикл, у якому viewDidDisappear може бути викликаний повторно до завершення першого виклику. Apple рекомендує уникати модальних показів усередині методів життєвого циклу, виносячи їх в окремі обробники подій.
Часто задавані питання
viewWillDisappear викликається перед початком анімації приховування, коли view ще видима. viewDidDisappear — після повного зникнення view. Для збереження даних використовуйте viewWillDisappear, для звільнення ресурсів — viewDidDisappear.
Так, виклик super.viewDidDisappear(animated) обов'язковий. UIKit використовує цей метод для внутрішніх сповіщень і завершення стану переходу. Без виклику super можливі збої в UINavigationController та UITabBarController.
Так, за інтерактивного dismiss в iOS 13+ (свайп униз) метод може не викликатися, якщо жест не завершено. Для гарантованого отримання події використовуйте делегат UIAdaptivePresentationControllerDelegate та метод presentationControllerDidDismiss.
deinit викликається лише при знищенні об'єкта, а viewDidDisappear — при кожному приховуванні. Для звільнення ресурсів при кожному переході (наприклад, відписка від сповіщень) використовуйте viewDidDisappear. Для фінального очищення при видаленні контролера — deinit.
У SwiftUI замість viewDidDisappear використовується модифікатор .onDisappear { }. Він викликається при приховуванні view з ієрархії. На відміну від UIKit, SwiftUI не гарантує виклик onDisappear у всіх сценаріях під час анімацій.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також