4 H4: Tekenen met Canvas
Tot nu toe hebben we standaard bouwstenen gebruikt: Text, Button, VStack. Maar de EnergyClock heeft een wijzerplaat nodig — en die kun je niet samenstellen uit standaard views. Daarvoor gebruik je Canvas: een leeg vel papier waarop je zelf alles kunt tekenen.
4.1 Wat gaan we bouwen?
Een ronde wijzerplaat met 24 gekleurde segmenten — één per uur. Elk segment is een boog op een cirkel. Dit is de kern van de EnergyClock.

4.2 Playground-voorbeeld
import SwiftUI
import PlaygroundSupport
struct KlokView: View {
var body: some View {
Canvas { context, size in
let middelpunt = CGPoint(x: size.width / 2, y: size.height / 2)
let straal: CGFloat = 100
// Teken een volledige cirkel
var pad = Path()
pad.addArc(
center: middelpunt,
radius: straal,
startAngle: .degrees(0),
endAngle: .degrees(360),
clockwise: false
)
context.stroke(pad, with: .color(.blue), lineWidth: 4)
}
.frame(width: 250, height: 250)
}
}
PlaygroundPage.current.setLiveView(KlokView())Je ziet een blauwe cirkel. Verander .blue in .red en kijk wat er gebeurt.
4.3 Concept uitgelegd
4.3.1 Canvas: een leeg vel
Een Canvas is als een leeg vel papier. Je kunt er alles op tekenen — maar je moet het zelf doen, lijn voor lijn, cirkel voor cirkel. SwiftUI tekent niets automatisch voor je.
Om iets te tekenen, geef je de Canvas een blok code mee. Dat blok krijgt twee dingen:
context— de “pen” waarmee je tekentsize— hoe groot het vel is
Canvas { context, size in
// hier teken je
}4.3.2 Coördinaten: ruitjespapier
Het Canvas-systeem werkt als ruitjespapier. De linkerbovenhoek is punt (0, 0). Naar rechts worden de x-waarden groter. Naar beneden worden de y-waarden groter.
(0,0) ─────────── x →
│
│ (100, 50)
↓
y
Het middelpunt van een Canvas van 250×250 pixels is (125, 125).
4.3.3 Een boog tekenen
Een boog is een stuk van een cirkel. Je definieert:
- Het middelpunt van de cirkel
- De straal (hoe groot)
- De beginhoek en eindhoek
Hoeken worden gemeten in graden. 0 graden is rechts, 90 graden is onder, 180 graden is links, 270 graden is boven.
Maar bij een klok wil je 0 bovenaan. Dat doen we door 90 graden af te trekken: startAngle: .degrees(-90).
// Een kwart cirkel rechtsboven
var pad = Path()
pad.addArc(
center: middelpunt,
radius: 100,
startAngle: .degrees(-90), // boven
endAngle: .degrees(0), // rechts
clockwise: false
)
context.stroke(pad, with: .color(.green), lineWidth: 8)4.4 Code schrijven
We bouwen de wijzerplaat stap voor stap.
Stap 1: een leeg Canvas
Maak een nieuw SwiftUI-bestand KlokView.swift:
import SwiftUI
// Bron: EnergyClock/EnergyClockView.swift
struct KlokView: View {
var body: some View {
Canvas { context, size in
let middelpunt = CGPoint(x: size.width / 2, y: size.height / 2)
let straal = (min(size.width, size.height) / 2) - 20
}
.frame(width: 300, height: 300)
}
}Stap 2: 24 segmenten tekenen
Elke uur krijgt een boog. We verdelen de cirkel in 24 gelijke stukken:
Canvas { context, size in
let middelpunt = CGPoint(x: size.width / 2, y: size.height / 2)
let straal = (min(size.width, size.height) / 2) - 20
for uur in 0..<24 {
// Bereken de beginhoek voor dit uur
// 360 graden delen door 24 uren = 15 graden per uur
let beginGraden = Double(uur) * 360.0 / 24.0 - 90
let eindGraden = Double(uur + 1) * 360.0 / 24.0 - 90 - 2 // -2 voor een kleine gap
var pad = Path()
pad.addArc(
center: middelpunt,
radius: straal,
startAngle: .degrees(beginGraden),
endAngle: .degrees(eindGraden),
clockwise: false
)
// Wissel af tussen groen en grijs
let kleur: Color = uur % 2 == 0 ? .green : .gray
context.stroke(pad, with: .color(kleur), lineWidth: 16)
}
}Stap 3: uurnummers toevoegen
// Voeg dit toe in de Canvas, na de segmenten
for uur in 0..<24 {
let hoek = Double(uur) * 360.0 / 24.0 - 90
let boogsHoek = CGFloat(hoek * .pi / 180)
// Bereken de positie van het label
let labelStraal = straal - 35
let positie = CGPoint(
x: middelpunt.x + cos(boogsHoek) * labelStraal,
y: middelpunt.y + sin(boogsHoek) * labelStraal
)
// Teken het label alleen voor uren deelbaar door 3
if uur % 3 == 0 {
let tekst = Text("\(uur)").font(.caption).bold()
context.draw(tekst, at: positie)
}
}Stap 4: de struct opruimen
Zet elk stuk tekening in een eigen functie, zodat de body leesbaar blijft:
// Bron: EnergyClock/EnergyClockView.swift
struct KlokView: View {
var body: some View {
Canvas { context, size in
let middelpunt = CGPoint(x: size.width / 2, y: size.height / 2)
let straal = (min(size.width, size.height) / 2) - 20
tekenSegmenten(context: context, middelpunt: middelpunt, straal: straal)
tekenUurLabels(context: context, middelpunt: middelpunt, straal: straal)
}
.frame(width: 300, height: 300)
}
private func tekenSegmenten(context: GraphicsContext, middelpunt: CGPoint, straal: CGFloat) {
for uur in 0..<24 {
let beginGraden = Double(uur) * 360.0 / 24.0 - 90
let eindGraden = Double(uur + 1) * 360.0 / 24.0 - 90 - 2
var pad = Path()
pad.addArc(
center: middelpunt,
radius: straal,
startAngle: .degrees(beginGraden),
endAngle: .degrees(eindGraden),
clockwise: false
)
context.stroke(pad, with: .color(.gray.opacity(0.4)), lineWidth: 16)
}
}
private func tekenUurLabels(context: GraphicsContext, middelpunt: CGPoint, straal: CGFloat) {
for uur in 0..<24 where uur % 3 == 0 {
let hoek = Double(uur) * 360.0 / 24.0 - 90
let boogsHoek = CGFloat(hoek * .pi / 180)
let labelStraal = straal - 35
let positie = CGPoint(
x: middelpunt.x + cos(boogsHoek) * labelStraal,
y: middelpunt.y + sin(boogsHoek) * labelStraal
)
context.draw(Text("\(uur)").font(.caption).bold(), at: positie)
}
}
}Canvas gebruikt immediate mode rendering: elke keer als SwiftUI de view opnieuw tekent, voert het je teken-functies opnieuw uit van begin tot eind. Er is geen boom van objecten die bijgehouden wordt.
Dit verschilt van de normale SwiftUI-benadering, waarbij een boomstructuur van views in het geheugen staat en SwiftUI alleen de veranderde delen opnieuw rendert (retained mode).
Immediate mode is efficiënter voor complexe tekeningen met veel losse elementen — zoals 48 bogen op een wijzerplaat. Het nadeel is dat toegankelijkheidsfuncties (zoals VoiceOver) de inhoud van een Canvas niet kunnen lezen, omdat er geen semantische informatie aanwezig is. Voeg voor toegankelijkheid altijd een .accessibilityLabel() toe aan de Canvas.
De Canvas gebruikt intern CGContext — hetzelfde tekenmodel als macOS en iOS al decennialang kennen via Core Graphics.
4.5 Apple documentatie
Alles over Canvas:
developer.apple.com/documentation/swiftui/canvas
Alles over Path en bogen:
developer.apple.com/documentation/swiftui/path
Zoek in de Path-documentatie naar addArc(center:radius:startAngle:endAngle:clockwise:). De parameternamen verklaren precies wat je moet opgeven.
4.6 Samenvatting
| Begrip | Betekenis |
|---|---|
Canvas |
Een leeg tekenvlak in SwiftUI |
context |
De pen waarmee je tekent |
size |
De beschikbare ruimte van het Canvas |
Path |
Een pad van lijnen en bogen |
addArc |
Voegt een boog toe aan een pad |
context.stroke |
Tekent de omtrek van een pad |
context.fill |
Vult een pad met kleur |
CGPoint |
Een punt met een x- en y-coördinaat |
CGFloat |
Een getal voor afmetingen en posities |
| Radialen | Hoekeenheid die wiskundige functies gebruiken (π = 180°) |
4.7 Opdracht
Pas KlokView aan:
- Verander de kleur van de segmenten: de eerste 12 uren (nacht) zijn donkerblauw, de laatste 12 (dag) zijn geel.
- Maak de segmenten voor de uren 7, 8, 9 en 17, 18, 19 oranje — dit zijn de drukste uren voor stroomverbruik.
// Hint: gebruik een switch of if-statement op het uur
let kleur: Color
if uur < 6 {
kleur = .blue
} else if uur < 12 {
kleur = ...
} else {
kleur = ...
}