لإنشاء معاينات SwiftUI تفاعلية، امنح كل معاينة حالتها المحلية الخاصة، ومرّر نموذج عرض قابلًا للملاحظة إلى المحرر، وأتح الخصائص القابلة للتحرير عبر الروابط. يشرح هذا الدليل إنشاء محرر تسميات صغير لنظام macOS باستخدام نصوص تجريبية اصطناعية.

اختر موضع تخزين حالة المعاينة

المطلوب هو معاينة محرر لسطح المكتب يغيّر فيه المستخدم عنوان التسمية ونص التفاصيل، ثم يبدّل خيار إظهار التفاصيل. ينبغي للمحرر أن يستجيب فورًا، وأن تبدأ كل معاينة بإعداد تجريبي صريح.

استخدم أبسط إدخال يدعم التفاعل. توصي Apple بتمرير البيانات التي تحتاج إليها المشاهدات فقط، لأن التبعيات المعقدة تجعل إنشاء المعاينات أصعب. يمكن للتسمية الثابتة أن تستقبل سلسلة نصية؛ أما المحرر فيحتاج إلى اتصال قابل للكتابة بحالته. إضافة معاينات إلى ملفات الواجهة.

ما تحتاج إليه المشاهدة الإدخال المناسب موضع الحالة التجريبية
عرض نص ثابت سلسلة نصية غير قابلة للتغيير في المعاينة مباشرةً
تحرير قيمة واحدة يملكها العنصر الأب خاصية @Binding @State محلية في حاوية المعاينة
تحرير عدة خصائص مترابطة قابلة للملاحظة نموذج قابل للملاحظة معروض عبر @Bindable مثيل نموذج مخزّن في @State بحاوية المعاينة
قراءة نموذج مقدّم عبر البيئة اعتماد بيئي حقن صريح في جذر المعاينة

في هذا المثال، يحتفظ نموذج العرض بسلسلتين نصيتين، وإعداد للرؤية، وعنوان عرض محسوب. وتملك كل معاينة مثيلًا مستقلًا. يوضح هذا شروط البدء بجوار تعريف المعاينة، ويمنع اعتماد المثال على كائن مفرد مشترك قابل للتغيير.

إعداد هدف SwiftUI لنظام macOS

استخدم Xcode 16 أو أحدث لصياغة @Previewable المضمّنة الموضحة هنا. قدمت Apple هذه الماكرو في Xcode 16. اجعل هدف النشر لهذا المثال macOS 14 أو أحدث، حيث يدعم SwiftUI إطار Observation؛ كما يسرد مرجع Previewable() الحالي من Apple التوفر على macOS 14. ملاحظات إصدار Xcode 16، إدارة بيانات النموذج في تطبيقك، Previewable().

أضف ملف Swift باسم LabelEditor.swift إلى هدف تطبيق SwiftUI موجود لنظام macOS. يشكّل مقطعا الشيفرة التاليان معًا مثال المعاينة الكامل. أبقِ نقطة الدخول الحالية للتطبيق؛ فهذا الملف يعرّف النموذج والمحرر والمعاينات.

تستخدم الشيفرة صياغة @State عبر سلاسل الأدوات المدعومة. تميّز وثائق Apple الحالية بين التطبيقين: يستخدم Xcode 26 وما قبله غلاف الخاصية State، بينما يستخدم Xcode 27 وما بعده ماكرو State(). ضع هذا الفرق في الحسبان عند فحص الشيفرة المُنشأة أو تقدير كلفة التهيئة. State، State().

إنشاء النموذج القابل للملاحظة والمشاهدة القابلة للتحرير

طبّق @Observable على النموذج كي يتمكن SwiftUI من تتبع الخصائص التي تقرؤها الواجهة. استخدم @Bindable في المحرر لإنشاء روابط الخصائص التي تحتاج إليها حقول النص ومفتاح التبديل. توثّق Apple هذا الجمع لتحرير النماذج القابلة للملاحظة. إدارة بيانات النموذج في تطبيقك، Bindable.

