How it works
AppStorage is a property wrapper that reads and writes a value in the user's defaults store, exposing it to your views as a source of truth. Reading the property returns the persisted value, and writing it both saves to user defaults and invalidates any views that depend on it, so the interface stays in sync with what's on disk. Reach for AppStorage when you want a small piece of state — a preference, a flag, the last value a person entered — to survive across launches without writing your own persistence layer. It behaves like State, but the value lives in UserDefaults rather than in memory.
Declare a wrapped property with a key and default
Annotate a stored property with
@AppStorageand pass the user-defaults key as its argument; the property's initial value becomes the fallback used when no value has been saved yet. Here@AppStorage("username") private var username: String = "Guest"reads the"username"key, defaulting to"Guest", and@AppStorage("isDarkMode") private var isDarkMode: Bool = falsedoes the same for aBool.Use the supported value types
AppStorageworks with the property-list typesUserDefaultsunderstands —String,Bool,Int,Double,Data,URL, andRawRepresentabletypes whose raw value is one of those. The example stores aStringinusernameand aBoolinisDarkMode, each a natively supported type that needs no extra conversion.Read the wrapped value directly
Accessing the property by name yields the current persisted value, and the enclosing view re-renders whenever that stored value changes.
Text("Hello, \(username)!")readsusernamestraight out of the wrapper, so the greeting reflects whatever was last saved to defaults.Bind to the projected value with $
The
$prefix projects aBindingto the stored value, which controls write back into user defaults whenever a control mutates it.TextField("Username", text: $username)andToggle("Dark Mode", isOn: $isDarkMode)both write through these bindings, so each keystroke or toggle persists immediately.Scope storage with a custom store
By default
AppStorageusesUserDefaults.standard, but an initializer overload accepts astore:parameter so you can target a shared suite — for example a defaults instance backed by an app group. The example relies on the standard store, which is the right choice for per-app preferences likeusernameandisDarkMode.
TextField bound to $username, then relaunch the app — the greeting reappears with your name instead of "Guest", because AppStorage restored it from user defaults.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct AppStorageDemo: View {
@AppStorage("username") private var username: String = "Guest"
@AppStorage("isDarkMode") private var isDarkMode: Bool = false
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Hello, \(username)!")
.font(.headline)
TextField("Username", text: $username)
.textFieldStyle(.roundedBorder)
Toggle("Dark Mode", isOn: $isDarkMode)
}
.padding()
}
}