@AppStorage في SwiftUI هو property wrapper للعمل مع UserDefaults يقوم بمزامنة القيمة مع UI تلقائياً. عند تغيير خاصية مُعلنة عبر @AppStorage، يتم حفظ القيمة الجديدة فوراً في UserDefaults، وعند تغيير UserDefaults من الخارج — عبر widget أو extension — يتم إعادة رسم View تلقائياً. وفقاً لـ Apple Developer Documentation (2025)، @AppStorage يدعم String وInt وDouble وBool وData وURL وإصداراتها الاختيارية، مما يوفر تخزيناً تفاعلياً لإعدادات المستخدم دون كتابة كود مراقبة يدوي.
النقاط الرئيسية
@AppStorage هو property wrapper قدمته Apple في iOS 14 يربط خاصية View بمفتاح في UserDefaults. عند قراءة الخاصية، يقوم SwiftUI بتحميل القيمة من UserDefaults باستخدام المفتاح المحدد. عند الكتابة، يحفظ القيمة الجديدة ويُعلم View بضرورة إعادة الرسم.
قبل @AppStorage، كان على المطورين قراءة UserDefaults يدوياً في onAppear، والاشتراك في UserDefaults.didChangeNotification، وتحديث @State عند التغييرات. @AppStorage يؤتمت الدورة بأكملها: تصريح بسطر واحد يحل محل 15–20 سطراً من كود التكرار. علاوةً على ذلك، @AppStorage يوفر مزامنة ثنائية الاتجاه — إذا تغيرت قيمة UserDefaults من عملية أخرى (مثل App Extension أو Widget)، ستستلم View التحديث على أي حال.
من الناحية المعمارية، @AppStorage مطبق كـ DynamicProperty، مما يسمح لـ SwiftUI بتتبع التبعيات وإعادة رسم View عند تغيير القيمة المراقبة. وهذا يجعله مثالياً لتخزين إعدادات المستخدم: لغة الواجهة، تمكين/تعطيل الميزات، آخر تبويب محدد، اسم المستخدم.
على الرغم من أن @AppStorage يستخدم UserDefaults داخلياً، فإن طرق العمل مع التخزين تختلف جوهرياً. UserDefaults هو API منخفض المستوى يتطلب إدارة يدوية للقراءة والكتابة وإشعارات التغيير. @AppStorage هو تجريد SwiftUI يوفر سلوكاً تفاعلياً مباشراً من الصندوق.
UserDefaults مناسب للعمليات لمرة واحدة: تحميل الإعدادات عند بدء التشغيل، كتابة التحليلات، تخزين الرموز مؤقتاً. @AppStorage مخصص للإعدادات التي يجب أن تحدث UI بشكل تفاعلي: مفاتيح التبديل للمظهر، اختيار اللغة، حفظ حالة الواجهة. استخدام UserDefaults مباشرة داخل View هو نمط سيئ، لأن View لا تعلم بالتغييرات دون اشتراك إضافي.
| المعامل | @AppStorage | UserDefaults |
|---|---|---|
| التفاعلية | تلقائية | تتطلب اشتراكاً في الإشعارات |
| Boilerplate | سطر واحد لكل خاصية | 15–20 سطراً لكل خاصية |
| الأنواع | String، Int، Double، Bool، Data، URL | جميع الأنواع + الكائنات المؤرشفة |
| الأنواع المخصصة | عبر RawRepresentable | عبر NSKeyedArchiver |
| App Extension | مزامنة تلقائية | اشتراك يدوي |
للإعدادات البسيطة مع UI تفاعلي @AppStorage هو الخيار المفضل. للبيانات المعقدة (المصفوفات، القواميس، الكائنات المخصصة) استخدم مزيجاً من UserDefaults مع @State واشتراك يدوي للتغييرات، أو انتقل إلى SwiftData / Core Data للتخزين المنظم.
@AppStorage يدعم الأنواع القياسية التي يمكن لـ UserDefaults تسلسلها مباشرة: String، Int، Double، Bool، Data، URL. لكل نوع يوجد إصدار اختياري (String?، Int?، Double?، Bool?، Data?، URL?)، مما يتيح التمييز بين "غير مضبوط" و"قيمة فارغة".
لتخزين الأنواع المخصصة المتوافقة مع بروتوكول RawRepresentable، يعمل @AppStorage تلقائياً أيضاً. إذا كان لدى enum rawValue من نوع String أو Int، يمكن استخدامه مباشرة: @AppStorage("theme") var theme: AppTheme = .system. يقوم SwiftUI تلقائياً بتسلسل/إلغاء تسلسل القيمة عبر rawValue.
enum AppTheme: String {
case system, light, dark
}
struct SettingsView: View {
@AppStorage("username") var username: String = "Guest"
@AppStorage("launchCount") var launchCount: Int = 0
@AppStorage("isDarkMode") var isDarkMode: Bool = false
@AppStorage("appTheme") var theme: AppTheme = .system
@AppStorage("lastOpened") var lastOpened: Date? = nil
var body: some View {
Form {
TextField("Username", text: $username)
Toggle("Dark mode", isOn: $isDarkMode)
Text("تم التشغيل \(launchCount) مرة")
}
}
}
يستخدم المثال أنواعاً مختلفة من @AppStorage: String بقيمة افتراضية "Guest"، وInt لعداد مرات التشغيل، وBool للمظهر الداكن، وenum AppTheme مع rawValue من نوع String، وDate? اختياري لآخر وقت فتح. كل خاصية مرتبطة بمفتاح UserDefaults محدد كأول وسيط. تُستخدم القيمة الافتراضية إذا كان المفتاح مفقوداً من التخزين عند التشغيل الأول.
إحدى المزايا الرئيسية لـ @AppStorage هي المراقبة التلقائية لتغييرات UserDefaults من أي مصدر. إذا قام App Extension أو Widget بتغيير قيمة، فإن @AppStorage في التطبيق الرئيسي يستلم الإشعار ويعيد رسم View. يتم تحقيق ذلك من خلال آلية KVO (Key-Value Observing) التي يقوم @AppStorage بإعدادها تلقائياً على UserDefaults.didChangeNotification.
عملياً، هذا يعني أنه إذا قام المستخدم بتغيير إعداد في Widget (مثلاً، تفعيل المظهر الداكن)، فإن التطبيق يلتقط التغيير فوراً. نفس المزامنة تعمل بين التطبيق الرئيسي وShare Extension وWatch App أو Today Widget. لا يحتاج المطور إلى كتابة كود لتبادل البيانات بين العمليات — @AppStorage يفعل ذلك تلقائياً.
struct ThemeSettingView: View {
@AppStorage("isDarkMode") var isDarkMode: Bool = false
var body: some View {
VStack {
Toggle("Dark Mode", isOn: $isDarkMode)
.onChange(of: isDarkMode) { oldValue, newValue in
print("تغير الوضع الداكن إلى \(newValue)")
}
}
}
}
Toggle مرتبط بـ $isDarkMode عبر @AppStorage. عند التبديل، تُحفظ القيمة تلقائياً في UserDefaults تحت مفتاح "isDarkMode". المُعدل .onChange يسمح بتنفيذ تأثير جانبي عند التغيير — مثلاً، إرسال تحليلات أو تحديث UI شاشات أخرى. إذا قام Widget بتغيير نفس المفتاح، فإن @AppStorage سيشغل أيضاً onChange، مما يضمن اتساق الحالة.
لنلقِ نظرة على شاشة إعدادات كاملة للتطبيق تستخدم @AppStorage لتخزين جميع الإعدادات. يحتوي النموذج على أقسام بأنواع مختلفة من الإعدادات: حقول نصية، مفاتيح تبديل، عدادات — جميع القيم تُحفظ تلقائياً في UserDefaults.
struct AppSettingsView: View {
@AppStorage("displayName") var displayName = ""
@AppStorage("notificationsEnabled") var notificationsEnabled = true
@AppStorage("maxResults") var maxResults = 25
@AppStorage("selectedTab") var selectedTab = "home"
var body: some View {
NavigationStack {
Form {
Section(header: Text("الملف الشخصي")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("التفضيلات")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("إعادة تعيين الإعدادات") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
يحتوي النموذج على أربع خصائص @AppStorage من أنواع مختلفة: String للاسم، وBool للإشعارات، وInt لعدد النتائج، وString للتبويب المحدد. جميع عناصر التحكم مرتبطة بالخصائص عبر Binding ($displayName، $notificationsEnabled، إلخ). زر "إعادة تعيين الإعدادات" يمسح جميع UserDefaults عن طريق إزالة نطاق التطبيق — بعد ذلك يعود @AppStorage تلقائياً إلى القيم الافتراضية.
struct SharedSettingsView: View {
let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")
@AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetTheme: String = "النظام"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "أزرق"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("النظام").tag("system")
Text("فاتح").tag("فاتح")
Text("داكن").tag("داكن")
}
Picker("Accent color", selection: $widgetColor) {
Text("أزرق").tag("blue")
Text("أخضر").tag("أخضر")
Text("أحمر").tag("أحمر")
}
}
}
}
من أجل App Group (تخزين مشترك بين التطبيق والإضافات) يقبل @AppStorage المعامل store: UserDefaults(suiteName:). تُحفظ القيم في الحاوية المشتركة المتاحة للتطبيق الرئيسي وWidget وWatch App والإضافات الأخرى من نفس المجموعة. يمكن لـ Widget قراءة هذه الإعدادات، وعند تغييرها في التطبيق، يتم تحديث Widget تلقائياً عبر آلية مراقبة UserDefaults.
الأسئلة الشائعة
@State يخزن القيمة في الذاكرة فقط ويُعاد تعيينها عند إعادة تشغيل التطبيق. @AppStorage يحفظ القيمة في UserDefaults ويستعيدها عند التشغيل التالي. استخدم @State للبيانات المؤقتة في الشاشة، و@AppStorage للإعدادات التي يجب أن تبقى بعد إعادة التشغيل.
نعم، إذا كان Enum يطبق بروتوكول RawRepresentable مع rawValue من نوع String أو Int. مثال: @AppStorage("theme") var theme: AppTheme = .system. يقوم SwiftUI تلقائياً بتسلسل enum عبر rawValue ويستعيده عند التحميل.
استدعِ UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) للتخزين القياسي أو removeObject(forKey:) لمفتاح محدد. بعد المسح، ستعود جميع خصائص @AppStorage إلى القيم الافتراضية المحددة في التصريح.
نعم، للمزامنة بين التطبيق والإضافات استخدم App Group: @AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!). يمكن لـ Widget وShare Extension وWatch App القراءة والكتابة في نفس UserDefaults، ويتم تتبع التغييرات تلقائياً.
@AppStorage يستخدم UserDefaults، وهو مصمم لكميات صغيرة من البيانات: الإعدادات، الرموز، العدادات. الحد الموصى به يصل إلى 100 كيلوبايت لكل تطبيق. للبيانات المنظمة أو الكبيرة (مصفوفات الكائنات، ملفات الوسائط) استخدم SwiftData أو Core Data أو نظام الملفات.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا