12 H12: Instellingen
De meeste apps hebben een instellingenscherm: kies je databron, pas kleuren aan, stel een voorkeur in. In dit hoofdstuk bouw je een instellingenscherm met tabbladen, pickers, tekstvelden en steppers — en je leert hoe je @Bindable gebruikt om instellingen direct door te schrijven naar de databeheerder.
12.1 Wat gaan we bouwen?
De SettingsView: een instellingenscherm met twee tabbladen — “Databron” en “Weergave”. De gebruiker kan de API-bron kiezen, drempelwaarden aanpassen en het thema instellen.

12.2 Playground-voorbeeld
import SwiftUI
import PlaygroundSupport
import Observation
@Observable
class Instellingen {
var naam = "Mijn app"
var aantalDagen = 7
var geluidsAan = true
}
struct InstellingenView: View {
@Bindable var instellingen: Instellingen
var body: some View {
Form {
TextField("Naam", text: $instellingen.naam)
Stepper("Dagen: \(instellingen.aantalDagen)", value: $instellingen.aantalDagen, in: 1...30)
Toggle("Geluid", isOn: $instellingen.geluidsAan)
}
.formStyle(.grouped)
.frame(width: 300, height: 200)
}
}
let inst = Instellingen()
PlaygroundPage.current.setLiveView(InstellingenView(instellingen: inst))Pas een waarde aan — de Instellingen-klasse wordt direct bijgewerkt.
12.3 Concept uitgelegd
12.3.1 @Bindable: twee-richtingsverkeer
In H2 leerde je @State voor lokale waarden. Met @Bindable kun je twee-richtingsverbindingen maken met een @Observable-object:
@Bindable var manager: EnergyDataManager
// Nu kun je $manager.colorMode gebruiken in een Picker
Picker("Kleurmodus", selection: $manager.colorMode) { ... }Het $-teken maakt een binding: als de gebruiker een keuze maakt in de Picker, schrijft die automatisch terug naar manager.colorMode.
12.3.2 Form: een instellingsformulier
Form is de standaard container voor instellingen. Hij past zijn opmaak automatisch aan het platform aan:
Form {
Section("Geluid") {
Toggle("Geluidseffecten", isOn: $instellingen.geluidsAan)
}
Section("Data") {
Stepper("Dagen: \(aantalDagen)", value: $aantalDagen, in: 1...30)
}
}
.formStyle(.grouped)12.3.3 TabView met het Tab-API
Een scherm met tabbladen gebruik je zo:
// Bron: EnergyClock/SettingsView.swift
TabView {
Tab("Databron", systemImage: "antenna.radiowaves.left.and.right") {
DatabronInstellingen(manager: manager)
}
Tab("Weergave", systemImage: "paintpalette") {
WeergaveInstellingen(manager: manager)
}
}Let op: gebruik altijd de Tab-API (niet .tabItem()). De Tab-API is de moderne variant.
12.4 Code schrijven
Stap 1: de buitenkant
Maak SettingsView.swift:
import SwiftUI
// Bron: EnergyClock/SettingsView.swift
struct SettingsView: View {
@Bindable var manager: EnergyDataManager
var body: some View {
TabView {
Tab("Databron", systemImage: "antenna.radiowaves.left.and.right") {
DatabronInstellingen(manager: manager)
}
Tab("Weergave", systemImage: "paintpalette") {
WeergaveInstellingen(manager: manager)
}
}
}
}Stap 2: het Databron-tabblad
// Bron: EnergyClock/SettingsView.swift – DatabronInstellingen
private struct DatabronInstellingen: View {
@Bindable var manager: EnergyDataManager
var body: some View {
Form {
Section {
// Kies de API-bron
Picker("Bron", selection: $manager.selectedSource) {
ForEach(EnergyPriceSource.allCases) { bron in
Text(bron.rawValue).tag(bron)
}
}
.onChange(of: manager.selectedSource) { _, nieuweBron in
manager.wisselNaarBronCache(nieuweBron)
Task { await manager.loadEnergyPrices() }
}
}
Section {
// Aantal historische dagen
HStack {
Text("Prijsgeschiedenis")
Spacer()
TextField("", value: $manager.historischeDagenOpslaan, format: .number)
.frame(width: 48)
.textFieldStyle(.roundedBorder)
.multilineTextAlignment(.trailing)
Text("dagen")
.foregroundStyle(.secondary)
Stepper(
"",
value: $manager.historischeDagenOpslaan,
in: 1...60,
step: 1
)
.labelsHidden()
}
} footer: {
Text("Hoeveel dagen worden opgeslagen voor de boxplot en relatieve kleuren.")
.font(.caption)
}
}
.formStyle(.grouped)
.frame(width: 540)
}
}Stap 3: het Weergave-tabblad
// Bron: EnergyClock/SettingsView.swift – WeergaveInstellingen
private struct WeergaveInstellingen: View {
@Bindable var manager: EnergyDataManager
var body: some View {
Form {
// Kleurmodus
Section {
Picker("Kleurmodus", selection: $manager.colorMode) {
ForEach(ColorMode.allCases) { modus in
Text(modus.rawValue).tag(modus)
}
}
.pickerStyle(.segmented)
.onChange(of: manager.colorMode) { _, _ in
manager.saveColorSettings()
}
} header: {
Text("Kleurmodus")
}
// Drempelwaarden
if manager.colorMode == .absolute {
Section("Drempelwaarden (EUR/kWh)") {
HStack {
Circle().fill(Color.green).frame(width: 12, height: 12)
Text("Groen tot")
Spacer()
TextField("", value: $manager.absoluteThresholds.greenMax,
format: .number.precision(.fractionLength(2)))
.frame(width: 80)
.textFieldStyle(.roundedBorder)
.multilineTextAlignment(.trailing)
.onChange(of: manager.absoluteThresholds.greenMax) { _, _ in
manager.saveColorSettings()
}
}
// oranje en rood op dezelfde manier...
}
}
}
.formStyle(.grouped)
.frame(width: 540)
}
}@Bindable is onderdeel van het Observation-framework (Swift 5.9+). Het verschil met oudere benaderingen:
Met ObservableObject (oud):
@ObservedObject var manager: EnergyDataManager
// $manager.colorMode werkte via @PublishedMet @Observable (nieuw):
@Bindable var manager: EnergyDataManager
// $manager.colorMode werkt via synthetische bindingen@Bindable genereert automatisch bindingen voor alle opslagbare (var) eigenschappen van een @Observable-object. Achter de schermen gebruikt het Swift’s DynamicProperty-protocol en macros die de compiler tijdens het compileren uitbreidt.
Een binding is geen kopie van de waarde — het is een verwijzing naar de locatie in het geheugen plus een setter-functie. Als je een binding doorgeeft via $manager.colorMode, geef je de view toegang om die specifieke eigenschap te lezen en te schrijven zonder het hele object te hoeven kennen.
12.5 Apple documentatie
Meer over Form:
developer.apple.com/documentation/swiftui/form
Meer over @Bindable:
developer.apple.com/documentation/swiftui/bindable
Meer over Picker:
developer.apple.com/documentation/swiftui/picker
Zoek in de Form-documentatie naar formStyle(.grouped) — dat is de macOS/iOS-stijl die lijkt op de Instellingen-app.
12.6 Samenvatting
| Begrip | Betekenis |
|---|---|
@Bindable |
Maakt twee-richtingsbindingen met een @Observable-object |
$ |
Binding-operator: stuurt wijzigingen terug naar de bron |
Form |
Container voor instellingselementen |
Section |
Groepeert elementen in een Form |
TabView |
Scherm met tabbladen onderaan of bovenaan |
Tab |
Eén tabblad in een TabView |
Toggle |
Een aan/uit-schakelaar |
Stepper |
Verhoog of verlaag een getal met knoppen |
Picker |
Kies één optie uit een lijst |
.onChange(of:) |
Voert code uit als een waarde verandert |
12.7 Opdracht
Voeg een derde tabblad toe aan SettingsView: “Info”. Dit tabblad toont:
- De versie van de app (gebruik
Bundle.main.infoDictionary?["CFBundleShortVersionString"]). - Een link-knop naar de GitHub-pagina van het project.
- Een knop “Cache wissen” die alle opgeslagen prijzen verwijdert uit UserDefaults (waarschuwing: de app laadt daarna nieuwe data).