Lernziel
Nach dieser Lektion kannst du kleine Werte wie Einstellungen mit @AppStorage dauerhaft speichern, sodass sie einen Neustart der App überleben. Du kennst die Grenzen dieses Speichers, speicherst auch Enums und einfache Structs und kennst @SceneStorage für den Zustand eines Fensters.
Das Problem: Alles vergessen
Starte eine deiner bisherigen Apps, ändere eine Einstellung und beende die App ganz. Beim nächsten Start ist die Einstellung wieder weg. Der Habit-Tracker hat seine Gewohnheiten vergessen, die Rezept-App ihre Favoriten.
Der Grund: @State und @Observable-Objekte leben nur im Arbeitsspeicher. Beendet iOS die App, ist dieser Speicher weg. Für Daten, die bleiben sollen, brauchst du einen dauerhaften Speicher auf dem Gerät.
Für große Datenmengen wie Hunderte Gewohnheiten oder Ausgaben gibt es SwiftData (ab der nächsten Lektion). Für kleine Werte wie Einstellungen gibt es etwas viel Einfacheres.
@AppStorage
struct SettingsView: View {
@AppStorage("userName") private var userName = ""
@AppStorage("notificationsOn") private var notificationsOn = true
@AppStorage("dailyGoal") private var dailyGoal = 3
var body: some View {
Form {
TextField("Dein Name", text: $userName)
Toggle("Benachrichtigungen", isOn: $notificationsOn)
Stepper("Tagesziel: \(dailyGoal)", value: $dailyGoal, in: 1...10)
}
}
}Das sieht fast genauso aus wie @State, mit einem Unterschied: Die Werte werden auf dem Gerät gespeichert. Beende die App, starte sie neu, und alles ist noch da.
Erwartetes Ergebnis
Gib einen Namen ein, schalte die Benachrichtigungen aus und erhöhe das Tagesziel. Beende die App im Simulator ganz (im App-Umschalter nach oben wischen) und starte sie neu. Alle Werte sind noch da.
So funktioniert es:
"userName"ist der Schlüssel, unter dem der Wert gespeichert wird. Er muss in der ganzen App eindeutig sein.= ""ist der Standardwert, solange noch nichts gespeichert wurde.$userNameliefert ein Binding (Lektion 2.10), genau wie bei@State.- Ändert sich der Wert, wird er sofort gespeichert, und alle Views, die denselben Schlüssel verwenden, aktualisieren sich.
Ein Bild für @AppStorage
Stell dir einen kleinen Zettelkasten vor, der fest zur App gehört. Jeder Zettel hat eine Beschriftung, den Schlüssel, und einen Wert. @AppStorage liest beim Start den passenden Zettel und schreibt bei jeder Änderung einen neuen.
Was gespeichert werden kann
@AppStorage funktioniert mit einfachen Typen:
| Typ | Beispiel |
|---|---|
String | Name, Sprache |
Int, Double | Ziele, Zähler, Größen |
Bool | Schalter |
URL | eine gespeicherte Adresse |
Data | beliebige Daten in Rohform |
| Optionals dieser Typen | String? für „noch nicht gesetzt“ |
Date | Zeitpunkte (in neueren iOS-Versionen) |
Und, besonders praktisch, Enums mit String- oder Int-Raw-Value:
enum AppTheme: String, CaseIterable, Identifiable {
case system = "System"
case light = "Hell"
case dark = "Dunkel"
var id: Self { self }
var colorScheme: ColorScheme? {
switch self {
case .system: nil
case .light: .light
case .dark: .dark
}
}
}
struct ThemePicker: View {
@AppStorage("theme") private var theme = AppTheme.system
var body: some View {
Picker("Design", selection: $theme) {
ForEach(AppTheme.allCases) { theme in
Text(theme.rawValue)
}
}
}
}Gespeichert wird der Raw Value, also "Hell". Beim Lesen macht @AppStorage daraus wieder AppTheme.light. Gibt es den gespeicherten Wert nicht mehr, etwa weil du einen Fall umbenannt hast, wird der Standardwert verwendet.
Die Einstellung anwenden
Damit das gewählte Design die ganze App betrifft, liest du es in der App-Struct:
@main
struct MyApp: App {
@AppStorage("theme") private var theme = AppTheme.system
var body: some Scene {
WindowGroup {
ContentView()
.preferredColorScheme(theme.colorScheme)
}
}
}.preferredColorScheme(nil) heißt: „Folge der Systemeinstellung.“ Ändert der Nutzer das Design in den Einstellungen deiner App, wechselt die ganze App sofort. Beide Stellen verwenden denselben Schlüssel "theme" und bleiben so automatisch synchron.
Schlüssel an einer Stelle sammeln
Schlüssel sind einfache Texte. Vertippst du dich an einer Stelle, "theme" hier und "Theme" dort, gibt es zwei verschiedene Einträge, und nichts passt zusammen. Kein Fehler, keine Warnung.
Sammle die Schlüssel deshalb an einer Stelle:
enum StorageKey {
static let theme = "theme"
static let userName = "userName"
static let dailyGoal = "dailyGoal"
}
@AppStorage(StorageKey.theme) private var theme = AppTheme.systemJetzt meldet Xcode einen Tippfehler sofort.
Wo @AppStorage gespeichert wird
Hinter @AppStorage steckt UserDefaults, ein einfacher Speicher, den iOS jeder App zur Verfügung stellt. Er ist gedacht für kleine Einstellungen und wird beim App-Start komplett in den Arbeitsspeicher geladen.
Was nicht in @AppStorage gehört
- Große Datenmengen: Listen mit Hunderten Einträgen, Bilder, Dokumente. Dafür gibt es SwiftData oder Dateien.
- Vertrauliches: Passwörter, Zugangsdaten, Tokens.
UserDefaultsist nicht verschlüsselt. Dafür gibt es die Keychain. - Daten, die oft und schnell ändern, etwa jeder Zwischenstand einer Animation.
Faustregel: Wenn es in den Einstellungen einer App stehen könnte, passt es in @AppStorage.
Einfache Structs speichern
Manchmal willst du eine kleine Struct speichern, etwa eine Liste von Favoriten-IDs. @AppStorage kann Data speichern, und mit Codable (Lektion 3.9) wird aus einer Struct Data. Ein bewährtes Muster ist eine berechnete Eigenschaft, die beides verbindet:
struct FavoritesView: View {
@AppStorage("favoriteIDs") private var favoriteData = Data()
private var favoriteIDs: Set<String> {
get { (try? JSONDecoder().decode(Set<String>.self, from: favoriteData)) ?? [] }
nonmutating set { favoriteData = (try? JSONEncoder().encode(newValue)) ?? Data() }
}
var body: some View {
Text("\(favoriteIDs.count) Favoriten")
Button("Hinzufügen") {
var ids = favoriteIDs
ids.insert(UUID().uuidString)
favoriteIDs = ids
}
}
}- Der Getter dekodiert die gespeicherten Daten, mit leerem Set als Ersatz.
- Der Setter kodiert den neuen Wert und speichert ihn.
nonmutatingerlaubt das Setzen in einer View-Struct, weil@AppStorageden Wert ohnehin außerhalb der Struct speichert. Set<String>ist automatischCodable.
Tipp
Für ein kleines Set aus IDs ist das völlig in Ordnung. Wird es größer oder komplexer, ist SwiftData die bessere Wahl.
@AppStorage in einer @Observable-Klasse
@AppStorage ist für Views gedacht. In einer @Observable-Klasse funktioniert es nicht direkt. Brauchst du gespeicherte Werte in einem Store, liest und schreibst du UserDefaults selbst:
@Observable
class FavoritesStore {
private(set) var ids: Set<String> {
didSet { save() }
}
init() {
let saved = UserDefaults.standard.stringArray(forKey: "favoriteIDs") ?? []
ids = Set(saved)
}
func toggle(id: String) {
if ids.contains(id) { ids.remove(id) } else { ids.insert(id) }
}
private func save() {
UserDefaults.standard.set(Array(ids), forKey: "favoriteIDs")
}
}didSetläuft nach jeder Änderung der Eigenschaft und speichert sofort.- Im
initwird der gespeicherte Stand geladen.didSetläuft dabei nicht, weil Eigenschaften im eigenen Initializer ohne Beobachter gesetzt werden. UserDefaultskann Arrays von Strings direkt speichern. EinSetwird dafür in ein Array umgewandelt.
Damit überleben die Favoriten aus Projekt D jetzt einen Neustart.
@SceneStorage: Wo war ich?
Es gibt noch einen zweiten, ähnlichen Property Wrapper: @SceneStorage. Er speichert Werte nicht für die ganze App, sondern für ein Fenster (eine Scene, Lektion 2.2), und zwar nur so lange, wie iOS dieses Fenster wiederherstellen kann.
@SceneStorage("selectedTab") private var selectedTab = "home"
@SceneStorage("draftText") private var draft = ""Typisch ist das für den Bedienzustand: Welcher Tab war offen? Was stand halb getippt im Notizfeld? Beendet iOS die App im Hintergrund, um Speicher freizugeben, und der Nutzer kehrt zurück, findet er alles so vor, wie er es verlassen hat.
@AppStorage | @SceneStorage | |
|---|---|---|
| Gilt für | die ganze App | ein Fenster |
| Hält | dauerhaft | bis das Fenster geschlossen wird |
| Typisch für | Einstellungen | gewählter Tab, Entwürfe, Scrollposition |
Typische Fehler
Zwei verschiedene Schlüssel
@AppStorage("dailyGoal") hier, @AppStorage("dailygoal") dort. Die Werte sind nicht verbunden. Sammle Schlüssel in einem Enum.
Unterschiedliche Standardwerte
Zwei Views lesen denselben Schlüssel mit verschiedenen Standardwerten, etwa = 3 und = 5. Solange nichts gespeichert ist, zeigen sie unterschiedliche Werte. Leg Standardwerte ebenfalls zentral fest.
Werte im Simulator „hängen fest“
Beim Testen willst du manchmal von vorn anfangen. Lösch die App im Simulator (lange auf das Symbol drücken → App entfernen). Dann ist auch UserDefaults leer.
Struct direkt in @AppStorage
@AppStorage("profile") private var profile = Profile()Fehlermeldung
No exact matches in call to initializer
@AppStorage unterstützt nur einfache Typen. Speichere die Struct als Data wie oben gezeigt.
FAQ
Anfänger
Was bedeutet das überhaupt?Was passiert mit den Daten, wenn der Nutzer die App löscht? Sie werden mitgelöscht. Bei einer Neuinstallation beginnt alles mit den Standardwerten.
Werden @AppStorage-Werte zwischen iPhone und iPad synchronisiert?
Nein, sie bleiben auf dem Gerät. Für die Synchronisation über iCloud gibt es NSUbiquitousKeyValueStore oder SwiftData mit CloudKit.
Wie viel darf ich in @AppStorage speichern?
Es gibt keine harte Grenze, aber UserDefaults ist für einige Kilobyte gedacht. Bleib bei Einstellungen und kleinen Listen.
Fortgeschritten
Warum verhält es sich so?Kann ich @AppStorage mit einer App Group teilen, etwa mit einem Widget?
Ja, mit @AppStorage("key", store: UserDefaults(suiteName: "group.com.deinname.app")). App und Widget müssen dafür derselben App Group angehören, einstellbar unter Signing & Capabilities. Das brauchst du in Lektion 4.18.
Wie lege ich Standardwerte für viele Schlüssel zentral fest?
Mit UserDefaults.standard.register(defaults: ["dailyGoal": 3, …]) beim Start der App. Diese Werte gelten, solange nichts anderes gespeichert ist. @AppStorage übernimmt dann aber trotzdem seinen eigenen Standardwert aus dem Code, achte also darauf, dass beide übereinstimmen.
Profi
Was passiert unter der Haube?Wann genau schreibt UserDefaults auf die Festplatte?
Änderungen landen sofort im Arbeitsspeicher und werden vom System gebündelt und zeitnah gesichert. Früher gab es dafür synchronize(), das heute nicht mehr nötig ist und in Apples Dokumentation als veraltet markiert ist.
Wie bemerkt @AppStorage Änderungen von anderer Stelle?
@AppStorage beobachtet UserDefaults auf Änderungen an seinem Schlüssel. Schreibt anderer Code direkt mit UserDefaults.standard.set(...) unter diesen Schlüssel, aktualisiert sich die View trotzdem. Umgekehrt bekommt eine @Observable-Klasse Änderungen aus einer View mit @AppStorage nicht automatisch mit. Halte jeden Wert deshalb möglichst an einer Stelle.
Übungen
Übung 1: Onboarding nur einmal
Zeig beim ersten Start der App einen Willkommensbildschirm mit einem Button „Los geht's“. Danach soll er nie wieder erscheinen. Speichere dafür mit @AppStorage einen Bool hasSeenOnboarding.
Lösung Übung 1Erst selbst probieren!
struct RootView: View {
@AppStorage("hasSeenOnboarding") private var hasSeenOnboarding = false
var body: some View {
if hasSeenOnboarding {
ContentView()
} else {
VStack(spacing: 24) {
Text("Willkommen!")
.font(.largeTitle).bold()
Button("Los geht's") {
hasSeenOnboarding = true
}
.buttonStyle(.borderedProminent)
}
}
}
}Übung 2: Einstellungen für den Habit-Tracker
Ergänze den Habit-Tracker aus Projekt C um eine Einstellungsseite: Design (System/Hell/Dunkel), Streaks anzeigen (ja/nein) und ein Tagesziel (1–10). Alles wird mit @AppStorage gespeichert, und das Design wirkt auf die ganze App.
Übung 3: Favoriten dauerhaft
Stell den FavoritesStore aus Projekt D auf UserDefaults um, wie in dieser Lektion gezeigt. Prüf, ob die Favoriten einen Neustart überleben. Was passiert, wenn du in der Simulator-Einstellung die App löschst und neu installierst?
Zusammenfassung
@AppStorage("schlüssel")speichert kleine Werte dauerhaft auf dem Gerät und funktioniert wie@State.- Unterstützt einfache Typen, Optionals und Enums mit Raw Value.
- Schlüssel zentral sammeln, um Tippfehler zu vermeiden.
- Dahinter steckt
UserDefaults: für Einstellungen, nicht für große Daten oder Passwörter. - Structs über
CodableundDataspeichern, in KlassenUserDefaultsdirekt verwenden. @SceneStoragemerkt sich den Bedienzustand eines Fensters.
Spickzettel
@AppStorage("userName") private var userName = ""
@AppStorage("goal") private var goal = 3
@AppStorage("theme") private var theme = AppTheme.system // Enum mit Raw Value
@SceneStorage("selectedTab") private var tab = "home"
TextField("Name", text: $userName)
ContentView().preferredColorScheme(theme.colorScheme)
UserDefaults.standard.set(value, forKey: "key")
UserDefaults.standard.string(forKey: "key")