Skip to Content
Course content

217: Building Bar and Line Charts with Swift Charts

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Swift Charts is honestly a breath of fresh air. Before this framework, if you wanted a custom chart, you were either wrestling with complex Core Graphics paths or paying for a third-party library. Now, we can describe our data visually using a declarative syntax that feels just like the rest of SwiftUI.

To get a feel for this, let's build a "Weekly Activity Tracker." We want to show a user's step counts for the last seven days. I think a hybrid approach is best here: bars to show the volume of each day, and a line overlaid on top to highlight the trend. It gives the user the best of both worlds.

Structuring the Step Data

First, we need a simple model. I'm going to use a basic struct. Since we're dealing with dates and numbers, a Date and an Int will do the trick. I'll create a small array of mock data so we have something to actually visualize.

struct StepCount: Identifiable {
    let id = UUID()
    let date: Date
    let count: Int
}

let weeklyData: [StepCount] = [
    StepCount(date: Calendar.current.date(byAdding: .day, value: -6, to: Date())!, count: 8000),
    StepCount(date: Calendar.current.date(byAdding: .day, value: -5, to: Date())!, count: 12000),
    StepCount(date: Calendar.current.date(byAdding: .day, value: -4, to: Date())!, count: 7000),
    StepCount(date: Calendar.current.date(byAdding: .day, value: -3, to: Date())!, count: 15000),
    StepCount(date: Calendar.current.date(byAdding: .day, value: -2, to: Date())!, count: 11000),
    StepCount(date: Calendar.current.date(byAdding: .day, value: -1, to: Date())!, count: 9000),
    StepCount(date: Calendar.current.date(byAdding: .day, value: 0, to: Date())!, count: 13000),
]

Visualizing Daily Volume with BarMark

Now for the chart itself. The Chart view acts as the container, and inside it, we use "Marks." To get those daily columns, I'll use BarMark. I'm mapping the date to the x-axis and the count to the y-axis.

Chart(weeklyData) { step in
    BarMark(
        x: .value("Day", step.date, unit: .day),
        y: .value("Steps", step.count)
    )
    .foregroundStyle(.blue.gradient)
}
.frame(height: 300)

I used unit: .day in the x-value. This is a neat feature of Swift Charts—it tells the framework how to group the dates automatically so we don't have to manually format strings for the axis labels.

Adding the Trend Line

Bars are great for volume, but a line helps the eye track the "flow" of the week. I'll add a LineMark right below the BarMark. Because they are in the same Chart block, they share the same coordinate system.

Chart(weeklyData) { step in
    BarMark(
        x: .value("Day", step.date, unit: .day),
        y: .value("Steps", step.count)
    )
    .foregroundStyle(.blue.opacity(0.3))

    LineMark(
        x: .value("Day", step.date, unit: .day),
        y: .value("Steps", step.count)
    )
    .foregroundStyle(.blue)
    .interpolationMethod(.catmullRom)
}

I added .interpolationMethod(.catmullRom) here. By default, lines are jagged (linear). Catmull-Rom smooths the line out, which usually looks more "premium" for fitness apps.

Fixing the Zig-Zag Mess

Here is where I messed up. When I first ran this with a larger, randomized dataset, my line chart looked like a heart monitor during a panic attack—it was zig-zagging back and forth across the screen. I realized I had added some data points out of chronological order.

Swift Charts doesn't automatically sort your data; it draws the line in the order the elements appear in the array. To fix this, I can't just pass the array as-is. I need to ensure it's sorted by date before it hits the Chart view.

// The fix: Sort the data before passing it to the Chart
let sortedData = weeklyData.sorted { $0.date < $1.date }

Chart(sortedData) { step in 
    // ... marks go here
}

Polishing the Axis and Details

To wrap this up, the chart looks a bit naked. I want to add a PointMark so the user can see exactly where the data points sit on the line, and I'll add a chart title and some axis styling.

Chart(sortedData) { step in
    BarMark(
        x: .value("Day", step.date, unit: .day),
        y: .value("Steps", step.count)
    )
    .foregroundStyle(.blue.opacity(0.2))

    LineMark(
        x: .value("Day", step.date, unit: .day),
        y: .value("Steps", step.count)
    )
    .foregroundStyle(.blue)
    .interpolationMethod(.catmullRom)

    PointMark(
        x: .value("Day", step.date, unit: .day),
        y: .value("Steps", step.count)
    )
    .foregroundStyle(.blue)
}
.chartYAxis {
    AxisMarks(position: .leading) { value in
        AxisGridLine()
        AxisTick()
        AxisValueLabel()
    }
}
.frame(height: 300)
.padding()

By moving the Y-axis to the .leading position, it feels more like a professional dashboard. We've now combined three different mark types to create a single, cohesive visualization.




📋 Practical Task

Exercise: Building a Monthly Temperature Range Chart

Your task is to create a chart that visualizes the temperature fluctuations for a city over a 5-day period. Instead of just a single line, you will create a "Range Chart" using AreaMark.

  • Create a TemperatureReading struct with date: Date, lowTemp: Double, and highTemp: Double.
  • Generate an array of 5 days of data.
  • Use an AreaMark to fill the space between the lowTemp and highTemp. Hint: You'll need to provide both a start and end value for the y-axis in the AreaMark.
  • Overlay a LineMark that tracks only the highTemp to show the peak trend.
  • Ensure the data is sorted by date to avoid any rendering glitches.
  • Apply a custom color (like .orange) to the area and line to represent heat.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.