{"id":"l-sw-3-1","courseId":"swift-essentials","lang":"ar","kind":"lesson","summary":"ابنِ الـ views في SwiftUI على شكل structs صغيرة، ورتّبها بالـ stacks، ونسّقها بالـ modifiers بالترتيب الصحيح، وتحقّق منها فورًا عبر الـ previews.","html":"<p>إن كنت قد استخدمت UIKit، أو تعاملت مع الـ DOM يدويًا، فأنت معتاد على أن تخبر الشاشة بما يجب أن تغيّره: اضبط نص هذا العنوان، وأخفِ ذلك الزر، وأدرج صفًا. أما SwiftUI فيقلب ذلك. أنت تصف كيف يجب أن تبدو الشاشة للبيانات الحالية، وSwiftUI يكتشف ما تغيّر ويحدّث البكسلات. تنحصر مهمّتك في كتابة هذا الوصف جيدًا.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ view هو struct له body</h2>\n<p>هذا هو الصف الذي يعرضه Pocket Budget لكل مصروف، باستخدام النوعين <code dir=\"ltr\">Expense</code> و<code dir=\"ltr\">Category</code> من القسم السابق:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">ExpenseRow.swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> SwiftUI</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ExpenseRow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">View </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense: Expense</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> body: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">some</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> View {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        HStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">spacing</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            Image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemName</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: expense.category.symbolName)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.title3)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">frame</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">width</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">32</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">foregroundStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.tint)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            VStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">alignment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .leading, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">spacing</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.title)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.headline)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.date, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">format</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .dateTime.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">day</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">().</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">())</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.subheadline)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">foregroundStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.secondary)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            Spacer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.amount, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">format</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">currency</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">code</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"USD\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.body.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">monospacedDigit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">())</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.vertical, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>يتوافق <code dir=\"ltr\">ExpenseRow</code> مع الـ protocol المسمّى <code dir=\"ltr\">View</code>، ومتطلّبه الوحيد هو <code dir=\"ltr\">body</code>. إنه struct، فإنشاؤه رخيص، وSwiftUI ينشئ هذه الـ structs ويتخلّص منها باستمرار. لا تفكّر في الـ view (العرض) على أنه الشيء الظاهر على الشاشة؛ فكّر فيه على أنه وصفة يقرؤها SwiftUI كلما تغيّرت البيانات.</p>\n<p><code dir=\"ltr\">some View</code> هو النوع المُعتِم (opaque type) من درس الـ protocols. النوع الحقيقي لهذا الـ <code dir=\"ltr\">body</code> هو generic متداخل بعمق، مبني من كل stack وكل modifier، و<code dir=\"ltr\">some View</code> تتيح للـ compiler أن يتتبّعه دون أن تكتبه أنت.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ stacks والـ spacers</h2>\n<p>ثلاث حاويات تتكفّل بمعظم التخطيطات. <code dir=\"ltr\">HStack</code> يضع العناصر الأبناء من اليسار إلى اليمين (ومن اليمين إلى اليسار في العربية والعبرية، تلقائيًا)، و<code dir=\"ltr\">VStack</code> من الأعلى إلى الأسفل، و<code dir=\"ltr\">ZStack</code> يرتّبها في طبقات من الأمام إلى الخلف. وتتحكّم <code dir=\"ltr\">alignment</code> و<code dir=\"ltr\">spacing</code> في اصطفاف الأبناء. أما <code dir=\"ltr\">Spacer()</code> فيتمدّد ليملأ المساحة الفارغة، وهكذا ينتهي المبلغ مثبّتًا عند الحافة الختامية.</p>\n<p>يستطيع <code dir=\"ltr\">Text</code> أن ينسّق القيم لك: <code dir=\"ltr\">Text(expense.amount, format: .currency(code: &quot;USD&quot;))</code> و<code dir=\"ltr\">Text(expense.date, format: .dateTime.day().month())</code> يتبعان الإعدادات المحلية للمستخدم، فيرى المستخدم في القاهرة أو برلين الفواصل وترتيب التاريخ اللذين يتوقّعهما. و<code dir=\"ltr\">.monospacedDigit()</code> يُبقي أعمدة المبالغ مصطفّة.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ modifiers تغلّف الـ views</h2>\n<p><code dir=\"ltr\">.font(.headline)</code> لا يغيّر النص في مكانه. بل يعيد view جديدًا يغلّف النص ويطبّق عليه خطًّا. اربط ثلاثة modifiers (معدِّلات) وتكون قد بنيت ثلاث طبقات. لهذا الترتيب مهم:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.blue)      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// blue covers the text AND the padding</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.blue)      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// blue covers only the text</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()              </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// clear space outside the blue</span></span></code></pre></div>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 220\" role=\"img\" aria-labelledby=\"l-sw-3-1-t1\"><defs><marker id=\"arrow-l-sw-3-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-sw-3-1-t1\">على اليسار: الحشوة أولًا ثم الخلفية، فتحيط الطبقة الزرقاء بالنص وحشوته معًا. على اليمين: الخلفية أولًا ثم الحشوة، فتلتصق الطبقة الزرقاء بالنص وتكون الحشوة خارجها.</title>\n  <text class=\"d-label-strong\" x=\"170\" y=\"24\" text-anchor=\"middle\">.padding().background(.blue)</text>\n  <rect class=\"d-box-accent\" x=\"70\" y=\"45\" width=\"200\" height=\"120\" rx=\"8\"/>\n  <rect class=\"d-box\" x=\"120\" y=\"85\" width=\"100\" height=\"40\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"170\" y=\"110\" text-anchor=\"middle\">Food</text>\n  <text class=\"d-label-muted\" x=\"170\" y=\"190\" text-anchor=\"middle\">الأزرق يشمل الحشوة</text>\n  <text class=\"d-label-strong\" x=\"510\" y=\"24\" text-anchor=\"middle\">.background(.blue).padding()</text>\n  <rect class=\"d-box d-dashed\" x=\"410\" y=\"45\" width=\"200\" height=\"120\" rx=\"8\"/>\n  <rect class=\"d-box-accent\" x=\"460\" y=\"85\" width=\"100\" height=\"40\" rx=\"4\"/>\n  <text class=\"d-code\" x=\"510\" y=\"110\" text-anchor=\"middle\">Food</text>\n  <text class=\"d-label-muted\" x=\"510\" y=\"190\" text-anchor=\"middle\">الحشوة خارج الأزرق</text>\n</svg><figcaption>كل modifier يغلّف الـ view الذي قبله</figcaption></figure>\n<p>اقرأ سلسلة الـ modifiers من الأعلى إلى الأسفل على أنها «خذ هذا، ثم غلّفه بذاك». بعض الـ modifiers، مثل <code dir=\"ltr\">.font</code> و<code dir=\"ltr\">.foregroundStyle</code>، تنتقل إلى كل الأبناء، ولهذا يؤدّي ضبط <code dir=\"ltr\">.font(.title3)</code> على <code dir=\"ltr\">VStack</code> إلى تنسيق كل النصوص بداخله.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>معاندة ترتيب الـ modifiers</span></p>\n<p>حين تكون الخلفية أو الإطار أو منطقة النقر بحجم خاطئ، فالسبب في أغلب الأحيان هو الترتيب. مثال شائع: <code dir=\"ltr\">.onTapGesture</code> قبل <code dir=\"ltr\">.padding()</code>، فالحشوة تحيط بالـ view القابل للنقر من الخارج، والنقر عليها لا يفعل شيئًا. انقل الـ gesture إلى ما بعد الحشوة (وأضِف <code dir=\"ltr\">.contentShape(Rectangle())</code> إن كانت المنطقة فارغة)، بدل أن تكدّس إطارًا آخر للتعويض.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">المنطق داخل body</h2>\n<p>لأن <code dir=\"ltr\">body</code> وصف، فكود Swift العادي هو ما يقرّر محتواه. جملة <code dir=\"ltr\">if</code> تضمّن view فقط حين يتحقّق شرط، و<code dir=\"ltr\">switch</code> تختار بين views:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">HStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(expense.title)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense.amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        Image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemName</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"exclamationmark.circle\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">foregroundStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.orange)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">accessibilityLabel</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Large expense\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>لا يوجد استدعاء «أظهِر» أو «أخفِ» في أي مكان. حين يتغيّر المبلغ، يقرأ SwiftUI الـ <code dir=\"ltr\">body</code> من جديد فتظهر الأيقونة أو تختفي من تلقاء نفسها. أبقِ الـ body خاليًا من الآثار الجانبية، مثل استدعاءات الشبكة أو الطباعة، لأن SwiftUI قد يقرؤه مرات كثيرة وفي لحظات لا تتحكّم فيها.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">الـ previews</h2>\n<p>أضف preview (معاينة) في أسفل الملف:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">#Preview</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    ExpenseRow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">expense</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Expense</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">title</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Groceries\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">amount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">64.2</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">category</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .food, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .now))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>يرسمها Xcode في الـ canvas بجانب كودك ويحدّثها وأنت تكتب. الـ previews هي أسرع حلقة تغذية راجعة في تطوير iOS. أضف واحدة لكل حالة مثيرة للاهتمام، مثل عنوان طويل أو مبلغ كبير أو تخطيط من اليمين إلى اليسار، وستكتشف أخطاء التخطيط قبل أن تشغّل الـ simulator أصلًا.</p>\n<h2 id=\"sec-6\" tabindex=\"-1\">views صغيرة تُركَّب معًا</h2>\n<p>حين يتجاوز الـ <code dir=\"ltr\">body</code> نحو شاشة كاملة، استخرج أجزاءه إلى structs مستقلّة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Swift</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CategoryBadge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">View </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> category: Category</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> body: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">some</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> View {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category.title)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">font</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.caption.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">bold</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">())</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.horizontal, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">padding</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.vertical, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">background</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.blue.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">opacity</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.15</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Capsule</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">())</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">foregroundStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.blue)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الآن يمكن أن يظهر <code dir=\"ltr\">CategoryBadge(category: expense.category)</code> في الصف وفي شاشة التفاصيل وفي شريط الفلاتر، بالتنسيق نفسه تمامًا. الاستخراج مجاني وقت التشغيل، ويعطي SwiftUI وحدات أصغر يقارنها حين تتغيّر البيانات.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الأنماط الدلالية أولًا</span></p>\n<p>فضّل <code dir=\"ltr\">.headline</code> و<code dir=\"ltr\">.secondary</code> و<code dir=\"ltr\">.tint</code> على أحجام النقاط الثابتة والألوان المكتوبة بنظام hex. فهي تتكيّف مع Dynamic Type والوضع الداكن وإعدادات إمكانية الوصول دون عمل إضافي.</p>\n</aside>\n<p>كل ما في هذا الدرس يعرض بيانات ثابتة. أما الشاشات الحقيقية فتتغيّر: المستخدم يكتب ويبدّل المفاتيح ويضيف المصاريف. يقدّم الدرس التالي الـ state، أي البيانات التي تقود هذه التغييرات، وكيف يعرف SwiftUI متى يعيد الرسم.</p>\n","toc":[{"id":"sec-1","text":"الـ view هو struct له body","level":2},{"id":"sec-2","text":"الـ stacks والـ spacers","level":2},{"id":"sec-3","text":"الـ modifiers تغلّف الـ views","level":2},{"id":"sec-4","text":"المنطق داخل body","level":2},{"id":"sec-5","text":"الـ previews","level":2},{"id":"sec-6","text":"views صغيرة تُركَّب معًا","level":2}],"takeaways":["الـ view في SwiftUI هو struct خفيف يصف الـ <code dir=\"ltr\">body</code> الخاص به كيف يجب أن تبدو الشاشة للبيانات الحالية.","VStack وHStack وZStack ترتّب العناصر الأبناء عموديًا وأفقيًا وفي طبقات؛ و<code dir=\"ltr\">Spacer</code> يدفع المحتوى بعيدًا عن بعضه.","كل modifier يغلّف الـ view في view جديد، لذلك الترتيب مهم؛ و<code dir=\"ltr\">padding</code> قبل <code dir=\"ltr\">background</code> يعني أن الخلفية تُرسم خلف الحشوة أيضًا.","استخرج subview بمجرّد أن يصعب قراءة الـ <code dir=\"ltr\">body</code>؛ الـ views الصغيرة لا تكلّف شيئًا وقت التشغيل."],"further":[{"title":"Configuring views","url":"https://developer.apple.com/documentation/swiftui/configuring-views"},{"title":"SwiftUI Tutorials","url":"https://developer.apple.com/tutorials/swiftui"}],"quiz":[{"q":"<p>كيف يبدو <code dir=\"ltr\">Text(&quot;Food&quot;).background(.blue).padding()</code>؟</p>","options":[{"text":"يملأ الأزرق النص والحشوة المحيطة به.","why":"هذا ما يفعله <code dir=\"ltr\">.padding().background(.blue)</code>. هنا تُطبَّق الخلفية قبل أن توجد الحشوة."},{"text":"يملأ الأزرق إطار النص نفسه فقط، مع مساحة شفّافة حوله.","why":"صحيح. يغلّف <code dir=\"ltr\">background</code> النص، ثم يضيف <code dir=\"ltr\">padding</code> مساحة فارغة خارج ذلك الـ view الملوّن."},{"text":"يعيد SwiftUI ترتيب الـ modifiers، فيبدو الشكل نفسه في الحالتين.","why":"يطبّق SwiftUI الـ modifiers بالترتيب الذي تكتبه بالضبط؛ وكل واحد منها يغلّف النتيجة السابقة."}],"answer":1},{"q":"<p>لماذا يُعلَن <code dir=\"ltr\">body</code> على أنه <code dir=\"ltr\">some View</code> بدل نوع محدّد؟</p>","options":[{"text":"لأن نوع الـ view قد يتغيّر وقت التشغيل.","why":"النوع ثابت وقت الترجمة. <code dir=\"ltr\">some</code> تُخفي اسمًا، ولا تجعل النوع ديناميكيًا."},{"text":"لأن النوع الحقيقي generic طويل ومتداخل لن ترغب أبدًا في كتابته يدويًا.","why":"صحيح. الـ compiler يعرف النوع الدقيق؛ و<code dir=\"ltr\">some View</code> توفّر عليك كتابته."},{"text":"لأن <code dir=\"ltr\">View</code> هو class و<code dir=\"ltr\">some</code> تحوّله إلى struct.","why":"<code dir=\"ltr\">View</code> هو protocol، والـ views الخاصة بك structs في كل الأحوال."}],"answer":1},{"q":"<p>كبر الـ <code dir=\"ltr\">body</code> لأحد الصفوف حتى صار 70 سطرًا. ماذا يفعل مطوّر SwiftUI متمرّس؟</p>","options":[{"text":"يتركه؛ تقسيم الـ views يكلّف أداءً.","why":"الـ views أنواع قيم رخيصة. تقسيمها لا كلفة تُذكر له وقت التشغيل، وكثيرًا ما يساعد SwiftUI على تحديث أجزاء أقل."},{"text":"ينقل أجزاءً إلى خصائص محسوبة تعيد <code dir=\"ltr\">AnyView</code>.","why":"<code dir=\"ltr\">AnyView</code> يمحو معلومات النوع التي يستخدمها SwiftUI للمقارنة بكفاءة. استخدم struct فرعيًا بدلًا من ذلك."},{"text":"يستخرج الأجزاء ذات المعنى إلى structs صغيرة مستقلّة من نوع <code dir=\"ltr\">View</code>.","why":"صحيح. الـ subviews المسمّاة تُقرأ كمخطّط للشاشة، ويمكن معاينة كلٍّ منها وحده."}],"answer":2}],"exercise":{"kind":"choice","options":["a","b","c","d"],"correct":["b"],"title":"ابنِ شارة التصنيف","prompt":"<p>يطلب تصميم Pocket Budget عنوان تصنيف على شكل حبّة دواء: خلفية زرقاء على شكل كبسولة، مع إزاحة للنص عن حواف الكبسولة تمنحه مساحة للتنفّس. أيّ سلسلة modifiers تنتج ذلك؟</p>\n","hints":["يجب أن يغطّي الأزرق المساحة حول النص، فيجب أن تكون هذه المساحة موجودة قبل تطبيق الخلفية.","<code dir=\"ltr\">clipShape</code> يقصّ الـ view على شكل معيّن. ماذا سيقصّ إن لم تكن الخلفية قد أُضيفت بعد؟","<code dir=\"ltr\">background(_:in:)</code> يرسم نمطًا داخل شكل في خطوة واحدة."],"explanation":"<p>تُطبَّق الـ modifiers من الأعلى إلى الأسفل، وكلٌّ منها يغلّف النتيجة حتى تلك اللحظة. في السلسلة الصحيحة، ينشئ استدعاءا <code dir=\"ltr\">padding</code> الإزاحة أولًا، ثم يرسم <code dir=\"ltr\">background(.blue, in: Capsule())</code> كبسولة خلف النص وحشوته.</p>\n<p>أما السلاسل الأخرى فتطبّق الخلفية مبكرًا جدًا (فتكون الحشوة خارج الأزرق) أو تقصّ قبل وجود أي خلفية تُقَصّ. حين يكون شكل في SwiftUI بحجم خاطئ، افحص ترتيب الـ modifiers قبل أي شيء آخر.</p>\n","checkLabels":{},"optionText":{"a":{"text":"<code dir=\"ltr\">Text(&quot;Food&quot;).background(.blue).padding().clipShape(Capsule())</code>","why":"يُطبَّق الأزرق قبل الحشوة، فيلتصق بالنص؛ ثم يدوّر القصّ منطقة معظمها شفّاف."},"b":{"text":"<code dir=\"ltr\">Text(&quot;Food&quot;).padding(.horizontal, 12).padding(.vertical, 6).background(.blue, in: Capsule())</code>","why":"صحيح. الحشوة موجودة أولًا، ثم تُرسم الكبسولة خلف النص مع حشوته."},"c":{"text":"<code dir=\"ltr\">Text(&quot;Food&quot;).clipShape(Capsule()).padding().background(.blue)</code>","why":"هذا يقصّ النص المجرّد، ثم يرسم مستطيلًا أزرق حول الحشوة. تحصل على مستطيل، لا على حبّة."},"d":{"text":"<code dir=\"ltr\">Text(&quot;Food&quot;).background(.blue).clipShape(Capsule()).padding()</code>","why":"هذا ينشئ حبّة صغيرة تلتصق بالحروف بلا إزاحة، ثم يضيف مساحة شفّافة خارجها."}}},"words":642,"readMinutes":4}