9 H9: Opslaan en laden
Als je de EnergyClock sluit en opnieuw opent, moeten de prijzen er nog staan. Elke keer opnieuw laden is traag en verbruikt onnodig data. In dit hoofdstuk leer je hoe je gegevens bewaart met UserDefaults, zodat de app onthoud wat er de laatste keer geladen was.
9.1 Wat gaan we bouwen?
De EnergyPersistence-struct: een oplaglaag die prijzen, instellingen en de laatste updatetijd bewaart. Na dit hoofdstuk start de app razendsnel op met de vorige data, en laadt nieuwe data alleen als het nodig is.

9.2 Playground-voorbeeld
import Foundation
// Sla een waarde op
UserDefaults.standard.set("Emma", forKey: "gebruikersnaam")
UserDefaults.standard.set(42, forKey: "highscore")
UserDefaults.standard.set(true, forKey: "geluidsAan")
// Lees de waarden terug
let naam = UserDefaults.standard.string(forKey: "gebruikersnaam") ?? "onbekend"
let score = UserDefaults.standard.integer(forKey: "highscore")
let geluid = UserDefaults.standard.bool(forKey: "geluidsAan")
print(naam) // Emma
print(score) // 42
print(geluid) // trueSluit Playgrounds, open het opnieuw — de waarden zijn nog steeds opgeslagen. Dat is UserDefaults.
9.3 Concept uitgelegd
9.3.1 UserDefaults: de brievenbus van je app
UserDefaults is als een brievenbus: je stopt er iets in, en het ligt er de volgende dag nog. Perfect voor kleine hoeveelheden data: instellingen, de laatste keuze van de gebruiker, een datum.
Je slaat iets op met een sleutel — een naam die je zelf kiest. Later gebruik je diezelfde sleutel om de waarde terug te lezen.
// Opslaan
UserDefaults.standard.set(14, forKey: "aantalUren")
// Lezen
let uren = UserDefaults.standard.integer(forKey: "aantalUren")9.3.2 Codable structs opslaan
Eenvoudige types (String, Int, Bool, Date) kun je direct opslaan. Maar voor een lijst van EnergyPrice-objecten moet je eerst encoderen naar Data, en bij het lezen weer decoderen:
// Opslaan
let encoder = JSONEncoder()
if let data = try? encoder.encode(mijnPrijzen) {
UserDefaults.standard.set(data, forKey: "opgeslagenPrijzen")
}
// Laden
if let data = UserDefaults.standard.data(forKey: "opgeslagenPrijzen"),
let prijzen = try? JSONDecoder().decode([EnergyPrice].self, from: data) {
print("Geladen: \(prijzen.count) prijzen")
}9.3.3 Sleutels centraal bewaren
Als je sleutels als losse strings overal in de code zet, vergeet je ze makkelijk of maak je een typfout. Zet ze op één plek in een enum:
enum OpslagSleutel {
static let prijzen = "savedEnergyPrices"
static let updateDatum = "lastUpdateDate"
}9.4 Code schrijven
Stap 1: de sleutels
Maak EnergyPersistence.swift en begin met de sleutels:
import Foundation
// Bron: EnergyClock/EnergyPersistence.swift
// Alle UserDefaults sleutels op één plek
enum OpslagSleutel {
static let prijzen = "savedEnergyPrices"
static let bron = "selectedEnergySource"
static let updateDatum = "lastUpdateDate"
static let eigenURL = "customURL"
}Stap 2: de persistentie-struct
// Bron: EnergyClock/EnergyPersistence.swift
// Laadt en slaat alle app-instellingen en prijsdata op
struct EnergyPersistence {
private let defaults = UserDefaults.standard
// MARK: - Opslaan
func slaOp(prijzen: [EnergyPrice], updateDatum: Date) {
if let encoded = try? JSONEncoder().encode(prijzen) {
defaults.set(encoded, forKey: OpslagSleutel.prijzen)
}
defaults.set(updateDatum, forKey: OpslagSleutel.updateDatum)
}
func slaOp(eigenURL: String) {
defaults.set(eigenURL, forKey: OpslagSleutel.eigenURL)
}
// MARK: - Laden
func laadPrijzen() -> [EnergyPrice] {
guard let data = defaults.data(forKey: OpslagSleutel.prijzen),
let decoded = try? JSONDecoder().decode([EnergyPrice].self, from: data) else {
return []
}
return decoded
}
func laadUpdateDatum() -> Date? {
defaults.object(forKey: OpslagSleutel.updateDatum) as? Date
}
func laadEigenURL() -> String {
defaults.string(forKey: OpslagSleutel.eigenURL) ?? ""
}
}Stap 3: de manager koppelen aan de opslag
Voeg opslag toe aan EnergyDataManager:
// Bron: EnergyClock/EnergyDataManager.swift
private let opslag = EnergyPersistence()
init() {
// Laad opgeslagen data direct bij opstarten
energyPrices = opslag.laadPrijzen()
lastUpdateDate = opslag.laadUpdateDatum()
}
func laadPrijzen() async {
isLoading = true
errorMessage = nil
do {
energyPrices = try await api.laadHuidigePrijzen()
let updateDatum = Date()
lastUpdateDate = updateDatum
// Sla de nieuwe data op
opslag.slaOp(prijzen: energyPrices, updateDatum: updateDatum)
} catch {
errorMessage = error.localizedDescription
energyPrices = opslag.laadPrijzen() // gebruik gecachte data als fallback
}
isLoading = false
}Stap 4: alleen laden als nodig
// Bron: EnergyClock/EnergyDataManager.swift
func laadBijOpstarten() async {
// Controleer of de opgeslagen data van vandaag is
let dataIsActueel = lastUpdateDate.map {
Calendar.current.isDateInToday($0)
} ?? false
if !dataIsActueel {
await laadPrijzen()
}
// Anders: gebruik de al gecachte data
}Apps op macOS en iOS draaien in een sandbox: een afgeschermde omgeving zonder toegang tot bestanden van andere apps. UserDefaults slaat gegevens op in een .plist-bestand binnen die sandbox, op een locatie als ~/Library/Containers/com.jouwnaam.app/Data/Library/Preferences/.
Voor de widget (zie H13) moeten de app en widget dezelfde data lezen. Dat gaat via een App Group: een gedeeld stukje opslagruimte waar beide toegang toe hebben. In plaats van UserDefaults.standard gebruik je dan:
UserDefaults(suiteName: "group.com.jouwnaam.app")Beide de app én de widget moeten dezelfde App Group ID hebben in hun Entitlements, en de groep moet aangemaakt zijn in je Apple Developer account.
UserDefaults is niet geschikt voor grote hoeveelheden data. Voor meer dan een paar megabyte gebruik je FileManager om naar een bestand te schrijven, of SwiftData voor een database.
9.5 Apple documentatie
Meer over UserDefaults:
developer.apple.com/documentation/foundation/userdefaults
Meer over App Groups:
developer.apple.com/documentation/xcode/configuring-app-groups
Zoek in de UserDefaults-documentatie naar set(_:forKey:) en data(forKey:). Kijk welke typen je direct kunt opslaan (de “Simple type” overzicht) en welke je eerst moet encoderen.
9.6 Samenvatting
| Begrip | Betekenis |
|---|---|
UserDefaults |
Sla kleine hoeveelheden data op die bewaard blijven |
| Sleutel | Een naam om een opgeslagen waarde mee terug te vinden |
JSONEncoder |
Zet een Codable struct om naar Data |
JSONDecoder |
Zet Data terug naar een Codable struct |
| App Group | Gedeelde opslag tussen app en widget |
try? |
Probeer iets; als het mislukt geeft het nil terug |
9.7 Opdracht
Breid EnergyPersistence uit:
- Voeg een functie
slaOp(kleurModus: ColorMode)toe die de kleurinstelling bewaart. - Voeg een functie
laadKleurModus() -> ColorModetoe die de instelling laadt, met.absoluteals standaardwaarde als er nog niets opgeslagen is. - Roep beide functies aan vanuit
EnergyDataManager: sla de kleurinstelling op als ze verandert, en laad hem bij initialisatie.