8 H8: Data beheren
Tot nu toe halen we data op en gebruiken die direct in één view. Maar een echte app heeft meerdere schermen die allemaal dezelfde data nodig hebben: de klok, de prijslijst, de grafiek. We willen niet voor elk scherm apart data ophalen. In dit hoofdstuk leer je hoe je een centrale databeheerder bouwt die alle schermen bedient.
8.1 Wat gaan we bouwen?
De EnergyDataManager: een @Observable-klasse die data ophaalt, opslaat en deelt met alle views. Elke view die data nodig heeft, vraagt het simpelweg op bij de manager — en wordt automatisch bijgewerkt als de data verandert.

8.2 Playground-voorbeeld
import SwiftUI
import PlaygroundSupport
import Observation
// Een eenvoudige databeheerder
@Observable
class TellerManager {
var teller = 0
var naam = "onbekend"
func verhoog() {
teller += 1
}
}
// View A gebruikt de manager
struct ViewA: View {
@Environment(TellerManager.self) var manager
var body: some View {
VStack {
Text("Teller: \(manager.teller)")
Button("Plus") { manager.verhoog() }
.buttonStyle(.borderedProminent)
}
.padding()
}
}
// View B gebruikt dezelfde manager
struct ViewB: View {
@Environment(TellerManager.self) var manager
var body: some View {
Text("Ook hier: \(manager.teller)")
.padding()
}
}
// Hoofdview geeft de manager door via Environment
struct HoofdView: View {
@State private var manager = TellerManager()
var body: some View {
VStack(spacing: 20) {
ViewA()
ViewB()
}
.environment(manager)
.frame(width: 200, height: 150)
}
}
PlaygroundPage.current.setLiveView(HoofdView())Klik op “Plus” in ViewA — ViewB werkt ook bij. Één manager, twee views, automatisch gesynchroniseerd.
8.3 Concept uitgelegd
8.3.1 @Observable: een slimme klas
Een struct kun je niet delen tussen meerdere views — elke kopie is onafhankelijk. Daarvoor gebruik je een class. Met @Observable weet SwiftUI precies welke eigenschappen een view gebruikt, en tekent hij die view opnieuw als zo’n eigenschap verandert.
@Observable
class Winkelwagen {
var producten: [String] = []
var totaal: Double = 0
}8.3.2 @Environment: de manager doorgeven
Je kunt de manager doorgeven via @Environment. Dat werkt als een gedeelde kast waaruit alle views kunnen pakken wat ze nodig hebben:
// In de hoofdview: stop de manager in de kast
.environment(manager)
// In een childview: pak de manager uit de kast
@Environment(Winkelwagen.self) var winkelwagen8.3.3 @MainActor: altijd op de hoofdthread
UI-updates mogen alleen op de hoofdthread. Door een klasse te markeren met @MainActor zorgt Swift ervoor dat alle aanpassingen aan die klasse automatisch op de hoofdthread plaatsvinden.
@MainActor
@Observable
class EnergyDataManager {
// Alles hier wordt uitgevoerd op de hoofdthread
}8.4 Code schrijven
Stap 1: de klasse opzetten
Maak EnergyDataManager.swift:
import SwiftUI
// Bron: EnergyClock/EnergyDataManager.swift
// Coordinator: verbindt de API-client en persistentie-laag met de UI
@MainActor
@Observable
class EnergyDataManager {
var energyPrices: [EnergyPrice] = []
var isLoading = false
var errorMessage: String?
private let api = EnergyAPIClient()
func laadPrijzen() async {
isLoading = true
errorMessage = nil
do {
energyPrices = try await api.laadHuidigePrijzen()
} catch {
errorMessage = error.localizedDescription
energyPrices = maakVoorbeeldData()
}
isLoading = false
}
private func maakVoorbeeldData() -> [EnergyPrice] {
let kalender = Calendar.current
let vandaag = kalender.startOfDay(for: Date())
return (0..<24).map { uur in
let datum = kalender.date(byAdding: .hour, value: uur, to: vandaag) ?? vandaag
return EnergyPrice(hour: uur, price: Double.random(in: 0.05...0.40), date: datum)
}
}
}Stap 2: de manager in de app injecteren
Open EnergyClockApp.swift en voeg de manager toe als @State:
import SwiftUI
@main
struct EnergyClockApp: App {
@State private var manager = EnergyDataManager()
var body: some Scene {
WindowGroup {
ContentView()
.environment(manager)
}
}
}Stap 3: de manager gebruiken in views
In ContentView.swift:
import SwiftUI
struct ContentView: View {
@Environment(EnergyDataManager.self) var manager
var body: some View {
VStack {
if manager.isLoading {
ProgressView("Laden...")
} else if let fout = manager.errorMessage {
Text("Fout: \(fout)")
.foregroundStyle(.red)
} else {
Text("\(manager.energyPrices.count) prijzen geladen")
}
}
.task {
await manager.laadPrijzen()
}
}
}Stap 4: automatisch bijwerken bij verandering
Voeg didSet toe zodat de kleurmapper automatisch herberekend wordt als de prijzen veranderen:
// Bron: EnergyClock/EnergyDataManager.swift
var energyPrices: [EnergyPrice] = [] {
didSet { herberekeningColorMapper() }
}
private(set) var colorMapper: EnergyColorMapper = EnergyColorMapper(
prices: [],
mode: .absolute,
absoluteThresholds: .init(greenMax: 0.10, yellowMax: 0.20, orangeMax: 0.30)
)
private func herberekeningColorMapper() {
colorMapper = EnergyColorMapper(
prices: energyPrices,
mode: .absolute,
absoluteThresholds: .init(greenMax: 0.10, yellowMax: 0.20, orangeMax: 0.30)
)
}Vóór iOS 17 gebruikte Swift ObservableObject met @Published-eigenschappen. @Observable (geïntroduceerd in Swift 5.9) is de moderne vervanging.
Het grote verschil: ObservableObject stuurt een signaal als iets verandert in het object, waarna SwiftUI alle views die het object gebruiken opnieuw tekent — ook al veranderde alleen een eigenschap die die view niet gebruikt. Dit heet over-invalidation en kost onnodig rekenkracht.
@Observable is slimmer: SwiftUI houdt bij welke specifieke eigenschappen een view leest tijdens het tekenen. Alleen als één van díé eigenschappen verandert, tekent SwiftUI die view opnieuw. Dit heet fine-grained dependency tracking.
Het resultaat is dat apps met @Observable sneller en zuiniger zijn, zeker bij complexere datamodellen.
8.5 Apple documentatie
Meer over @Observable:
developer.apple.com/documentation/observation
Meer over @Environment:
developer.apple.com/documentation/swiftui/environment
Meer over @MainActor:
developer.apple.com/documentation/swift/mainactor
Zoek in de Observation-documentatie naar het voorbeeld “Conforming a type to Observable”. Dat laat precies zien hoe weinig code je nodig hebt.
8.6 Samenvatting
| Begrip | Betekenis |
|---|---|
class |
Een referentietype: gedeeld tussen views |
struct |
Een waardetype: elke kopie is onafhankelijk |
@Observable |
Markeert een klasse zodat SwiftUI veranderingen detecteert |
@MainActor |
Zorgt dat alle code op de hoofdthread draait |
@Environment |
Haalt een gedeeld object op uit de omgeving |
.environment() |
Injecteert een object in de omgeving |
didSet |
Voert code uit na het aanpassen van een eigenschap |
ProgressView |
Een laadindicator |
8.7 Opdracht
Breid de EnergyDataManager uit:
- Voeg een eigenschap
lastUpdateDate: Date?toe die bijhoudt wanneer de data voor het laatste geladen is. - Zet
lastUpdateDateop de huidige tijd aan het einde vanlaadPrijzen(). - Toon in de
ContentViewde updatetijd als de data geladen is: “Bijgewerkt om 14:32”. Gebruik.formatted(date: .omitted, time: .shortened)om de datum op te maken.