@AppStorage στο SwiftUI — property wrapper για εργασία με UserDefaults, που συγχρονίζει αυτόματα την τιμή με το UI. Όταν αλλάζει μια ιδιότητα που δηλώνεται μέσω @AppStorage, η νέα τιμή αποθηκεύεται αμέσως στο UserDefaults, και όταν το UserDefaults αλλάζει από εξωτερικά — από ένα widget ή μια επέκταση — το 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 γραμμές boilerplate κώδικα. Επιπλέον, το @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 | 1 γραμμή ανά ιδιότητα | 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 κ.λπ.). Το κουμπί «Reset settings» επαναφέρει όλα τα UserDefaults, διαγράφοντας το domain της εφαρμογής — μετά από αυτό, το @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 KB ανά εφαρμογή. Για δομημένα ή μεγάλα δεδομένα (πίνακες αντικειμένων, αρχεία πολυμέσων) χρησιμοποιήστε SwiftData, Core Data ή σύστημα αρχείων.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης