Swift Charts w SwiftUI — kompletny przewodnik po wykresach w iOS 26

Kompletny przewodnik po Swift Charts w SwiftUI na iOS 26: markery, interaktywna selekcja, przewijanie, dostępność i praktyczny dashboard z gotowym kodem.

Swift Charts iOS 26: Przewodnik SwiftUI

Zaktualizowano: 28 sierpnia 2026

Swift Charts to natywny, deklaratywny framework Apple do budowy wykresów w SwiftUI. Dodajesz wizualizację danych tak samo jak zwykły View, opisując „co ma się pojawić" zamiast rysowania piksel po pikselu. W iOS 26 framework doczekał się głębszej integracji z Liquid Glass, zestawu nowych markerów oraz uproszczonego API do selekcji punktów i przewijania. Ten przewodnik prowadzi Cię od pierwszego LineMark po interaktywny dashboard produkcyjny, z kompletnymi, uruchamialnymi listingami.

  • Swift Charts jest dostępny od iOS 16, natomiast w iOS 26 znacznie rozszerzono interaktywność (chartXSelection, chartScrollableAxes, wieloosiowe zaznaczenia).
  • Framework operuje na markerach: LineMark, BarMark, PointMark, AreaMark, RuleMark i SectorMark. Możesz je swobodnie mieszać w jednym wykresie.
  • Selekcja punktów w iOS 26 działa przez binding, więc jedna linia kodu zamienia statyczny wykres w tooltip z adnotacją.
  • Dla dużych zbiorów danych używaj chartScrollableAxes z chartXVisibleDomain. SwiftUI renderuje wtedy tylko widoczny wycinek.
  • Dostępność (VoiceOver, Audio Graphs) uzyskujesz przez accessibilityLabel i accessibilityValue na każdym markerze. Nie zapominaj o niej w produkcji.
  • W iOS 26 wykresy dziedziczą materiały Liquid Glass, więc dobrze komponują się z resztą UI bez ręcznego stylowania tła.

Czym jest Swift Charts i kiedy warto go używać?

Swift Charts został ogłoszony na WWDC 2022 razem z iOS 16 i od początku pomyślany był jako natywny odpowiednik zewnętrznych bibliotek typu Charts (dawnej ios-charts) czy DGCharts. Piszę tutoriale w Swifcie od czasów Objective-C i szczerze, do dziś pamiętam, jak żmudne bywało rysowanie wykresów przez Core Graphics albo klejenie API z bibliotek open source. Swift Charts zamyka tę erę. Framework jest deklaratywny, w pełni zintegrowany z @Observable, respektuje Dynamic Type, obsługuje Dark Mode i od iOS 17 potrafi generować Audio Graphs dla VoiceOver.

Kiedy sięgnąć po Swift Charts? W praktyce zawsze, gdy Twoja aplikacja pokazuje więcej niż jedną liczbę: analytics dashboardy, HealthKit, monitor wydajności, wizualizacja SwiftData, panele finansowe, statystyki gier w SpriteKit. Jeżeli wymagasz wsparcia dla iOS 15 lub starszych, framework nie zadziała. Pozostaje wtedy DGCharts lub SwiftUI Canvas. Dla nowych projektów, w których minimum to iOS 17 lub 26, wybór jest jednoznaczny.

W iOS 26 dochodzi jeszcze jedno: Liquid Glass w SwiftUI automatycznie przenosi się na tło Chart, więc wykresy embeddowane w karty i sheety wyglądają spójnie bez ręcznego stylowania. To odróżnia je od bibliotek trzecich, które zwykle mają własną estetykę i wymagają nadpisywania kolorów.

Twój pierwszy wykres w SwiftUI

Najkrótszy działający wykres w Swift Charts to trzy linie kodu wewnątrz ForEach otoczonego widokiem Chart. Model musi być Identifiable lub mieć jawny id:. Swift Charts nie wymaga @Observable, ale idealnie się z nim komponuje, kiedy chcesz odświeżać dane w reakcji na akcje użytkownika. Poniżej minimalny, kompletny przykład wykresu liniowego z dziennymi krokami z HealthKit.

