← All shorts
71iOS 16+2 min read

Swift Charts: Declarative Data Visualisation

Swift Charts builds charts out of SwiftUI-style marks instead of hand-rolled Core Graphics drawing code.

Before Swift Charts, a bar chart meant a GeometryReader, manual scaling math against the tallest data point, and a stack of Rectangle views you positioned and sized by hand.

#The old way

import SwiftUI

struct HandRolledBarChart: View {
    let data: [(day: String, sales: Double)]

    var body: some View {
        GeometryReader { geometry in
            let maxSales = data.map(\.sales).max() ?? 1
            HStack(alignment: .bottom, spacing: 8) {
                ForEach(data, id: \.day) { point in
                    Rectangle()
                        .fill(Color.blue)
                        .frame(height: geometry.size.height * point.sales / maxSales)
                }
            }
        }
    }
}

#The new way

import SwiftUI
import Charts

struct SalesPoint: Identifiable {
    let id = UUID()
    let day: String
    let sales: Double
}

struct SalesChart: View {
    let data: [SalesPoint]

    var body: some View {
        Chart(data) { point in
            BarMark(
                x: .value("Day", point.day),
                y: .value("Sales", point.sales)
            )
            .foregroundStyle(.blue)
        }
        .chartYAxisLabel("Sales ($)")
    }
}

#Why it matters

  • Marks (BarMark, LineMark, PointMark, AreaMark) compose the same way SwiftUI views do — no manual geometry math for bar heights or point positions
  • Axes, legends, and scales are inferred from the data types passed in, and fully overridable when the defaults are wrong
  • Mixing mark types in one Chart — a LineMark trend line over BarMark bars — is just adding another mark, not a separate rendering pass
  • Accessibility (VoiceOver chart descriptions) and Dynamic Type on labels come from the framework, not something built by hand

#Gotcha

Chart re-renders marks from the data array on every state change like any SwiftUI view, so a chart backed by a large, frequently-updating dataset — a live sensor feed, for example — needs the same identity and diffing discipline as a List: give each point a stable Identifiable id, and avoid rebuilding the array from scratch on every tick. If bars aren't animating smoothly across data changes, check whether the array's identity is stable across updates before assuming the chart itself is broken.

swift-chartsiosdata-visualization

Related shorts