Saving Data Locally: `UserDefaults` and `@AppStorage` in SwiftUI


Every time Dory swims away and comes back, she forgets everything. Your app has the same problem — when the user closes it, all the data stored in @State properties vanishes. To make data survive between launches, you need persistence.

You’ll learn how to save simple values with UserDefaults, how @AppStorage connects saved data directly to SwiftUI views, and when these tools are (and aren’t) the right choice. We won’t cover databases like SwiftData or file-based storage — those get their own posts.

Apple Docs: UserDefaults — Foundation

This guide assumes you’re familiar with SwiftUI state management.

What You’ll Learn

What Is UserDefaults?

UserDefaults is a simple key-value store built into every iOS app. Think of it like the label maker in Andy’s room — you write a name on a toy’s foot and later you can look up which kid owns it. You store a value with a string key, and later you retrieve it using that same key.

Under the hood, UserDefaults writes to a small property list (.plist) file in your app’s sandbox. It’s designed for small, simple data like user preferences, settings, and flags — not for storing large datasets like a list of every Pixar movie ever made.

Saving and Reading Values

To save a value, call set(_:forKey:) on UserDefaults.standard. To read it back, use the appropriate typed method like string(forKey:) or integer(forKey:).

// Save Woody's favorite catchphrase
UserDefaults.standard.set(
    "There's a snake in my boot!",
    forKey: "favoriteCatchphrase"
)

// Read it back
let phrase = UserDefaults.standard.string(forKey: "favoriteCatchphrase")
print(phrase ?? "No catchphrase saved")
There's a snake in my boot!

The string(forKey:) method returns an optional String? because the key might not exist yet. If you read a key that was never set, you get nil.

For numeric values, UserDefaults returns 0 instead of nil when the key doesn’t exist:

UserDefaults.standard.set(4, forKey: "toyStorySequels")

let count = UserDefaults.standard.integer(forKey: "toyStorySequels")
print("Sequels: \(count)")
Sequels: 4

Tip: You don’t need to call “save” after setting a value. UserDefaults automatically synchronizes to disk periodically. The data is available immediately in memory.

Using @AppStorage in SwiftUI

@AppStorage is a SwiftUI property wrapper that reads from and writes to UserDefaults automatically. When the stored value changes, your view updates — just like @State.

Apple Docs: @AppStorage — SwiftUI

import SwiftUI

struct SettingsView: View {
    @AppStorage("characterName") private var name = "Woody"
    @AppStorage("darkModeEnabled") private var isDark = false

    var body: some View {
        Form {
            TextField("Favorite Character", text: $name)
            Toggle("Dark Mode", isOn: $isDark)
        }
    }
}

Every time the user types a new name or flips the toggle, @AppStorage saves the value to UserDefaults and the view re-renders. When the app relaunches, the values are restored automatically.

The string you pass to @AppStorage (like "characterName") is the UserDefaults key. The default value you assign (like "Woody") is used only when no saved value exists yet.

Supported Data Types

UserDefaults and @AppStorage support a specific set of types. Here’s what you can store directly:

TypeExample
String@AppStorage("name") var name = "Buzz"
Int@AppStorage("score") var score = 0
Double@AppStorage("rating") var rating = 9.8
Bool@AppStorage("isFavorite") var isFavorite = true
URL@AppStorage("website") var url: URL?
Data@AppStorage("avatar") var avatar: Data?

For types not in this list — like arrays, custom structs, or enums — you need to convert them to Data first using Codable. But if your data is getting that complex, it’s time to consider SwiftData instead.

Warning: UserDefaults is not encrypted. Never store sensitive information like passwords, tokens, or credit card numbers here. Use Keychain for secrets.

Common Mistakes

Using UserDefaults as a Database

UserDefaults is meant for small preferences, not large datasets. Storing hundreds of objects or large blobs of data will slow down your app’s launch time because the entire plist is loaded into memory at startup.

// ❌ Don't do this — too much data for UserDefaults
let allMovies: [Data] = // hundreds of encoded Movie objects
UserDefaults.standard.set(allMovies, forKey: "allPixarMovies")
// ✅ Do this instead — use a database for large datasets
// Store the data in SwiftData (see the next post!)
// Use UserDefaults only for simple settings
UserDefaults.standard.set("Toy Story", forKey: "lastWatchedMovie")

Typos in Key Strings

Since UserDefaults keys are plain strings, a typo means you’ll silently read nil or a default value instead of your saved data.

// ❌ Don't do this — key mismatch
UserDefaults.standard.set("Remy", forKey: "favoriteChef")
let chef = UserDefaults.standard.string(forKey: "favouriteChef") // nil!
// ✅ Do this instead — define keys as constants
enum StorageKeys {
    static let favoriteChef = "favoriteChef"
}

UserDefaults.standard.set("Remy", forKey: StorageKeys.favoriteChef)
let chef = UserDefaults.standard.string(
    forKey: StorageKeys.favoriteChef
)

What’s Next?

  • UserDefaults is a key-value store for saving small, simple data like settings and preferences
  • @AppStorage connects UserDefaults to SwiftUI views, so changes automatically update the UI
  • Supported types include String, Int, Double, Bool, URL, and Data
  • Never store sensitive data in UserDefaults — use Keychain instead
  • For anything beyond simple preferences, reach for a real database

Ready to store real structured data that can be queried, sorted, and filtered? Head over to Getting Started with SwiftData to build your first persistent model.