.onDisappear هو معدّل SwiftUI ينفذ إغلاقًا عند إزالة View من تسلسل الواجهة. يحدث الاستدعاء عند إغلاق الشاشة، أو تبديل التبويب، أو إلغاء النافذة المشروطة، أو تمرير عنصر خارج نطاق الرؤية. وفقًا لوثائق مطوري Apple (2026)، لا يضمن onDisappear الاستدعاء في سيناريوهات التعطل أو عند إنهاء التطبيق بواسطة مراقب النظام. اقرأ المزيد عن 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("Detail Screen")
.onDisappear {
print("اختفت DetailView من الشاشة")
}
}
}
آلية الاستدعاء لـ onDisappear معاكسة لـ onAppear: العناصر الفرعية تستقبل onDisappear أولاً، ثم الأصل. تضمن هذه القاعدة child-first تحرير الموارد الفرعية قبل تحرير موارد الأصل. إذا كان العنصر الفرعي يعتمد على بيانات الأصل، يجب أن يكون قادرًا على الإنهاء بشكل صحيح دون سياق الأصل.
SwiftUI يستدعي onDisappear عندما تتم إزالة View من رسم البيان. المشغلات تشمل: pop من NavigationStack، تبديل TabView، إلغاء المشروط (sheet/fullScreenCover)، أو View معروض بشكل شرطي (if/switch). في List و ScrollView، يُستدعى onDisappear عندما يتم تمرير خلية خارج مخزن التحميل المسبق.
ترتيب 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("Child \(id)")
.onDisappear {
print("Child \(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 عند الإلغاء البرمجي (عبر @Environment(\.dismiss)) أو إيماءة السحب لأسفل. تفاصيل مهمة: إذا تم فتح sheet لكن المستخدم بدّل التطبيق، فإن onDisappear لا يُستدعى حتى الإغلاق الفعلي.
| السيناريو | استدعاء onDisappear | ملاحظة |
|---|---|---|
| Pop في NavigationStack | نعم | مباشرة بعد الرسم |
| تبديل تبويب TabView | نعم | التبويب الحالي |
| إلغاء sheet | نعم | قبل اكتمال الرسم |
| التمرير في List | نعم | الخلية خرجت من منطقة التحميل المسبق |
| تصغير التطبيق | لا | لا يوجد ضمان للاستدعاء |
| تعطل/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 الحالية، مما يسبب تحذير وقت التشغيل.
مدة المكوث على الشاشة هي سيناريو تتبع كلاسيكي. سجل الوقت في onAppear، احسب الفرق في onDisappear وأرسل حدث تحليلي بمدة الجلسة.
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 و 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، يلغي onDisappear الاشتراك عبر cancellable?.cancel(). هذا يمنع تحديثات UI بعد مغادرة الشاشة، وهو مهم بشكل خاص للقوائم ذات الترقيم الصفحي واستعلامات البحث.
السيناريو 3: إغلاق WebSocket. في التطبيقات ذات الاتصالات الفورية (المراسلات، خلاصات الأسعار)، يغلق onDisappear اتصال WebSocket لتوفير البطارية والبيانات. تتم إعادة الاتصال في onAppear عند العودة إلى الشاشة.
WebSocket هو مورد نموذجي يجب إغلاقه عند مغادرة الشاشة. في المثال أدناه، يفصل onDisappear الاتصال، ويعيد 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، قد يتسبب هذا في دورة فصل-إعادة اتصال تخلق حملاً غير ضروري. الحل هو استخدام تأخير أو التحقق مما إذا كان الاتصال مطلوبًا على الشاشة التالية.
الأسئلة المتداولة
نعم، لا يضمن .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 أو إلغاء المشروط. على macOS، يُستدعى onDisappear أيضًا عند إخفاء النافذة (وليس فقط عند الإغلاق)، وهو أمر مهم يجب مراعاته لتطبيقات macOS.
احفظ مرجعًا لـ URLSessionTask في @State واستدعِ task.cancel() في onDisappear. بدلاً من ذلك، استخدم المعدّل .task الذي يلغي العملية غير المتزامنة تلقائيًا عند اختفاء View. يُفضل .task لجميع العمليات غير المتزامنة بما في ذلك URLSession.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.