@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 рядків шаблонного коду. Більше того, @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, видаляючи домен застосунку — після цього @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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також