@AppStorage no SwiftUI é um property wrapper para trabalhar com UserDefaults que sincroniza automaticamente o valor com a UI. Quando uma propriedade declarada com @AppStorage muda, o novo valor é imediatamente salvo em UserDefaults, e quando UserDefaults muda externamente — por um widget ou extensão — a View é redesenhada automaticamente. De acordo com Apple Developer Documentation (2025), @AppStorage suporta String, Int, Double, Bool, Data, URL e suas versões opcionais, fornecendo armazenamento reativo de configurações do usuário sem código de observação manual.
Principais Pontos
@AppStorage é um property wrapper apresentado pela Apple no iOS 14 que vincula uma propriedade da View a uma chave no UserDefaults. Ao ler a propriedade, o SwiftUI carrega o valor do UserDefaults pela chave especificada. Ao escrever, salva o novo valor e notifica a View de que precisa ser redesenhada.
Antes do @AppStorage, os desenvolvedores tinham que ler UserDefaults manualmente no onAppear, assinar o UserDefaults.didChangeNotification e atualizar @State nas mudanças. O @AppStorage automatiza todo o ciclo: uma declaração de uma linha substitui 15–20 linhas de código repetitivo. Além disso, @AppStorage fornece sincronização bidirecional — se o valor do UserDefaults mudar de outro processo (por exemplo, App Extension ou Widget), a View ainda receberá a atualização.
Arquitetonicamente, @AppStorage é implementado como DynamicProperty, o que permite ao SwiftUI rastrear dependências e redesenhar a View quando o valor observado muda. Isso o torna ideal para armazenar configurações do usuário: idioma da interface, ativação/desativação de recursos, última aba selecionada, nome do usuário.
Embora @AppStorage use UserDefaults internamente, as abordagens para trabalhar com armazenamento são fundamentalmente diferentes. UserDefaults é uma API de baixo nível que requer gerenciamento manual de leitura, escrita e notificações de mudança. @AppStorage é uma abstração do SwiftUI que fornece comportamento reativo pronto para uso.
UserDefaults é adequado para operações únicas: carregar configurações na inicialização do aplicativo, escrever análises, armazenar tokens em cache. @AppStorage é para configurações que devem atualizar a UI reativamente: alternadores de tema, seleção de idioma, salvar o estado da interface. Usar UserDefaults diretamente dentro de uma View é um antipadrão, pois a View não sabe sobre mudanças sem assinatura adicional.
| Parâmetro | @AppStorage | UserDefaults |
|---|---|---|
| Reatividade | Automática | Requer assinatura de notificações |
| Boilerplate | 1 linha por propriedade | 15–20 linhas por propriedade |
| Tipos | String, Int, Double, Bool, Data, URL | Todos os tipos + objetos arquivados |
| Tipos personalizados | Via RawRepresentable | Via NSKeyedArchiver |
| App Extension | Sincronização automática | Assinatura manual |
Para configurações simples com UI reativa @AppStorage é a escolha preferida. Para dados complexos (arrays, dicionários, objetos personalizados) use uma combinação de UserDefaults com @State e assinatura manual de mudanças, ou mude para SwiftData / Core Data para armazenamento estruturado.
@AppStorage suporta tipos padrão que UserDefaults pode serializar diretamente: String, Int, Double, Bool, Data, URL. Para cada tipo existe uma versão opcional (String?, Int?, Double?, Bool?, Data?, URL?), permitindo distinguir entre "não definido" e "valor vazio".
Para armazenar tipos personalizados que estão em conformidade com o protocolo RawRepresentable, @AppStorage também funciona automaticamente. Se um enum tem rawValue do tipo String ou Int, ele pode ser usado diretamente: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI serializa/desserializa automaticamente o valor via 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) vezes")
}
}
}
O exemplo usa diferentes tipos de @AppStorage: String com valor padrão "Guest", Int para um contador de inicializações, Bool para tema escuro, enum AppTheme com rawValue do tipo String e um Date? opcional para a última hora de abertura. Cada propriedade está vinculada a uma chave UserDefaults especificada como primeiro argumento. O valor padrão é usado se a chave não estiver presente no armazenamento na primeira inicialização.
Uma das principais vantagens do @AppStorage é a observação automática de mudanças do UserDefaults de qualquer fonte. Se uma App Extension ou Widget muda um valor, o @AppStorage no aplicativo pai recebe a notificação e redesenha a View. Isso é alcançado através do mecanismo KVO (Key-Value Observing) que o @AppStorage configura automaticamente no UserDefaults.didChangeNotification.
Na prática, isso significa que se o usuário mudar uma configuração em um Widget (por exemplo, ativar o tema escuro), o aplicativo captura imediatamente a mudança. A mesma sincronização funciona entre o aplicativo principal e Share Extension, Watch App ou Today Widget. O desenvolvedor não precisa escrever código para troca de dados entre processos — @AppStorage faz isso automaticamente.
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 escuro alterado para \(newValue)")
}
}
}
}
Toggle está vinculado a $isDarkMode via @AppStorage. Ao alternar, o valor é automaticamente salvo em UserDefaults sob a chave "isDarkMode". O modificador .onChange permite executar um efeito colateral na mudança — por exemplo, enviar análises ou atualizar a UI de outras telas. Se um Widget mudar a mesma chave, @AppStorage também acionará onChange, garantindo consistência de estado.
Vamos ver uma tela completa de configurações do aplicativo que usa @AppStorage para armazenar todas as configurações. O formulário contém seções com diferentes tipos de configurações: campos de texto, alternadores, contadores — todos os valores são automaticamente salvos em 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("Preferências")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("Redefinir configurações") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
O formulário contém quatro propriedades @AppStorage de diferentes tipos: String para o nome, Bool para notificações, Int para a quantidade de resultados e String para a aba selecionada. Todos os controles estão vinculados às propriedades via Binding ($displayName, $notificationsEnabled, etc.). O botão "Reset settings" limpa todos os UserDefaults removendo o domínio do aplicativo — depois disso @AppStorage retorna automaticamente aos valores padrão.
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("Escuro").tag("escuro")
}
Picker("Accent color", selection: $widgetColor) {
Text("Azul").tag("blue")
Text("Verde").tag("verde")
Text("Vermelho").tag("vermelho")
}
}
}
}
Para App Group (armazenamento compartilhado entre o aplicativo e extensões) @AppStorage aceita o parâmetro store: UserDefaults(suiteName:). Os valores são salvos no contêiner compartilhado disponível para o aplicativo principal, Widget, Watch App e outras extensões do mesmo grupo. Um Widget pode ler essas configurações, e quando elas mudam no aplicativo, o Widget é atualizado automaticamente através do mecanismo de observação do UserDefaults.
Perguntas Frequentes
@State armazena o valor apenas na memória e é redefinido quando o aplicativo reinicia. @AppStorage salva o valor em UserDefaults e o restaura na próxima inicialização. Use @State para dados temporários de tela, @AppStorage para configurações que devem sobreviver a uma reinicialização.
Sim, se o Enum implementar o protocolo RawRepresentable com rawValue do tipo String ou Int. Exemplo: @AppStorage("theme") var theme: AppTheme = .system. SwiftUI serializa automaticamente o enum via rawValue e o restaura ao carregar.
Chame UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) para o armazenamento padrão ou removeObject(forKey:) para uma chave específica. Após limpar, todas as propriedades @AppStorage retornarão aos valores padrão especificados na declaração.
Sim, para sincronização entre o aplicativo e extensões use App Group: @AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!). Widget, Share Extension e Watch App podem ler e escrever no mesmo UserDefaults, e as mudanças são rastreadas automaticamente.
@AppStorage usa UserDefaults, que é projetado para pequenas quantidades de dados: configurações, tokens, contadores. O limite recomendado é de até 100 KB por aplicativo. Para dados estruturados ou grandes (arrays de objetos, arquivos de mídia) use SwiftData, Core Data ou o sistema de arquivos.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também