Skip to content

How to use @AppStorage in SwiftUI | SwiftUI Bootcamp #52

@State disappears when the app closes. @AppStorage persists. It's SwiftUI's built-in wrapper around UserDefaults that makes saving and loading small pieces of data — user preferences, settings, login state, names — a single line of code. After this lesson you'll know how to store and retrieve values that survive app launches, and understand the types and key naming conventions that make @AppStorage safe to use.

What You'll Learn

  • How @AppStorage connects a property to a UserDefaults key automatically
  • Which types @AppStorage supports natively (String, Int, Double, Bool, Data, URL)
  • How to use an optional @AppStorage property to distinguish "never set" from a default value
  • How changes to @AppStorage automatically trigger SwiftUI re-renders, just like @State

Mental Model

Think of @AppStorage as a sticky note attached to the app's front door. When the app closes, the sticky note stays on the door. When the app opens again, the note is still there. @State is like writing a note on your hand — useful while you're awake, but gone when you wake up the next morning.

The key string "name" is the label on the sticky note. If two views use the same label (@AppStorage("name")), they're reading and writing the same note. If they use different labels, they're different notes. This is both @AppStorage's power (same key = shared value across views) and its main risk (accidentally reusing a key name can overwrite unrelated data).

Detailed Explanation

@AppStorage is a property wrapper that reads from and writes to UserDefaults.standard using a string key. Reading the property retrieves the current value from UserDefaults. Writing to the property updates UserDefaults and triggers a SwiftUI re-render — exactly like @State would, except the change is also persisted to disk.

Supported value types are: String, Int, Double, Bool, Data, and URL. For custom types, you must encode them to Data first (using JSONEncoder or Codable). Trying to use an unsupported type directly with @AppStorage produces a compile error.

Optional @AppStorage is a powerful pattern: @AppStorage("name") var currentUserName: String?. This starts as nil when the key has never been set, which lets you distinguish between "the user has not provided a name yet" vs. "the user set their name to an empty string." When you want a default fallback value, use @AppStorage("name") var currentUserName: String = "Guest" — this returns "Guest" if the key doesn't exist in UserDefaults.

Because all views that share the same @AppStorage key see the same UserDefaults value, @AppStorage is effectively a global shared state for small data. This is convenient for authentication state, theme preference, onboarding completion flags, and feature toggles — but it is not appropriate for large data sets, sensitive data (use the Keychain instead), or complex relational data.

Code Structure

AppStorageBootcamp.swift is a minimal but complete demonstration. A single @AppStorage("name") var currentUserName: String? drives two displays: a fallback text view and a conditionally-rendered Text. A Button saves a hardcoded name "Emily" to the storage. Restart the app and the name will still be there.

Complete Code

AppStorageBootcamp.swift

swift
import SwiftUI

struct AppStorageBootcamp: View {
    
    @AppStorage("name") var currentUserName: String? // reads/writes UserDefaults key "name"; nil if key has never been set
    
    var body: some View {
        VStack(spacing: 20) {
            Text(currentUserName ?? "Add Name Here") // nil-coalesces: shows placeholder until a name is saved
            
            if let name = currentUserName { // only shown after the user taps Save; absent before first save
                Text(name)
            }
            
            Button("Save".uppercased()) { // uppercased() transforms "Save" → "SAVE" for a bold button label
                let name: String = "Emily"
                currentUserName = name // writing to @AppStorage persists to UserDefaults AND triggers re-render
            }
        }
    }
}

struct AppStorageBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        AppStorageBootcamp()
    }
}

Code Walkthrough

  1. @AppStorage("name") var currentUserName: String? — Declares a property backed by UserDefaults under the key "name". The type is String? (optional), so the initial value is nil when the key has never been set. Once a value is saved, it persists across app launches, background/foreground cycles, and device restarts (unless the user deletes the app).

  2. Text(currentUserName ?? "Add Name Here") — The nil-coalescing operator ?? provides a fallback string for display. Before the user taps "Save", currentUserName is nil and the text shows "Add Name Here". After saving, it shows the stored name. This gives the UI a meaningful initial state without any conditional logic.

  3. if let name = currentUserName { Text(name) } — An optional binding that conditionally renders a second text view. This view is absent from the hierarchy when currentUserName is nil, and appears after the first save. It demonstrates that @AppStorage changes, like @State changes, trigger view re-evaluation.

  4. Button("Save".uppercased())uppercased() is a String method that converts "Save" to "SAVE". This is a stylistic choice — using a method call inside the button label rather than a string literal demonstrates that button labels can be any expression.

  5. currentUserName = name — Writing to an @AppStorage property does two things atomically: it calls UserDefaults.standard.set(name, forKey: "name") and it notifies SwiftUI to re-render the view. You don't need to call UserDefaults directly at all — the property wrapper handles both sides.

  6. Persistence test — If you run this app, tap "Save", then kill and relaunch the app, "Emily" will be showing immediately — currentUserName reads from UserDefaults on init, so the stored value is available before the view even appears for the first time.

Common Mistakes

Mistake: Using the same key string for different pieces of data in different views
@AppStorage("name") anywhere in your app reads and writes the same UserDefaults entry. If one view stores a username under "name" and another stores a product name under the same key "name", they will overwrite each other. Use descriptive, namespaced keys like "user.profile.displayName" or define them as constants to avoid collisions.

Mistake: Storing large data (images, complex objects) in @AppStorage
UserDefaults is intended for small preference values — typically a few kilobytes total. Storing large Data blobs (images, large JSON payloads) bloats the defaults database, slows down reads, and can cause performance issues on app launch (because UserDefaults loads its entire dictionary into memory at startup). Use the file system or Core Data for larger datasets.

Mistake: Storing sensitive information like passwords or tokens in @AppStorage
UserDefaults is stored in a plain-text plist file that is accessible on jailbroken devices and through iTunes backups. Never store passwords, authentication tokens, credit card numbers, or any personally sensitive data in @AppStorage. Use the Keychain (KeychainServices or SecItem) for sensitive values.

Key Takeaways

  • @AppStorage("key") is a direct SwiftUI wrapper over UserDefaults — reading the property reads from disk, writing persists to disk and triggers a re-render automatically.
  • Use optional @AppStorage (String?) to distinguish "never set" from a default value; use non-optional @AppStorage with a default (String = "Guest") when a fallback should always be available.
  • Keep keys consistent and descriptive across your app — the same key string accesses the same value everywhere, which is both the power and the responsibility of using @AppStorage.

Last updated: June 27, 2026

Released under the MIT License.