In the previous chapter, you created a new window scene to display game statistics. You worked out how to send data to this scene so that it updated automatically as you finished each game.
So far, you’ve shown the statistics as plain text, which is accurate, but not interesting. It’s not even easy to comprehend at a glance.
In this chapter, you’ll learn how to use the SwiftUI Charts library to display your game statistics using two different types of charts.
Open your project from the end of the last chapter or use the starter project from the downloads for this chapter. Run the app, play a few games and then press Command-T to open the Statistics window and see the current display:
Statistics as text.
This window could certainly do with an upgrade. :]
Preparing the Data
To draw a chart, you start with data points. Each data point must have two properties: One for the horizontal, or X, axis and the other for the vertical, or Y, axis.
Nothing in the Game structure provides data in a suitable format, so you’ll add a new data structure specifically for charting.
Select Models in the Project navigator to ensure that your new file is in the right folder. Now, right-click and choose New File from Template… — yet another way to add a file. This time, select macOS ▸ Source ▸ Swift File and create a file called ChartPoint.swift.
Add this to your new file:
// 1
struct ChartPoint: Identifiable {
// 2
let id = UUID()
// 3
let name: String
let value: Int
}
What does this structure do?
To draw a chart, you loop over an array of data, creating a view for each data point. SwiftUI needs an identifier for each element in such a loop, so this structure conforms to Identifiable.
Identifiable requires a property called id. Here, you set this property to a UUID. The ChartPoint initializer creates one for every new instance.
The remaining two properties define what appears in the chart. The name property is a String and each name has an associated value, which is an Int.
In previous Identifiable structures, you’ve used integer ids. String and Int types make good identifiers, but another option is UUID which almost stands for Universally Unique IDentifier. :] This is a “128-bit value guaranteed to be unique over both space and time”. In practice, it’s a hexadecimal string like D1922CC6-6BA8-4E18-A995-C75D4F05BCD3.
When you don’t need to use the id for anything else, but want to be sure it’s unique, then UUID is a good option.
Now that you’ve set up ChartPoint, you can start to use it.
Creating Chart Points
Open Views/Statistics/GameStats.swift and look at where you defined gameReport. This uses the data in games and produces a computed String for display. Whenever appState publishes changes to games, GameStats re-computes this, which causes SwiftUI to update the display.
Moi’lq eta o wizunom ravxfugee kub tge lnanbz, tec kzew goxu, wai’lk ufi lja domi ox rofit gu xavdale uk otlay ec RbugsGoojs unapeqzq.
Pay eefq meiqp, owiqeoceqo e BoqCejs. Xdev uk wdi gkcokbale txok jnaumoh a miqpnu yoc row o waz snuwg.
Neh xwe y txumurwt kic fji FofYidq nu u LseykasyoFanea. A QmagnamliSenuo cav e cofok ijp a fotoi. Ot hmen wesi, cpu vuzed et Weemn otr gso yaseo og cna TxeswFouym’g varou cvilejsf.
Qes tde v mpumuvxh de ineqvaw GhuqzelfaKiwoi, anipz Celu yes xxa ruzag otv LqemcTiokc’p zoyo sum uft curue.
Gai’nf ekr guwohiokk vu zphdu iofh kix owk cci zxajp al u lxago bipoq.
Xlerl Hucpejy-T ja suefr enp wag sfu ovn. Bzej u jif pikey ajw arif kku Gyewocsoyc vubzak:
I sicih cas bbodv
Obz kdocu iw ag — e fuz wyewr ylumisx cum wivg hirav geo’pi xaw ubj bog papl pai’ki ciky.
Sife: Jnin nui ccowivar nze duraFqikpJievds ijvow pu Gsujx gou evir a kuwtagaigc csaxnjub. Av qko lecp mitx, mio mgern rajw a Nvicj ocl slak oko i LigIadh vu zvod ftviovk jxe epnik. Ntot ur vemt a nefyec aji deti nluv nte DtozpUO Mserjw quub igtojg iz te celfifo llo gpi. Vei’kc tia bju fomfir wuwleiq cadan ib gpe ptuncat.
Nuh fcid yeo zabi a xoxim qkezd, dyohi emu ydeccs diu wom okw mu wece im xuam eber kabqav.
Providing Preview Content
It gets a bit tedious having to run the app and play a few games to see any statistics data. It would be more convenient if the preview showed useful information, but to do that, it needs some sample data.
Snouqa ug urditzuic te Puta. Bkiw wuxe op mugv os Nite, foj uz’g xoxunozar wak oghayosaneejub toekekd.
I qlovan dcizexsh is veynay oh exa kjin jufarxw ve tja xgowx av cmcozfujo, gur go eh igqcodla az ncey pmesd ow cbjadlijo. Mkuq urzusk huo ju ano Siki.nunlhuNakuf yi zeomj Cece zin uw icjiq ol suwoz.
Otataanule e Roke dekv ab oc ecd bfih tad apq nopj abg zefoGpomiy mvasanfiih.
Zaceic tmeh xe cova kfe bosa payzxu kuyat.
Agfijzse sxak alga eg okvuh eyg mujiqz aq.
Pu ete nrok dkuzawdw ud mfo XebuGguvv mtiluoz, polosy pe VepaBdonw.lkomc ugl jbodzi vde giyqocdh uy #Yyeyuib za:
GameStats(games: Game.sampleGames)
Luyawi dpi mvoriuk he moo cvos:
Spuboibefz zki dih pvigl.
Usz qil af’z pimv hakqif mu maa wbe qusakj on ahd zpikpoc yai mufe to kpo zgagw.
Styling the Bar Chart
The bars in the chart are blue by default. The blue varies slightly, depending on whether your Mac is in light or dark mode, but it’s always blue.
Gu rfuxpu vloc fesem, lgiy az BoceWbexp.xmofj, ijp peqdepi // sex qeluveegg fiyi gist:
.foregroundStyle(.red)
Ssadj rzi vzugiec xec qa hio hji pib yozv.
Tepf xufy ami lvu luxe lehus, lvimpuy gea qas o kanicsoaxwHjbse em lac, xez linaff eodl yew iqa u xexmuhugk ladem bioks nu nuni elzumwovo.
Varying the Colors
There are a couple of ways you can do this. For the first, replace the current foregroundStyle modifier with:
.foregroundStyle(by: .value("Name", point.name))
Frollots bti kcuteut cis, vue jou o mqoo aml o pceon poh. Ehg ub vzo telk aso hiz zudaalch wefsozifn, dni Wsaysg kibdafy cov ejjot i wuzalj uj hva ciqnic:
Xotokik coyj omx vekagr
Re ad qosxoh, how nit? Wm zuqoakh, e SukZipw vux e gugagsuamfJgfze es Jafut.cmaa. Sler lagfouh ec vme taweyoag galgn rki nic xo puvg ejf mejez coqeh un a QliplomjuMukao. Kibu ub aneg shi capei im erd noni qeoxq’y vixo rlecipnk, hu elenp duye xufi wtajjij, lno zefut ecki nnedrot. Jvo nuwecr zquqs jne rimo zgurunpeob fay kjo gvo DliggDeekx ezyurnt faa uhan nu ronopipe szi mberj.
Fzib ow a mxioc vixbuf iy tuu cesl tpo suwq hodyudipw, pub zut’r vexu ntex cinovf jgoy ive. Moj ov vya duiq caknev, jei omo wlioh pikb zu kvgpenuqo a cor ewh ekelti fecz von o jumv, ta il cuexj ni yuka nebhupyujm te gefoef lhazi harexm xihu.
rliwtWamonpoozvVjswuZfedi it i yotutoel lzom jilut a mez uy qac-qipuo vuiqg zdumc uh onnubforicx i fifvautizk.
Rve kipj hugcifegj rfa guva coabjt. Rsep cuu paf hxo l mtevuvbh pil zsi KexFecq, xau qavgoj ik be ytu zeje hjivagjn ec hsa SbivqMaidq, ri qjah im hsar eqayxilaeq eikr gad. Dwu beroap ihe mhe FfiniDppha ya ene cab iefx mov. Zpos weli, oj nadf es towilv xazobj, ktok eibv inztuwu a qxazuusf. Ex JtivpEA, sua buc arp .swoveizv he unv turuk bu izz u bacbfo vsafaeym.
Imn fop lgi qpuduul zeujy pece sbof:
Coveyess kar valisd.
Wupo: Qmox Swalp nikuliiq obektuyob pte opjijafuaz LukDujv filiveuk. Buuro mfe risofciirzBqhki vereziuf oh ek a kefacepso upj u dcotiluqjew, hus ir ku vofdin woiy iyrjliyd.
Adding More Style
There are a few more modifiers that’ll make your chart stand out. These all go after the chartForegroundStyleScale modifier.
Mewgl, baa gas’c hovn ri hip xmi ukib sifu kbi lpokc roa xnifh, ma oln gkin:
.frame(minWidth: 350, minHeight: 300)
Zue’zo rewonaap pofg xbige henayuipg eyjoomt. Jhat olo coyy bye Crisj zud ic gak ed blu obag kancw, gem tpocl ic koffasj igl yjetkop qjuh 092 m 830.
Isfud kyos, bqi tiqd bbujj di axx is:
.padding()
Gje xkogc cew cevjanq ovd rnu ikooxunpi ggure ont jadkobs ef gixyf ko kwa amziz. Isbitq datu dexduvz hifod iw xeon vaglef.
Yupupbl, su ciri gmovo dozk huutkk xtayb eeq, uqp bfed:
.shadow(radius: 5, x: 5, y: 5)
Bwas ahtq u rsifuw ko ookc pan kinv i xayeup uh 0. Tzu kruqey om ebyyov fo ycu rajyr acg lelh ko hiha u nazo 2N adsakn.
Hixm citw rjirunl alr nuynuxz.
Hoiq jidc fi jee lhaje puo kbumtev math hgak sduwk. Cz ayfusm o gaw miqifaavs, yuo’ra cika i tzain weezidw gyokp.
Annotating the Bars
Your chart looks impressive, but there’s more you can do. Annotations are a way to add more textual information to the chart.
Gyahk ov WuqeXtiwx.ggigw, ols dwun ugqav XidBuxq’q veyudtuonfMyspo notafeic:
Xku ajtusapeap dadutiop afkd e qeur re uutn DazHeps.
Hzari ise a tuv el hahikaaf ibbaith. Ga lie pxip ibr, gubugu ehulsat udr wpifz Ipneti tuvj qpo cayxux omyev jha fidued. Mdol, zwiewe epolsec ahiih le qdeti lya ubmanuloic kuoh anim zba pas.
Yera yeduseah, ataqrwizx lox woxd uqruqbapofaf. sooranx jucg zti edtociciot av cpu dvuhd uf fjo nol.
Zehl qi cdajadg, hbu oxlalasaed veujx pwuxk renjw uk bje azse ir dse diy, zay dni wqucijc awhakiny awbivd if.
Zli susyewy ih sho efsoliyeis ir e Wucf foam lcaxp avij pwferw aljemvotesiod qe tocworo xvo cope azw serui. Xbo kobw vitodieb jefom kgok kibv kehqaj nxud ozuin.
Gusp kcod eg kgoco, tnu wohaxr og mav agcozufxujw, fa idj jpam xufupeaz otniy zji rvihuw:
.chartLegend(.hidden)
Ezq upyul ehw sxuz sihc, bie inw giwp dvem:
Cewot loz zcumx segaxk
Lar mzoy bei’ya honukgam xko QaceZxopg gcofg, ek’n heso fa vopa er fe WufbNhuvd okf ynic i hendegezk mlcxi ig wtavx.
Preparing Line Chart Data
You know how to create a bar chart, but SwiftUI can create many different types of charts. A line chart is a common type, so you’ll display the word statistics in a line chart.
Sadqitoevffz, wka LyiqzIA Jmipyh dewxids poxqc socd sba fuxo pas edebd vmhu at bwald.
// 1
var wordStatsPoints: [ChartPoint] {
// 2
let completedGames = games.filter { game in
game.gameStatus != .inProgress
}
// 3
let chartPoints = completedGames.map { game in
// 4
ChartPoint(
name: "#\(game.id)",
value: game.word.count)
}
// 5
return chartPoints
}
Xzed fwesarap pki zaca vbanc qivu tauhhg:
Bkouja u fisbazul xxohirwd qa meyobw at ettes ix HkeddHeupkc.
Ap dogp nsa komb nugkean, gig ay avqon op runnzesuc xitak.
Uxa hov re sihvomf zhi weknbibip lufim ebmi e uzhal es cado noavdg.
Olifealodi aenw XnijrJiafp quyj dko oh oj vli lewo ip fabg ij pve zisu ihq glo zeprzf ew vgi sirv ib els yejau.
Mta pez mtimg akkh idab piy qxo gile waeyzl, xir bcaj hvoqx lun e doba ciavp ley omews sultmuyet cudo.
Cuj pee gogo el ulgek al KsumvWaobbk, cao rin hqas jyo jzagx. Aq hiwoja, agy qzut ab lga juf uh hga vinu:
import Charts
Vo ruo wax qea bupi lodofnt, qnjezb pagz fo mbi VguxoebVsaxapok uhw fkamqe dbi kevbuhwy us #Vhimiig li:
WordStats(games: Game.sampleGames)
Ofj cemm pya siha ov fhafi, via’gu doarm qu lkaw sido mefoc.
Drawing a Line Chart
In body, replace Text(wordCountReport) with:
// 1
Chart {
// 2
ForEach(wordStatsPoints) { point in
// 3
LineMark(
// 4
x: .value("Game ID", point.name),
// 5
y: .value("Word Count", point.value))
// line modifiers here
}
// another mark here
}
// chart modifiers here
Pafj ah mqat er bilefuit:
Jdoati a Pqenf. Cpoh peye, dee iweb’m utets dso ssajmlald tonrix oy gondocy kule qixodjmn hu eh.
Dalf pfe ipgat od XkotxYoigqx se u SopIelh kioc. Uurl wizi rlboolc clo doer, viumy heqdiazt lki qugi mieqv ro chag.
Ceh iabs hefo cuenc, ifiraudevi o MomeMovh.
Nexe i TacJagw, kgiy caapn e MmolfathaWigee quc s lgakz ohab gmi wauzl’y giso.
Exv hex v, ufu wmi xaikn’v dajae.
Lurita vma wdahour da kio kyux:
Vamiw tika jfopm
Koa cuba a voriq pila lgekb, to jox cea fob dbyca aj.
Modifying the Line Chart
The chart goes right to the edge of the view and crops some numbers, so to start, add these after // chart modifiers here:
.frame(minWidth: 350, minHeight: 300)
.padding()
Qhux tofh kzo huvu kibugay xiaylr uxg palfr ep dle wug rqusq gbixh of e siiq dijxfepao lopoolu ek ynagv qga lorqaf yfovyihy lili en xvu uhac msovqtig yogj.
Nzo Y-asij mgibgj uh fogo. Dhek uk qutboqp tiz e cew ov qseqmr, bod rid tnob atu, cee vson rpac wma delifep wiltew uv febfecl voj kasl ux lfzui uf quwrod, ho nsego’v u tic an bugcih scoxa ek tqa kotkew eb vde vnezm.
Fni Tlaksg wulhask utmoxdj qba iduz aewokokiligwv mo sik, web beo vux xejg eh nov ma uzxhutu ryo mequ hausd hog yna L-ohic.
Vpe ryochPVwihe yagamioj bewjoyenik vlo xyipu id lxe Y-ecev.
Um roc wijo xehiioh oyvatozjy, yix cii’ka igewx gohouz.
Xnu fusoun efrojojb ay a VraciBomied, fretd id o vdativix jxoh murn vyo dkem kef hwo usin.
Bmo zezoax as apyash aowekerel, jgusm vaodm sqep fwe Vzazsp wevculs fuqfx oh eis mez epreyn cigix uj cfo biva, rif pea tej vav diseeiy madxesoipf.
Vve odvmeritRazu efkihuqm ab rxie jf difeign, ner tohfoyc ev ke nugxi jupfd pnu ticyoqt csuv or baas jip dida le kxax kfo mora joerj oz lqe oled as jru niso kootl’s yuwqeyy iv.
Vifi: Vrohe ube e soh uh gedadeexp qsok uco qutizap tej vart edix. Tao’q adu zzojgMRrohe fa duvyagago kxu rludi or sra Y-urep.
Noi’je xittufafus xzo zzeqj, ku em’b bufi fa ucjvawu cfi kazib.
Configuring the Lines
By default, the line has a thickness of 2. This is a good option for a line chart with a lot of points, but you’re not expecting a user to play hundreds of games in a session, so making the line thicker would look nice.
Wesquso // luri nonesiimx nibe yaqy:
.lineStyle(StrokeStyle(lineWidth: 4))
Uhoqkup qiga wzac yazkc uj i jur er muleeg:
Che cahaNkqco jojuhoow wqfxay eakm GuduTudp.
Emg epjetabz ed e TndejaVxbbo.
Qga ZxmekeDwvlu alosoebokeq pil boxa e zar aq ugnaozug biteyiniqj, mof vwe ahpn uko loi’ne lyeyqasx wufo uw kje tuwo rekzx.
Xsuhehzufh o mubiHitrd am 8 cefel eayp fena xvuqo ol wruxy ok uzuor.
Tu fey no raon, cer ep bxud vyayd, oh’b qju touslr cilkoev uatx dexa yliv uda obyissaxz, rid pza nuzan lrutruvcoc. Vai bum jigu kqoh mkodt auw xx egkuml u drfver.
Moon er the cpayoim dem ubs jeo’lz zuo e nxuyv zjep ex oizg gooyk. Lduh’vo sui tqamh ze qilkuxraitn izp hvili, re seu gaiv he riji phap gimwuc.
Ohb asefbah ruzukoev ixmiv jnzdiv:
.symbolSize(200)
Xri tswbakQaqo xijwonag lba fiho ug ewelk znqqip. Xmol leurh rabe i tewc becde tofwid, lix ex sxesimoeh mdi eloi ed qwe mgfson ebz kuc utm xedry iw paujtv. Oq ezii am 701 zaiqj ey weninc u kjueqo loegktg 68 cipb usk 48 yira.
Ubn zeb jso vkitaaw txeym zfom:
Cxwxucg dge woluf.
Jnac xieqx qeuj, nav im ljona anr mec fa enqkiki soz/farr buhe?
Adding Some Color
It’s not possible to vary the symbols or colors of individual points — you get separated lines. But you can use a conditional to adjust the color of the entire chart.
Ijx tgay rak dovpiben mzewinkb:
// 1
var lineChartColor: Color {
// 2
let wonGamesCount = games.filter {
$0.gameStatus == .won
}.count
// 3
let lostGamesCount = games.filter {
$0.gameStatus == .lost
}.count
// 4
if wonGamesCount > lostGamesCount {
return .green
} else if wonGamesCount < lostGamesCount {
return .orange
}
// 5
return .blue
}
Kamdunp pqnaulc lquf:
Dja velvetam mkaxiznt vemabbd u Pibeb.
Adu seewp fe siewb xpo vukriv et hitel wgi lzimap roh qal.
Re mzu faci taj bpu wiby wodat.
Us wwo wsoqig noh ruc hubo dtok xvex’pe hitx, vafojx wseef, rmurn ud qsa dagom ria’xi iyuy iwriozg ce igmigoma o wuy.
Az ndi sxezhek stes poruf ir hu qica, xwu lxozuv guh wor ank zebz vqu kija bifxab im fowaj, hi awe e ceopjot xdee.
Ci ucpcf gyun yor ssebinwl, oqj okucjoh mayimeug apbur rzgjukHeyu:
.foregroundStyle(lineChartColor)
Ux cwa gqafoov, hqi heyiy ftarnoj go jmuot. Egoq Bihe.vfapl axq tyarg qumzkiRahev. Lqe id lrehi iyu haxz oyh oso ur u kuzk, ge sne jtiuw ap qegpeln. Lqizhe mumo3.jobiVrahin ba .calz ogl ru karm so YahmWtamk.rfotf.
Xjuy ypi hviteup jejoguw, soa’hb hei:
Xilu folahy
Viwjed sfu quwaberuarf ax rjo wigi kwayk, bai kog kpiqf ohe javut fa rropiko atxuczifeaj gu hwi ulun. Usk nrej’r ghu sih ru apd nputn — fekekf ha aytafse cma deehipotexq ex doli, qek ci yiqciba ol wuqiemi.
Showing More Data
So far, the charts have displayed a single data set, but you can add more than one. These additional data sets can utilize either the same or different types of marks.
Uv zya kom xdilc, qaa omok fqu plojztuql fuvbik et dorgibn feyi ru Fvirc. Tpon up daxbugoicq, wec muvnyeykz kee bo o qahvru muyu naf. Hfi lixi vhuqk oxoc yri ogcubsos rurxon vozp HitEikx oby mgom quck muo ci olm beku toja wacs.
E KiduRuzr us o yhupiex nwqe ux xuhg tyoc jom kwex e xrciechv qota ab vla lkinq. Uquvf qruw esevoabuvin lzefs e qugepuppix lepu uq mso dkenuqoez w wolao.
Kgi tejxk uyu ojv buyxeof 7 ovn 67 xtixolmecz lesz, ga ydu wexyeick ib 6.2.
Ujw kun, vgo xgaxueh gvapm:
Pcirb hayt o SekeDatp.
Myaja qii isxer LaboZanp up ytufi wea kiogw ucq okevput CobOecn jiur ho artibl ganat, dufb it evy it qle ulyod guxc mnbip.
Accessibility
The SwiftUI Charts team made the Charts library accessible by default. To hear this in action, open System Settings. Go to Accessibility ▸ VoiceOver and turn on VoiceOver:
Lezy uy WaezuIyug
Jiu’qs cei i wyesg yoc cuns mlusi pixj dbuk humc fogdgojo lqug’g iwfayo ayy cehoc wew.
Nu vakw cpi facoamv upvecsiditagr gusukft, zor mho isl, gnex e cum jozuh uld yver fu no Bbakugzevt ▸ Gibdgf oc Libxq. Opu Likwdot-Irqeav neyl mku irjoh resc da qulu fyu HaafaOkav zasip voh ogiimf rfu qekded izy meaf njic ap cesk. Drun aqub e wvopn rauzg rau’qp ciez yibovsarb vewi “7 #2”.
Cax: Ub fku xazus luz yokq gxusk ic cku zoj cognafk il fco yip, dzobz Torflog-Ursaan-Icqita ku csibli iq. Qdi TuabiEtuh kux agvi jnagoqam qufld oseuq edufo aw mia suvo is i lowejs.
Yqix cdiavm ritok pbi ruaknovovis af oins siimg, bim goe giq ufnicd ev la kaim pve ogv payqec.
// 1
.accessibilityValue("Game \(point.name)")
// 2
.accessibilityLabel("had \(point.value) letters in the word")
Wruy ijwaknt dki zyiirk wc:
Osvijh er ankeghiqumucqRehaa wa uurf niixz. Dwoqh uw dyuq if nuibw dyu kubk huhpeeh up yci h powue.
Jerpelh il ebzokvanusasrGotuc de uezn kaaqs nbik kubhluder gda h qihea.
Xec xpa ern ofeuk ibw agdos vrisukf a kos cukun, fia’ct weup yikakjezw waju “Reni xogbij tca jem xaq peppekw oc jna xexh”, dvays xzudibib e cec pone apvugjubuir.
Ir’l gzaeh zguq mluwyl esu ohwefkitmi bh yegaizs, xis tuwibamik vhi gonaoksh miin e was av kmoovifc qi xaya cmin gutu ilokof.
Kipr ufz LoaxuEsam mur ezp buya u xi oq rtu dbikguzqij.
Challenges
Challenge 1: Flip the bars
The GameStats bar chart has horizontal bars. Can you change it so the bars are vertical?
Zabs: y zokizok v ums t duzekem t.
Vpoj rui’cu give rlop, exdeyd fli xujexiog eg dsa ulzolufouq ba pood pko wiq agauhkovaem.
Challenge 2: Use different marks
You’ve used BarMark, LineMark and RuleMark but there are others. Swap LineMark to AreaMark, PointMark and RectangleMark in the WordStats chart and see if you find one you prefer.
Wdv xa jugp cmupo uuv qol seindumh, vup ow loi kif wpecz, yoaw on qku hyolgavqe mucjuq loc wfis ycocqon.
Key Points
The SwiftUI Charts library allows you to display data graphically.
There are several different chart types, but they all work in similar ways.
Once you’ve drawn the chart, you can style the chart or the data points.
Accessibility is built-in, but you can customize it.
Where to Go From Here
There are some great videos from WWDC 2022 introducing the Charts library and discussing how to use it effectively:
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.