SwiftUI Charts: Vodič za izradu interaktivnih grafikona u iOS 26

Praktični vodič kroz Swift Charts framework u iOS 26: BarMark, LineMark, prilagodba osi, interaktivnost (chartXSelection), scrollable grafikoni i razlike između iOS-a, iPadOS-a, macOS-a, watchOS-a i visionOS-a.

SwiftUI Charts Vodič: iOS 26 (2026)

Ažurirano: 2. kolovoza 2026.

SwiftUI Charts je deklarativni framework koji omogućuje izradu statistički preciznih grafikona s minimalno koda, koristeći iste konstrukcije koje već poznajete iz SwiftUI-ja. U iOS-u 26, macOS-u 15 i visionOS-u 26 Apple je proširio Swift Charts s naprednim chartScrollPosition API-jima, boljim highlightingom i punom podrškom za watchOS 11. U ovom vodiču pokrivamo tipove oznaka, prilagodbu osi, gestualnu interaktivnost, scrollable ponašanje i (najzanimljivije) kako se isti kod ponaša drugačije na iPadu, Mac Catalystu i visionOS-u.

  • Swift Charts je Appleov službeni framework za grafikone, dostupan od iOS-a 16, a od iOS-a 26 uključuje puni skup interaktivnih i scrollable modifikatora.
  • Osnovne oznake su BarMark, LineMark, PointMark, AreaMark, RectangleMark i RuleMark, a mogu se kombinirati u istom Chart containeru.
  • Interaktivnost se dodaje putem chartXSelection, chartYSelection i chartOverlay modifikatora, bez potrebe za DragGesture.
  • Za velike setove podataka koristite chartScrollableAxes(.horizontal) u kombinaciji s chartXVisibleDomain za paginirano listanje.
  • Isti kod se ponaša drugačije na iPadu (veći padding), Mac Catalystu (hover highlighting) i visionOS-u (dubinska hijerarhija). Modifikator compositingGroup() pomaže s renderiranjem.
  • Framework automatski generira VoiceOver opise, ali za produkcijsku pristupačnost eksplicitno dodajte accessibilityLabel i accessibilityValue po oznaci.

Što je Swift Charts framework?

Swift Charts je Appleov deklarativni framework za vizualizaciju podataka, predstavljen na WWDC-u 2022. i uključen u iOS 16, iPadOS 16, macOS 13, watchOS 9 i tvOS 16. Dizajniran je da izgleda i ponaša se kao SwiftUI: gradite grafikon kombiniranjem "oznaka" (marks) unutar Chart kontejnera, a framework se brine za osi, legende, boje i pristupačnost. Nasljeđivanjem SwiftUI konvencija dobivate i besplatnu podršku za Dynamic Type, Dark Mode, RTL jezike i State-restoration.

Za razliku od nekad popularnih third-party rješenja poput Charts (Daniel Cohen Gindi) ili SwiftUICharts, Appleov framework je "zero dependency". Dolazi s SDK-om, radi izvan kutije i podržava sve Apple platforme s istim source kodom. U iOS-u 26 Apple je dodao nekoliko značajki koje su prije zahtijevale ručno crtanje: chartScrollableAxes, chartXSelection, ChartProxy value-to-position mapiranje i integraciju sa SwiftUI NavigationStack tranzicijama.

U praksi sam Swift Charts koristio za sve što ide van proizvoda: sažetke financijskih izvještaja, health data prikaze, statistike igara pa čak i dijagrame komora u visionOS aplikaciji. Ono što ga izdvaja je koliko malo koda treba za nešto što izgleda "dovršeno", jedan BarMark po redu i eto vam prezentabilnog grafikona.

Kako dodati grafikon u SwiftUI aplikaciju

Za dodavanje grafikona trebate importati Charts, definirati Identifiable tip podataka i staviti oznake unutar Chart bloka. Evo minimalnog primjera koji radi na iOS-u 16 i novijim verzijama:

import SwiftUI
import Charts

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

struct StepsChart: View {
    let data: [DailySteps] = [
        .init(day: "Pon", steps: 4820),
        .init(day: "Uto", steps: 6110),
        .init(day: "Sri", steps: 3200),
        .init(day: "Čet", steps: 7480),
        .init(day: "Pet", steps: 5990),
        .init(day: "Sub", steps: 9210),
        .init(day: "Ned", steps: 2500)
    ]

    var body: some View {
        Chart(data) { entry in
            BarMark(
                x: .value("Dan", entry.day),
                y: .value("Koraci", entry.steps)
            )
            .foregroundStyle(.tint)
        }
        .frame(height: 240)
        .padding()
    }
}