import Foundation
import Observation
import SwiftUI

@MainActor
@Observable
final class LabelViewModel {
    var title: String
    var detail: String
    var showsDetail: Bool

    init(
        title: String,
        detail: String,
        showsDetail: Bool = true
    ) {
        self.title = title
        self.detail = detail
        self.showsDetail = showsDetail
    }

    var displayedTitle: String {
        let trimmed = title.trimmingCharacters(
            in: .whitespacesAndNewlines
        )
        return trimmed.isEmpty ? "Untitled label" : trimmed
    }
}

@MainActor
struct LabelEditor: View {
    @Bindable var model: LabelViewModel

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            VStack(alignment: .leading, spacing: 6) {
                Text("Title")
                TextField("Label title", text: $model.title)
            }

            VStack(alignment: .leading, spacing: 6) {
                Text("Detail")
                TextField("Label detail", text: $model.detail)
            }

            Toggle("Show detail", isOn: $model.showsDetail)

            Divider()

            Text("Rendered label")
                .font(.caption)
                .foregroundStyle(.secondary)

            VStack(alignment: .leading, spacing: 8) {
                Text(model.displayedTitle)
                    .font(.title2)
                    .fixedSize(horizontal: false, vertical: true)

                if model.showsDetail {
                    Text(model.detail)
                        .foregroundStyle(.secondary)
                        .fixedSize(horizontal: false, vertical: true)
                }
            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding()
            .background(
                .quaternary,
                in: RoundedRectangle(cornerRadius: 10)
            )
        }
        .textFieldStyle(.roundedBorder)
        .padding(20)
    }
}

يتلقى النموذج عمدًا جميع القيم التجريبية عبر المُهيّئ. ولا يفعل المُهيّئ سوى إسناد خصائص محلية. يغيّر الحساب الاحتياطي ما تعرضه التسمية المرسومة من دون استبدال العنوان الذي أدخله المستخدم: يظل العنوان المكوّن من مسافات بيضاء فقط قابلًا للتحرير، بينما تكون النتيجة المعروضة Untitled label.

يتلقى المحرر نموذجه من المستدعي. ويوفر تعريف @Bindable روابط مثل $model.title؛ أما ملكية الحالة فتكون في حاوية المعاينة. لو كانت هذه المشاهدة تعرض قيم النموذج فقط، لكفى مرجع عادي إلى النموذج لمراقبة التغييرات. يفيد تخزين Bindable هنا لأن عناصر التحكم تحتاج إلى روابط خصائص قابلة للكتابة. Bindable.

منح كل معاينة حالة تجريبية مستقلة

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

#Preview("Standard label") {
    @Previewable @State var model = LabelViewModel(
        title: "Sample tile",
        detail: "Blue square, medium size"
    )

    LabelEditor(model: model)
        .frame(width: 420)
        .preferredColorScheme(.light)
}

#Preview("Long label in a narrow editor") {
    @Previewable @State var model = LabelViewModel(
        title: "Sample collection with a longer descriptive label",
        detail: "Round tiles arranged in three columns"
    )

    LabelEditor(model: model)
        .frame(width: 300)
        .preferredColorScheme(.dark)
}

#Preview("Blank title and hidden detail") {
    @Previewable @State var model = LabelViewModel(
        title: "   ",
        detail: "An optional detail line",
        showsDetail: false
    )

    LabelEditor(model: model)
        .frame(width: 420)
}

تجعل @Previewable تعريف الحالة المحلية جزءًا من مشاهدة SwiftUI مضمّنة ينشئها #Preview. توثّق Apple أن التعريفات الموسومة تصبح خصائص في تلك المشاهدة المُنشأة. ضع التعريف عند جذر إغلاق المعاينة؛ فاستخدام @Previewable خارج جسم المعاينة خطأ. Previewable().

