{"id":"l-sw-3-3","courseId":"swift-essentials","lang":"ar","kind":"lesson","summary":"اعرض المصاريف في List، وانتقل إلى شاشة تفاصيل باستخدام NavigationStack والروابط المبنية على القيم، واجمع المصاريف الجديدة في Form يتحقّق من المدخلات ويُعرض كـ sheet.","html":"<p>صار لدى Pocket Budget مخزن، وview للصف، وstate تقود الشاشة. الآن يجب أن يصبح تطبيقًا يستطيع الناس استخدامه: قائمة بمصاريف هذا الشهر، ونقرة تفتح التفاصيل، وزر + يفتح نموذجًا. هذه الأنماط الثلاثة (القائمة، والتنقّل بالدفع، والنموذج المنبثق) تشكّل هيكل معظم تطبيقات iPhone التي استخدمتها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">قائمة من صفوف قابلة للتمييز</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">ExpensesScreen.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\"> ExpensesScreen</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\"> store: BudgetStore</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> isAdding </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> false</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\">        NavigationStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            List</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                ForEach</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(store.expenses) { expense </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    NavigationLink</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: expense) {</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\">: expense)</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">onDelete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { offsets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    store.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">delete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">at</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: offsets)</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">overlay</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">                if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> store.expenses.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isEmpty</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    ContentUnavailableView</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"No expenses yet\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemImage</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"tray\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                                           description</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Tap + to add your first one.\"</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">navigationTitle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Expenses\"</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\">navigationDestination</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Expense.</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { expense </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                ExpenseDetail</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\">: expense)</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\">toolbar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Add expense\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemImage</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"plus\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                    isAdding </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> true</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">sheet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isPresented</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $isAdding) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                AddExpenseView</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">store</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: store)</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>\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\">ForEach(store.expenses)</code> صفًا لكل مصروف. ويحتاج إلى هوية ثابتة لكل عنصر حتى يحرّك SwiftUI الصف الصحيح عند إدراج مصروف أو حذفه بدل إعادة رسم كل شيء، وهذه الهوية هي الـ <code dir=\"ltr\">id</code> الذي يتطلّبه <code dir=\"ltr\">Identifiable</code>. ويضيف <code dir=\"ltr\">.onDelete</code> الحذف بالسحب ويسلّمك مواضع العناصر المراد حذفها؛ و<code dir=\"ltr\">delete(at:)</code> في المخزن سطر واحد: <code dir=\"ltr\">expenses.remove(atOffsets: offsets)</code>. هذه الـ method معرّفة في SwiftUI، لذلك يحتاج الملف <code dir=\"ltr\">BudgetStore.swift</code> إلى <code dir=\"ltr\">import SwiftUI</code> إلى جانب <code dir=\"ltr\">Observation</code>.</p>\n<p><code dir=\"ltr\">ContentUnavailableView</code> هو الحالة الفارغة القياسية في النظام. القائمة الفارغة بلا تفسير تبدو معطّلة؛ أما رسالة قصيرة وتلميح إلى الخطوة التالية فيجعلانها تبدو مكتملة.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">التنقّل بالقيم</h2>\n<p>يدير <code dir=\"ltr\">NavigationStack</code> مكدّسًا من الشاشات: القائمة في الأسفل، والتفاصيل تُدفع فوقها، مع زر الرجوع وإيماءة السحب من النظام دون أي جهد.</p>\n<p>ينقسم التنقّل إلى نصفين. <code dir=\"ltr\">NavigationLink(value: expense)</code> يقول <em>ما</em> الذي نُقر عليه. و<code dir=\"ltr\">.navigationDestination(for: Expense.self)</code> يقول <em>أيّ شاشة</em> تعرض <code dir=\"ltr\">Expense</code>. يجب أن تكون القيمة <code dir=\"ltr\">Hashable</code>، و<code dir=\"ltr\">Expense</code> كذلك أصلًا. إبقاء النصفين منفصلين يعني أن الوجهة تُعلَن مرة واحدة مهما كثرت الأماكن التي تربط إلى مصروف، ويجعل التنقّل البرمجي ممكنًا:</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:#CF222E;--shiki-dark:#FF7B72\">@State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> path: [Expense] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> []</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// In body:</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">NavigationStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">path</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $path) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">    // …the same List and modifiers as above</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:#6E7781;--shiki-dark:#8B949E\">// Anywhere in this view, for example after saving:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">path.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">append</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(newExpense)    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// pushes ExpenseDetail</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">path.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">removeAll</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">()           </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// pops back to the list</span></span></code></pre></div>\n<p>المسار state عادية، فيصبح التنقّل بيانات تستطيع فحصها واختبارها وتغييرها من الكود. هكذا تفتح مصروفًا محدّدًا من إشعار، أو تقفز عائدًا إلى الجذر بعد مسار من عدة خطوات. وللشاشات التي فيها أنواع وجهات مختلطة، يؤدّي <code dir=\"ltr\">NavigationPath</code> الدور نفسه بقيم ممحوّة النوع.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-sw-3-3-t1\"><defs><marker id=\"arrow-l-sw-3-3-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-3-t1\">داخل NavigationStack، تدفع قائمة المصاريف شاشة تفاصيل المصروف عند النقر على صف، والرجوع يُخرجها. وبشكل منفصل، يعرض زر + نموذج إضافة مصروف كـ sheet، تُغلق بـ Save أو Cancel.</title>\n  <rect class=\"d-box d-dashed\" x=\"10\" y=\"20\" width=\"430\" height=\"190\" rx=\"14\"/>\n  <text class=\"d-label-muted\" x=\"225\" y=\"44\" text-anchor=\"middle\">NavigationStack</text>\n  <rect class=\"d-box-primary\" x=\"30\" y=\"70\" width=\"170\" height=\"110\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"115\" y=\"118\" text-anchor=\"middle\">المصاريف</text>\n  <text class=\"d-label-muted\" x=\"115\" y=\"142\" text-anchor=\"middle\">List</text>\n  <rect class=\"d-box-accent\" x=\"250\" y=\"70\" width=\"170\" height=\"110\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"335\" y=\"118\" text-anchor=\"middle\">التفاصيل</text>\n  <text class=\"d-label-muted\" x=\"335\" y=\"142\" text-anchor=\"middle\">عبر push</text>\n  <path class=\"d-arrow\" d=\"M200 105 L248 105\" marker-end=\"url(#arrow-l-sw-3-3-1)\"/>\n  <path class=\"d-arrow d-dashed\" d=\"M250 150 L202 150\" marker-end=\"url(#arrow-l-sw-3-3-1)\"/>\n  <text class=\"d-label\" x=\"225\" y=\"96\" text-anchor=\"middle\">نقرة</text>\n  <text class=\"d-label\" x=\"225\" y=\"172\" text-anchor=\"middle\">رجوع</text>\n  <rect class=\"d-box-success\" x=\"500\" y=\"70\" width=\"180\" height=\"110\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"590\" y=\"118\" text-anchor=\"middle\">إضافة مصروف</text>\n  <text class=\"d-label-muted\" x=\"590\" y=\"142\" text-anchor=\"middle\">sheet</text>\n  <path class=\"d-arrow\" d=\"M200 85 C 300 0, 450 0, 520 68\" marker-end=\"url(#arrow-l-sw-3-3-1)\"/>\n  <text class=\"d-label\" x=\"470\" y=\"30\" text-anchor=\"middle\">زر +</text>\n</svg><figcaption>التنقّل بالدفع وsheet منبثقة</figcaption></figure>\n<p>يمكن أن تكون شاشة التفاصيل <code dir=\"ltr\">Form</code> يُستخدم للعرض، بصفوف <code dir=\"ltr\">LabeledContent</code> تنسّق القيم لك:</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:#CF222E;--shiki-dark:#FF7B72\">struct</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ExpenseDetail</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\">        Form</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            LabeledContent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Amount\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">value</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:#0550AE;--shiki-dark:#79C0FF\">            LabeledContent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Category\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: expense.category.title)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            LabeledContent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">value</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 style=\"color:#0550AE;--shiki-dark:#79C0FF\">year</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\">navigationTitle</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<h2 id=\"sec-3\" tabindex=\"-1\">نموذج داخل sheet</h2>\n<p>الدفع مخصّص للتعمّق في المحتوى. أما الـ <strong>sheet</strong> (الورقة المنبثقة) فلمهمّة مستقلّة يُنهيها المستخدم أو يتخلّى عنها، مثل إضافة مصروف. يعرض <code dir=\"ltr\">.sheet(isPresented: $isAdding)</code> النموذج كلما أصبحت <code dir=\"ltr\">isAdding</code> صحيحة. إليك النموذج:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">AddExpenseView.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\"> AddExpenseView</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\"> store: BudgetStore</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @Environment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(\\.dismiss) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> dismiss</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> title </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"\"</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount: Decimal</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> category: Category </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> .food</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    @State</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> date </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Date.now</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    private</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> var</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> canSave: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Bool</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        !</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">title.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">trimmingCharacters</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\">: .whitespaces).</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isEmpty</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</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:#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\">        NavigationStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">            Form</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Section</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Details\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    TextField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Title\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $title)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    TextField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Amount\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $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\">keyboardType</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.decimalPad)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                }</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                Section</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    Picker</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Category\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selection</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $category) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                        ForEach</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(Category.allCases, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: \\.</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { category </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                            Label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category.title, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">systemImage</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: category.symbolName)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                                .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">tag</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(category)</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>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    DatePicker</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Date\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">selection</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $date, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">Date.now, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">displayedComponents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .date)</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">navigationTitle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"New Expense\"</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\">navigationBarTitleDisplayMode</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(.inline)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            .</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">toolbar</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                ToolbarItem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">placement</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .cancellationAction) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Cancel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">dismiss</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:#0550AE;--shiki-dark:#79C0FF\">                ToolbarItem</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">placement</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: .confirmationAction) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                    Button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Save\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">                        guard</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">else</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">                        store.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">add</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\">: title, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">amount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: amount, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">category</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: category, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">date</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: date))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">                        dismiss</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\">disabled</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">canSave)</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>\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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>تغلّف الـ sheet نموذجها بـ <code dir=\"ltr\">NavigationStack</code> خاص بها. فالـ sheet عرض منفصل لا يرث مكدّس القائمة، وبدونه لا يوجد شريط يحمل العنوان أو زرَّي Cancel وSave. ووضعهما عبر <code dir=\"ltr\">.cancellationAction</code> و<code dir=\"ltr\">.confirmationAction</code> يضعهما حيث يتوقّعهما مستخدمو iOS.</p>\n<p>كل حقل مربوط بـ <code dir=\"ltr\">@State</code> خاصة به، فيكون النموذج مسوّدة: لا شيء يمسّ المخزن حتى النقر على Save. وCancel يرمي المسوّدة. هذا سلوك القيمة يؤتي ثماره مرة أخرى.</p>\n<p>حقل المبلغ هو الأهم. يحلّل <code dir=\"ltr\">TextField(value:format:)</code> ما يكتبه المستخدم حسب إعداداته المحلية، فتصبح “12,50” في ألمانيا 12.5، ولا تحمل <code dir=\"ltr\">amount</code> إلا رقمًا حُلّل بنجاح، أو <code dir=\"ltr\">nil</code> ما دام لا يوجد رقم. هذا يستبدل التحليل المتساهل بـ <code dir=\"ltr\">Decimal(string:)</code> من درس الـ optionals. ويفكّها <code dir=\"ltr\">guard let amount</code> في إجراء Save، ويعطّل <code dir=\"ltr\">canSave</code> الزر حتى يكون العنوان والمبلغ منطقيين، فلا يمكن حفظ مدخلات غير صالحة أصلًا. والنطاق <code dir=\"ltr\">in: ...Date.now</code> يمنع التواريخ المستقبلية. أما <code dir=\"ltr\">dismiss</code> فيأتي من الـ environment ويغلق كل ما عرض هذا الـ view.</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>tags من نوع خاطئ في Picker</span></p>\n<p>إن كان <code dir=\"ltr\">selection</code> من النوع <code dir=\"ltr\">Category</code> وكتبت <code dir=\"ltr\">.tag(category.rawValue)</code>، يُترجَم الكود، ويظهر المنتقي، واختيار صف لا يغيّر شيئًا، لأن tag من النوع <code dir=\"ltr\">String</code> لا يساوي أبدًا اختيارًا من النوع <code dir=\"ltr\">Category</code>. يجب أن تكون الـ tags من نوع الاختيار نفسه تمامًا. ومع اختيار optional مثل <code dir=\"ltr\">Category?</code>، علّم الصفوف بـ <code dir=\"ltr\">Optional(category)</code>.</p>\n</aside>\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>زر Save المعطّل يمنع البيانات السيئة، لكنه وحده قد يترك الناس يخمّنون. لأي شيء أقل وضوحًا من «املأ العنوان»، أضف تذييلًا قصيرًا للقسم يقول ما الذي ينقص.</p>\n</aside>\n<p>صار Pocket Budget الآن تطبيقًا يعمل بمسار حقيقي من القائمة إلى التفاصيل إلى النموذج. لكنه ينسى كل شيء حين تغلقه، ولا يعرف شيئًا خارج الهاتف. القسم الأخير يصلح الأمرين، بدءًا بكيفية تنفيذ Swift للعمل الذي يستغرق وقتًا.</p>\n","toc":[{"id":"sec-1","text":"قائمة من صفوف قابلة للتمييز","level":2},{"id":"sec-2","text":"التنقّل بالقيم","level":2},{"id":"sec-3","text":"نموذج داخل sheet","level":2}],"takeaways":["<code dir=\"ltr\">List</code> و<code dir=\"ltr\">ForEach</code> يحتاجان إلى التمييز بين الصفوف، ولهذا تتوافق نماذجك مع <code dir=\"ltr\">Identifiable</code>.","<code dir=\"ltr\">NavigationLink(value:)</code> مع <code dir=\"ltr\">.navigationDestination(for:)</code> يفصلان بين ما نُقر عليه وبين الشاشة التي تعرضه.","استخدم sheet لمهمة مستقلّة مثل إضافة مصروف، وأغلقها عبر قيمة <code dir=\"ltr\">dismiss</code> من الـ environment.","حقل <code dir=\"ltr\">TextField</code> مع <code dir=\"ltr\">format:</code> يحوّل الكتابة إلى قيمة محدّدة النوع حسب الإعدادات المحلية للمستخدم، بدل تحليل النصوص يدويًا.","قيم <code dir=\"ltr\">.tag</code> في <code dir=\"ltr\">Picker</code> يجب أن تكون من نوع <code dir=\"ltr\">selection</code> نفسه تمامًا، وإلا يتوقّف المنتقي عن العمل بصمت."],"further":[{"title":"NavigationStack","url":"https://developer.apple.com/documentation/swiftui/navigationstack"},{"title":"Lists","url":"https://developer.apple.com/documentation/swiftui/lists"},{"title":"Form","url":"https://developer.apple.com/documentation/swiftui/form"}],"quiz":[{"q":"<p>كتبت <code dir=\"ltr\">NavigationLink(value: expense)</code> لكن النقر على الصف لا يفعل شيئًا. ما السبب الأرجح؟</p>","options":[{"text":"لا يوجد <code dir=\"ltr\">.navigationDestination(for: Expense.self)</code> داخل الـ <code dir=\"ltr\">NavigationStack</code>.","why":"صحيح. رابط القيمة يقول فقط ما الذي اختير؛ أما الـ modifier الخاص بالوجهة فيقرّر ما يُعرض له."},{"text":"<code dir=\"ltr\">Expense</code> لا يتوافق مع <code dir=\"ltr\">Codable</code>.","why":"قيم التنقّل يجب أن تكون <code dir=\"ltr\">Hashable</code>، لا <code dir=\"ltr\">Codable</code>. و<code dir=\"ltr\">Codable</code> لا يهم إلا إن كنت تحفظ المسار."},{"text":"الصف يحتاج إلى <code dir=\"ltr\">.onTapGesture</code>.","why":"<code dir=\"ltr\">NavigationLink</code> يتولّى النقر بنفسه؛ وإضافة gesture قد تحجبه فعلًا."}],"answer":0},{"q":"<p>يستخدم منتقي التصنيفات <code dir=\"ltr\">selection: $category</code> حيث <code dir=\"ltr\">category</code> من النوع <code dir=\"ltr\">Category</code>، وصفوفه معلّمة بـ <code dir=\"ltr\">.tag(category.rawValue)</code>. ماذا يحدث؟</p>","options":[{"text":"يعمل، لأن SwiftUI يحوّل القيم الخام تلقائيًا.","why":"يقارن SwiftUI الـ tags بالاختيار حسب النوع والقيمة؛ وقيمة <code dir=\"ltr\">String</code> لا تساوي <code dir=\"ltr\">Category</code> أبدًا."},{"text":"يظهر المنتقي، لكن اختيار صف لا يغيّر الاختيار أبدًا.","why":"صحيح. الـ tags من النوع <code dir=\"ltr\">String</code> والاختيار من النوع <code dir=\"ltr\">Category</code>، فلا شيء يتطابق. علّم الصفوف بقيمة الـ enum نفسها."},{"text":"تفشل الترجمة بخطأ في النوع.","why":"<code dir=\"ltr\">.tag</code> تقبل أي قيمة <code dir=\"ltr\">Hashable</code>، فيُترجَم الكود ويفشل بهدوء وقت التشغيل، وهذا ما يجعله خبيثًا."}],"answer":1},{"q":"<p>لماذا تربط حقل المبلغ بقيمة <code dir=\"ltr\">Decimal?</code> مع <code dir=\"ltr\">format: .currency(code: &quot;USD&quot;)</code> بدل <code dir=\"ltr\">String</code>؟</p>","options":[{"text":"النصوص لا يمكن استخدامها مع <code dir=\"ltr\">TextField</code>.","why":"<code dir=\"ltr\">TextField(&quot;Title&quot;, text: $title)</code> هو أشيع حقل نصي على الإطلاق."},{"text":"لأنه يجعل لوحة المفاتيح رقمية تلقائيًا.","why":"الـ format لا يغيّر لوحة المفاتيح؛ هذا دور <code dir=\"ltr\">.keyboardType(.decimalPad)</code>."},{"text":"الحقل يحلّل مدخلات المستخدم حسب إعداداته المحلية ويسلّمك <code dir=\"ltr\">Decimal</code> محدّد النوع، فلا يبقى نص تحلّله.","why":"صحيح. “12,50” في ألمانيا تصبح 12.5، ولا تكتب أنت أي محلّل."}],"answer":2}],"exercise":{"kind":"fill","language":"swift","template":"NavigationStack {\n    List(store.expenses) { expense in\n        NavigationLink({{0}}: expense) {\n            ExpenseRow(expense: expense)\n        }\n    }\n    .navigationTitle(\"Expenses\")\n    .{{1}}(for: Expense.self) { expense in\n        ExpenseDetail(expense: expense)\n    }\n    .toolbar {\n        Button(\"Add expense\", systemImage: \"plus\") { isAdding = true }\n    }\n    .{{2}}(isPresented: $isAdding) {\n        AddExpenseView(store: store)\n    }\n}","blanks":[{"answers":["value"]},{"answers":["navigationDestination"]},{"answers":["sheet"]}],"title":"اربط الشاشة الرئيسية","prompt":"<p>أكمل الشاشة الرئيسية في Pocket Budget. يجب أن يدفع النقر على صف الشاشة <code dir=\"ltr\">ExpenseDetail</code> لذلك المصروف باستخدام التنقّل المبني على القيم، ويجب أن يعرض زر + الشاشة <code dir=\"ltr\">AddExpenseView</code> بشكل منبثق ما دامت <code dir=\"ltr\">isAdding</code> صحيحة.</p>\n","hints":["الرابط المبني على القيم يسمّي القيمة التي يحملها عبر تسمية وسيط.","الـ modifier الذي يربط نوع قيمة بشاشة وجهة في اسمه كلمة “navigation” ويأخذ <code dir=\"ltr\">for:</code>.","الـ modifier الذي يعرض بطاقة منبثقة فوق الشاشة الحالية اسمه كلمة واحدة."],"explanation":"<p><code dir=\"ltr\">NavigationLink(value: expense)</code> يسجّل فقط أيّ مصروف نُقر عليه. و<code dir=\"ltr\">.navigationDestination(for: Expense.self)</code> يخبر الـ <code dir=\"ltr\">NavigationStack</code> المحيط بما يعرضه لأي قيمة <code dir=\"ltr\">Expense</code>، فيعمل الاثنان معًا كزوج؛ وبدون الوجهة لا يفعل النقر شيئًا. أما <code dir=\"ltr\">.sheet(isPresented: $isAdding)</code> فيعرض النموذج كلما أصبحت <code dir=\"ltr\">isAdding</code> مساوية لـ <code dir=\"ltr\">true</code>، ويعيدها إلى <code dir=\"ltr\">false</code> حين تُغلق الـ sheet.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"swift\" data-index=\"0\"><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\">NavigationLink</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: expense) { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">…</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\">navigationDestination</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: Expense.</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">self</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">…</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\">sheet</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">isPresented</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: $isAdding) { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">…</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span></code></pre></div>\n","checkLabels":{}},"words":587,"readMinutes":3}