Nekoliko stvari na koje treba paziti pri prvoj implementaciji. Prvo, Chart može primiti kolekciju direktno u inicijalizatoru (kao ForEach) ili možete koristiti "empty init" i ručno dodavati oznake za višeslojne grafikone. Drugo, PlottableValue koji se predaje u x: i y: odlučuje kako će framework tretirati podatak: String vrijednosti tretiraju se kategorijski, a Int, Double i Date numerički. Treće, ako gradite asinkrono dohvaćene podatke, ovijte Chart u Group s placeholderom da izbjegnete blank state za vrijeme prvog await-a.

Tipovi oznaka: BarMark, LineMark, PointMark, AreaMark

Swift Charts nudi šest osnovnih vrsta oznaka, a njihova kombinacija je mjesto gdje se počinje razlikovati "OK grafikon" od produkcijskog. Evo brzog pregleda:

OznakaKada koristitiPodržane osiInteraktivnost
BarMarkKategorijski podaci, usporedbex + y (može biti stacked)tap, highlight, animacija
LineMarkVremenske serije, trendovix + y (najčešće Date x)selection, scroll, tooltip
PointMarkScatter plot, korelacijex + y (numerički)tap, size mapiranje
AreaMarkKumulativni volumen, stacked areax + y (može biti stacked)gradient fill, animacija
RectangleMarkHeatmape, gantt-blokovixStart/xEnd + yStart/yEndselection po ćeliji
RuleMarkProsjek, threshold, annotationx ili ylabel overlay

Za kombinirani prikaz (recimo dnevni koraci kao stupci s crvenom crtom prosjeka) jednostavno stavite više oznaka u isti Chart:

Chart {
    ForEach(data) { entry in
        BarMark(
            x: .value("Dan", entry.day),
            y: .value("Koraci", entry.steps)
        )
        .foregroundStyle(by: .value("Serija", "Koraci"))
    }

    RuleMark(y: .value("Prosjek", data.averageSteps))
        .foregroundStyle(.red)
        .lineStyle(StrokeStyle(lineWidth: 2, dash: [4]))
        .annotation(position: .top, alignment: .trailing) {
            Text("Prosjek: \(data.averageSteps)")
                .font(.caption)
                .foregroundStyle(.red)
        }
}
.frame(height: 260)

Zapazite foregroundStyle(by:). To je način na koji kažete Chartu da automatski generira legendu i dodijeli boje po seriji. Ako želite fiksne boje, koristite chartForegroundStyleScale s eksplicitnim mapiranjem.

Prilagodba osi, boja i legendi

Zadano ponašanje osi je "pametno": Swift Charts sam odabire raspon, korak i format. To je izvrsno za prototipe, ali za produkciju gotovo uvijek želite kontrolu. Tri modifikatora koje ćete koristiti najčešće su chartXAxis, chartYAxis i chartXScale/chartYScale.

Chart(data) { entry in
    LineMark(
        x: .value("Vrijeme", entry.timestamp),
        y: .value("Temperatura", entry.celsius)
    )
    .interpolationMethod(.catmullRom)
}
.chartXAxis {
    AxisMarks(values: .stride(by: .hour, count: 3)) { value in
        AxisGridLine()
        AxisTick()
        AxisValueLabel(format: .dateTime.hour())
    }
}
.chartYAxis {
    AxisMarks(position: .leading, values: .automatic(desiredCount: 5)) { value in
        AxisGridLine().foregroundStyle(.secondary.opacity(0.3))
        AxisValueLabel {
            if let celsius = value.as(Double.self) {
                Text("\(Int(celsius))°C")
            }
        }
    }
}
.chartYScale(domain: -10 ... 40)

Nekoliko stvari koje sam morao naučiti na teži način. Kada koristite chartYScale(domain:) s fiksnim rasponom, morate biti sigurni da vaši podaci uvijek staju unutra, inače će oznake biti clipirane bez upozorenja. Za dinamičke raspone koristite .chartYScale(domain: .automatic(includesZero: true)). Kod boja izbjegavajte hex vrijednosti direktno, radije koristite .foregroundStyle(by: .value("Kategorija", entry.category)) i pustite framework da odabere pristupačnu paletu iz sistemskih semantic colora. Više o vođenju sistemskog izgleda kroz aplikaciju pronaći ćete u vodiču kroz Swift Observation framework gdje je @Observable stanje često izvor podataka za Chart.

Kako napraviti interaktivni grafikon u SwiftUI

Iskreno, interaktivnost je područje gdje je iOS 17 (i finalno iOS 26) donio najveće promjene. Do iOS-a 16 morali ste ručno crtati overlay Rectangle, hvatati DragGesture i konvertirati poziciju u vrijednost preko ChartProxy. Sada je sve deklarativno preko chartXSelection:

