```html SwiftUI: How to Build a Bar Chart (iOS 17+, 2026)

How to implement a bar chart in SwiftUI

iOS 17+ Xcode 16+ Intermediate APIs: Charts / BarMark Updated: May 11, 2026
TL;DR

Import Charts, wrap your data in a Chart view, and use BarMark(x: .value(...), y: .value(...)) for each data point. That's the entire public API — no third-party libraries needed.

import Charts
import SwiftUI

struct SalesBar: View {
    let data = [("Jan", 42), ("Feb", 67), ("Mar", 55), ("Apr", 80)]

    var body: some View {
        Chart(data, id: \.0) { month, value in
            BarMark(
                x: .value("Month", month),
                y: .value("Sales", value)
            )
        }
        .frame(height: 200)
        .padding()
    }
}

Full implementation

The example below builds a polished monthly-sales bar chart with a custom color scheme, rounded bar tops, an annotated selection highlight, and a proper accessibility label on each bar. The @State-driven selection uses the .chartXSelection modifier introduced in iOS 17 — no gesture recognizers needed.

import Charts
import SwiftUI

// MARK: - Model

struct MonthlySale: Identifiable {
    let id = UUID()
    let month: String
    let revenue: Double
}

// MARK: - View

struct SalesBarChartView: View {
    let sales: [MonthlySale]
    @State private var selectedMonth: String?

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Monthly Revenue")
                .font(.title2.bold())
                .padding(.horizontal)

            Chart(sales) { item in
                BarMark(
                    x: .value("Month", item.month),
                    y: .value("Revenue", item.revenue)
                )
                // Dim bars that are not selected
                .foregroundStyle(
                    selectedMonth == nil || selectedMonth == item.month
                        ? Color.accentColor
                        : Color.accentColor.opacity(0.3)
                )
                .cornerRadius(6)
                // Accessible label for each bar
                .accessibilityLabel("\(item.month)")
                .accessibilityValue("$\(Int(item.revenue))k revenue")

                // Annotation on the selected bar
                if selectedMonth == item.month {
                    RuleMark(x: .value("Month", item.month))
                        .foregroundStyle(Color.accentColor.opacity(0.2))
                        .lineStyle(StrokeStyle(lineWidth: 2, dash: [5]))
                        .annotation(position: .top, alignment: .center) {
                            Text("$\(Int(item.revenue))k")
                                .font(.caption.bold())
                                .padding(6)
                                .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 6))
                        }
                }
            }
            // Tap/drag to select a bar
            .chartXSelection(value: $selectedMonth)
            // Style the axes
            .chartXAxis {
                AxisMarks(values: .automatic) { _ in
                    AxisValueLabel()
                        .font(.caption)
                }
            }
            .chartYAxis {
                AxisMarks(preset: .automatic, position: .leading) { value in
                    AxisGridLine()
                    AxisValueLabel {
                        if let v = value.as(Double.self) {
                            Text("$\(Int(v))k")
                                .font(.caption)
                        }
                    }
                }
            }
            .frame(height: 280)
            .padding(.horizontal)
        }
    }
}

// MARK: - Preview

#Preview {
    SalesBarChartView(sales: [
        MonthlySale(month: "Jan", revenue: 42),
        MonthlySale(month: "Feb", revenue: 67),
        MonthlySale(month: "Mar", revenue: 55),
        MonthlySale(month: "Apr", revenue: 80),
        MonthlySale(month: "May", revenue: 73),
        MonthlySale(month: "Jun", revenue: 91),
    ])
    .padding(.vertical)
}

How it works

  1. Chart container + BarMark. Chart(sales) iterates your data collection and passes each element into the closure. BarMark(x: .value("Month", item.month), y: .value("Revenue", item.revenue)) declares one vertical bar per element — the string on the x-axis becomes a categorical scale automatically.
  2. Dynamic foreground style. The .foregroundStyle(...) modifier compares selectedMonth against the current item's month. Unselected bars render at 30 % opacity, giving immediate visual feedback without custom drawing code.
  3. chartXSelection modifier (iOS 17+). .chartXSelection(value: $selectedMonth) binds the chart's touch-drag interaction to your @State string. The framework handles hit-testing and snapping to the nearest bar automatically.
  4. RuleMark annotation. When selectedMonth matches the current item, a RuleMark is overlaid at the same x-position, and a .annotation callout floats above it with the formatted revenue value — all declarative, no geometry readers.
  5. Accessibility labels. Each BarMark has .accessibilityLabel and .accessibilityValue applied, so VoiceOver reads "January, $42k revenue" rather than a raw number, satisfying WCAG 1.1.1 non-text-content guidance.

Variants

Grouped bar chart (multiple series)

Add a foregroundStyle(by:) modifier to split each x-category into side-by-side bars — one per series. The Chart legend is rendered automatically.

struct GroupedSale: Identifiable {
    let id = UUID()
    let month: String
    let category: String
    let revenue: Double
}

struct GroupedBarChart: View {
    let data: [GroupedSale]

    var body: some View {
        Chart(data) { item in
            BarMark(
                x: .value("Month", item.month),
                y: .value("Revenue", item.revenue)
            )
            .foregroundStyle(by: .value("Category", item.category))
            .cornerRadius(4)
        }
        .chartLegend(position: .bottom, alignment: .center)
        .frame(height: 260)
        .padding()
    }
}

#Preview {
    GroupedBarChart(data: [
        GroupedSale(month: "Jan", category: "iOS", revenue: 42),
        GroupedSale(month: "Jan", category: "macOS", revenue: 18),
        GroupedSale(month: "Feb", category: "iOS", revenue: 67),
        GroupedSale(month: "Feb", category: "macOS", revenue: 29),
    ])
}

Horizontal bar chart

Swap the roles of x and y in BarMark — pass the categorical label to y and the numeric value to x. SwiftUI Charts automatically reorients the axis scales and grid lines. This pattern works well for ranked lists (top countries, top screens) where labels are long strings that would overlap on a vertical x-axis.

BarMark(
    x: .value("Revenue", item.revenue),  // numeric → horizontal
    y: .value("Month",   item.month)     // categorical → vertical
)
.cornerRadius(4)

Common pitfalls

Prompt this with Claude Code

When using Soarias or Claude Code directly to implement this:

Implement a bar chart in SwiftUI for iOS 17+.
Use Charts and BarMark.
Support tap-to-select with a callout annotation using chartXSelection.
Make it accessible (VoiceOver labels with formatted currency values).
Add a #Preview with realistic monthly-revenue sample data.

Drop this prompt into Soarias during the Build phase to generate and instantly preview the component in the live simulator — no boilerplate needed before your first compile.

Related

FAQ

Does this work on iOS 16?

The base Chart + BarMark API is available from iOS 16.0, but interactive features like .chartXSelection and certain annotation positions require iOS 17. If your deployment target is iOS 16, wrap those modifiers in an if #available(iOS 17, *) check or use a separate view. For new apps in 2026, targeting iOS 17+ is strongly recommended — Apple's own analytics show iOS 16 below 5 % global share.

How do I animate bars when the data changes?

Wrap your data mutation in a withAnimation(.easeInOut) block. The Charts framework automatically interpolates bar heights between old and new values, producing a smooth grow/shrink transition. For initial appearance, apply .chartPlotStyle { $0.frame(height: 260) } and drive your data update inside .onAppear — Charts will animate from zero on first render.

What's the UIKit equivalent?

UIKit has no native bar chart API. In UIKit you would typically use a third-party library such as Charts (DanielGindi/Charts), Core Graphics with hand-drawn UIBezierPath rectangles, or wrap a SwiftUI bar chart view inside a UIHostingController. The SwiftUI Charts framework is the official Apple solution and is the recommended path for all new projects on iOS 16+.

Last reviewed: 2026-05-11 by the Soarias team.

```