@AppStorage en SwiftUI es un property wrapper para trabajar con UserDefaults que sincroniza automáticamente el valor con la UI. Cuando cambia una propiedad declarada con @AppStorage, el nuevo valor se guarda inmediatamente en UserDefaults, y cuando UserDefaults cambia externamente — mediante un widget o extensión — la View se redibuja automáticamente. Según Apple Developer Documentation (2025), @AppStorage soporta String, Int, Double, Bool, Data, URL y sus versiones opcionales, proporcionando almacenamiento reactivo de configuraciones de usuario sin código manual de observación.
Puntos Clave
@AppStorage es un property wrapper presentado por Apple en iOS 14 que vincula una propiedad de View con una clave en UserDefaults. Al leer la propiedad, SwiftUI carga el valor de UserDefaults mediante la clave especificada. Al escribir, guarda el nuevo valor y notifica a la View que necesita redibujarse.
Antes de @AppStorage, los desarrolladores tenían que leer UserDefaults manualmente en onAppear, suscribirse a UserDefaults.didChangeNotification y actualizar @State en los cambios. @AppStorage automatiza todo el ciclo: una declaración de una línea reemplaza 15–20 líneas de código repetitivo. Además, @AppStorage proporciona sincronización bidireccional — si el valor de UserDefaults cambia desde otro proceso (por ejemplo, App Extension o Widget), la View igualmente recibirá la actualización.
Arquitectónicamente, @AppStorage se implementa como DynamicProperty, lo que permite a SwiftUI rastrear dependencias y redibujar la View cuando cambia el valor observado. Esto lo hace ideal para almacenar configuraciones de usuario: idioma de la interfaz, activación/desactivación de funciones, última pestaña seleccionada, nombre de usuario.
Aunque @AppStorage utiliza UserDefaults internamente, los enfoques para trabajar con el almacenamiento son fundamentalmente diferentes. UserDefaults es una API de bajo nivel que requiere gestión manual de lectura, escritura y notificaciones de cambios. @AppStorage es una abstracción de SwiftUI que proporciona comportamiento reactivo listo para usar.
UserDefaults es adecuado para operaciones únicas: cargar configuraciones al iniciar la aplicación, escribir analíticas, almacenar en caché tokens. @AppStorage es para configuraciones que deben actualizar la UI de forma reactiva: interruptores de tema, selección de idioma, guardar el estado de la interfaz. Usar UserDefaults directamente dentro de una View es un antipatrón, ya que la View no se entera de los cambios sin una suscripción adicional.
| Parámetro | @AppStorage | UserDefaults |
|---|---|---|
| Reactividad | Automática | Requiere suscripción a notificaciones |
| Boilerplate | 1 línea por propiedad | 15–20 líneas por propiedad |
| Tipos | String, Int, Double, Bool, Data, URL | Todos los tipos + objetos archivados |
| Tipos personalizados | Mediante RawRepresentable | Mediante NSKeyedArchiver |
| App Extension | Sincronización automática | Suscripción manual |
Para configuraciones simples con UI reactiva @AppStorage es la opción preferida. Para datos complejos (arrays, diccionarios, objetos personalizados) use una combinación de UserDefaults con @State y suscripción manual a cambios, o cambie a SwiftData / Core Data para almacenamiento estructurado.
@AppStorage soporta tipos estándar que UserDefaults puede serializar directamente: String, Int, Double, Bool, Data, URL. Para cada tipo existe una versión opcional (String?, Int?, Double?, Bool?, Data?, URL?), que permite distinguir entre "no establecido" y "valor vacío".
Para almacenar tipos personalizados que se ajustan al protocolo RawRepresentable, @AppStorage también funciona automáticamente. Si un enum tiene rawValue de tipo String o Int, se puede usar directamente: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI serializa/deserializa automáticamente el valor mediante 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("Iniciada \(launchCount) veces")
}
}
}
El ejemplo utiliza diferentes tipos de @AppStorage: String con valor predeterminado "Guest", Int para un contador de inicios, Bool para tema oscuro, enum AppTheme con rawValue de tipo String y un Date? opcional para la última hora de apertura. Cada propiedad está vinculada a una clave de UserDefaults especificada como primer argumento. El valor predeterminado se usa si la clave no está presente en el almacenamiento en el primer inicio.
Una de las ventajas clave de @AppStorage es la observación automática de cambios de UserDefaults desde cualquier fuente. Si una App Extension o Widget cambia un valor, @AppStorage en la aplicación principal recibe la notificación y redibuja la View. Esto se logra mediante el mecanismo KVO (Key-Value Observing) que @AppStorage configura automáticamente en UserDefaults.didChangeNotification.
En la práctica, esto significa que si el usuario cambia una configuración en un Widget (por ejemplo, activa el tema oscuro), la aplicación recoge inmediatamente el cambio. La misma sincronización funciona entre la aplicación principal y Share Extension, Watch App o Today Widget. El desarrollador no necesita escribir código para el intercambio de datos entre procesos — @AppStorage lo hace automáticamente.
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("Modo oscuro cambiado a \(newValue)")
}
}
}
}
Toggle está vinculado a $isDarkMode mediante @AppStorage. Al alternar, el valor se guarda automáticamente en UserDefaults con la clave "isDarkMode". El modificador .onChange permite realizar un efecto secundario al cambiar — por ejemplo, enviar analíticas o actualizar la UI de otras pantallas. Si un Widget cambia la misma clave, @AppStorage también activará onChange, asegurando la consistencia del estado.
Veamos una pantalla completa de configuración de la aplicación que usa @AppStorage para almacenar todas las configuraciones. El formulario contiene secciones con diferentes tipos de configuraciones: campos de texto, interruptores, contadores — todos los valores se guardan automáticamente en 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("Perfil")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("Preferencias")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("Restablecer ajustes") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
El formulario contiene cuatro propiedades @AppStorage de diferentes tipos: String para el nombre, Bool para notificaciones, Int para la cantidad de resultados y String para la pestaña seleccionada. Todos los controles están vinculados a las propiedades mediante Binding ($displayName, $notificationsEnabled, etc.). El botón "Restablecer ajustes" borra todos los UserDefaults eliminando el dominio de la aplicación — después de eso @AppStorage vuelve automáticamente a los valores predeterminados.
struct SharedSettingsView: View {
let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")
@AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetTheme: String = "sistema"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "azul"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("Sistema").tag("system")
Text("Claro").tag("claro")
Text("Oscuro").tag("oscuro")
}
Picker("Accent color", selection: $widgetColor) {
Text("Azul").tag("blue")
Text("Verde").tag("verde")
Text("Rojo").tag("rojo")
}
}
}
}
Para App Group (almacenamiento compartido entre la aplicación y las extensiones) @AppStorage acepta el parámetro store: UserDefaults(suiteName:). Los valores se guardan en el contenedor compartido disponible para la aplicación principal, Widget, Watch App y otras extensiones del mismo grupo. Un Widget puede leer estas configuraciones, y cuando cambian en la aplicación, el Widget se actualiza automáticamente mediante el mecanismo de observación de UserDefaults.
Preguntas Frecuentes
@State almacena el valor solo en memoria y se restablece al reiniciar la aplicación. @AppStorage guarda el valor en UserDefaults y lo restaura en el siguiente inicio. Use @State para datos temporales de pantalla, @AppStorage para configuraciones que deben sobrevivir a un reinicio.
Sí, si el Enum implementa el protocolo RawRepresentable con rawValue de tipo String o Int. Ejemplo: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI serializa automáticamente el enum mediante rawValue y lo restaura al cargar.
Llame a UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) para el almacenamiento estándar o removeObject(forKey:) para una clave específica. Después de limpiar, todas las propiedades @AppStorage volverán a los valores predeterminados especificados en la declaración.
Sí, para la sincronización entre la aplicación y las extensiones use App Group: @AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!). Widget, Share Extension y Watch App pueden leer y escribir en el mismo UserDefaults, y los cambios se rastrean automáticamente.
@AppStorage usa UserDefaults, que está diseñado para pequeñas cantidades de datos: configuraciones, tokens, contadores. El límite recomendado es hasta 100 KB por aplicación. Para datos estructurados o grandes (arrays de objetos, archivos multimedia) use SwiftData, Core Data o el sistema de archivos.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también