@ViewBuilder هي تعليق result builder في SwiftUI مصممة للبناء التصريحي لتسلسلات View. وفقًا لـ Apple Developer Documentation, 2024، @ViewBuilder تحول كتلة من الكود بتعبيرات متعددة ومنطق شرطي إلى نوع View واحد يمكن لمترجم Swift فهمه. بدون هذا التعليق، سيكون من المستحيل استخدام صيغة SwiftUI التصريحية المألوفة مع if/else وعناصر متعددة في body.
الخلاصة
@ViewBuilder هو تعليق يطبق نمط result builder (SE-0289)، الذي يسمح لـ SwiftUI بتجميع عدة Views في تركيب واحد باستخدام صيغة تصريحية. يقوم تلقائيًا بلف التعبيرات المتعددة والإنشاءات الشرطية والقيم الاختيارية في أنواعها المقابلة: TupleView و ConditionalContent و OptionalContent.
قبل ظهور result builders، كان على المطورين لف العناصر يدويًا في VStack أو HStack، واستخدام العوامل الثلاثية أو طرق المصنع للمنطق الشرطي. جعل @ViewBuilder صيغة SwiftUI موجزة وقابلة للقراءة، مما سمح بكتابة كود يبدو مثل Swift العادي مع if/else والحلقات.
وفقًا لـ Swift Evolution SE-0289، فإن result builders هي آلية عامة غير مرتبطة بـ SwiftUI. @ViewBuilder هو أحد تطبيقات هذه الآلية، إلى جانب @StringBuilder لبناء السلاسل وتطبيقات المكتبات لـ DSL الأخرى. في SwiftUI، يُستخدم @ViewBuilder ليس فقط لـ body ولكن أيضًا لمعاملات الإغلاق للحاويات (VStack, HStack, ZStack, List).
في UIKit الأمري، تقوم بإنشاء UIView بشكل صريح، وتكوين خصائصه وإضافته إلى التسلسل عبر addSubview. في SwiftUI مع @ViewBuilder، تصف بشكل تصريحي أي Views يجب عرضها، وتتولى SwiftUI إنشاء العناصر وتحديثها وإزالتها بناءً على تغييرات الحالة.
Result builder هي آلية في Swift تحول تسلسل التعبيرات إلى قيمة مركبة واحدة من خلال الطرق الثابتة buildBlock و buildOptional و buildEither وغيرها. عندما يرى المترجم تعليق @ViewBuilder، يطبق تلقائيًا هذه الطرق على كتلة الكود أثناء التجميع.
@resultBuilder
struct ViewBuilder {
static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)>
static func buildIf<C>(_ c: C?) -> C?
static func buildEither<T, F>(first: T) -> ConditionalContent<T, F>
static func buildEither<T, F>(second: F) -> ConditionalContent<T, F>
}
buildBlock يقبل من 1 إلى 10 تعبيرات ويعيد TupleView. كل عدد (عدد التعبيرات) له تحميل زائد خاص به لـ buildBlock: من buildBlock
buildEither (first/second) يعالج إنشاءات if/else. يتم تمرير كل فرع إلى الطريقة المقابلة، ويتم لف النتيجة في ConditionalContent — وهو نوع يخفي الأنواع المحددة للفروع ويوفر واجهة موحدة لـ SwiftUI.
في SwiftUI، الخاصية body مُعلَّقة ضمنيًا بـ @ViewBuilder — لا ترى هذا التعليق في الكود، لكن المترجم يطبقه تلقائيًا. ومع ذلك، للخصائص المخصصة التي تعيد عدة Views، أو لمعاملات الإغلاق، يجب تحديد التعليق بشكل صريح.
القيود 1 — 10 عناصر في كتلة. هذا هو القيد الأكثر شهرة لـ @ViewBuilder. إذا كنت بحاجة إلى عرض أكثر من 10 عناصر في نفس المستوى، سيصدر المترجم خطأ. تشمل الحلول Group و ForEach و List أو التقسيم إلى مكونات فرعية. Group لا يضيف تداخلًا بصريًا، لكن كل Group يُحتسب كعنصر واحد.
struct ManyElementsView: View {
var body: some View {
Group {
Text("1"); Text("2"); Text("3")
Text("4"); Text("5"); Text("6")
Text("7"); Text("8"); Text("9")
}
Group {
Text("10"); Text("11"); Text("12")
}
}
}
القيود 2 — عدم دعم بعض الإنشاءات. @ViewBuilder لا يدعم do/catch و guard و for-in (بدون ForEach) وإنشاءات التحكم في التدفق الأخرى. للحلقات، استخدم ForEach مع بيانات قابلة للتحديد. لمعالجة الأخطاء، استخدم Views منفصلة تقبل Result أو قيمًا اختيارية.
القيود 3 — تعقيد التصحيح. عند حدوث أخطاء في @ViewBuilder، يصدر المترجم رسائل مطولة يصعب فيها العثور على السبب الجذري. المشكلات النموذجية: عدم تطابق الأنواع في فروع if/else، تجاوز حد 10 عناصر أو عدم وجود التحميلات الزائدة المطلوبة لـ buildBlock.
النمط 1: العرض الشرطي عبر if/else. حالة الاستخدام الأكثر شيوعًا لـ @ViewBuilder. يسمح بعرض Views مختلفة حسب الحالة دون استخدام العوامل الثلاثية أو طرق المصنع.
struct StatusView: View {
var status: LoadStatus
@ViewBuilder
var body: some View {
switch status {
case .loading:
ProgressView("Loading...")
case .loaded(let data):
DataView(data: data)
case .error(let message):
ErrorView(message: message)
}
}
}
النمط 2: @ViewBuilder في معاملات الدوال والمهيئات. يُستخدم لإنشاء حاويات قابلة لإعادة الاستخدام تقبل Views فرعية عبر إغلاق. هذا هو النمط القياسي للمكتبات ومكونات واجهة المستخدم.
struct SectionCard<Content: View>: View {
let title: String
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading) {
Text(title).font(.headline)
content
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(12)
}
}
النمط 3: التركيب مع ForEach. يعمل @ViewBuilder بشكل صحيح مع ForEach، مما يسمح بالتوليد الديناميكي للعناصر من مصفوفة بيانات. كل عنصر في ForEach يُحتسب كتعبير واحد في سياق @ViewBuilder.
ViewBuilder مخصص هو دالة أو خاصية معرفة من قبل المستخدم مُعلَّقة بـ @ViewBuilder وتعيد some View. تسمح هذه الدوال بتغليف منطق العرض المعقد وإعادة استخدامه في أجزاء مختلفة من التطبيق.
struct FormRow<Content: View>: View {
let label: String
@ViewBuilder let content: Content
var body: some View {
HStack {
Text(label)
.frame(width: 120, alignment: .trailing)
content
}
}
}
// الاستخدام:
FormRow(label: "Name") {
TextField("Enter name", text: $name)
}
FormRow(label: "Gender") {
Picker("Select", selection: $gender) {
Text("ذكر").tag(Gender.male)
Text("أنثى").tag(Gender.female)
}
}
قاعدة مهمة: يجب أن تعيد الدالة المخصصة مع @ViewBuilder some View، وليس نوعًا محددًا أو بروتوكول View. النوع المعتم فقط هو الذي يسمح بإخفاء التنفيذ الملموس مع الحفاظ على مرونة التركيب.
الأداء: الدوال المخصصة @ViewBuilder لا تضيف أي حمل إضافي مقارنة بالكود المباشر في body. يقوم المترجم بدمج الاستدعاءات وتحسين الكود الناتج. تقسيم body إلى دوال @ViewBuilder يحسن قابلية القراءة دون التضحية بالأداء.
الأسئلة الشائعة
@ViewBuilder هو تعليق result builder يحول كتلة من الكود بتعبيرات متعددة وشروط إلى نوع View واحد. يسمح باستخدام صيغة Swift المألوفة (if/else, switch, التعبيرات الاختيارية) داخل واجهة SwiftUI التصريحية.
القيود ناتج عن تنفيذ buildBlock — يوجد تحميل زائد منفصل للطريقة لكل عدد من 1 إلى 10. لا تدعم Swift الأنواع العامة المتغيرة العدد، لذلك فإن عدد التحميلات الزائدة ثابت. لتجاوز ذلك، استخدم Group أو ForEach أو مكونات فرعية.
لا، بروتوكول View يطبق ضمنيًا @ViewBuilder على الخاصية body. ومع ذلك، للخصائص المخصصة والطرق ومعاملات الإغلاق التي تعيد عدة Views، يجب تحديد التعليق صراحة. بدونها، لن يتمكن المترجم من معالجة التعبيرات المتعددة.
للتعبيرات الاختيارية، تُستخدم طريقة buildIf، التي تقبل View اختياريًا وتعيده إذا كانت هناك قيمة. إذا كانت القيمة nil، تعيد buildIf nil ولا يتم عرض العنصر. يتيح ذلك استخدام if let داخل body.
نعم، منذ Swift 5.9 يدعم @ViewBuilder switch من خلال طريقة buildExpression. يحول المترجم كل فرع case إلى استدعاء buildElse المقابل. دعم switch يجعل الكود أكثر قابلية للقراءة مقارنة بإنشاءات if/else المتداخلة.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.