SwiftUI: ثلاثة أنماط تبدو أنيقة ثم تتعبك
أنماط شائعة في SwiftUI تقرأ جيدًا في مثال من عشرة أسطر، وتبدأ في إيذائك حين تكبر الشاشة. وما أستخدمه بدلًا منها.

الإجابة المختصرة
الأنماط الثلاثة: وضع كل الحالة في كائن واحد للتطبيق كله، وبناء الشاشة كتعبير واحد متداخل بلا تقسيم، وربط كل شيء بـ Binding يمتد عبر عدة مستويات. كلها تقرأ جيدًا في مثال صغير وتصبح عبئًا حين تنمو الشاشة.
في هذه الصفحة
SwiftUI يجعل الشيء الجميل سهلًا في أول عشرة أسطر. المشكلة تظهر عند السطر المئة، وغالبًا بسبب ثلاثة أنماط يتبنّاها الجميع تقريبًا في البداية — تبنّيتها أنا أيضًا.
1. كائن حالة واحد للتطبيق كله
يبدأ الأمر بريئًا: AppState واحد فيه المستخدم والإعدادات. ثم يُضاف إليه محتوى الشاشة الحالية، ثم حالة التحميل، ثم رسائل الخطأ. النتيجة كائن يُعيد رسم كل شيء عند تغيّر أي شيء.
العرض الذي يراقب الكائن كله يُعاد بناؤه عند تغيّر أي خاصية فيه، حتى تلك التي لا يقرأها. هذا لا يُلاحظ في شاشة بسيطة، ويُلاحظ جدًا في قائمة طويلة.
البديل: حالة لكل شاشة، ومشاركة ما يُشارك فعلًا فقط — الجلسة والإعدادات مثلًا. القاعدة: إن لم تستطع تسمية شاشتين تحتاجان هذه القيمة، فهي ليست حالة عامة.
2. الشاشة كتعبير واحد متداخل
VStack داخل ScrollView داخل NavigationStack مع ForEach في المنتصف، وكله في body واحد بطول شاشتين. تعمل، وتُقرأ بصعوبة، وتُبطئ المترجم بوضوح.
البديل: فكّك إلى أنواع View صغيرة لا إلى دوال. النوع المستقل يمنح SwiftUI هوية يعرف بها ما تغيّر وما لم يتغيّر، وهو ما تحتاجه لإعادة الرسم بدقة. الدالة التي تعيد some View تحسّن القراءة فقط.
// بدلًا من body واحد طويل
struct OrderRow: View {
let order: Order
var body: some View { /* ... */ }
}3. Binding يمتد عبر مستويات
تمرير @Binding من الشاشة إلى القسم إلى الصف إلى الحقل. كل مستوى يصبح قادرًا على الكتابة في حالة لا يملكها، وتصبح الإجابة عن سؤال «من غيّر هذه القيمة؟» بحثًا في الملفات.
البديل: مرّر القيمة إلى الأسفل، والحدث إلى الأعلى. العرض الابن يستقبل let ويُبلغ عن نيّته بإغلاق (closure)، ويبقى التغيير في مكان واحد يمكن قراءته.
القاعدة التي أعود إليها: العرض يعرض. إن كان يستطيع الكتابة في شيء لا يملكه، فقد صار جزءًا من منطق التطبيق دون أن ينتقل إليه اسمه.
لماذا تبدو هذه الأنماط صحيحة؟
لأن الأمثلة التعليمية شاشة واحدة. في شاشة واحدة يكون الكائن العام أبسط، والتعبير المتداخل أوضح، والـ Binding أقصر طريق. الكلفة كلها تظهر عند الشاشة العاشرة، وقد صار النمط عادة.
اختبار عملي
قبل أن تضيف قيمة إلى كائن الحالة العام، اسأل: كم شاشة تقرأ هذه القيمة؟ إن كانت واحدة، فمكانها في تلك الشاشة. وقبل أن تمرّر Binding مستوى ثالثًا، اسأل: هل يحتاج هذا العرض أن يكتب، أم أن يخبر؟ غالبًا الثاني.
أسئلة شائعة
- متى يكون كائن الحالة العام صحيحًا؟
- حين تشترك فيه شاشات فعلًا ولا يتغيّر كثيرًا: الجلسة، والإعدادات، وحالة الاتصال. المعيار هو عدد القرّاء ومعدّل التغيّر معًا، لا نوع البيانات.
- هل تقسيم العروض يؤثر على الأداء فعلًا؟
- نعم، لأن الأنواع المستقلة تمنح SwiftUI هوية أدق لما تغيّر، فيعيد رسم الجزء بدل الشاشة. وله أثر آخر ملموس: زمن الترجمة للتعبيرات المتداخلة الطويلة.
- ما بديل الـ Binding الممتد؟
- تمرير القيمة للأسفل وإغلاق للحدث إلى الأعلى، أو نموذج عرض واحد للشاشة يملك التغيير. المهم أن يبقى مكان الكتابة واحدًا ومعروفًا بالاسم.
المصادر
- SwiftUI documentation — Apple Developer
- The Swift Programming Language — Swift.org
نشرته
MrBebo
محتوى تقني عربي يقدمه بهاء طه حول البرمجة، الذكاء الاصطناعي، Web2 وWeb3، تطوير التطبيقات، بناء المنتجات وأدوات المطورين.
عن المدوّنة