- Published on
- 6 min read Intermediate
> Getting Started with Swift Charts
// What_You_Will_Learn
- Create bar, line, and area charts with Swift Charts
- Customize chart styling with foreground colors and annotations
- Add interactivity with chart selection gestures
Apple introduced Swift Charts at WWDC 2022, giving SwiftUI developers a declarative way to build data visualizations. The framework follows SwiftUI's compositional approach, letting you construct charts from simple building blocks called marks.
Your First Chart
Creating a chart starts with the Chart view and one or more marks. Here's a simple bar chart:
import SwiftUI
import Charts
struct MonthlySales: Identifiable {
let id = UUID()
let month: String
let revenue: Double
}
struct SalesChartView: View {
let data: [MonthlySales] = [
MonthlySales(month: "Jan", revenue: 5000),
MonthlySales(month: "Feb", revenue: 8000),
MonthlySales(month: "Mar", revenue: 7500),
MonthlySales(month: "Apr", revenue: 12000),
MonthlySales(month: "May", revenue: 9500),
MonthlySales(month: "Jun", revenue: 11000)
]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
}
.frame(height: 300)
.padding()
}
}
The Chart view takes a collection and a closure that returns marks for each data point. BarMark creates rectangular bars, with x and y defining the axes. The .value() function labels each axis for accessibility and tooltips.
Types of Marks
Swift Charts provides several mark types for different visualizations:
struct MultipleMarksView: View {
let data: [MonthlySales] = [
MonthlySales(month: "Jan", revenue: 5000),
MonthlySales(month: "Feb", revenue: 8000),
MonthlySales(month: "Mar", revenue: 7500),
MonthlySales(month: "Apr", revenue: 12000)
]
var body: some View {
VStack(spacing: 40) {
Chart(data) { item in
LineMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
}
.frame(height: 150)
Chart(data) { item in
PointMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
}
.frame(height: 150)
Chart(data) { item in
AreaMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
}
.frame(height: 150)
}
.padding()
}
}
LineMark connects points with lines, PointMark shows individual dots, and AreaMark fills the area under the line. You can combine multiple mark types in the same chart for richer visualizations.
Combining Marks
Layering marks creates more informative charts. Here's a line chart with points highlighted:
struct CombinedMarksView: View {
let data: [MonthlySales] = [
MonthlySales(month: "Jan", revenue: 5000),
MonthlySales(month: "Feb", revenue: 8000),
MonthlySales(month: "Mar", revenue: 7500),
MonthlySales(month: "Apr", revenue: 12000)
]
var body: some View {
Chart(data) { item in
LineMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
.foregroundStyle(.blue)
PointMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
.foregroundStyle(.blue)
.symbolSize(100)
}
.frame(height: 300)
.padding()
}
}
Working with Multiple Series
Charts often need to display multiple data series. Use foregroundStyle(by:) to differentiate them:
struct SalesByCategory: Identifiable {
let id = UUID()
let month: String
let category: String
let revenue: Double
}
struct MultiSeriesChartView: View {
let data: [SalesByCategory] = [
SalesByCategory(month: "Jan", category: "Electronics", revenue: 5000),
SalesByCategory(month: "Jan", category: "Clothing", revenue: 3000),
SalesByCategory(month: "Feb", category: "Electronics", revenue: 8000),
SalesByCategory(month: "Feb", category: "Clothing", revenue: 4500),
SalesByCategory(month: "Mar", category: "Electronics", revenue: 7500),
SalesByCategory(month: "Mar", category: "Clothing", revenue: 5200),
SalesByCategory(month: "Apr", category: "Electronics", revenue: 12000),
SalesByCategory(month: "Apr", category: "Clothing", revenue: 6800)
]
var body: some View {
Chart(data) { item in
LineMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
.foregroundStyle(by: .value("Category", item.category))
.symbol(by: .value("Category", item.category))
}
.frame(height: 300)
.padding()
}
}
Swift Charts automatically generates a legend and assigns distinct colors to each category.
Customizing Appearance
You can style charts with familiar SwiftUI modifiers:
struct StyledChartView: View {
let data: [MonthlySales] = [
MonthlySales(month: "Jan", revenue: 5000),
MonthlySales(month: "Feb", revenue: 8000),
MonthlySales(month: "Mar", revenue: 7500),
MonthlySales(month: "Apr", revenue: 12000)
]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
.foregroundStyle(
.linearGradient(
colors: [.blue, .purple],
startPoint: .bottom,
endPoint: .top
)
)
.cornerRadius(8)
}
.chartYAxis {
AxisMarks(position: .leading)
}
.chartXAxis {
AxisMarks { value in
AxisValueLabel()
.font(.caption)
}
}
.frame(height: 300)
.padding()
}
}
The chartYAxis and chartXAxis modifiers give you control over axis appearance, positioning, and labels.
Date-Based Charts
For time series data, use Date values on the x-axis:
struct DailyMetric: Identifiable {
let id = UUID()
let date: Date
let value: Double
}
struct TimeSeriesChartView: View {
let data: [DailyMetric]
init() {
let calendar = Calendar.current
let today = Date()
data = (0..<14).map { dayOffset in
let date = calendar.date(byAdding: .day, value: -dayOffset, to: today)!
let value = Double.random(in: 100...500)
return DailyMetric(date: date, value: value)
}.reversed()
}
var body: some View {
Chart(data) { item in
LineMark(
x: .value("Date", item.date, unit: .day),
y: .value("Value", item.value)
)
.interpolationMethod(.catmullRom)
AreaMark(
x: .value("Date", item.date, unit: .day),
y: .value("Value", item.value)
)
.foregroundStyle(.blue.opacity(0.1))
.interpolationMethod(.catmullRom)
}
.chartXAxis {
AxisMarks(values: .stride(by: .day, count: 3)) { value in
AxisGridLine()
AxisValueLabel(format: .dateTime.month().day())
}
}
.frame(height: 300)
.padding()
}
}
The unit: .day parameter tells the chart to treat dates as discrete days. The interpolationMethod smooths the line between points.
Adding Interactivity
Swift Charts supports selection through the chartOverlay or chartGesture modifiers:
struct InteractiveChartView: View {
let data: [MonthlySales] = [
MonthlySales(month: "Jan", revenue: 5000),
MonthlySales(month: "Feb", revenue: 8000),
MonthlySales(month: "Mar", revenue: 7500),
MonthlySales(month: "Apr", revenue: 12000),
MonthlySales(month: "May", revenue: 9500),
MonthlySales(month: "Jun", revenue: 11000)
]
@State private var selectedMonth: String?
var body: some View {
VStack {
Chart(data) { item in
BarMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
.foregroundStyle(selectedMonth == item.month ? .blue : .gray)
}
.chartXSelection(value: $selectedMonth)
.frame(height: 300)
if let month = selectedMonth,
let item = data.first(where: { $0.month == month }) {
Text("\(month): $\(Int(item.revenue))")
.font(.headline)
.padding()
}
}
.padding()
}
}
The chartXSelection modifier binds a selection state to tap or drag gestures on the chart.
Rule Marks for Reference Lines
Use RuleMark to add reference lines like averages or thresholds:
struct ChartWithReferenceLineView: View {
let data: [MonthlySales] = [
MonthlySales(month: "Jan", revenue: 5000),
MonthlySales(month: "Feb", revenue: 8000),
MonthlySales(month: "Mar", revenue: 7500),
MonthlySales(month: "Apr", revenue: 12000),
MonthlySales(month: "May", revenue: 9500),
MonthlySales(month: "Jun", revenue: 11000)
]
var average: Double {
data.map(\.revenue).reduce(0, +) / Double(data.count)
}
var body: some View {
Chart {
ForEach(data) { item in
BarMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
.foregroundStyle(.blue)
}
RuleMark(y: .value("Average", average))
.foregroundStyle(.orange)
.lineStyle(StrokeStyle(lineWidth: 2, dash: [5, 5]))
.annotation(position: .top, alignment: .leading) {
Text("Avg: $\(Int(average))")
.font(.caption)
.foregroundStyle(.orange)
}
}
.frame(height: 300)
.padding()
}
}
Accessibility
Swift Charts automatically provides VoiceOver support through the labels you pass to .value(). You can enhance this with the accessibilityLabel modifier:
struct AccessibleChartView: View {
let data: [MonthlySales] = [
MonthlySales(month: "January", revenue: 5000),
MonthlySales(month: "February", revenue: 8000),
MonthlySales(month: "March", revenue: 7500)
]
var body: some View {
Chart(data) { item in
BarMark(
x: .value("Month", item.month),
y: .value("Revenue", item.revenue)
)
.accessibilityLabel("\(item.month)")
.accessibilityValue("$\(Int(item.revenue)) in revenue")
}
.frame(height: 300)
.padding()
}
}
VoiceOver users can swipe through each bar and hear the month and revenue values.
Swift Charts handles most of the complexity of data visualization for you. Define your data, choose appropriate marks, and let the framework manage scales, legends, and layout. For more advanced customizations like custom scales, annotations, or animations, check Apple's Swift Charts documentation.
Sample Project
Want to see this code in action? Check out the complete sample project on GitHub:
The repository includes a working Xcode project with all the examples from this article, plus unit tests you can run to verify the behavior.
// Continue_Learning
Deep Linking and Universal Links Setup in iOS
How to set up custom URL schemes and Universal Links in iOS, handle incoming URLs in SwiftUI, and route users to the right screen.
Scheduling Local Notifications with UNUserNotificationCenter
A practical guide to scheduling and handling local notifications in iOS using UNUserNotificationCenter, from permission requests to actionable notification categories.
Building Interactive Glass Controls in SwiftUI
Make your glass elements respond to touch with scaling, shimmer effects, and touch-point illumination using the interactive() modifier and glass button styles.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.