سلسلة الملكية الناتجة واضحة: تخزّن حاوية المعاينة المُنشأة النموذج في @State، وتتلقى LabelEditor مرجع النموذج، ويكشف @Bindable خصائصه القابلة للتحرير. يدعم SwiftUI تخزين نموذج Observation في الحالة وتمرير ذلك المرجع إلى المشاهدات الفرعية. State.

تعامل مع قيم المُهيّئ على أنها بيانات تجهيز أولية. يمكن للمعاينة التفاعلية تغييرها أثناء تشغيلها. عند إعادة إنشاء تخطيط معين، ارجع إلى مثيل معاينة جديد قبل مقارنة مظهره الأولي.

فحص السلوك باستخدام مصفوفة سيناريوهات صغيرة

افتح LabelEditor.swift، واختر Show Canvas، ثم اختر معاينة مسماة من علامات تبويب اللوحة. إذا كانت اللوحة متوقفة مؤقتًا، فاختر Restart. فعّل وضع Live لاختبار إدخال النص ومنطق عناصر التحكم. تصف وثائق اللوحة الحالية من Apple عناصر التحكم هذه. التفاعل مع المعاينات في اللوحة.

استخدم الفحوص التالية للتمييز بين مشكلات الحالة ومشكلات التخطيط. النتائج المتوقعة مستنبطة من شيفرة المثال؛ وليست نتائج اختبار مُبلّغًا عنها.

السيناريو الإجراء النتيجة المتوقعة
تسمية عادية استبدل العنوان بـ Amber circle يتغير العنوان المعروض فورًا
إظهار التفاصيل أوقف Show detail ثم شغّله تختفي التفاصيل ثم تعود مع الاحتفاظ بنصها
عنوان فارغ احذف جميع أحرف العنوان يصبح العنوان المعروض Untitled label
عنوان من مسافات بيضاء أدخل مسافات فقط يحتفظ الحقل بتلك المسافات؛ ويستخدم العنوان المعروض القيمة الاحتياطية
محرر ضيق افحص معاينة العنوان الطويل يلتف العنوان المعروض داخل البطاقة
بيانات مستقلة حرّر إحدى المعاينات ثم افحص معاينة أخرى جديدة تبدأ المعاينة الأخرى بقيم النموذج المعرّفة لها

إذا نجح إدخال النص لكن ظل الناتج المعروض دون تغيير، فتحقق من الاتصال بين رابط الحقل والخاصية التي يقرؤها الناتج. هنا، يقود كلا المسارين إلى model.title، مباشرةً أو عبر displayedTitle. إذا أدى تبديل الرؤية إلى مسح التفاصيل، فابحث عن شيفرة تسند قيمة فارغة؛ فهذا المثال لا يغيّر إلا showsDetail.

لم تتوفر أداة ترجمة Swift إلى شيفرة تنفيذية ولا بيئة تشغيل لمعاينات macOS في بيئة البحث، لذا فُحصت الأمثلة بمقارنتها بواجهات API الموثقة بدلًا من ترجمتها أو تشغيلها. أنشئ الهدف وأكمل فحوص التفاعل في Xcode قبل تكييف المحرر.

إبقاء الإعداد المشترك منفصلًا عن بيانات التجهيز القابلة للتحرير

في نموذج صغير كهذا، يسهل فهم كل معاينة عند إنشائها محليًا. إذا احتاجت عدة معاينات لاحقًا إلى إعداد مشترك مكلف وقابل لإعادة الاستخدام، توفر Apple PreviewModifier: إذ تُخزّن سياقاته المشتركة مؤقتًا ويُعاد استخدامها بين المعاينات المشاركة. تتطلب الواجهة macOS 15 أو أحدث. PreviewModifier.

