استخدم sheet(item:onDismiss:content:) مع عنصر محدد اختياري واحد، وأنشئ محتوى الورقة من العنصر الذي تتلقاه دالتها. يمنح هذا تطبيق Mac قيمة واحدة تحدد ما إذا كان سيعرض ورقة وما الذي ستعرضه.
ينفّذ هذا البرنامج التعليمي مهمة صغيرة على سطح المكتب: انقر على عنصر محلي تجريبي واعرض تفاصيله فورًا. كما يقدّم تسلسلًا تشخيصيًا لورقة تُفتح فارغة، أو تعرض التحديد السابق، أو تعرض نصًا قديمًا داخل عرض AppKit.
لماذا تستخدم ورقة تعتمد على عنصر؟
يوفّر SwiftUI طريقتين مناسبتين للعرض. تعرض صيغة القيمة المنطقية الورقة عندما تصبح قيمة الربط true. أما صيغة العنصر فتقبل قيمة اختيارية قابلة للتعرّف، وتمرّرها إلى إغلاق المحتوى. اختر صيغة القيمة المنطقية للوحة ثابتة، واختر صيغة العنصر عندما يعتمد محتوى اللوحة على تحديد ما. راجع وثائق Apple حول الأوراق المعتمدة على قيمة منطقية والأوراق المعتمدة على عنصر.
يسمح التصميم الذي يستخدم خصائص منفصلة للتحديد والعرض بحالات مثل طلب العرض من دون تحديد أي عنصر. يؤدي جمعهما في قيمة اختيارية واحدة إلى استبعاد هذه الحالة من نموذج الحالة الخاص بك: تمثّل nil عدم وجود عنصر مطلوب، وتمثّل القيمة المعبأة العنصر المراد فحصه. هذا استنتاج تصميمي مبني على واجهتي API، وليس ادعاءً بشأن خلل غير موثّق في جدولة SwiftUI.
إذا كان التنفيذ الحالي يعيّن عنصرًا محددًا ثم يبدّل قيمة منطقية، فهاتان العبارتان وحدهما لا تفسّران سبب فشل العرض الأول. افحص مسار البيانات في المحتوى قبل إرجاع المشكلة إلى التوقيت. السؤال المفيد هو ما إذا كانت الورقة تعرض المدخل الممرّر إليها، أم تقرأ خاصية أخرى، أم تعرض نسخة مخزّنة بشكل منفصل.
أنشئ مثالًا كاملًا لنظام macOS
أنشئ مشروع تطبيق macOS في Xcode بواجهة SwiftUI. عيّن macOS 12 أو إصدارًا أحدث كهدف للنشر في هذا المثال. تتوفر واجهة الورقة المعتمدة على عنصر منذ macOS 10.15، بينما يتطلب إجراء استبعاد البيئة المستخدم أدناه macOS 12. توثّق Apple حدود التوفر هذه في الأوراق المعتمدة على عنصر وقيمة بيئة الاستبعاد.
استبدل تعريف التطبيق وعرض المحتوى اللذين أنشأهما Xcode بهذا البرنامج الكامل. أبقِ نقطة دخول واحدة للتطبيق في الهدف. جميع التسميات والأوصاف أدناه بيانات محلية تجريبية.
import SwiftUI
struct SampleItem: Identifiable {
let id: String
let title: String
let detail: String
}
@main
struct FirstSheetDemoApp: App {
var body: some Scene {
WindowGroup {
ItemListView()
}
}
}
struct ItemListView: View {
private let items = [
SampleItem(
id: "tile-a",
title: "Amber Tile",
detail: "A square sample with an amber finish."
),
SampleItem(
id: "tile-b",
title: "Blue Tile",
detail: "A rectangular sample with a blue finish."
)
]
@State private var presentedItem: SampleItem? = nil
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Sample Items")
.font(.title2)
ForEach(items) { item in
Button("Inspect \(item.title)") {
presentedItem = item
}
}
}
.padding(24)
.frame(minWidth: 360, minHeight: 200)
.sheet(item: $presentedItem) { item in
ItemDetailView(item: item)
}
}
}
struct ItemDetailView: View {
let item: SampleItem
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text(item.title)
.font(.title)
Text(item.detail)
Text("Item ID: \(item.id)")
.font(.caption)
HStack {
Spacer()
Button("Done") {
dismiss()
}
}
}
.padding(24)
.frame(width: 380, height: 220)
}
}
يعيّن الزر القيمة التي نُقر عليها إلى presentedItem. ثم تمرّر دالة العرض معاملها item إلى ItemDetailView. لاحظ أن عرض التفاصيل لديه خاصية إدخال عادية، let item، بدلًا من خاصية حالة ثانية للعنصر المحدد. توصي Apple بالحفاظ على مصدر حقيقة واحد ومشاركة القيم للقراءة فقط عبر الخصائص، واستخدام الروابط عندما يحتاج العنصر الفرعي إلى صلاحية الكتابة. راجع State.
ينتمي إجراء الاستبعاد إلى ItemDetailView، حيث يقرأ البيئة الخاصة بالورقة. تحذّر Apple صراحةً من أن قراءته في العرض المقدِّم واستدعاء ذلك الإجراء من محتوى الورقة يستخدمان بيئة العرض المقدِّم. أبقِ التصريح بجوار محتوى الورقة نفسه كما هو موضّح. راجع dismiss.
تمت مراجعة اتساق المدخلات والمراجع في البرنامج؛ لكنه لم يُترجم أو يُشغّل هنا لعدم توفر سلسلة أدوات Swift في هذه البيئة. نفّذ الفحوص التالية في Xcode على إصدار macOS المستهدف.
تحقّق من العرض الأول باستخدام تسلسل نقرات مقصود
ابدأ كل فحص للعرض الأول بتشغيل جديد. قد يخفي الاختبار بعد فتح عنصر آخر المشكلة الأصلية. التوقعات التالية مستمدة من التسميات وتعيينات البيانات الصريحة في المثال؛ وهي معايير قبول وليست نتائج اختبار مُعلنة.
| الإجراء | محتوى الورقة المتوقع | ما الذي يفحصه |
|---|---|---|
شغّل التطبيق وانقر أولًا على Inspect Blue Tile |
Blue Tile، ووصفها المستطيل، وtile-b |
يستخدم العرض الأول العنصر الذي نُقر عليه |
انقر على Done، ثم افحص Amber Tile |
Amber Tile، ووصفها المربع، وtile-a |
يصل التحديد اللاحق إلى المحتوى |
استبعد الورقة وافحص Amber Tile مرة أخرى |
تفاصيل العنبر نفسها | تظل إعادة فتح العنصر متسقة |
أعد تشغيل التطبيق وافحص Amber Tile أولًا |
تفاصيل العنبر فورًا | لا تعتمد صحة العرض على أي عينة تُفتح أولًا |
إن عرض العنوان والمعرّف معًا يجعل هذا الفحص أنفع من فحص العنوان وحده. إذا كانت واجهتك الحقيقية تسمح بأسماء معروضة مكررة، فاستخدم أثناء التطوير عنصرين اصطناعيين لهما العنوان نفسه ومعرّفان ووصفان مختلفان. يكشف ذلك التحديد العرضي بحسب النص المعروض.
شخّص المحتوى القديم من الزر حتى العرض النهائي
نفّذ هذه الفحوص بالترتيب. تضيق كل خطوة نطاق موضع الاختلاف، ما يتيح لك تعديل الطبقة المسؤولة دون إضافة حلول التفافية غير مرتبطة بالعرض.
- افحص مدخل الإجراء. ضع نقطة توقف في إجراء الزر الذي نُقر عليه وافحص
item.id. تأكد من أن الإجراء يتلقى السجل المقصود. - افحص مدخل العرض. افحص المعامل
itemداخل إغلاق الورقة. مرّر هذا المعامل إلى عرض التفاصيل بدلًا من البحث مجددًا عن تحديد منفصل. - افحص الخاصية المعروضة أخيرًا. تأكد من أن العنوان والوصف يقرآن من مدخل عرض التفاصيل. ابحث عن خاصية مخزّنة مؤقتًا أو مسودة محلية تحتوي على قيمة سابقة.
- افحص أي جسر إلى AppKit. إذا كان نص SwiftUI العادي صحيحًا بينما النص المُغلّف قديم، فافحص طريقة التحديث في العنصر المُمثِّل.
حافظ على ثبات الهوية
تتطلب واجهة API المعتمدة على عنصر بروتوكول Identifiable. يعرّف Swift هذا البروتوكول حول هوية مستقرة، ويترك مدة هذه الهوية ونطاقها للنوع الذي يلتزم بالبروتوكول ولمستهلكه. يستخدم المثال معرّفات نصية مخزّنة تميّز السجلين. راجع Identifiable.
في قائمة حقيقية، اختر معرّفًا يواصل تحديد السجل نفسه عبر التحديثات التي تدعمها واجهتك. تجنّب اشتقاق الهوية من عنوان قابل للتغيير أو يمكن أن تتشاركه عناصر متعددة. وتجنّب أيضًا خاصية id محسوبة تنشئ UUID جديدًا في كل مرة تُقرأ فيها؛ فهذا لا يوفّر هوية مستقرة.
افصل مدخل العرض عن المسودة القابلة للتحرير
يمكن لأداة الفحص عرض item.title مباشرةً. وقد ينشئ المحرر مسودة محلية عن قصد، لكن المسودة تضيف قيمة أخرى يجب تحديد مدة بقائها. قرر متى تُنشأ، وما إذا كان ينبغي لتغييرات النموذج اللاحقة أن تستبدلها، وما الذي يحدث عندما ينتهي المستخدم أو يلغي.
تشرح Apple أن SwiftUI يحافظ على مساحة تخزين الحالة لعرضٍ بحسب هويته، ويهيئها في بداية مدة تلك الهوية. لا يؤدي إنشاء قيمة عرض جديدة بحد ذاته إلى إنشاء حالة جديدة. لذلك، فإن تهيئة حالة محلية انطلاقًا من مدخل ليست آلية عامة لمزامنة تلك الحالة مع المدخلات اللاحقة. راجع Demystify SwiftUI.
في هذه المهمة المخصصة للقراءة فقط، أبقِ خاصية الإدخال العادية. إذا وسّعت المهمة لتشمل التحرير، فاختر تصميمًا صريحًا للمسودة والتثبيت أو رابطًا بقيمة النموذج المقصودة. تجنّب تغيير هوية العرض لمجرد تحديث النص؛ فتغيير الهوية يستبدل الحالة المرتبطة بها، وقد يؤدي إلى فقدان مسودة غير مكتملة. ويترتب ذلك على نموذج مدة الحالة الذي توثّقه Apple.
ما الذي يتغير عندما تحتوي الورقة على عرض نص AppKit؟
تظل الورقة التي تحتوي على NSTextView بحاجة إلى مسار البيانات نفسه للعنصر المحدد. كما تحتاج إلى جسر منفّذ على نحو صحيح. يفصل بروتوكول Apple NSViewRepresentable بين الإنشاء الأولي في makeNSView(context:) والتهيئة اللاحقة في updateNSView(_:context:). الغلاف الذي يعيّن نصه أثناء الإنشاء فقط لا يوفّر تنفيذًا لتطبيق مدخلات النص اللاحقة. راجع NSViewRepresentable.
بالنسبة إلى غلاف للقراءة فقط، هيّئ العرض الأصلي بالنص الممرّر، واجعل طريقة التحديث تطابق النص المعروض مع المدخل الحالي عند الحاجة. توثّق Apple أن SwiftUI يستدعي طريقة التحديث عند حدوث تغييرات تؤثر في عرض AppKit المقابل. راجع updateNSView(_:context:).
يحتاج الغلاف القابل للتحرير إلى سياسة إضافية: لا تستبدل نصه بلا تمييز في كل تحديث، فتطمس بذلك التعديلات الجارية. استخدم منسّقًا لإيصال تغييرات المفوّض أو الإجراء المستهدف من العرض الأصلي إلى SwiftUI، كما تصف Apple في وثائق العنصر المُمثِّل. دع SwiftUI يتحكم في إطار العرض المُمثَّل وحدوده؛ إذ تحذّر Apple من ضبط خصائص التخطيط هذه مباشرةً.
يعالج TextKit تخزين النصوص وتخطيطها وعرضها. توثّق Apple مساري NSTextLayoutManager الحديث وNSLayoutManager القديم اللذين يتيحهما NSTextView. لا توفّر هذه الإمكانات سجل الورقة المحدد. تحقّق من أن الجسر يتلقى نص العنصر الصحيح قبل البحث في تخطيط النص، وافحص التوفر على حدة قبل إضافة واجهات API أخرى لـ TextKit. راجع TextKit.
اختر نموذج العرض المناسب للمهمة
استخدم صيغة القيمة المنطقية عندما يكون المحتوى ثابتًا ويكون الظهور هو المدخل المتغير الوحيد. واستخدم صيغة العنصر لمهمة أداة الفحص هذه. توثّق Apple أن تغيير العنصر الممرّر قد يستبعد ورقة موجودة ويستبدل بها ورقة أخرى، لذا لا تعتبر استبدال العنصر ضمانًا لمواصلة التحرير بلا انقطاع ضمن العرض نفسه. راجع sheet(item:onDismiss:content:).
لضمان صحة العرض الأول، اجعل تتبّع السجل المحدد سهلًا: تعيين واحد في إجراء الزر، ومعامل عنصر واحد في إغلاق الورقة، ومدخل واحد في عرض التفاصيل. تحقّق من العنصرين التجريبيين بعد تشغيل جديد. إذا ظلت النتيجة مختلفة، فسيساعد تسلسل التشخيص على تحديد ما إذا كان الاختلاف يدخل عبر التحديد أو الحالة المحتفظ بها أو مسار تحديث العرض الأصلي.
المصادر
- sheet(item:onDismiss:content:) — Apple
- sheet(isPresented:onDismiss:content:) — Apple
- State — Apple
- dismiss — Apple
- Identifiable — Apple
- Demystify SwiftUI — Apple
- NSViewRepresentable — Apple
- updateNSView(_:context:) — Apple
- TextKit — Apple