Lernziel
Nach dieser Lektion kannst du Daten mit Swift Charts als Diagramme darstellen: Balken, Linien, Flächen, Punkte und Kreis- bzw. Ringdiagramme. Du kannst Reihen nach Kategorien einfärben, mit Datumswerten arbeiten, Achsen und Farben anpassen, Linien für Durchschnittswerte einzeichnen und Werte per Fingertipp auswählen.
Das Problem: Zahlen sagen wenig
Eine Tabelle mit den Ausgaben der letzten 30 Tage enthält alle Informationen. Aber wann hast du am meisten ausgegeben? Wird es mehr oder weniger? Wofür geht das meiste Geld drauf? Um das aus 30 Zahlen herauszulesen, musst du lange hinschauen.
Ein Diagramm zeigt dieselben Informationen auf einen Blick. Mit Swift Charts baust du Diagramme so, wie du Views baust: deklarativ, mit wenigen Zeilen, und automatisch passend zu Dark Mode, Dynamic Type und VoiceOver.
Das erste Balkendiagramm
import SwiftUI
import Charts
struct DailySteps: Identifiable {
let id = UUID()
let day: String
let steps: Int
}
struct StepsChart: View {
let data = [
DailySteps(day: "Mo", steps: 6_400),
DailySteps(day: "Di", steps: 8_200),
DailySteps(day: "Mi", steps: 4_900),
DailySteps(day: "Do", steps: 10_300),
DailySteps(day: "Fr", steps: 7_600),
DailySteps(day: "Sa", steps: 12_100),
DailySteps(day: "So", steps: 3_800)
]
var body: some View {
Chart(data) { entry in
BarMark(
x: .value("Tag", entry.day),
y: .value("Schritte", entry.steps)
)
}
.frame(height: 240)
.padding()
}
}Erwartetes Ergebnis
Ein Balkendiagramm mit sieben Balken von Montag bis Sonntag. Die y-Achse ist automatisch beschriftet, die Balken haben die Akzentfarbe, und der Samstag sticht als höchster Balken heraus.
Was hier passiert:
import Chartsbindet das Framework ein.Chart(data) { entry in ... }funktioniert wieListoderForEach: Für jedes Element beschreibst du, was gezeichnet wird.BarMarkist eine Markierung, hier ein Balken..value("Tag", entry.day)verbindet eine Beschriftung mit einem Wert. Die Beschriftung verwendet VoiceOver, um das Diagramm vorzulesen.
Achsen, Skalierung, Gitterlinien und Abstände berechnet Swift Charts selbst. Das ist der große Vorteil: Du beschreibst was, nicht wie.
Die Markierungen
Der Typ der Markierung bestimmt die Art des Diagramms. Tausch BarMark aus, und du bekommst ein anderes Diagramm mit denselben Daten:
LineMark(x: .value("Tag", entry.day), y: .value("Schritte", entry.steps)) // Linie
PointMark(x: .value("Tag", entry.day), y: .value("Schritte", entry.steps)) // Punkte
AreaMark(x: .value("Tag", entry.day), y: .value("Schritte", entry.steps)) // FlächeMarkierungen lassen sich auch kombinieren. Eine Linie mit Punkten:
Chart(data) { entry in
LineMark(x: .value("Tag", entry.day), y: .value("Schritte", entry.steps))
.interpolationMethod(.catmullRom)
PointMark(x: .value("Tag", entry.day), y: .value("Schritte", entry.steps))
}.interpolationMethod(.catmullRom) zeichnet eine weiche Kurve statt gerader Verbindungen.
Waagrechte Balken
Tauschst du x und y, liegen die Balken waagrecht. Das ist besonders gut für lange Beschriftungen:
BarMark(
x: .value("Betrag", entry.amount),
y: .value("Kategorie", entry.category)
)Einfärben nach Kategorie
Oft gehören Werte zu verschiedenen Gruppen. Mit .foregroundStyle(by:) färbt Swift Charts jede Gruppe automatisch anders ein und erzeugt eine Legende:
struct Expense: Identifiable {
let id = UUID()
let month: String
let category: String
let amount: Double
}
let expenses = [
Expense(month: "Jan", category: "Essen", amount: 320),
Expense(month: "Jan", category: "Verkehr", amount: 90),
Expense(month: "Jan", category: "Freizeit", amount: 150),
Expense(month: "Feb", category: "Essen", amount: 290),
Expense(month: "Feb", category: "Verkehr", amount: 110),
Expense(month: "Feb", category: "Freizeit", amount: 80),
Expense(month: "Mär", category: "Essen", amount: 340),
Expense(month: "Mär", category: "Verkehr", amount: 95),
Expense(month: "Mär", category: "Freizeit", amount: 210)
]
Chart(expenses) { expense in
BarMark(
x: .value("Monat", expense.month),
y: .value("Betrag", expense.amount)
)
.foregroundStyle(by: .value("Kategorie", expense.category))
}Erwartetes Ergebnis
Drei gestapelte Balken für Januar bis März, jeder aufgeteilt in drei Farben für Essen, Verkehr und Freizeit. Darunter erscheint automatisch eine Legende.
Balken mit demselben x-Wert werden standardmäßig gestapelt. Sollen sie nebeneinander stehen:
.position(by: .value("Kategorie", expense.category))Bei Linien erzeugt .foregroundStyle(by:) eine eigene Linie pro Gruppe, etwa für den Vergleich mehrerer Jahre.
Eigene Farben
Die automatischen Farben kannst du durch eigene ersetzen:
.chartForegroundStyleScale([
"Essen": Color.orange,
"Verkehr": Color.blue,
"Freizeit": Color.purple
])Für ein einzelnes, einfarbiges Diagramm reicht .foregroundStyle(.orange) an der Markierung, auch mit Verlauf: .foregroundStyle(.orange.gradient).
Kreis- und Ringdiagramme
Für Anteile an einem Ganzen gibt es SectorMark:
struct CategoryTotal: Identifiable {
let id = UUID()
let name: String
let total: Double
}
let totals = [
CategoryTotal(name: "Essen", total: 950),
CategoryTotal(name: "Verkehr", total: 295),
CategoryTotal(name: "Freizeit", total: 440)
]
Chart(totals) { item in
SectorMark(
angle: .value("Betrag", item.total),
innerRadius: .ratio(0.6),
angularInset: 2
)
.foregroundStyle(by: .value("Kategorie", item.name))
.cornerRadius(4)
}
.frame(height: 240)angle:bestimmt die Größe des Stücks. Die Anteile berechnet Swift Charts selbst.innerRadius: .ratio(0.6)macht aus dem Kuchen einen Ring mit 60 % Loch. Ohne diese Angabe entsteht ein volles Kreisdiagramm.angularInset: 2lässt kleine Lücken zwischen den Stücken.
In der Mitte des Rings ist Platz für eine Summe:
.chartBackground { _ in
VStack {
Text("Gesamt").font(.caption).foregroundStyle(.secondary)
Text(1685, format: .currency(code: "EUR")).font(.title3.bold())
}
}Wann ein Kreisdiagramm?
Kreis- und Ringdiagramme eignen sich für wenige Anteile, etwa drei bis fünf. Bei mehr Kategorien oder ähnlich großen Werten kann das Auge die Stücke kaum vergleichen. Dann sind waagrechte Balken die bessere Wahl.
Datumswerte
Für Zeitverläufe übergibst du echte Date-Werte und sagst, in welcher Einheit sie zusammengefasst werden sollen:
struct DayAmount: Identifiable {
let id = UUID()
let date: Date
let amount: Double
}
Chart(days) { day in
BarMark(
x: .value("Tag", day.date, unit: .day),
y: .value("Betrag", day.amount)
)
}
.chartXAxis {
AxisMarks(values: .stride(by: .day, count: 7)) { _ in
AxisGridLine()
AxisValueLabel(format: .dateTime.day().month(.abbreviated))
}
}unit: .daysorgt dafür, dass jeder Balken genau einen Tag breit ist. Mit.monthoder.weekOfYearfasst Swift Charts Werte desselben Monats oder derselben Woche zusammen..chartXAxispasst die Achse an: Hier gibt es nur jede Woche eine Beschriftung, formatiert als „12. Okt.“.
Achsen und Skalen anpassen
Ein paar nützliche Einstellungen:
.chartYScale(domain: 0...15_000) // feste Spannweite der y-Achse
.chartYAxis(.hidden) // y-Achse ausblenden
.chartXAxisLabel("Wochentag") // Achsenbeschriftung
.chartLegend(.hidden) // Legende ausblenden
.chartLegend(position: .top) // Legende obenEine feste Spannweite ist wichtig, wenn mehrere Diagramme vergleichbar sein sollen, oder damit sich die Achse beim Aktualisieren nicht ständig verändert.
Referenzlinien
Ein Durchschnitt oder ein Ziel lässt sich als Linie einzeichnen, mit RuleMark:
Chart {
ForEach(data) { entry in
BarMark(x: .value("Tag", entry.day), y: .value("Schritte", entry.steps))
.foregroundStyle(entry.steps >= 10_000 ? Color.green : Color.orange)
}
RuleMark(y: .value("Ziel", 10_000))
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
.foregroundStyle(.secondary)
.annotation(position: .top, alignment: .leading) {
Text("Ziel").font(.caption).foregroundStyle(.secondary)
}
}Beachte die zweite Schreibweise: Chart { ... } ohne Daten, mit einem ForEach darin. So kannst du Markierungen aus verschiedenen Quellen in ein Diagramm packen: die Balken aus dem Array und dazu eine einzelne Linie.
RuleMark(y:)zeichnet eine waagrechte Linie über die ganze Breite..lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))macht sie gestrichelt..annotationhängt eine beliebige View an eine Markierung, hier die Beschriftung „Ziel“.
Die Balken sind grün, wenn das Ziel erreicht wurde, sonst orange. Jede Markierung kann ihre eigene Farbe haben.
Werte auswählen
Tippt oder streicht der Nutzer über das Diagramm, soll der genaue Wert erscheinen. Dafür gibt es .chartXSelection:
struct InteractiveStepsChart: View {
let data: [DailySteps]
@State private var selectedDay: String?
var body: some View {
Chart(data) { entry in
BarMark(x: .value("Tag", entry.day), y: .value("Schritte", entry.steps))
.foregroundStyle(selectedDay == nil || selectedDay == entry.day ? Color.orange : Color.orange.opacity(0.3))
if entry.day == selectedDay {
RuleMark(x: .value("Tag", entry.day))
.foregroundStyle(.clear)
.annotation(position: .top) {
Text("\(entry.steps) Schritte")
.font(.caption.bold())
.padding(6)
.background(.regularMaterial, in: .rect(cornerRadius: 6))
}
}
}
.chartXSelection(value: $selectedDay)
.frame(height: 240)
}
}.chartXSelection(value: $selectedDay)schreibt den x-Wert unter dem Finger inselectedDay, solange der Finger auf dem Diagramm liegt. Danach wird er wiedernil.- Der gewählte Balken bleibt kräftig, die anderen werden blasser.
- Eine unsichtbare
RuleMarkbeim gewählten Tag trägt die Beschriftung mit dem genauen Wert.
Tipp
Bei Datumsachsen ist die Auswahl ein Date?. Um den passenden Eintrag zu finden, vergleichst du mit Calendar.current.isDate(entry.date, inSameDayAs: selected).
Animation
Ändern sich die Daten mit withAnimation, wachsen und schrumpfen die Balken automatisch:
Button("Neue Woche") {
withAnimation(.smooth) {
data = generateRandomWeek()
}
}Weil jedes Element eine stabile id hat, weiß Swift Charts, welcher Balken zu welchem wird.
Typische Fehler
Cannot find 'Chart' in scope
import Charts fehlt oben in der Datei.
Balken in falscher Reihenfolge
Bei Text-Werten auf der x-Achse ordnet Swift Charts nach dem ersten Auftreten. Sortiere die Daten vorher, oder lege die Reihenfolge fest mit .chartXScale(domain: ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]).
Datumsbalken sind hauchdünn
Ohne unit: behandelt Swift Charts Datumswerte als exakte Zeitpunkte, und Balken bekommen kaum Breite. Gib unit: .day oder eine andere passende Einheit an.
Diagramm füllt den ganzen Bildschirm
Ein Chart nimmt sich so viel Platz, wie er bekommt. Gib ihm eine Höhe mit .frame(height:).
FAQ
Anfänger
Was bedeutet das überhaupt?Muss ich Swift Charts extra installieren? Nein, es ist Teil von iOS und muss nur importiert werden.
Kann VoiceOver Diagramme vorlesen?
Ja, automatisch. Dafür sind die Beschriftungen in .value("Tag", ...) wichtig: VoiceOver liest etwa „Tag: Mo, Schritte: 6400“. Mit „Audiografik“ kann es den Verlauf sogar als Tonfolge abspielen.
Fortgeschritten
Warum verhält es sich so?Wie mache ich ein Diagramm waagrecht scrollbar, etwa für ein ganzes Jahr?
Mit .chartScrollableAxes(.horizontal) und .chartXVisibleDomain(length: ...), das festlegt, wie viel auf einmal sichtbar ist. Bei Datumswerten ist die Länge in Sekunden, etwa 3600 * 24 * 30 für 30 Tage.
Wie zeichne ich eine Funktion statt Datenpunkten?
Mit LinePlot(x: "x", y: "y") { x in sin(x) } und .chartXScale(domain: -10...10). Swift Charts berechnet die Punkte selbst. Das ist praktisch für Lern- und Mathe-Apps.
Profi
Was passiert unter der Haube?Wie viele Datenpunkte schafft Swift Charts?
Mit einzelnen Markierungen einige tausend flüssig. Für sehr große Datenmengen gibt es Vectorized Plots wie BarPlot(data, x: .value(...), y: .value(...)), die ganze Sammlungen auf einmal zeichnen und deutlich schneller sind. Bei Zehntausenden Punkten lohnt es sich außerdem, die Daten vorher zusammenzufassen.
Wie greife ich auf Positionen im Diagramm zu, etwa für eigene Gesten?
Über .chartOverlay { proxy in ... }. Der ChartProxy rechnet zwischen Bildschirmpositionen und Datenwerten um: proxy.value(atX: location.x, as: String.self) liefert den Wert unter einer Fingerposition. Damit lassen sich eigene Auswahl- und Zoom-Gesten bauen.
Übungen
Übung 1: Temperaturen
Zeig die Temperaturen einer Woche als Liniendiagramm mit Punkten und weicher Kurve. Färbe die Linie mit einem Verlauf von Blau nach Rot.
Übung 2: Schritte mit Ziel
Baue das Schritte-Diagramm mit Ziellinie und färbe Balken über dem Ziel grün. Füge .chartXSelection hinzu, sodass beim Darüberstreichen der genaue Wert erscheint. Darunter steht der Durchschnitt der Woche.
Übung 3: Monatsübersicht
Erzeuge zufällige Ausgaben für die letzten 30 Tage in drei Kategorien. Zeig darüber ein Ringdiagramm der Anteile mit der Summe in der Mitte und darunter ein Balkendiagramm pro Tag, gestapelt nach Kategorie, mit Beschriftung nur jede Woche. Beide Diagramme verwenden dieselben Farben.
Lösung Übung 3Erst selbst probieren!
import SwiftUI
import Charts
struct Spending: Identifiable {
let id = UUID()
let date: Date
let category: String
let amount: Double
}
struct MonthOverview: View {
let categories = ["Essen", "Verkehr", "Freizeit"]
let colors: [String: Color] = ["Essen": .orange, "Verkehr": .blue, "Freizeit": .purple]
let spendings: [Spending] = {
let today = Calendar.current.startOfDay(for: .now)
return (0..<30).flatMap { offset in
["Essen", "Verkehr", "Freizeit"].map { category in
Spending(
date: Calendar.current.date(byAdding: .day, value: -offset, to: today)!,
category: category,
amount: Double.random(in: 0...40)
)
}
}
}()
var total: Double { spendings.reduce(0) { $0 + $1.amount } }
func total(for category: String) -> Double {
spendings.filter { $0.category == category }.reduce(0) { $0 + $1.amount }
}
var body: some View {
ScrollView {
VStack(spacing: 32) {
Chart(categories, id: \.self) { category in
SectorMark(angle: .value("Betrag", total(for: category)),
innerRadius: .ratio(0.6), angularInset: 2)
.foregroundStyle(by: .value("Kategorie", category))
}
.chartBackground { _ in
Text(total, format: .currency(code: "EUR")).font(.title3.bold())
}
.chartForegroundStyleScale(domain: categories, range: categories.map { colors[$0]! })
.frame(height: 220)
Chart(spendings) { spending in
BarMark(x: .value("Tag", spending.date, unit: .day),
y: .value("Betrag", spending.amount))
.foregroundStyle(by: .value("Kategorie", spending.category))
}
.chartXAxis {
AxisMarks(values: .stride(by: .day, count: 7)) { _ in
AxisGridLine()
AxisValueLabel(format: .dateTime.day().month(.abbreviated))
}
}
.chartForegroundStyleScale(domain: categories, range: categories.map { colors[$0]! })
.frame(height: 220)
}
.padding()
}
}
}Beide Diagramme bekommen dieselbe .chartForegroundStyleScale mit derselben Reihenfolge der Kategorien. So hat jede Kategorie überall dieselbe Farbe.
Zusammenfassung
- Swift Charts baut Diagramme deklarativ:
Chart(data) { BarMark(...) }. Vorherimport Charts. - Markierungen:
BarMark,LineMark,PointMark,AreaMark,SectorMark,RuleMark. Sie lassen sich kombinieren. .value("Beschriftung", wert)verbindet Werte mit Namen, auch für VoiceOver..foregroundStyle(by:)färbt nach Kategorie und erzeugt eine Legende,.chartForegroundStyleScalelegt die Farben fest.- Datumswerte mit
unit:, Achsen mit.chartXAxisund.chartYScaleanpassen. RuleMarkund.annotationfür Ziel- und Durchschnittslinien..chartXSelection(value:)für interaktive Auswahl.
Spickzettel
import Charts
Chart(data) { item in
BarMark(x: .value("Tag", item.date, unit: .day),
y: .value("Betrag", item.amount))
.foregroundStyle(by: .value("Kategorie", item.category))
}
.chartForegroundStyleScale(["Essen": Color.orange, "Verkehr": Color.blue])
.chartYScale(domain: 0...500)
.chartXSelection(value: $selected)
.frame(height: 240)
SectorMark(angle: .value("Betrag", x), innerRadius: .ratio(0.6), angularInset: 2)
RuleMark(y: .value("Ziel", 100))
.lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
.annotation(position: .top) { Text("Ziel") }