View Protocol هو البروتوكول الأساسي لـ SwiftUI الذي يجب أن يتوافق معه أي مكون واجهة مرئي. وفقًا لـ Apple Developer Documentation, 2024، يُحدد View عقدًا واحدًا: البنية أو الفئة التي تنفذ هذا البروتوكول يجب أن توفر الخاصية المحسوبة body. من خلال هذا البروتوكول، يبني SwiftUI التسلسل الهرمي الكامل للشاشات من تسميات النص البسيطة إلى هياكل التنقل المعقدة.
الخلاصة
View Protocol هو البروتوكول المركزي لـ SwiftUI الذي يُحدد كيف يصف أي عنصر مرئي محتواه. على عكس UIKit، حيث يرث كل عنصر من UIView عبر الفئات، يستخدم SwiftUI نهجًا موجهًا بالبروتوكولات: أي نوع يتوافق مع بروتوكول View يمكن عرضه على الشاشة.
يتطلب بروتوكول View تنفيذ خاصية محسوبة واحدة body تُرجع بعض المحتوى. ولكن وراء هذه البساطة يكمن نظام تركيب قوي: يمكن لـ body إرجاع أي نوع يتوافق مع View، بما في ذلك البدائيات (Text, Image, Button) والحاويات (VStack, HStack, ZStack) والمكونات المركبة المخصصة.
وفقًا لـ WWDC 2023، يتم بناء أكثر من 95% من جميع الشاشات في تطبيقات SwiftUI من خلال تركيب الهياكل التي تنفذ بروتوكول View. وهذا يجعل View Protocol أساس بنية SwiftUI بأكملها.
يتطلب SwiftUI أن يكون View نوع قيمة (value type) (struct)، وليس فئة. هذا قرار معماري رئيسي: أنواع القيم لها عمر افتراضي قابل للتنبؤ، ولا تحتوي على حالة قابلة للتغيير مشتركة، وتسمح لـ SwiftUI بتحديد أي أجزاء التسلسل الهرمي تغيرت وتحتاج إلى إعادة رسم بكفاءة.
إذا حاولت جعل View فئة، فسيصدر المترجم خطأً: بروتوكول View يرث من بروتوكول DynamicViewProperty، الذي يتطلب دلالات القيمة. يمكن للفئات أن تتوافق مع View، لكن هذا يخرق النهج الاصطلاحي ويخسر مزايا التحديثات التلقائية.
body هو المطلب الإلزامي الوحيد لبروتوكول View. إنها خاصية محسوبة تُرجع المحتوى المعروض على الشاشة. نوع القيمة المُعادة هو some View، مما يعني “نوعًا يتوافق مع View، سيتم تحديده بواسطة المترجم”.
struct GreetingView: View {
var name: String
var body: some View {
VStack {
Text("Hello, \(name)!")
.font(.title)
.foregroundColor(.blue)
Button("Start") {
print("Button pressed")
}
}
}
}
كيف يعمل body: يستدعي SwiftUI الخاصية body في كل مرة تتغير فيها حالة التطبيق ويتطلب إعادة رسم. يقارن الإطار الشجرة الجديدة لـ Views مع القديمة ويطبق فقط التغييرات الضرورية (diffing). هذا نهج تصريحي بالكامل — أنت تصف ما يجب عرضه، ويتولى SwiftUI كيفية تنفيذه.
تفصيل مهم: body يجب ألا يكون له آثار جانبية. يتم استدعاؤه مرات عديدة خلال عمر التطبيق، وإذا قام body بتعديل حالة خارجية — فإنه يؤدي إلى سلوك غير متوقع. للآثار الجانبية، استخدم task أو onChange أو DispatchQueue.
يفرض SwiftUI قيدًا: body يمكنه إرجاع عنصر جذر واحد فقط. إذا كنت بحاجة إلى عرض عدة عناصر في نفس المستوى، فلفها في حاوية — VStack أو HStack أو ZStack أو Group. مع ظهور @ViewBuilder، أصبح هذا القيد أقل وضوحًا، ولكن من الناحية المفاهيمية، يعيد body دائمًا View واحدًا.
some View هو صياغة النوع غير الشفاف (opaque type) التي تم تقديمها في Swift 5.1 خصيصًا لـ SwiftUI. تعني أن دالة أو خاصية تُرجع نوعًا ملموسًا يتوافق مع بروتوكول View، لكن الكود المستدعي لا يعرف ولا يحتاج إلى معرفة أي نوع بالضبط يتم إرجاعه.
مترجم Swift يُثبت النوع الملموس في وقت الترجمة لكل تنفيذ body، لكنه يخفيه عن العالم الخارجي. هذا يسمح لـ SwiftUI بتحسين التسلسل الهرمي لـ Views من خلال معرفة الأنواع الدقيقة لجميع المكونات، مع إعطاء المطور المرونة لتغيير التنفيذ دون تغيير التوقيع.
struct ContentView: View {
var body: some View {
Text("Hello, World!") // Compiler knows this is Text
}
}
لماذا some View وليس View فقط؟ إذا كان body يُرجع View فقط (كبروتوكول)، فلن يتمكن SwiftUI من تحديد النوع الملموس في وقت الترجمة. هذا يضيف عبئًا إضافيًا للتغليف في حاوية وجودية (existential container). some View يعطي المترجم معلومات كافية للتحسين مع الحفاظ على مرونة البروتوكول.
القيد الرئيسي هو body يجب أن يُرجع نفس النوع. لا يمكنك إرجاع Text في فرع من الشرط و Image في فرع آخر بدون أغلفة خاصة (AnyView أو Group أو @ViewBuilder). يتحقق المترجم من هذا في وقت الترجمة: جميع مسارات الإرجاع الممكنة يجب أن يكون لها نفس النوع.
لتجاوز هذا القيد، يُستخدم @ViewBuilder (ينشئ نوع TupleView واحدًا) أو Group (الذي يُرجع أيضًا نوعًا واحدًا) أو AnyView (يمسح النوع لكنه يضيف عبئًا). يجب استخدام AnyView فقط عندما لا تكون الخيارات الأخرى ممكنة، لأنه يعطل تحسينات SwiftUI.
@ViewBuilder هو منشئ نتائج (result builder) يسمح توجيهه بتجميع عدة Views في تركيب واحد بدون حاويات متداخلة. @ViewBuilder يلف تلقائيًا عدة تعبيرات في tuple (TupleView) أو يطبق منطقًا شرطيًا (If / else / switch) مع نوع الإرجاع الصحيح.
struct DashboardView: View {
var isLoggedIn: Bool
@ViewBuilder
var body: some View {
if isLoggedIn {
Text("Welcome!")
.font(.largeTitle)
ProfileCard()
} else {
LoginButton()
.padding()
}
}
}
كيف يعمل @ViewBuilder: يحول المترجم كل كتلة كود داخل @ViewBuilder إلى استدعاءات للطرق الثابتة buildBlock و buildEither و buildOptional إلخ. إذا كانت الكتلة تحتوي على عدة تعبيرات، تُلف في TupleView. إذا كانت الكتلة تحتوي على منطق شرطي، يُنشئ المترجم ConditionalContent، مخفيًا نوع الفرع.
يفرض @ViewBuilder قيدًا: حتى 10 عناصر في الكتلة الواحدة (حد TupleView). إذا كنت بحاجة إلى تجميع أكثر من عشرة عناصر، استخدم Group أو ForEach أو قسم إلى مكونات فرعية. هذا القيد موجود لأن Swift يُنشئ حملًا زائدًا منفصلًا لـ buildBlock لكل عدد من 1 إلى 10.
التركيب (Composition) مبدأ رئيسي في SwiftUI: يتم بناء الواجهات المعقدة من مكونات View صغيرة وقابلة لإعادة الاستخدام. كل مكون ينفذ بروتوكول View ويكون مسؤولًا عن جزئه من الشاشة. تُطبق المُعدّلات (font, padding, foregroundColor) على View وتُعيد View جديدًا بإعدادات معدلة.
المُعدّلات في SwiftUI ليست تغييرات، بل إنشاء غلاف جديد حول View الأصلي. كل مُعدّل يُعيد نوعًا جديدًا (ModifiedContent)، مما يسمح لـ SwiftUI ببناء شجرة مُعدّلات وإعادة رسم الأجزاء المتغيرة فقط بكفاءة. ترتيب تطبيق المُعدّلات مهم: ترتيبات مختلفة تعطي نتائج بصرية مختلفة.
Text("Hello, SwiftUI!")
.font(.title) // ModifiedContent
.padding() // ModifiedContent<..., PaddingModifier>
.background(.yellow) // ModifiedContent<..., BackgroundModifier>
.cornerRadius(8) // ModifiedContent<..., CornerRadiusModifier>
تحسين الأداء: لا يقارن SwiftUI قيم View الملموسة بل هويتها من خلال آلية الهوية (id, ForEach, الهوية المستقرة للهياكل). إذا لم يتغير هيكل View، لا يتم استدعاء body. يتم تحقيق ذلك من خلال مقارنة Equatable وآلية PreferenceKey لنقل البيانات لأعلى التسلسل الهرمي.
للحصول على تركيب فعال، يُوصى بتقسيم الشاشات المعقدة إلى مكونات فرعية مستقلة، لكل منها حالته الدنيا. هذا يسمح لـ SwiftUI بإعادة رسم الأجزاء المتغيرة فقط من التسلسل الهرمي، وليس الشاشة بأكملها.
الأسئلة الشائعة
View Protocol هو بروتوكول SwiftUI الأساسي الذي يجب أن يتوافق معه أي مكون معروض. يتطلب خاصية body محسوبة واحدة تُرجع المحتوى. جميع عناصر SwiftUI القياسية — Text, Button, Image, VStack — تنفذ هذا البروتوكول.
يستخدم SwiftUI دلالات القيمة (value semantics) لتحديثات واجهة قابلة للتنبؤ. الهياكل لا تحتوي على حالة قابلة للتغيير مشتركة، مما يسمح لـ SwiftUI بمقارنة التسلسل الهرمي القديم والجديد لـ Views بكفاءة وإعادة رسم العناصر المتغيرة فقط. الفئات تُعطل هذا التحسين.
body تُرجع some View — نوعًا غير شفاف يخفي التنفيذ الملموس. في الواقع تُرجع أي نوع يتوافق مع View: Text, Image, VStack, هياكل مخصصة. يُثبت المترجم النوع الملموس في وقت الترجمة للتحسين.
some View هو نوع غير شفاف مع تثبيت النوع الملموس في وقت الترجمة. AnyView هو مسح نوع (type erasure) يلف أي View في حاوية واحدة. some View أكثر كفاءة؛ AnyView يضيف عبئًا ويُستخدم فقط عندما يكون التبديل الديناميكي للنوع مطلوبًا.
حتى 10 عناصر — هذا هو حد TupleView، الذي يُنشئ buildBlock للأعداد من 1 إلى 10. إذا كنت بحاجة إلى عناصر أكثر، استخدم Group أو ForEach أو List أو قسم إلى مكونات فرعية. هذا القيد موجود على مستوى مترجم Swift.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.