SwiftUI Daten speichern – @AppStorage, UserDefaults und SwiftData
Warum Daten persistieren?
Deine App startet, der User stellt Dark Mode ein, legt Favoriten an, gibt seinen Namen ein — und nach dem nächsten App-Neustart ist alles weg. Ohne Persistenz fühlt sich eine App kaputt an. Die gute Nachricht: SwiftUI macht es einfach, Daten dauerhaft zu speichern.
Es gibt drei Wege, je nach Komplexität:
| Ansatz | Wann verwenden |
|---|---|
| <code>@AppStorage</code> | Einfache Einstellungen (Bool, String, Int) |
| <code>UserDefaults</code> direkt | Wenn <code>@AppStorage</code> nicht reicht (Arrays, Dictionaries) |
| <code>SwiftData</code> | Komplexe Datenmodelle, Beziehungen, Suche |
@AppStorage – Einstellungen in einer Zeile
@AppStorage ist ein Property Wrapper, der direkt an UserDefaults gebunden ist. Er liest und schreibt automatisch — du musst dich um nichts kümmern:
struct SettingsView: View {
@AppStorage("isDarkMode") private var isDarkMode = false
@AppStorage("username") private var username = ""
@AppStorage("fontSize") private var fontSize = 16.0
var body: some View {
Form {
Toggle("Dark Mode", isOn: $isDarkMode)
TextField("Benutzername", text: $username)
Slider(value: $fontSize, in: 12...24, step: 1) {
Text("Schriftgröße: \(Int(fontSize))")
}
}
}
}
Der String "isDarkMode" ist der Key in UserDefaults. Der Wert wird sofort gespeichert, wenn er sich ändert, und beim nächsten App-Start automatisch geladen.
Was @AppStorage kann — und was nicht
Unterstützte Typen: Bool, Int, Double, String, URL, Data
Nicht unterstützt: Arrays, Dictionaries, eigene Structs, Enums (außer mit RawValue)
Für Enums funktioniert ein Workaround über RawRepresentable:
enum AppTheme: String {
case light, dark, system
}
struct SettingsView: View {
@AppStorage("appTheme") private var theme: String = AppTheme.system.rawValue
private var selectedTheme: AppTheme {
AppTheme(rawValue: theme) ?? .system
}
}
Wichtig: @AppStorage ist für kleine Werte gedacht — Einstellungen, Flags, Präferenzen. Speichere keine großen Datenmengen damit. UserDefaults ist nicht für Megabytes ausgelegt.
UserDefaults direkt – für Arrays und mehr
Wenn du Arrays oder Dictionaries speichern willst, greifst du direkt auf UserDefaults zu:
// Speichern
let favoriten = ["artikel-1", "artikel-2", "artikel-3"]
UserDefaults.standard.set(favoriten, forKey: "favoriten")
// Laden
let gespeichert = UserDefaults.standard.stringArray(forKey: "favoriten") ?? []
Für eigene Structs kombinierst du Codable mit JSONEncoder:
struct Bookmark: Codable {
let slug: String
let title: String
let date: Date
}
// Speichern
func saveBookmarks(_ bookmarks: [Bookmark]) {
if let data = try? JSONEncoder().encode(bookmarks) {
UserDefaults.standard.set(data, forKey: "bookmarks")
}
}
// Laden
func loadBookmarks() -> [Bookmark] {
guard let data = UserDefaults.standard.data(forKey: "bookmarks") else {
return []
}
return (try? JSONDecoder().decode([Bookmark].self, from: data)) ?? []
}
UserDefaults in einer @Observable-Klasse
In der Praxis kapselst du den Zugriff in einer Klasse, die du per .environment() injizierst:
@Observable
@MainActor
final class BookmarkStore {
private let key = "bookmarks"
var bookmarks: [String] = [] {
didSet {
UserDefaults.standard.set(bookmarks, forKey: key)
}
}
init() {
bookmarks = UserDefaults.standard.stringArray(forKey: key) ?? []
}
func toggle(_ slug: String) {
if bookmarks.contains(slug) {
bookmarks.removeAll { $0 == slug }
} else {
bookmarks.append(slug)
}
}
}
So hast du reaktiven State, der automatisch in UserDefaults persistiert wird.
SwiftData – für echte Datenmodelle
Wenn deine App mehr als Einstellungen speichern muss — Artikel, Notizen, Projekte mit Beziehungen — ist SwiftData die richtige Wahl. Es ist Apples moderner Ersatz für Core Data, direkt in SwiftUI integriert.
Model definieren
import SwiftData
@Model
final class Note {
var title: String
var content: String
var createdAt: Date
var isPinned: Bool
init(title: String, content: String) {
self.title = title
self.content = content
self.createdAt = .now
self.isPinned = false
}
}
Container einrichten
In deiner App-Struct:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Note.self)
}
}
Daten lesen und schreiben
struct NoteListView: View {
@Query(sort: \Note.createdAt, order: .reverse) private var notes: [Note]
@Environment(\.modelContext) private var context
var body: some View {
NavigationStack {
List {
ForEach(notes) { note in
VStack(alignment: .leading) {
Text(note.title)
.font(.headline)
Text(note.content)
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
.onDelete { indexSet in
for index in indexSet {
context.delete(notes[index])
}
}
}
.navigationTitle("Notizen")
.toolbar {
Button("Neu", systemImage: "plus") {
let note = Note(title: "Neue Notiz", content: "")
context.insert(note)
}
}
}
}
}
@Query lädt die Daten automatisch und aktualisiert die View bei Änderungen. modelContext übernimmt das Speichern — du musst kein save() aufrufen, SwiftData speichert automatisch.
Filtern mit #Predicate
@Query(
filter: #Predicate<Note> { $0.isPinned == true },
sort: \Note.createdAt
)
private var pinnedNotes: [Note]
Welchen Ansatz wählen?
| Kriterium | @AppStorage | UserDefaults | SwiftData |
|---|---|---|---|
| Setup | Keine | Keine | ModelContainer |
| Datentypen | Primitiv | Primitiv + Codable | Komplexe Modelle |
| Beziehungen | Nein | Nein | Ja |
| Suche & Filter | Nein | Nein | Ja (#Predicate) |
| Datenmenge | Wenige KB | Wenige KB | Unbegrenzt |
| Migration | Manuell | Manuell | Automatisch |
@AppStorage. Wenn du Arrays brauchst, nimm UserDefaults. Sobald du Beziehungen, Suche oder große Datenmengen hast, wechsle zu SwiftData.
Fazit
Datenpersistenz ist kein Nice-to-have — sie ist das Fundament jeder App, die sich fertig anfühlen soll. Mit @AppStorage für Einstellungen, UserDefaults für einfache Listen und SwiftData für komplexe Modelle hast du drei Werkzeuge, die zusammen jeden Anwendungsfall abdecken. Fang einfach an und skaliere, wenn es nötig wird.
Mehr über unsere App-Entwicklung →
Dieser Artikel wurde zuletzt am 25. März 2026 aktualisiert. Getestet mit Xcode 26 / iOS 26 / Swift 6.