يقود سلوك المشاركة الموثّق هذا إلى خيار تصميم عملي: أبقِ نماذج بيانات التجهيز القابلة للتحرير بشكل مستقل محلية، وخصّص السياق المشترك للإعداد الذي تريد عمدًا أن تعيد المعاينات المشاركة استخدامه. كما أن النموذج المشترك القابل للتغيير سيشارك تعديلاته، ما قد يصعّب مقارنة السيناريوهات المنفصلة.

إذا كانت مشاهدة موجودة تسترجع نموذجها حسب النوع من البيئة، فاحقن المثيل المطلوب في جذر المعاينة. توثّق Apple أن غياب نموذج غير اختياري من النوع المطلوب يؤدي إلى استثناء. لذلك ينبغي للمعاينة توفير الاعتمادات التي يقرأها جذرها المختار فعليًا. إدارة بيانات النموذج في تطبيقك.

الحفاظ على حدود AppKit وTextKit

يستخدم المحرر أعلاه عناصر تحكم SwiftUI. إذا كان محرر macOS الحالي لديك يتضمن مشاهدة نص من AppKit، فاستخدم NSViewRepresentable لدمجها في تسلسل SwiftUI الهرمي. أنشئها واضبطها عبر makeNSView، ثم طبّق حالة SwiftUI الواردة عبر updateNSView. مرّر تغييرات تفويض AppKit عبر منسّق عندما يلزم أن تعود التعديلات إلى SwiftUI. دع SwiftUI يتحكم في إطار المشاهدة الممثّلة وحدودها. NSViewRepresentable.

تدعم Apple أيضًا المعاينات المباشرة لمشاهدات AppKit ومتحكمات المشاهدة. غير أن @Previewable خاصية مقتصرة على SwiftUI ولا تنطبق على معاينات AppKit المباشرة. لاستخدام نمط الحالة المحلية في هذه المقالة مع محتوى AppKit، اعرض حاوية SwiftUI تتضمن المشاهدة الممثّلة. إضافة معاينات إلى ملفات الواجهة.

حافظ على اختيار محرك النص عند إنشاء NSTextView. توثّق Apple NSTextView(usingTextLayoutManager: true) لاختيار TextKit 2 أثناء التهيئة. كما توضح إرشادات TextKit أن الوصول إلى layoutManager الخاص بـ TextKit 1 قد يؤدي إلى تراجع أحادي الاتجاه لهذا المثيل من مشاهدة النص. استخدم واجهات TextKit 2 المناسبة عندما يكون هذا هو المحرك الذي تقصده؛ فالمعاينة لا تلغي تلك الحدود. ما الجديد في TextKit ومشاهدات النص.

تشخيص إخفاقات المعاينة عند حدود الملكية

ابدأ بأول تشخيص للبناء. تنشئ ماكروهات Swift الشيفرة وقت الترجمة، لذا قد يرتبط الخطأ المميز عند استدعاء ماكرو بتعريفات مُنشأة. يتيح لك الأمر Editor > Expand Macro في Xcode فحص تلك الشيفرة. تطبيق الماكروهات.

ثم تحقق من آلية الحالة. يحتاج ObservableObject الحالي المستند إلى Combine إلى نمط الملكية المقابل: تحذر Apple من أن تخزين مثل هذا الكائن في State لا يتتبع التغييرات على خصائصه المنشورة، وتوجّه المالكين إلى StateObject. يستخدم المثال هنا @Observable، لذا تتبع ملكية حالته مسار Observation. State.

اجعل ملف المعاينة النهائي متمحورًا حول قيم بدء صريحة وتفاعلات واضحة. بعد أن تعمل سيناريوهات التسمية العادية والعنوان الطويل والعنوان الفارغ كما هو متوقع، أعد استخدام المحرر نفسه مع نموذج تملكه المشاهدة الأم المناسبة في تطبيقك. وتظل بيانات التجهيز في المعاينة طريقة موجزة لإعادة إنشاء ظروف الواجهة تلك.

المصادر