SwiftUI Charts Reference
SwiftUI Charts Reference
Section titled “SwiftUI Charts Reference”Table of Contents
Section titled “Table of Contents”- Overview
- Availability
- Core APIs
- Chart Types
- Axis Tweaks
- Selection APIs
- Annotations
- ChartProxy and Custom Touch Handling
- Modifier Scope
- Styling and Visual Channels
- Composing Multiple Marks
- Animating Chart Data
- Best Practices
Overview
Section titled “Overview”Swift Charts is Apple’s native charting framework for SwiftUI. Use Chart with one or more marks to build bar, line, area, point, rule, rectangle, and sector charts. This reference covers the standard 2D chart APIs, axis customization, built-in selection APIs, annotations, and custom touch handling.
Availability
Section titled “Availability”Base Chart, custom axes, scales, and most marks require iOS 16 or later.
BarMark,LineMark,AreaMark,PointMark,RectangleMark, andRuleMarkare available on iOS 16+SectorMark, built-in selection, and scrollable chart axes require iOS 17+- Data-driven plot types such as
BarPlotandLinePlotrequire iOS 18+ - Chart3D and Z-axis APIs exist on iOS 26+; this reference is primarily about 2D
Chart, with a dedicated Chart3D section below
if #available(iOS 17, *) { // Selection, SectorMark, scrollable axes} else { // Base Chart, axes, scales, and core marks}Core APIs
Section titled “Core APIs”Import the Framework
Section titled “Import the Framework”Always check that the file imports Charts before using Chart, Chart3D, BarMark, SectorMark, or ChartProxy.
import SwiftUIimport ChartsIf chart types are unresolved, the first thing to verify is that Charts is imported in that file.
Chart Container
Section titled “Chart Container”Chart is the root view. Add one or more marks inside it.
Chart(sales) { item in BarMark( x: .value("Month", item.month), y: .value("Revenue", item.revenue) )}Data Models Should Be Identifiable
Section titled “Data Models Should Be Identifiable”Prefer Identifiable models for chart data so identity stays stable as data changes.
struct SalesPoint: Identifiable { let id: UUID let month: String let revenue: Double}If your model cannot conform to Identifiable, provide an explicit id key path:
Chart(sales, id: \.month) { item in BarMark( x: .value("Month", item.month), y: .value("Revenue", item.revenue) )}Plottable Values
Section titled “Plottable Values”Use .value(_, _) to describe what each axis value means. Those labels are reused by axes, legends, and accessibility.
LineMark( x: .value("Day", entry.date), y: .value("Steps", entry.count))Chart Types
Section titled “Chart Types”BarMark
Section titled “BarMark”BarMark( x: .value("Product", product.name), y: .value("Units", product.units))Stacking via MarkStackingMethod: .standard, .normalized, .center, .unstacked.
LineMark
Section titled “LineMark”LineMark( x: .value("Day", day.date), y: .value("Steps", day.count)).interpolationMethod(.monotone)Interpolation methods: .linear, .monotone, .cardinal, .catmullRom, .stepStart, .stepCenter, .stepEnd. Cardinal and Catmull-Rom accept optional tension/alpha parameters.
AreaMark
Section titled “AreaMark”AreaMark( x: .value("Hour", sample.hour), y: .value("Temperature", sample.value), stacking: .unstacked)Ranged areas use yStart/yEnd for bands like min/max or confidence intervals:
AreaMark( x: .value("Day", sample.day), yStart: .value("Low", sample.low), yEnd: .value("High", sample.high))PointMark
Section titled “PointMark”PointMark( x: .value("Time", measurement.time), y: .value("Value", measurement.value))RectangleMark
Section titled “RectangleMark”RectangleMark( xStart: .value("Start Day", cell.startDay), xEnd: .value("End Day", cell.endDay), yStart: .value("Low", cell.low), yEnd: .value("High", cell.high))RuleMark
Section titled “RuleMark”RuleMark(y: .value("Goal", 10_000)) .foregroundStyle(.red)SectorMark
Section titled “SectorMark”Use SectorMark for pie and donut-style charts. SectorMark requires iOS 17 or later.
Chart(expenses) { expense in SectorMark( angle: .value("Amount", expense.amount), innerRadius: .ratio(0.6), angularInset: 2 ) .foregroundStyle(by: .value("Category", expense.category))}Use innerRadius to turn a pie chart into a donut chart, and angularInset to separate slices visually.
Plot Types (iOS 18+)
Section titled “Plot Types (iOS 18+)”iOS 18 adds data-driven plot wrappers: AreaPlot, BarPlot, LinePlot, PointPlot, RectanglePlot, RulePlot, and SectorPlot.
LinePlot and AreaPlot also accept function closures for plotting mathematical functions without discrete data:
if #available(iOS 18, *) { Chart { LinePlot(x: "x", y: "sin(x)") { x in sin(x) } } .chartXScale(domain: -Double.pi ... Double.pi) .chartYScale(domain: -1.5 ... 1.5)}Use plot types when you want a data-first API surface or need function plotting. The underlying chart families stay the same.
Chart3D (iOS 26+)
Section titled “Chart3D (iOS 26+)”Chart3D is a separate API for 3D chart content. It supports 3D PointMark, RectangleMark, RuleMark, and SurfacePlot.
if #available(iOS 26, *) { Chart3D(points) { point in PointMark( x: .value("X", point.x), y: .value("Y", point.y), z: .value("Z", point.z) ) } .chart3DPose(.front) .chart3DCameraProjection(.perspective)}SurfacePlot visualizes mathematical surfaces by evaluating a two-variable function:
if #available(iOS 26, *) { Chart3D { SurfacePlot(x: "x", y: "height", z: "z") { x, z in sin(x) * cos(z) } } .chartXScale(domain: -Double.pi ... Double.pi) .chartZScale(domain: -Double.pi ... Double.pi)}Camera and pose configuration:
- Projection:
.chart3DCameraProjection(.orthographic)(default, precise measurements) or.perspective(depth effect) - Pose presets:
.chart3DPose(.default),.front,.back,.left,.right - Custom pose:
.chart3DPose(azimuth: .degrees(45), inclination: .degrees(30)) - On visionOS, Chart3D supports natural 3D interaction gestures for rotation and exploration
Always gate Chart3D with #available(iOS 26, *) — it is not available on earlier OS versions.
Axis Tweaks
Section titled “Axis Tweaks”Axis Visibility and Labels
Section titled “Axis Visibility and Labels”Use chartXAxis, chartYAxis, chartXAxisLabel, and chartYAxisLabel on the Chart container.
Axis visibility supports .automatic, .visible, and .hidden.
Chart(data) { item in BarMark( x: .value("Month", item.month), y: .value("Revenue", item.revenue) )}.chartXAxis(.visible).chartYAxis(.hidden).chartXAxisLabel("Month").chartYAxisLabel("Revenue")Custom Axis Marks
Section titled “Custom Axis Marks”Use AxisMarks to control tick placement, labels, and grid lines.
Chart(steps) { day in LineMark( x: .value("Day", day.date), y: .value("Steps", day.count) )}.chartXAxis { AxisMarks( preset: .aligned, position: .bottom, values: .stride(by: .day) ) { AxisGridLine() AxisTick(length: .label) AxisValueLabel(format: .dateTime.weekday(.abbreviated)) }}Useful AxisMarks inputs:
preset:.automatic,.extended,.aligned,.insetposition:.automatic,.leading,.trailing,.top,.bottomvalues:.automatic,.automatic(desiredCount:),.stride(by:),.stride(by:count:), or an explicit array
Axis Components
Section titled “Axis Components”Within AxisMarks, combine the built-in axis components as needed:
AxisGridLine()AxisTick()AxisValueLabel()AxisValueLabel can be tuned for dense axes:
AxisValueLabel( collisionResolution: .greedy(minimumSpacing: 8), orientation: .vertical)Label orientations: .automatic, .horizontal, .vertical, .verticalReversed.
Collision strategies: .automatic, .greedy, .greedy(priority:minimumSpacing:), .truncate, .disabled.
Axis Domains and Plot Area Tweaks
Section titled “Axis Domains and Plot Area Tweaks”Use scales when you need explicit axis domains or plot area control.
Chart(data) { item in LineMark( x: .value("Index", item.index), y: .value("Score", item.score) )}.chartXScale(domain: 0...30).chartYScale(domain: 0...100).chartPlotStyle { plotArea in plotArea .background(.gray.opacity(0.08))}You can set one axis domain without forcing the other:
.chartXScale(domain: startDate...endDate)Scrollable Axes (iOS 17+)
Section titled “Scrollable Axes (iOS 17+)”For larger datasets, make the plot area scroll and control the visible domain.
@State private var scrollX = 7
Chart(data) { item in BarMark( x: .value("Day", item.day), y: .value("Value", item.value) )}.chartScrollableAxes(.horizontal).chartXVisibleDomain(length: 7).chartScrollPosition(x: $scrollX)Selection APIs
Section titled “Selection APIs”Single-Value Selection
Section titled “Single-Value Selection”Use chartXSelection(value:) or chartYSelection(value:) for one selected value.
@State private var selectedDate: Date?
Chart(steps) { day in LineMark(x: .value("Day", day.date), y: .value("Steps", day.count))
if let selectedDate { RuleMark(x: .value("Selected Day", selectedDate)) .foregroundStyle(.secondary) }}.chartXSelection(value: $selectedDate)Range Selection
Section titled “Range Selection”Use chartXSelection(range:) or chartYSelection(range:) for a dragged range. Bind to a ClosedRange whose bound type matches the plotted axis value.
@State private var selectedWeeks: ClosedRange<Int>?
Chart(weeks) { week in BarMark(x: .value("Week", week.index), y: .value("Revenue", week.revenue))}.chartXSelection(range: $selectedWeeks)Choosing Single vs Range
Section titled “Choosing Single vs Range”- Use
value:bindings when only one point or axis value should be selected. - Use
range:bindings when users should brush a span (for zoom windows, comparisons, or grouped summaries).
Angle Selection
Section titled “Angle Selection”Use chartAngleSelection(value:) with SectorMark charts. No built-in range overload for angle selection.
@State private var selectedAmount: Double?
Chart(expenses) { expense in SectorMark(angle: .value("Amount", expense.amount)) .foregroundStyle(by: .value("Category", expense.category))}.chartAngleSelection(value: $selectedAmount)Important: Selection bindings return the plottable axis value, not the full data element. Map back to your model if you need the selected record.
Annotations
Section titled “Annotations”Use annotation(position:) on a mark when you need labels, callouts, or highlighted values attached to the plotted content.
BarMark( x: .value("Month", item.month), y: .value("Revenue", item.revenue)).annotation(position: .top) { Text(item.revenue.formatted())}This is useful for selected values, thresholds, summaries, and direct labeling. Common positions include .overlay, .top, .bottom, .leading, and .trailing.
ChartProxy and Custom Touch Handling
Section titled “ChartProxy and Custom Touch Handling”Use chartOverlay/chartBackground (iOS 16+) or chartGesture (iOS 17+) with ChartProxy when built-in selection modifiers are not enough.
.chartOverlay { proxy in GeometryReader { geometry in Rectangle().fill(.clear).contentShape(Rectangle()) .gesture( DragGesture(minimumDistance: 0) .onChanged { value in guard let plotFrame = proxy.plotFrame else { return } // iOS 16: use proxy.plotAreaFrame let frame = geometry[plotFrame] let x = value.location.x - frame.origin.x guard x >= 0, x <= frame.size.width else { return } selectedDate = proxy.value(atX: x, as: Date.self) } .onEnded { _ in selectedDate = nil } ) }}Use proxy.plotFrame (iOS 17+) or proxy.plotAreaFrame (iOS 16) to get the plot area anchor.
ChartProxy gives you lower-level access to:
value(atX:as:),value(atY:as:), andvalue(at:as:)for converting gesture coordinates into chart valuesposition(forX:),position(forY:), andposition(for:)for placing custom overlays or indicatorsselectXValue(at:),selectYValue(at:),selectXRange(from:to:), andselectYRange(from:to:)for driving built-in selection from custom gesturesplotFrame(iOS 17+) orplotAreaFrame(iOS 16) withplotSizefor converting between gesture coordinates and the plot area
select* ChartProxy selection methods and chartGesture are available on iOS 17+.
Modifier Scope
Section titled “Modifier Scope”Apply chart-wide modifiers to the Chart container and mark-specific modifiers to the individual mark.
Chart(data) { item in LineMark( x: .value("Day", item.date), y: .value("Value", item.value) ) .interpolationMethod(.monotone) // Mark-level modifier}.chartXAxis { AxisMarks() } // Chart-level modifier.chartYScale(domain: 0...100) // Chart-level modifier.chartPlotStyle { $0.background(.thinMaterial) }Styling and Visual Channels
Section titled “Styling and Visual Channels”Categorical Coloring
Section titled “Categorical Coloring”Use foregroundStyle(by: .value(...)) to color marks by a data property. Swift Charts generates a legend automatically.
Chart(sales) { item in BarMark( x: .value("Month", item.month), y: .value("Revenue", item.revenue) ) .foregroundStyle(by: .value("Region", item.region))}Avoid applying .foregroundStyle(.red) per mark for categorical data — this suppresses the automatic legend and breaks accessibility.
Custom Color Scales
Section titled “Custom Color Scales”Use chartForegroundStyleScale to control the mapping from data values to colors.
.chartForegroundStyleScale([ "North": .blue, "South": .orange, "East": .green])For dynamic data where not all series appear at every point, use the mapping overload:
.chartForegroundStyleScale(domain: regions, mapping: { region in colorForRegion(region)})Symbol and Size Channels
Section titled “Symbol and Size Channels”Use symbol(by:) and symbolSize(by:) to encode additional data dimensions on PointMark and LineMark.
Chart(measurements) { item in PointMark( x: .value("Time", item.time), y: .value("Value", item.value) ) .foregroundStyle(by: .value("Category", item.category)) .symbol(by: .value("Category", item.category)) .symbolSize(by: .value("Weight", item.weight))}Legend Control
Section titled “Legend Control”.chartLegend(.visible).chartLegend(.hidden).chartLegend(position: .bottom, alignment: .center)Composing Multiple Marks
Section titled “Composing Multiple Marks”Combine different mark types inside the same Chart closure:
// Line with pointsLineMark(x: .value("Day", day.date), y: .value("Steps", day.count)) .interpolationMethod(.monotone)PointMark(x: .value("Day", day.date), y: .value("Steps", day.count))
// Bars with threshold lineBarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue))RuleMark(y: .value("Target", 10_000)) .foregroundStyle(.red) .lineStyle(StrokeStyle(dash: [5, 3]))Animating Chart Data
Section titled “Animating Chart Data”Chart marks animate automatically when data identity is stable and changes are wrapped in an animation.
withAnimation(.easeInOut) { chartData = updatedData}Always use Identifiable models (or explicit id:) so Swift Charts can match old and new data points and animate transitions between them.
Best Practices
Section titled “Best Practices”- Use semantic
.value(_, _)labels so axes and accessibility read clearly - Prefer
Identifiablemodels (or explicitid:) for stable chart data identity - Use
foregroundStyle(by:)for categorical series to get automatic legends and accessibility - Use
RuleMarkfor goals, thresholds, and selected-value indicators - Use explicit
AxisMarks(values:)when automatic tick generation gets crowded - Use
chartXScaleandchartYScalewhen you need stable visual comparisons - Use
chartXSelection(range:)orchartYSelection(range:)for brushed selection - Gate iOS 17+ APIs such as
SectorMarkand selection with#available
- Put chart-wide modifiers such as
chartXAxisorchartXSelectionon individual marks - Apply manual
.foregroundStyle(.color)per mark for categorical data — useforegroundStyle(by:)instead - Rely on unstable identities when chart data can be inserted, removed, or reordered
- Use string values for naturally numeric or date-based axes unless you want categorical behavior
- Stack unrelated series by default just because
BarMarkandAreaMarkallow it - Force every tick label to display when collision handling or stride values would be clearer
- Assume selection returns a model object; it only returns the plottable axis value
- Forget that range selection is available only for X and Y axes, not angle selection
For chart accessibility (VoiceOver, Audio Graph, AXChartDescriptorRepresentable), fallback strategies, WWDC sessions, and a full summary checklist, see charts-accessibility.md.