@AppStorage în SwiftUI — property wrapper pentru lucrul cu UserDefaults, care sincronizează automat valoarea cu UI. La modificarea proprietății declarate prin @AppStorage, noua valoare este imediat salvată în UserDefaults, iar când UserDefaults se schimbă din exterior — printr-un widget sau extensie — View se redesenează automat. Conform Apple Developer Documentation (2025), @AppStorage suportă String, Int, Double, Bool, Data, URL și versiunile lor opționale, asigurând stocarea reactivă a setărilor utilizatorului fără cod de observare manual.
Principalele
@AppStorage este un property wrapper introdus de Apple în iOS 14, care leagă o proprietate View de o cheie din UserDefaults. La citirea proprietății, SwiftUI încarcă valoarea din UserDefaults după cheia specificată. La scriere — salvează noua valoare și notifică View-ul despre necesitatea redesenării.
Înainte de apariția @AppStorage, dezvoltatorii trebuiau să citească manual UserDefaults în onAppear, să se aboneze la notificarea UserDefaults.didChangeNotification și să actualizeze @State la modificări. @AppStorage automatizează întregul ciclu: o declarație pe o singură linie înlocuiește 15–20 de linii de cod boilerplate. Mai mult, @AppStorage asigură sincronizare bidirecțională — dacă valoarea UserDefaults este modificată dintr-un alt proces (de exemplu, App Extension sau Widget), View-ul va primi totuși actualizarea.
Arhitectural, @AppStorage este implementat ca DynamicProperty, permițând SwiftUI să urmărească dependențele și să redeseneze View-ul la modificarea valorii observate. Acest lucru îl face ideal pentru stocarea setărilor utilizatorului: limba interfeței, activarea/dezactivarea funcțiilor, ultimul tab selectat, numele utilizatorului.
Deși @AppStorage folosește UserDefaults sub capotă, abordările de lucru cu stocarea diferă fundamental. UserDefaults este o API de nivel scăzut care necesită gestionarea manuală a citirii, scrierii și notificărilor de modificare. @AppStorage este o abstractizare SwiftUI care oferă comportament reactiv din cutie.
UserDefaults este potrivit pentru operații unice: încărcarea setărilor la pornirea aplicației, scrierea analiticelor, cache-ul token-urilor. @AppStorage — pentru setări care trebuie să actualizeze reactiv UI: comutatoare de teme, selectarea limbii, salvarea stării interfeței. Utilizarea directă a UserDefaults în interiorul View-ului este un antipattern, deoarece View-ul nu află despre modificări fără o abonare suplimentară.
| Parametru | @AppStorage | UserDefaults |
|---|---|---|
| Reactivitate | Automată | Necesită abonare la notificări |
| Boilerplate | 1 linie per proprietate | 15–20 linii per proprietate |
| Tipuri | String, Int, Double, Bool, Data, URL | Toate tipurile + obiecte arhivate |
| Tipuri personalizate | Prin RawRepresentable | Prin NSKeyedArchiver |
| App Extension | Sincronizare automată | Abonare manuală |
Pentru setări simple cu UI reactiv, @AppStorage este alegerea preferată. Pentru date complexe (tablouri, dicționare, obiecte personalizate) folosiți combinația UserDefaults cu @State și abonare manuală la modificări, sau treceți la SwiftData / Core Data pentru stocare structurată.
@AppStorage suportă tipurile standard pe care UserDefaults le poate serializa direct: String, Int, Double, Bool, Data, URL. Pentru fiecare tip există o versiune opțională (String?, Int?, Double?, Bool?, Data?, URL?), permițând diferențierea între „nesetat” și „valoare goală”.
Pentru stocarea tipurilor personalizate conforme cu protocolul RawRepresentable, @AppStorage funcționează și automat. Dacă enum are rawValue de tip String sau Int, poate fi folosit direct: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI serializează/deserializează automat valoarea prin 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("Lansat de \(launchCount) ori")
}
}
}
În exemplu sunt folosite diferite tipuri de @AppStorage: String cu valoarea implicită „Guest”, Int pentru contorul de porniri, Bool pentru tema întunecată, enum AppTheme cu rawValue de tip String și Date? opțional pentru ultima deschidere. Fiecare proprietate este legată de cheia UserDefaults specificată ca prim argument. Valoarea implicită este folosită dacă cheia lipsește din stocare la prima pornire.
Unul dintre avantajele cheie ale @AppStorage — observarea automată a modificărilor UserDefaults din orice sursă. Dacă App Extension sau Widget modifică o valoare, @AppStorage în aplicația părinte primește notificare și redesenează View-ul. Acest lucru se realizează prin mecanismul KVO (Key-Value Observing), pe care @AppStorage îl setează automat pe UserDefaults.didChangeNotification.
În practică, aceasta înseamnă că dacă utilizatorul modifică o setare în Widget (de exemplu, activează tema întunecată), aplicația preia imediat această modificare. Similar funcționează sincronizarea între aplicația principală și Share Extension, Watch App sau Today Widget. Dezvoltatorul nu trebuie să scrie cod pentru schimbul de date interproces — @AppStorage face acest lucru automat.
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("Modul întunecat schimbat la \(newValue)")
}
}
}
}
Toggle este legat de $isDarkMode prin @AppStorage. La comutare, valoarea este automat salvată în UserDefaults sub cheia „isDarkMode”. Modificatorul .onChange permite executarea unei acțiuni secundare la modificare — de exemplu, trimiterea de analitice sau actualizarea UI-ului altor ecrane. Dacă Widget modifică aceeași cheie, @AppStorage va apela și onChange, asigurând consistența stării.
Să examinăm un ecran complet de setări al aplicației care folosește @AppStorage pentru stocarea tuturor configurațiilor. Formularul conține secțiuni cu diferite tipuri de setări: câmpuri text, comutatoare, contoare — toate valorile sunt salvate automat în 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("Profil")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("Preferințe")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("Resetează setările") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
Formularul conține patru proprietăți @AppStorage de diferite tipuri: String pentru nume, Bool pentru notificări, Int pentru numărul de rezultate și String pentru tab-ul selectat. Toate controalele sunt legate de proprietăți prin Binding ($displayName, $notificationsEnabled etc.). Butonul „Reset settings” resetează toate UserDefaults, ștergând domeniul aplicației — după aceasta, @AppStorage revine automat la valorile implicite.
struct SharedSettingsView: View {
let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")
@AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetTheme: String = "sistem"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "albastru"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("Sistem").tag("system")
Text("Luminos").tag("luminos")
Text("Întunecat").tag("Întunecat")
}
Picker("Accent color", selection: $widgetColor) {
Text("Albastru").tag("blue")
Text("Verde").tag("verde")
Text("Roșu").tag("roșu")
}
}
}
}
Pentru App Group (stocare partajată între aplicație și extensii) @AppStorage acceptă parametrul store: UserDefaults(suiteName:). Valorile sunt salvate într-un container comun accesibil aplicației principale, Widget, Watch App și altor extensii din același grup. Widget poate citi aceste setări, iar la modificarea în aplicație, Widget se actualizează automat prin mecanismul de observare UserDefaults.
Întrebări frecvente
@State stochează valoarea doar în memorie și se resetează la repornirea aplicației. @AppStorage salvează valoarea în UserDefaults și o restabilește la următoarea pornire. Folosiți @State pentru date temporare ale ecranului, @AppStorage — pentru setări care trebuie să supraviețuiască repornirii.
Da, dacă Enum implementează protocolul RawRepresentable cu rawValue de tip String sau Int. Exemplu: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI serializează automat enum-ul prin rawValue și îl restabilește la încărcare.
Apelați UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) pentru stocarea standard sau removeObject(forKey:) pentru o cheie specifică. După ștergere, toate proprietățile @AppStorage revin la valorile implicite specificate în declarație.
Da, pentru sincronizarea între aplicație și extensii folosiți App Group: @AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!). Widget, Share Extension și Watch App pot citi și scrie în același UserDefaults, iar modificările sunt urmărite automat.
@AppStorage folosește UserDefaults, care este destinat volumelor mici de date: setări, token-uri, contoare. Limita recomandată — până la 100 KB per aplicație. Pentru date structurate sau mari (tablouri de obiecte, fișiere media) utilizați SwiftData, Core Data sau sistemul de fișiere.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și