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 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.
Swift Charts nudi šest osnovnih vrsta oznaka, a njihova kombinacija je mjesto gdje se počinje razlikovati "OK grafikon" od produkcijskog. Evo brzog pregleda:
Oznaka
Kada koristiti
Podržane osi
Interaktivnost
BarMark
Kategorijski podaci, usporedbe
x + y (može biti stacked)
tap, highlight, animacija
LineMark
Vremenske serije, trendovi
x + y (najčešće Date x)
selection, scroll, tooltip
PointMark
Scatter plot, korelacije
x + y (numerički)
tap, size mapiranje
AreaMark
Kumulativni volumen, stacked area
x + y (može biti stacked)
gradient fill, animacija
RectangleMark
Heatmape, gantt-blokovi
xStart/xEnd + yStart/yEnd
selection po ćeliji
RuleMark
Prosjek, threshold, annotation
x ili y
label overlay
Za kombinirani prikaz (recimo dnevni koraci kao stupci s crvenom crtom prosjeka) jednostavno stavite više oznaka u isti Chart:
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:
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:
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:
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.
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.
Vodič za izradu Live Activities i Dynamic Island u iOS 26 pomoću ActivityKit okvira. Postavljanje Widget ekstenzije, ActivityAttributes, sva tri stanja Dynamic Islanda, App Intents i testiranje u Xcode 26 simulatoru.
Praktičan vodič kroz TipKit u iOS 26: kako izraditi savjete, definirati pravila, koristiti TipGroup, sinkronizirati stanje preko iClouda i lokalizirati poruke uz radne SwiftUI primjere.
App Intents u iOS 26 omogućuje da jedan Swift tip radi kao Siri naredba, interaktivni widget i Apple Intelligence akcija. Kompletan vodič s primjerima za Swift 6.