@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?), позволяющая различать «не установлено» и «пустое значение».
Для хранения кастомных типов, соответствующих протоколу 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("Launched \(launchCount) times")
}
}
}
В примере используются разные типы @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("Dark mode changed to \(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("Profile")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("Preferences")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("Reset settings") {
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 = "system"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "blue"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("System").tag("system")
Text("Light").tag("light")
Text("Dark").tag("dark")
}
Picker("Accent color", selection: $widgetColor) {
Text("Blue").tag("blue")
Text("Green").tag("green")
Text("Red").tag("red")
}
}
}
}
Для 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также