@Environment في SwiftUI هو property wrapper لقراءة القيم من بيئة النظام، والتي تنتشر تلقائياً عبر تسلسل العروض (Views). يوفر المكون الوصول إلى نمط الألوان، الإعدادات المحلية، حجم الخط، managedObjectContext وعشرات المعلمات الأخرى للنظام. وفقًا لـ توثيق Apple Developer (2025)، يضمن @Environment أن أي تغيير في قيمة البيئة يؤدي إلى إعادة رسم جميع العروض المشتركة، مما يوفر تحديثات تفاعلية للواجهة دون استدعاءات يدوية.
النقاط الرئيسية
@Environment هو property wrapper في SwiftUI مصمم لقراءة القيم من بيئة النظام. البيئة هي حاوية هرمية من القيم يقوم SwiftUI بتوزيعها تلقائياً من العروض الأب إلى العروض الفرعية. كل قيمة بيئة تتم تحديدها بواسطة مفتاح — نوع يتوافق مع بروتوكول EnvironmentKey.
تشبه آلية البيئة حقن التبعيات (dependency injection) على مستوى الإطار: يوفر النظام مجموعة محددة من القيم — نمط الألوان (فاتح/داكن)، الإعدادات المحلية، حجم الخط، managedObjectContext لـ Core Data، dismiss لإغلاق الشاشات، وغيرها الكثير. العرض (View) الذي يعلن @Environment بمفتاح محدد يتلقى تلقائياً القيمة الحالية ويعاد رسمه عند تغييرها.
تعتمد معمارية بيئة SwiftUI على بروتوكول EnvironmentValues — هيكل يحتوي على جميع قيم النظام. كل قيمة تخزن كخاصية لهذا الهيكل مع getter و setter. يستخدم @Environment key path للوصول إلى خاصية محددة: @Environment(\.colorScheme) — الوصول إلى نمط الألوان، @Environment(\.locale) — الوصول إلى الإعدادات المحلية.
ينفذ property wrapper @Environment آليتين رئيسيتين: قراءة قيمة من البيئة والاشتراك في تغييراتها. عند إنشاء عرض (View)، يمر SwiftUI على جميع خصائص @Environment ويربطها بالقيم المقابلة من السياق الحالي. إذا قام عرض أب بتغيير قيمة عبر المعدل .environment()، فإن جميع العروض الفرعية التي تقرأ تلك القيمة تعاد رسمها تلقائياً.
ميزة مهمة: @Environment يدعم القيم الاختيارية. إذا لم تكن القيمة معينة في التسلسل، يتم إرجاع القيمة الافتراضية المحددة في EnvironmentKey. لمفاتيح النظام، القيمة الافتراضية دائماً معقولة — على سبيل المثال، نمط الألوان الافتراضي هو .light. أما بالنسبة لـ المفاتيح المخصصة، فالمطور يحدد القيمة الافتراضية في طريقة defaultValue لبروتوكول EnvironmentKey.
struct EnvironmentReaderView: View {
@Environment(\.colorScheme) var colorScheme
@Environment(\.locale) var locale
@Environment(\.sizeCategory) var sizeCategory
var body: some View {
VStack {
Text("Current scheme: \(colorScheme == .dark ? "Dark" : "Light")")
Text("Locale: \(locale.identifier)")
Text("Font size: \(sizeCategory)")
}
}
}
في المثال، يقرأ العرض ثلاث قيم بيئة نظامية. عندما يتغير colorScheme — على سبيل المثال، قام المستخدم بتفعيل الوضع الداكن في الإعدادات — يعاد رسم العرض تلقائياً بالقيمة الجديدة. بشكل مماثل عند تغيير المنطقة أو حجم الخط (Dynamic Type). لا حاجة للعرض للاشتراك في الإشعارات أو استدعاء refresh — SwiftUI يدير ذلك تلقائياً.
يوفر SwiftUI عشرات قيم البيئة النظامية تغطي جوانب مختلفة من الواجهة والسلوك. نمط الألوان (\.colorScheme) هو واحد من أكثر القيم استخداماً، مما يسمح للواجهة بالتكيف مع الموضوعين الفاتح والداكن. الإعدادات المحلية (\.locale) تحتوي على إعدادات المستخدم الإقليمية لتنسيق التواريخ والأرقام والعملات.
لـ Core Data، يتم استخدام managedObjectContext (\.managedObjectContext) — سياق يتم تمريره عبر البيئة من حاوية الاستمرارية. للتنقل، يتوفر dismiss (\.dismiss) لإغلاق الشاشة الحالية و isPresented (\.isPresented) للعروض المشرطة. بالنسبة للتقويم والمنطقة الزمنية — calendar و timeZone على التوالي.
| Key Path | النوع | الغرض |
|---|---|---|
| \.colorScheme | ColorScheme | الموضوع الفاتح أو الداكن |
| \.locale | Locale | الإعدادات الإقليمية |
| \.sizeCategory | ContentSizeCategory | حجم خط Dynamic Type |
| \.managedObjectContext | NSManagedObjectContext | سياق Core Data |
| \.dismiss | DismissAction | إغلاق الشاشة |
| \.calendar | Calendar | التقويم الحالي |
| \.timeZone | TimeZone | المنطقة الزمنية |
| \.horizontalSizeClass | UserInterfaceSizeClass | الحجم الأفقي للشاشة |
للوصول إلى قيم النظام، استخدم key path مع نقطة: @Environment(\.dismiss) var dismiss. يتحقق المجمع من وجود key path في EnvironmentValues، لذا فإن المفتاح الخاطئ سيؤدي إلى خطأ في وقت الترجمة. تضيف Apple قيمًا نظامية جديدة مع كل إصدار من iOS — القائمة الكاملة متاحة في توثيق EnvironmentValues.
على الرغم من الأسماء المتشابهة، فإن @Environment و @EnvironmentObject يخدمان أغراضًا مختلفة. @Environment يقرأ قيم النظام أو القيم المخصصة المسجلة عبر EnvironmentKey. @EnvironmentObject هو property wrapper لـ ObservableObject يتم تمريره عبر البيئة حسب النوع، دون مفتاح صريح.
يستخدم @EnvironmentObject لحقن التبعيات: يقوم العرض الأب بإنشاء كائن (على سبيل المثال، ViewModel) ويمرره إلى العروض الفرعية عبر المعدل .environmentObject(). تتلقاه العروض الفرعية عبر @EnvironmentObject ويمكنها كتابة وقراءة خصائصه. @Environment من ناحية أخرى هو لـ القراءة فقط لقيم النظام ولا يدعم التغذية الراجعة.
| المعلمة | @Environment | @EnvironmentObject |
|---|---|---|
| الغرض | قيم النظام والقيم المخصصة | حقن ObservableObject |
| المفتاح | Key path لـ EnvironmentValues | حسب نوع الكائن |
| الكتابة | قراءة فقط | قراءة وكتابة |
| القيمة المخصصة | عبر EnvironmentKey | عبر فئة ObservableObject |
| القيمة الافتراضية | نعم (defaultValue) | لا (يجب تمريرها) |
في الممارسة: استخدم @Environment للوصول إلى معلمات النظام (الموضوع، الإعدادات المحلية، حجم الخط) والتكوينات المخصصة التي لا تتغير في وقت التنفيذ. استخدم @EnvironmentObject لتمرير ViewModel أو خدمة عبر تسلسل العروض عندما تحتاج الحالة إلى التعديل من مكونات فرعية.
دعنا ننظر إلى إنشاء قيمة بيئة مخصصة. للقيام بذلك، تحتاج إلى تعريف هيكل يتوافق مع بروتوكول EnvironmentKey وتوسيع EnvironmentValues بخاصية جديدة. يسمح هذا بتمرير تكوين الموضوع أو إعدادات التطبيق عبر شجرة العروض بأكملها دون حاجة للخصائص (props).
struct AppThemeKey: EnvironmentKey {
static let defaultValue: AppTheme = .system
}
extension EnvironmentValues {
var appTheme: AppTheme {
get { self[AppThemeKey.self] }
set { self[AppThemeKey.self] = newValue }
}
}
enum AppTheme { case system, light, dark }
يتطلب بروتوكول EnvironmentKey تنفيذ الخاصية الثابتة defaultValue — القيمة التي ستستخدم إذا لم يقم العرض الأب بتعيين بيئة مخصصة. يضيف توسيع EnvironmentValues خاصية محسوبة appTheme باستخدام subscript مع المفتاح. بعد ذلك، يمكن لأي عرض قراءة القيمة عبر @Environment(\.appTheme).
struct ThemedView: View {
@Environment(\.appTheme) var appTheme
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
if appTheme == .dark || (appTheme == .system && colorScheme == .dark) {
Text("Dark mode active")
.foregroundStyle(.white)
.background(Color.black)
} else {
Text("Light mode active")
.foregroundStyle(.black)
.background(Color.white)
}
}
}
}
struct ContentView: View {
@State private var selectedTheme = AppTheme.system
var body: some View {
ThemedView()
.environment(\.appTheme, selectedTheme)
}
}
يقرأ ThemedView بيئتين: appTheme المخصصة و colorScheme النظامية. تسمح المجموعة بتكوين مرن للموضوع: يمكن للمستخدم اختيار الموضوع الفاتح، الداكن أو النظامي. إذا تم تحديد نظامي، تؤخذ القيمة من colorScheme، التي تتغير تلقائياً عند تبديل الموضوع في إعدادات iOS. يقوم العرض الأب (ContentView) بتعيين قيمة appTheme عبر المعدل .environment().
struct ModalView: View {
@Environment(\.dismiss) var dismiss
@State private var name = ""
var body: some View {
NavigationStack {
Form {
TextField("Your name", text: $name)
Button("Save") { dismiss() }
}
.navigationTitle("Edit Profile")
}
}
}
يوضح هذا المثال الاستخدام العملي لـ dismiss — مثال من DismissAction من البيئة. يؤدي استدعاء dismiss() كدالة إلى إغلاق الشاشة المشرطة أو العودة في NavigationLink. الشرط الوحيد هو أن يتم عرض العرض بشكل مشرط أو يكون داخل NavigationStack. يتم تحديد dismiss تلقائياً من السياق: إذا تم فتح العرض كشيت (sheet) — يتم إغلاق الشيت، وإذا كان كـ popover — يتم إغلاق popover.
الأسئلة الشائعة
لا، @Environment هو للقراءة فقط. لتغيير القيم، استخدم @EnvironmentObject مع ObservableObject أو @Binding. يمكن أن يكون لـ EnvironmentKeys المخصصة setter في التوسيع، ولكن التغيير عبره لا يحفز تحديثات واجهة المستخدم — هذا ممكن تقنياً ولكن غير موصى به.
@Binding ينشئ اتصالاً ثنائي الاتجاه مع مصدر الحقيقة (State، StateObject، ObservableObject). @Environment هو قراءة أحادية الاتجاه من السياق الهرمي. @Binding مناسب لنقل البيانات إلى عرض فرعي، @Environment — للوصول إلى إعدادات النظام أو الإعدادات العالمية.
عرف هيكلاً ينفذ بروتوكول EnvironmentKey مع defaultValue ثابت. ثم وسع EnvironmentValues بخاصية تستخدم getter/setter عبر subscript[key]. بعد التسجيل، استخدم @Environment(\.yourKey) للقراءة و .environment(\.yourKey, value) للتعيين.
يوفر SwiftUI أكثر من 50 قيمة نظامية: colorScheme, locale, sizeCategory, managedObjectContext, dismiss, calendar, timeZone, horizontalSizeClass, verticalSizeClass, accessibilityEnabled, layoutDirection, legibilityWeight وغيرها. القائمة الكاملة في توثيق EnvironmentValues.
نعم، @Environment يعمل في Preview، ولكن القيم الافتراضية قد تختلف عن المحاكي. للاختبار في Preview، استخدم المعدل .environment() مباشرة في كود Preview: ThemedView().environment(\.colorScheme, .dark). يسمح هذا بالتحقق البصري من حالات البيئة المختلفة.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.