import SwiftUI
import Charts

struct DailySteps: Identifiable {
    let id = UUID()
    let day: Date
    let steps: Int
}

struct StepsChart: View {
    let data: [DailySteps]

    var body: some View {
        Chart(data) { entry in
            LineMark(
                x: .value("Dzień", entry.day, unit: .day),
                y: .value("Kroki", entry.steps)
            )
            .interpolationMethod(.catmullRom)
        }
        .frame(height: 220)
        .padding()
    }
}

PlottableValue (x: .value(...)) opisuje nie tylko wartość, ale też etykietę osi. Dzięki temu Swift Charts sam generuje sensowne osie i legendę. Jeżeli chcesz płynniejszą linię, ustaw interpolationMethod na .catmullRom; do wykresów finansowych bardziej pasuje .stepStart. Cały widok ma wysokość 220 pt, ale szerokość dziedziczy po rodzicu, więc automatycznie wypełni NavigationStack, Sheet czy widżet.

Rodzaje marków: LineMark, BarMark, PointMark i inne

Każdy typ wykresu w Swift Charts to inny marker. Wszystkie można łączyć w jednym Chart, co pozwala np. nałożyć średnią kroczącą (LineMark) na słupki (BarMark) i dorzucić linię progu (RuleMark). Poniżej lista głównych markerów, których używam praktycznie codziennie.

  • LineMark: wykres liniowy, dobry dla trendów czasowych.
  • BarMark: słupki; wspiera stackowanie przez foregroundStyle(by:).
  • PointMark: scatter plot; z symbolSize daje bubble chart.
  • AreaMark: obszar pod krzywą; świetny w kombinacji z LineMark.
  • RuleMark: pionowa lub pozioma linia progu, cel, uśrednienie.
  • SectorMark: wycinki (donut, pie); wprowadzone w iOS 17.
  • RectangleMark: heatmapa i kalendarze aktywności.

Poniżej wykres kombinowany: słupki dziennych przychodów, linia średniej z siedmiu dni i pozioma reguła z celem miesięcznym. Zwróć uwagę, że foregroundStyle przyjmuje PlottableValue, co automatycznie generuje legendę. Nie musisz pisać własnej.

Chart {
    ForEach(revenue) { day in
        BarMark(
            x: .value("Dzień", day.date, unit: .day),
            y: .value("Przychód", day.amount)
        )
        .foregroundStyle(by: .value("Kategoria", day.category))
    }

    ForEach(movingAverage) { point in
        LineMark(
            x: .value("Dzień", point.date, unit: .day),
            y: .value("Średnia 7d", point.value)
        )
        .foregroundStyle(.orange)
        .interpolationMethod(.monotone)
    }

    RuleMark(y: .value("Cel", monthlyTarget))
        .foregroundStyle(.green.opacity(0.7))
        .lineStyle(StrokeStyle(lineWidth: 1, dash: [4, 3]))
        .annotation(position: .top, alignment: .trailing) {
            Text("Cel").font(.caption2).foregroundStyle(.secondary)
        }
}
.chartLegend(position: .bottom, alignment: .leading, spacing: 8)

Wykres kołowy w Swift Charts to SectorMark z angle: równym PlottableValue. Ustawienie innerRadius: .ratio(0.6) tworzy donut, a angularInset dodaje separator między wycinkami (użyteczne przy transparencji). Cała dokumentacja markerów jest zebrana w oficjalnej dokumentacji Charts.

Jak dodać interaktywność do wykresu w iOS 26?

Do iOS 16 interaktywność wymagała DragGesture i ręcznego mapowania współrzędnych przez ChartProxy. Od iOS 17, a w iOS 26 domyślnie, wystarczy modyfikator chartXSelection(value:) z bindingiem. Użytkownik długim naciśnięciem wybiera punkt, a Ty otrzymujesz jego wartość osi X. Reszta to prosta adnotacja albo overlay.

struct InteractiveChart: View {
    let data: [DailySteps]
    @State private var selectedDay: Date?

    private var selectedEntry: DailySteps? {
        guard let selectedDay else { return nil }
        return data.min {
            abs($0.day.timeIntervalSince(selectedDay))
          < abs($1.day.timeIntervalSince(selectedDay))
        }
    }

    var body: some View {
        Chart(data) { entry in
            LineMark(
                x: .value("Dzień", entry.day, unit: .day),
                y: .value("Kroki", entry.steps)
            )

            if let selectedEntry, selectedEntry.id == entry.id {
                RuleMark(x: .value("Wybrany", selectedEntry.day, unit: .day))
                    .foregroundStyle(.secondary.opacity(0.4))
                    .annotation(position: .top, spacing: 8) {
                        VStack(alignment: .leading) {
                            Text(selectedEntry.day, format: .dateTime.day().month())
                                .font(.caption).foregroundStyle(.secondary)
                            Text("\(selectedEntry.steps) kroków")
                                .font(.headline.monospacedDigit())
                        }
                        .padding(8)
                        .background(.regularMaterial, in: .rect(cornerRadius: 8))
                    }
            }
        }
        .chartXSelection(value: $selectedDay)
        .frame(height: 240)
    }
}

chartXSelection zwraca wartość osi (tu: Date), a nie samego rekordu, więc trzeba go dopasować ręcznie. Ta drobna niespójność jest świadoma: framework nie chce Cię zmuszać do trzymania danych w jednym słowniku. W iOS 26 istnieje też wariant chartXSelection(range:) zwracający ClosedRange, idealny do zaznaczania zakresu i pokazywania podsumowań.

Do przewijania dużych zbiorów służy chartScrollableAxes(.horizontal) plus chartXVisibleDomain(length:). Widok pokazuje wtedy okno np. 30 dni, a użytkownik przewija w lewo i w prawo. Możesz to łączyć z chartScrollPosition, żeby zapamiętać ostatnią pozycję albo podpiąć się pod @Observable ViewModel i logować widoczny zakres do analityki.

Style, kolory, osie i legenda

Domyślne osie Swift Charts są sensowne, ale w produkcji prawie zawsze chcesz je dostroić: format daty, gęstość znaczników, orientację etykiet. Modyfikator chartXAxis przyjmuje builder z AxisMarks, co daje pełną kontrolę bez konieczności rysowania osi ręcznie.

.chartXAxis {
    AxisMarks(values: .stride(by: .day, count: 7)) { value in
        AxisGridLine()
        AxisTick()
        AxisValueLabel(format: .dateTime.day().month(.narrow))
    }
}
.chartYAxis {
    AxisMarks(position: .leading) { value in
        AxisGridLine().foregroundStyle(.secondary.opacity(0.2))
        AxisValueLabel {
            if let intVal = value.as(Int.self) {
                Text(intVal, format: .number.notation(.compactName))
            }
        }
    }
}
.chartYScale(domain: 0...20_000)

Format .notation(.compactName) zamienia 20 000 na „20 tys." (bardzo przydatne w Live Activity albo widżetach, gdzie miejsca jest mało). chartYScale(domain:) zapina oś na sztywno; bez tego framework skaluje ją do min/max danych, co powoduje „skakanie" wykresu przy odświeżeniach.

Kolory pobieraj z Colorów semantycznych (.accentColor, .blue, .pink), a nie z hex-a. Dzięki temu wykresy respektują Dark Mode oraz increase contrast. Jeżeli mimo wszystko potrzebujesz palety kategorycznej, użyj chartForegroundStyleScale z jawnym słownikiem, żeby kolory były deterministyczne między odświeżeniami. Kwestię palet pod względem dostępności bardzo dobrze opisuje sekcja Charting Data w Human Interface Guidelines.

Animacje, dostępność i wydajność

Animacje w Swift Charts włączasz zwykłym withAnimation wokół zmiany danych. Framework animuje położenie każdego markera, co dla wykresów liniowych daje efekt „interpolacji" między dwoma zbiorami, a dla słupków spring-owe wysuwanie się. Ważne: animuj zmiany stanu, nie sam widok. Poniżej fragment ViewModelu w stylu nowego @Observable.

@Observable
final class RevenueViewModel {
    var range: DateInterval = .init(start: .now.addingTimeInterval(-7*24*3600),
                                    end: .now)
    var samples: [DailySteps] = []

    func reload(with new: [DailySteps]) {
        withAnimation(.smooth(duration: 0.35)) {
            samples = new
        }
    }
}

Dostępność to obszar, który przez lata cierpiał w bibliotekach trzecich (VoiceOver „widział" tam pusty obraz). Swift Charts renderuje każdy marker jako osobny element accessibility. Dodaj do markerów accessibilityLabel i accessibilityValue, a użytkownicy VoiceOver dostaną też Audio Graphs: dźwiękową reprezentację krzywej, przydatną dla osób niewidomych. Kod poniżej pokazuje kompletny wzorzec.

Chart(data) { entry in
    LineMark(
        x: .value("Dzień", entry.day, unit: .day),
        y: .value("Kroki", entry.steps)
    )
    .accessibilityLabel(entry.day.formatted(.dateTime.day().month().weekday()))
    .accessibilityValue("\(entry.steps) kroków")
}
.accessibilityChartDescriptor(RevenueDescriptor(data: data))

Wydajność: dla zbiorów do kilku tysięcy punktów Chart radzi sobie bez optymalizacji. Powyżej, użyj chartScrollableAxes, agreguj dane po dniu lub tygodniu przed przekazaniem do widoku, i unikaj przeliczania modelu w kompilowaniu widoku (użyj @Observable albo cache'a). Jeżeli danych masz naprawdę dużo (setki tysięcy), rozważ pre-renderowanie do Image przez ImageRenderer. Wtedy interaktywność tracisz, ale scroll pozostaje płynny. W moim ostatnim projekcie z ~800 tys. samplami taki hybrydowy render obniżył czas pierwszego malowania z 1,2 s do 90 ms.

Praktyczny dashboard z metrykami

Poniżej kompletny widok „karty metryki", element, którego używam praktycznie w każdej aplikacji analytics. Łączy w sobie tytuł, aktualną wartość, mały sparkline (LineMark bez osi) i podpis zmiany procentowej. Jest w pełni deklaratywny, waży kilkadziesiąt linii i integruje się z SwiftData bez dodatkowego mostka.

struct MetricCard: View {
    let title: String
    let value: Double
    let deltaPercent: Double
    let series: [DailySteps]

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(title)
                .font(.caption)
                .foregroundStyle(.secondary)

            HStack(alignment: .firstTextBaseline) {
                Text(value, format: .number.notation(.compactName))
                    .font(.title.monospacedDigit().weight(.semibold))
                Spacer()
                DeltaBadge(value: deltaPercent)
            }

            Chart(series) { p in
                AreaMark(
                    x: .value("Dzień", p.day, unit: .day),
                    y: .value("Wartość", p.steps)
                )
                .foregroundStyle(.linearGradient(
                    colors: [.accentColor.opacity(0.35), .clear],
                    startPoint: .top, endPoint: .bottom
                ))

                LineMark(
                    x: .value("Dzień", p.day, unit: .day),
                    y: .value("Wartość", p.steps)
                )
                .foregroundStyle(.accentColor)
                .interpolationMethod(.monotone)
            }
            .chartXAxis(.hidden)
            .chartYAxis(.hidden)
            .frame(height: 60)
        }
        .padding(16)
        .background(.regularMaterial, in: .rect(cornerRadius: 16))
    }
}

private struct DeltaBadge: View {
    let value: Double

    var body: some View {
        let up = value >= 0
        Label {
            Text(value, format: .percent.precision(.fractionLength(1)))
        } icon: {
            Image(systemName: up ? "arrow.up.right" : "arrow.down.right")
        }
        .font(.caption.bold())
        .foregroundStyle(up ? .green : .red)
        .padding(.horizontal, 6)
        .padding(.vertical, 3)
        .background(
            (up ? Color.green : Color.red).opacity(0.15),
            in: .capsule
        )
    }
}

Karta ma jasną hierarchię: liczba jest najbardziej widoczna, delta pełni rolę scanning-cue, a sparkline daje kontekst historyczny. W siatce LazyVGrid kilkanaście takich kart tworzy dashboard w stylu App Store Connect lub TestFlight. Dla porównania kart między okresami, dodaj chartYScale(domain: yDomain) ze wspólnym zakresem, żeby wielkość sparkline'ów była porównywalna między metrykami. Inaczej wzrokowo zakłamują się proporcje (sam wpadłem w tę pułapkę na jednej z pierwszych prezentacji klientowi).

Jeżeli chcesz, żeby dashboard zapisywał ustawienia użytkownika (widoczne kolumny, zakres dat), połącz go z Foundation Models. Podpowiedzi generowane on-device na podstawie widocznych metryk sprawiają, że karty przestają być statyczne, a stają się interaktywnym asystentem analitycznym. To spójna architektura: jeden model danych, dwa punkty prezentacji.

Podsumowanie

Swift Charts w iOS 26 jest już frameworkiem dojrzałym. Obsługuje wszystkie typy wykresów, których na co dzień potrzebujesz, i wspiera interaktywność, przewijanie oraz dostępność bez pisania własnych gestów. W praktyce oznacza to, że nawet skomplikowany dashboard analytics możesz zbudować w kilkadziesiąt linijek deklaratywnego kodu, mając za darmo Dark Mode, VoiceOver, Dynamic Type i Liquid Glass. Jeżeli dopiero zaczynasz, wybierz LineMark na dziennych krokach, a jak tylko poczujesz się pewnie, przełącz się na chartXSelection. Wtedy wykresy staną się częścią doświadczenia użytkownika, a nie tylko obrazkiem na ekranie.

Najczęściej zadawane pytania

Od jakiej wersji iOS dostępny jest Swift Charts?

Swift Charts wymaga iOS 16 lub nowszego, iPadOS 16, macOS 13, watchOS 9 i tvOS 16. Interaktywna selekcja przez chartXSelection działa dopiero od iOS 17, a przewijanie w widżetach dopiero od iOS 26.

Czy Swift Charts jest darmowy?

Tak, Swift Charts jest częścią SDK Apple i nie wymaga żadnej dodatkowej licencji ani zależności. Jedyny „koszt" to minimalna wersja systemu, którą musisz ustawić w projekcie Xcode.

Jak zrobić wykres kołowy w Swift Charts?

Użyj SectorMark(angle: .value(...), innerRadius: .ratio(0.6)). Ustawienie innerRadius tworzy donut; angularInset dodaje przerwy między wycinkami. SectorMark wymaga iOS 17 lub nowszego.

Jak animować zmianę danych w Swift Charts?

Owiń zmianę tablicy w withAnimation. Swift Charts sam animuje przejścia markerów między dwoma stanami. Dla płynnego efektu użyj .smooth(duration: 0.3). Nie animuj samego widoku, animuj model.

Czy Swift Charts obsługuje VoiceOver i Audio Graphs?

Tak. Każdy marker jest osobnym elementem accessibility. Dodaj accessibilityLabel i accessibilityValue, a użytkownicy VoiceOver otrzymają zarówno odczyt tekstowy, jak i dźwiękową reprezentację przez Audio Graphs (od iOS 17).

Kiedy Swift Charts nie jest dobrym wyborem?

Gdy potrzebujesz wsparcia poniżej iOS 16, wykresów 3D albo bardzo specyficznych typów (Sankey, sunburst, radar). W takich przypadkach lepiej sięgnąć po DGCharts lub napisać własne renderowanie na Canvas z Path.

Lukas Müller
O Autorze Lukas Müller

iOS developer and Swift author since the Objective-C days. Spends his evenings on side projects and his mornings on SwiftUI internals.