struct InteractiveTemperatureChart: View {
    let readings: [TemperatureReading]
    @State private var selectedDate: Date?

    private var selectedReading: TemperatureReading? {
        guard let selectedDate else { return nil }
        return readings.min(by: { abs($0.timestamp.timeIntervalSince(selectedDate))
                                  < abs($1.timestamp.timeIntervalSince(selectedDate)) })
    }

    var body: some View {
        Chart(readings) { reading in
            LineMark(
                x: .value("Vrijeme", reading.timestamp),
                y: .value("°C", reading.celsius)
            )

            if let selectedReading, selectedReading.id == reading.id {
                PointMark(
                    x: .value("Vrijeme", reading.timestamp),
                    y: .value("°C", reading.celsius)
                )
                .symbolSize(120)
                .foregroundStyle(.orange)
                .annotation(position: .top) {
                    VStack(alignment: .leading, spacing: 2) {
                        Text(reading.timestamp, format: .dateTime.hour().minute())
                            .font(.caption2)
                            .foregroundStyle(.secondary)
                        Text("\(Int(reading.celsius))°C")
                            .font(.headline)
                    }
                    .padding(6)
                    .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8))
                }
            }
        }
        .chartXSelection(value: $selectedDate)
        .frame(height: 260)
    }
}

Ključna stvar je chartXSelection(value:) koji binda Date? (ili bilo koji Plottable tip) na trenutačnu poziciju gesta. Na iOS-u to reagira na tap i drag, na macOS-u na hover, a na visionOS-u na look-and-pinch. Isti kod, tri različita ponašanja, i to je upravo ono što volim kod Swift Chartsa.

Za višekategorijsku selekciju (recimo označavanje serije i vrijednosti) postoji chartXSelection(value:) i chartYSelection(value:) u kombinaciji. Za raspon (range selection) koristi se chartXSelection(range:) koji vraća ClosedRange<Date>?. Savršeno za "odaberi period" funkcionalnost.

Scrollable grafikoni i chartScrollPosition

Do iOS-a 17, prikaz mjesečnih podataka na tjednom prozoru značio je ručno filtriranje niza i praćenje offseta. Od iOS-a 17 chartScrollableAxes i chartXVisibleDomain to rješavaju u dva retka:

Chart(monthlyReadings) { reading in
    BarMark(
        x: .value("Datum", reading.date, unit: .day),
        y: .value("Prodaja", reading.amount)
    )
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 24 * 7)  // sedam dana
.chartScrollPosition(x: $scrollPositionDate)
.chartScrollTargetBehavior(
    .valueAligned(matching: .init(hour: 0), majorAlignment: .matching(.init(weekday: 2)))
)

chartXVisibleDomain(length:) definira "prozor" koji je vidljiv u bilo kojem trenutku (u primjeru sedam dana pretvorenih u sekunde). chartScrollPosition(x:) binda trenutačnu poziciju scrolla nazad na @State, što je zlato kad želite prikazati "trenutačno gledate: 15. do 22. srpnja" naslov iznad grafikona. chartScrollTargetBehavior osigurava da scrolling "snapa" na ponedjeljke, inače biste dobivali frustrirajuće poluvidljive dane.

Za velike setove podataka (preko 10.000 točaka) preporučam sample-anje prije nego što uopće dođu do Charta. Framework je optimiziran, ali crtanje 100.000 PointMark-ova će usporiti čak i M4 iPad. U jednom mojem projektu s health senzorima, downsample s originalnih 86.400 mjerenja dnevno na 1.440 (svake minute) dao je gotovo identičan vizualni rezultat uz šesnaest puta manje memorije. Koristite ChartProxy.value(atX:) unutar chartOverlay da downsample-ate podatke prema trenutnom vidljivom domenu.

Cross-platform: iOS, iPadOS, macOS, watchOS i visionOS ponašanje

