خاصية body هي العنصر المركزي لبروتوكول View في SwiftUI، وهي تحدد المحتوى الذي يظهر على الشاشة. وفقاً Apple Developer Documentation, 2024، body هو المتطلب الإلزامي الوحيد لبروتوكول View ويعيد نوعاً يطابق هذا البروتوكول نفسه. يستدعي SwiftUI الخاصية body عند كل تغيير في الحالة لبناء ومقارنة شجرة عناصر جديدة.
النقاط الرئيسية
body هي خاصية محسوبة تمثل المتطلب الإلزامي الوحيد لبروتوكول View. كل بنية تطابق View يجب أن تنفذ body. تعيد الخاصية المحتوى الذي يعرضه SwiftUI على الشاشة — يمكن أن يكون نصاً أو صورة أو زراً أو حاوية بعناصر متداخلة أو أي نوع آخر يطابق بروتوكول View.
توقيع body ثابت دائماً: var body: some View { get }. نوع الإرجاع هو some View (نوع غير شفاف)، وليس نوعاً محدداً. هذا يعني أن Views مختلفة قد تعيد أنواعاً محددة مختلفة في body، لكن مترجم Swift يثبت النوع المحدد لكل تنفيذ في وقت الترجمة.
وفقاً WWDC 2022، body هي نقطة الدخول لوصف الواجهة التصريحي. على عكس UIKit، حيث تقوم بإنشاء وتكوين UIView بشكل أمري، في SwiftUI تصف بشكل تصريحي ما يجب عرضه، و SwiftUI نفسه يحسب كيفية تنفيذه.
يجب أن تتصرف body كـ دالة نقية — مع نفس المدخلات (خصائص البنية والحالة) يجب أن تعيد نفس شجرة View. إذا كانت body تعتمد على حالة خارجية قابلة للتغيير (متغيرات عامة، UserDefaults بدون غلاف @AppStorage)، يصبح السلوك غير متوقع وقد يعيد SwiftUI رسم الشاشة بشكل غير صحيح.
الخاصية المحسوبة body لا تخزن قيمة — يتم حسابها في كل مرة يتم الوصول إليها. عندما يحدد SwiftUI أن الحالة تغيرت، يعيد بناء بنية View ويقرأ القيمة الجديدة لـ body للحصول على شجرة العناصر الحالية للعرض.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("العداد: \(count)")
.font(.largeTitle)
Button("زيادة") {
count += 1
}
.padding()
.background(.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
في هذا المثال، body تعيد VStack يحتوي على نص وزر مع معدلات. عند الضغط على الزر، تزداد خاصية @State count، يعيد SwiftUI بناء بنية CounterView ويستدعي body مرة أخرى للحصول على الشجرة المحدثة بقيمة النص الجديدة.
المعدلات (.font, .padding, .background, .foregroundColor, .cornerRadius) لا تعدل View الأصلية بل تلفها في ModifiedContent — نوع جديد يضيف التعديل. كل معدل ينشئ مستوى تداخل آخر، وهو أمر مهم لمراعاة الأداء.
some View في نوع إرجاع body ليس مجرد اتفاقية بل متطلب من المترجم. يتطلب Swift أن جميع مسارات الإرجاع في body لها نفس النوع المحدد. بدون @ViewBuilder لا يمكنك إرجاع Text في فرع و Button في فرع آخر — سينتج المترجم خطأ.
struct ConditionalView: View {
var isReady: Bool
@ViewBuilder
var body: some View {
if isReady {
Text("جاهز")
.foregroundColor(.green)
} else {
ProgressView()
}
}
}
@ViewBuilder على body يسمح باستخدام المنطق الشرطي (if/else, switch) بدون أخطاء ترجمة. ViewBuilder يلف تلقائياً الفروع المختلفة في ConditionalContent — نوع خاص يخفي اختلافات الأنواع المحددة. هذه قدرة أساسية لبناء واجهات ديناميكية.
بدون @ViewBuilder يحاول المترجم استنتاج نوع واحد لجميع مسارات الإرجاع. إذا اختلفت الأنواع — يحدث خطأ. لهذا السبب يطبق SwiftUI ضمنياً @ViewBuilder على body في تصريحات View، على الرغم من أنه في كود المستخدم تحتاج إلى إضافة التعليق التوضيحي صراحةً للطرق والخصائص المخصصة التي تعيد عدة Views.
استخدام some View بدلاً من نوع محدد لا يقلل الأداء — المترجم يعرف النوع الدقيق في وقت الترجمة وينشئ كوداً مباشراً بدون إرسال ديناميكي. AnyView، على العكس، يستخدم محو النوع (type erasure) مع الحمل الزائد للالتفاف في حاوية وجودية.
body تُستدعى بواسطة SwiftUI في ثلاثة سيناريوهات رئيسية: عند عرض View لأول مرة، عند تغيير @State/@Binding/@ObservedObject/@StateObject، وعندما تمرر View الأم قيماً جديدة عبر المُهيئ. قد يستدعي SwiftUI أيضاً body عند تغيير قيم البيئة (@Environment).
تكرار استدعاءات body لا ينبغي أن يقلقك — SwiftUI يحسن إعادة الرسم من خلال آلية الهوية. كل View في التسلسل الهرمي لها معرف فريد. إذا لم تتغير الهوية وبيانات الإدخال — لا تُستدعى body حتى إذا أعيد رسم View الأم. يتم تحقيق ذلك من خلال مقارنة Equatable والاستقرار البنيوي.
struct ParentView: View {
var body: some View {
ChildView(name: "Alice") // هوية مستقرة
}
}
struct ChildView: View {
let name: String
var body: some View {
Text("مرحباً، \(name)!")
}
}
في هذا المثال، إذا أعيد رسم ParentView لكنه يمرر نفس قيمة name — لا يُستدعى ChildView.body. يقارن SwiftUI بيانات إدخال البنية، وإذا لم تتغير، يتخطى إعادة رسم المكون الابن. هذه هي آلية تفريق المشاهدات.
هناك عدة مخاطر تؤدي إلى استدعاءات غير متوقعة لـ body: استخدام كلاسات بدون ObservableObject، تمرير closures تم إنشاؤها داخل body (كل إنشاء closure يعطي هوية جديدة)، والاستخدام غير الصحيح لـ EquatableView. إذا كانت body تُستدعى كثيراً — تحقق من استقرار هوية جميع المكونات الابنة.
القاعدة الأولى: يجب أن تكون body ضئيلة. انقل المنطق المعقد إلى خصائص محسوبة أو طرق منفصلة تعيد View. هذا يحسن قابلية القراءة ويسمح لـ SwiftUI بتحديد أجزاء التسلسل الهرمي التي تغيرت بدقة أكبر. قسم الـ bodies الكبيرة إلى مكونات فرعية بحدود مسؤولية واضحة.
القاعدة الثانية: لا تستخدم body لتنفيذ العمل. تحميل البيانات، عمليات الشبكة، الكتابة في قاعدة البيانات — كل هذا يجب أن يحدث خارج body، في المهام (tasks) أو معدّلات onChange أو من خلال ObservableObject. body مخصص فقط للإعلان عن الواجهة.
القاعدة الثالثة: استخدم خاصية EquatableView أو بروتوكول Equatable مخصص لـ Views إذا كانت المقارنة البنيوية القياسية غير كافية. هذا يسمح لك بإخبار SwiftUI صراحةً متى يحتاج المكون الابن إلى إعادة رسم وتجنب استدعاءات body غير الضرورية.
القاعدة الرابعة: إذا كانت body تحتوي على حسابات معقدة (تنسيق، تصفية، فرز) — استخدم @State للتخزين المؤقت للنتيجة أو انقل الحسابات إلى طريقة منفصلة تُستدعى من onChange. الحسابات المتكررة في body مع كل تحديث للحالة هي سبب شائع لبطء الرسوم المتحركة.
القاعدة الخامسة: للقوائم (List, ForEach) تأكد من المعرفات المستقرة عبر المعامل id. بدون هوية مستقرة، يعيد ForEach إنشاء جميع العناصر عند أي تغيير، مستدعياً body لكل منها، حتى لو تغير عنصر واحد فقط.
الأسئلة الشائعة
body هي الخاصية المحسوبة لبروتوكول View التي تعيد المحتوى للعرض. إنها المتطلب الإلزامي الوحيد للبروتوكول. نوع الإرجاع هو some View، مما يسمح لـ SwiftUI بتحسين التسلسل الهرمي في وقت الترجمة.
نعم، يستدعي SwiftUI body عند كل تغيير في الحالة (@State, @Binding, @ObservedObject) أو بيانات الإدخال. هذا سلوك طبيعي لإطار عمل تصريحي. يحسن SwiftUI تكرار الاستدعاءات من خلال آلية الهوية ومقارنة Equatable.
some View هو نوع غير شفاف يخفي التنفيذ المحدد. يثبت المترجم النوع في وقت الترجمة، مما يضمن أداء الاستدعاء المباشر. هذا يوفر مرونة: يمكنك تغيير نوع الإرجاع دون تغيير التوقيع.
لا، body لا يمكن أن تكون اختيارية — نوع الإرجاع some View لا يسمح بـ nil. إذا كنت بحاجة لإخفاء عنصر بشكل شرطي، استخدم المنطق الشرطي داخل @ViewBuilder أو أعد EmptyView، الذي لا يشغل مساحة في التسلسل الهرمي.
كل معدل ينشئ طبقة ModifiedContent جديدة، مما يزيد عمق التسلسل الهرمي. لمعظم الشاشات (حتى 50 معدلاً) التأثير ضئيل. العدد المفرط من المعدلات (مئات) قد يبطئ عملية المقارنة. جمّع المعدلات المرتبطة في امتدادات مخصصة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا