How does @AppStorage behave: where is the value, what happens when it is removed or changed elsewhere, and what can you store?

8.10.6 · L2 · Interview

Predict first

What does the view render as the stored value changes?

UserDefaults.standard.removeObject(forKey: "k")
struct V: View {
    @AppStorage("k") var n = 3
    var body: some View { let _ = note("body n=\(n)"); Text("\(n)") }
}
_ = show(V()); flush()
UserDefaults.standard.set(5, forKey: "k"); spin(); flush()
UserDefaults.standard.removeObject(forKey: "k"); spin(); flush()
print("stored value is now", UserDefaults.standard.object(forKey: "k") as Any)
  • 3, then 5 after an external set, then 3 after removal, nothing stored
  • 3, then 3, then 3
  • 5 from the start

Mental model. @AppStorage is a wall-mounted settings board that every view reads from: the initial value is the printed default on the board, not something written on it.

@AppStorage("key") var n = 3 reads and writes UserDefaults (the standard suite unless you pass store:). The declared value is only a default: it is not written until you set something. In the check the view first shows 3 with nothing stored, a UserDefaults.standard.set(5, …) from outside any view re-renders it with 5 (it observes the defaults key, so changes from code, other views or an extension sharing the suite all arrive), and removing the key brings back the default 3 and leaves nothing stored (nil). Several views using the same key stay in sync for the same reason.

Limits to remember: supported types are the property-list ones plus URL, Data, RawRepresentable enums with an Int or String raw value, and optionals of those; anything else (a Codable struct) needs encoding yourself. Defaults is for small preferences, not caches or secrets (use the Keychain), and it is not transactional: two writers race. @SceneStorage is the per-scene sibling for UI state such as a selected tab or draft text; the system may discard it, so it restores convenience state and never data you cannot lose. Changing the key string later silently starts from the default, so give keys constants.

@AppStorage observes UserDefaults and falls back to its default.

Common traps

  • Treating the initial value as stored. It is a fallback until you write.
  • Putting large or secret data in it. Defaults is for small preferences.

They might follow up with

  • How do you share it with a widget? An app-group suite via store:.
  • How do you store a struct? Encode it to Data or a string raw value yourself.

Related questions

  • 8.4.2 What is the App protocol, what are scenes, and what does scenePhase tell you?
  • 8.10.5 If a parent changes one environment value, which child views re-run their body?
  • 8.3.1 What do @State, @Binding and @Bindable do, and who owns the data?

← If a parent changes one environment value, which child views re-run…