Ovo je poglavlje koje me držalo najviše noću tijekom migracije jedne aplikacije iz iOS-only u multi-platform. Isti Chart se ponaša drugačije ovisno o platformi, i to na načine koje dokumentacija ne pokriva eksplicitno:

  • iOS/iPadOS: podržava sve modifikatore. Na iPadu, chartXSelection radi s Apple Pencil hoverom (od iPada Pro s M2 nadalje) što daje "desktop-like" preview bez taphanja.
  • Mac Catalyst: chartXSelection se automatski mapira na mouse hover, ali legende zauzimaju više prostora nego na iPhoneu. Koristite chartLegend(position:) s .leading ili .trailing da izbjegnete "high" layout.
  • macOS 15 native: podržava onHover tooltipe direktno, pa chartXSelection ovdje često nije potreban ako želite samo prikaz vrijednosti.
  • watchOS 11: puna podrška od 2024., ali digitalna kruna se automatski koristi za scroll u chartScrollableAxes(.horizontal). Legenda je često prevelika, pa je skrijte s .chartLegend(.hidden).
  • visionOS 26: Chart dobiva dubinu automatski, ali PointMark annotationi mogu biti sjeckasti bez .drawingGroup(). Za Reality Composer Pro scenografije koristite Chart unutar Attachment.
  • tvOS 17+: selection radi preko focus enginea, ali samo za diskretne kategorijske vrijednosti. Kontinuirani Date range ne funkcionira dobro s daljinskim.

Pristupačnost i optimizacija izvedbe

Swift Charts automatski generira UIAccessibilityChartDescriptor koji omogućava VoiceOver navigaciju kroz podatkovne točke, uključujući audio graf (sonifikaciju). To radi bez ijedne linije koda, ali kvalitativne razlike u produkciji dobiju se s accessibilityLabel i accessibilityValue:

BarMark(
    x: .value("Dan", entry.day),
    y: .value("Koraci", entry.steps)
)
.accessibilityLabel("\(entry.day)")
.accessibilityValue("\(entry.steps) koraka")
.accessibilityHidden(false)

Za lokalizirane brojeve koristite .formatted(.number) koji poštuje lokalne postavke. U hrvatskoj lokalizaciji 1000 će biti izgovoreno kao "tisuću", a ne "one thousand". Za sonifikaciju, framework koristi AudioGraphContentDescriptor automatski, ali morate ga eksplicitno omogućiti u Info.plist ako želite rad u pozadini.

Kod izvedbe, dva pravila. Prvo, Chart renderira cijeli view hierarhije kad se izvor podataka promijeni, pa koristite .equatable() na Chart podviewu ili wrappajte podatke u @Observable class s dobro definiranom Equatable implementacijom. Drugo, chartOverlay s GeometryReader je izuzetno skup na svakoj promjeni orijentacije, pa koristite ChartProxy umjesto GeometryProxy gdje god možete.

Ako vas zanima kako upravljati modelima podataka koji hrane Chart, pogledajte i vodič za SwiftData perzistenciju, koji dobro nadopunjuje ovu priču kad grafikoni čitaju iz lokalne baze. Za dublji uvid u sam API preporučam službenu Swift Charts dokumentaciju, WWDC sesiju "Explore pie charts and interactivity in Swift Charts" te Chart API referencu koja pokriva sve inicijalizatore i modifikatore.

Česta pitanja

Koja je minimalna verzija iOS-a potrebna za Swift Charts?

Swift Charts zahtijeva iOS 16, iPadOS 16, macOS 13, tvOS 16 ili watchOS 9. Interaktivni modifikatori poput chartXSelection i chartScrollableAxes zahtijevaju iOS 17 i noviji. Za punu podršku scrollable range selection potreban je iOS 18 ili noviji.

Koja je razlika između BarMark i LineMark?

BarMark je namijenjen kategorijskim podacima gdje želite usporediti diskretne vrijednosti (dani u tjednu, proizvodi). LineMark je za kontinuirane serije poput vremenskih ili numeričkih trendova. Oba se mogu kombinirati u istom Chart bloku.

Podržava li Swift Charts pie i donut grafikone?

Da, od iOS-a 17 dostupni su SectorMark koji podržavaju i pie i donut oblik. Za donut samo proslijedite innerRadius: parametar, npr. SectorMark(angle: .value("Kut", value), innerRadius: .ratio(0.6)).

Kako izvesti Chart kao sliku ili PDF?

Koristite ImageRenderer iz SwiftUI-ja (iOS 16+). Instancirajte ga s vašim Chart viewom kao contentom i pristupite .uiImage ili .render(rasterizationScale:renderer:) za PDF izvoz. Za produkciju postavite proposedSize eksplicitno da izbjegnete pogrešne dimenzije.

Zašto se moj Chart ne osvježava kad se podaci promijene?

Najčešći uzrok je što izvor podataka nije observable. Ako koristite običnu @State array, promjena mora zamijeniti cijelu kolekciju, a ne samo mutirati elemente. Preporuka je koristiti @Observable class s Equatable implementacijom, ili wrappati podatke u @State private var data i ponovno dodijeliti novu instancu.

Hiroshi Sato
O Autoru Hiroshi Sato

Apple Platforms specialist building for iOS, macOS, visionOS, and the occasional watchOS app nobody asked for.