@AppStorage в SwiftUI — property wrapper за работа с UserDefaults, който автоматично синхронизира стойността с UI. При промяна на свойство, декларирано чрез @AppStorage, новата стойност незабавно се запазва в UserDefaults, а когато UserDefaults се промени отвън — от уиджет или разширение — 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?), позволяваща разграничаване между „nе е зададено” и „празна стойност”.
За съхранение на потребителски типове, следващи протокола 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, изтривайки домейна на приложението — след това @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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също