@AppStorage в SwiftUI — что это, UserDefaults и хранение настроек

Автор: IT Sectr Опубликовано: 2026-06-25 Время чтения: 7 мин

@AppStorage в SwiftUI — property wrapper для работы с UserDefaults, который автоматически синхронизирует значение с UI. При изменении свойства, объявленного через @AppStorage, новое значение немедленно сохраняется в UserDefaults, а при изменении UserDefaults извне — виджетом или расширением — View автоматически перерисовывается. По данным Apple Developer Documentation (2025), @AppStorage поддерживает String, Int, Double, Bool, Data, URL и их опциональные версии, обеспечивая реактивное хранение пользовательских настроек без ручного кода наблюдения.

Главное

  • @AppStorage — property wrapper для реактивной работы с UserDefaults в SwiftUI
  • Автосохранение — значение записывается в UserDefaults при каждом изменении
  • Автообновление UI — View перерисовывается при изменении UserDefaults из любого источника
  • Поддерживаемые типы: String, Int, Double, Bool, Data, URL и опциональные версии
  • Значение по умолчанию задаётся в объявлении и используется при первом запуске

Что такое @AppStorage в SwiftUI?

@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 vs UserDefaults: сравнение

Хотя @AppStorage использует UserDefaults под капотом, подходы к работе с хранилищем принципиально отличаются. UserDefaults — это низкоуровневый API, требующий ручного управления чтением, записью и уведомлениями об изменениях. @AppStorage — это SwiftUI-абстракция, предоставляющая реактивное поведение из коробки.

UserDefaults подходит для одноразовых операций: загрузка настроек при старте приложения, запись аналитики, кэширование токенов. @AppStorage — для настроек, которые должны реактивно обновлять UI: переключатели тем, выбор языка, сохранение состояния интерфейса. Использование UserDefaults напрямую внутри View — антипаттерн, так как View не узнаёт об изменениях без дополнительной подписки.

Параметр@AppStorageUserDefaults
РеактивностьАвтоматическаяТребует подписки на уведомления
Boilerplate1 строка на свойство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.

swift
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, указанному первым аргументом. Значение по умолчанию используется, если ключ отсутствует в хранилище при первом запуске.

Наблюдение за изменениями Store

Одно из ключевых преимуществ @AppStorage — автоматическое наблюдение за изменениями UserDefaults из любого источника. Если App Extension или Widget изменяет значение, @AppStorage в родительском приложении получает уведомление и перерисовывает View. Это достигается через механизм KVO (Key-Value Observing), который @AppStorage автоматически устанавливает на UserDefaults.didChangeNotification.

На практике это означает, что если пользователь меняет настройку в Widget (например, включает тёмную тему), приложение немедленно подхватывает это изменение. Аналогично работает синхронизация между основным приложением и Share Extension, Watch App или Today Widget. Разработчику не нужно писать код для межпроцессного обмена данными — @AppStorage делает это автоматически.

swift
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

Рассмотрим полноценный экран с настройками приложения, использующий @AppStorage для хранения всех конфигураций. Форма содержит секции с разными типами настроек: текстовые поля, переключатели, счётчики — все значения автоматически сохраняются в UserDefaults.

swift
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 автоматически вернётся к значениям по умолчанию.

Синхронизация @AppStorage с App Group

swift
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.

Часто задаваемые вопросы

В чём разница между @AppStorage и @State?

@State хранит значение только в памяти и сбрасывается при перезапуске приложения. @AppStorage сохраняет значение в UserDefaults и восстанавливает его при следующем запуске. Используйте @State для временных данных экрана, @AppStorage — для настроек, которые должны переживать перезапуск.

Можно ли использовать @AppStorage с Enum?

Да, если Enum реализует протокол RawRepresentable с rawValue типа String или Int. Пример: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI автоматически сериализует enum через rawValue и восстанавливает при загрузке.

Как очистить все @AppStorage значения?

Вызовите UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) для стандартного хранилища или removeObject(forKey:) для конкретного ключа. После очистки все @AppStorage свойства вернутся к дефолтным значениям, указанным в объявлении.

Работает ли @AppStorage с App Extensions?

Да, для синхронизации между приложением и расширениями используйте App Group: @AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!). Widget, Share Extension и Watch App могут читать и писать в тот же UserDefaults, а изменения автоматически отслеживаются.

Какой объём данных можно хранить в @AppStorage?

@AppStorage использует UserDefaults, который предназначен для небольших объёмов данных: настроек, токенов, счётчиков. Рекомендуемый лимит — до 100 КБ на приложение. Для структурированных или больших данных (массивы объектов, медиафайлы) используйте SwiftData, Core Data или файловую систему.

Итоги

  • @AppStorage — property wrapper для реактивного хранения настроек в UserDefaults
  • Автосохранение и автообновление UI при изменении значения из любого источника
  • Поддерживает String, Int, Double, Bool, Data, URL и RawRepresentable enums
  • Значение по умолчанию задаётся в объявлении и восстанавливается при первом запуске
  • App Group позволяет синхронизировать @AppStorage между приложением и расширениями
  • UserDefaults подходит только для небольших объёмов данных — до 100 КБ
  • Используйте @AppStorage для пользовательских настроек, @State — для временных состояний экрана

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также