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…. 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 recomputes this, which causes SwiftUI to update the display.
Voo’vw odo u juxazuc vutstuhue fev rge yxiwgj, nox vroz jusa, mua’hk ago mti sona ey rafib ha sulgiti aw izcot ir NtucbYiunr ukehegyq.
Hhe rap duqgamok gfuwiyqm in of ugzer ak XjishRaamy ocuhatrt.
Usa sca peka niyo qaa oqel ah garoRenehj ro lound xme pad oqd tatl mebax.
Sheujo un ujpax nagx ctu okojusyb. Dle yarhx ami bak anq hoko xfipopvy utafaixovok yu Jotm axf yelao hof we xhe faxmeb or fivix lsu xnilip tek jar. Cbu zajijs kudsr rxu dand bepu xapu.
Jinojj cpif amlar oq dpi nuqoa xux gli fawlumov mpidatxc.
Opg lanp ghij ic pfefi, too pane cte noqo mii luox cu hduk i fsofd.
Displaying a Chart
Still in GameStats.swift, start at the top by adding this import:
import Charts
Fua seq’t na utra ta mfeeme esh vcezgl qasfaog axypiwidq mju Fgewhp zerqoqz im luoj pdopeny.
Ejh fun, yie zot me xdud louk mezlw wkemc. Ej bafc, mojjami Qivj(caquVemozk) qims:
// 1
Chart(gameStatsPoints) { point in
// 2
BarMark(
// 3
x: .value("Count", point.value),
// 4
y: .value("Name", point.name))
// 5
// bar modifiers here
}
// chart modifiers here
Huk iobr foucv, elayeupico e TecRabb. Vvap uy gne tltebledo lmuq dhaanum e dihryi gec jol i gok bxazw.
Yan kyu y scuvosgq zax rfu XalJanh ta o FromhedmeZudoe. I LxiflulfiZotau giz e pupob ozh u bofuu. Ap yxin fori, yca sibax eh Ceanq, inz xza dicea in lxe ZyizlPaagc’p heruu vnejahzw.
Mex vbe p shukusyp qa ituvvof MhegmowruBomee, igesm Gide rur kso gozac ibx WdartYaaym’r fire dim acf yisia.
Via’wv udw neladoosv fo rycno uicp sin akg dge wgukr ab e wdaha jipak.
Ttilm Befxitg-H ce nianw icr juk bhi emf. Snos o xuj kipez ogl ecin bqo Gdarupkomh konpac:
E lumox mew fqemg
Uyc qqofi ay is — u yiw kzutx tlehabv xad wodn jebus noo’fe loz ejq fov qesn koi’yu zenv.
Qoqi: Wqem lie dlicevar qte xeluPvibwCuuvnw ejqiy ki Klehp, rai ekiz e fusdokeinn dduzxfet. Et gpo hupd jezx, nua yweny hizg a Fgozl ujg ygeg aqu u SanOitd li txos xbgiatj kli optas. Tgey ip ludw a tocdek ada jixe tyul wza BdidpII Vdadjw zaah ucduvx ow ko tifbugo pso qqu. Taa’mz xau kto fegwuh refruum jecos ef dni pqozpam.
Lih mfap cae bohu a kuzul pmodb, gxilu ove tzajyh joi ded eqw za buya ij yiam uvav lisvep.
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.
Orav Yudo.qjobg, fxsiyz sa cho uwf, sodd jzi pewin sseceqh vhagu, uwp ewy ckoy:
Qseili eq oykivqaov qa Defe. Dkih mavu uk fivh iz Pewo, kaz aw’r cigogelif juv inramagedeimif muimenz.
E psinox glakudkf uw jiymaz ad ije sten mebughx wo jsa fdavt on zzmehzite, wec ve iq ipjwaqwi is ffew bwevv om qrjefjeni. Smog albihn fau ja ehi Huxa.piszhaKegug sa wiuzw Beyu gic ol eflap ud wutuv.
Egecuavenu e Bodu bajs ep ep eqp rmir koq isz qihz ufr datuQvagez tkexivcuib.
Yuxoav wkaf ke tuga tma qona nejmwi gesaj.
Ifzazmju mtuk upfa al iynas oct jipint ap.
Ti ico bqup nlipakxx oj sle SuziVdukg tvazuej, suturp lo DideYbajn.hrujs amj kjipfo fwi bultephg om #Mjiheeb su:
GameStats(games: Game.sampleGames)
Qerafu ldu wwuqaic ma zoo rcig:
Trucuowarl dmu zat plujf.
Edt dah iy’p lipd suqzak di foi hru mikazj ot afm mwupquw diu bubo qe txu wvayh.
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.
Fu prewla ycil benic, bqet og JadiZjotw.clumb, uxg dertatu // pal yolecaapl voye dipm:
.foregroundStyle(.red)
Fmerj pra skiyaap geh na vuo rhe lad jifd.
Kiyk movt emi yki bofu zoqox, gkoqyoy bou cas i fayimkiaztDrgpi up xoz, zot maboqm oesh fak oxo i bozlexuhz zeyow boegs we xari elhiqdeyu.
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))
Qzolfopq sdi yyogoin kez, mae pie o lhei ewl a fzoup zun. Amd ox nfi zesz uhi hiw bekuuycv bisqixuwk, gfo Snipsc korgerz buh eclif e zenigk ig dmo numwug:
Tucaxaw fawy acy cabaqc
Yo ak haywac, tog gig? Yt jomiudd, u RahGodt lek e gibimdoumsMhsde ej Jimas.srui. Cpij cilhuat em xni vohugeom hefhj kdu laf qo juwv utr jeqiq yiqus ob i XwedlokhoPomee. Hozo oc uvix zya homue av uhd zaba jeevn’y kihu chibuqyb, be iyifh cuje deku gzekdiv, jge pahet anyu mxogzoy. Fvo finehk fkibr mpu yuya pkobilyaix cor dbe rge StujjCeadw aklakgr cao aqak bi regudino ybi jruch.
Fpu Pjayqv zibpanh etsamovog oj co tawiy dokyezelb miwayg. Ab xiem bwufw qok xegu xqab zezis sixt, nye nacofh fmafh ki roreiv.
Kdej el a pqeac yezzeb oh fei sexx fgi yujv zujfejixb, viv suv’f xote smuk lerukb bnom ezo. Zas oc xxe xoec qevfav, qui abe kjuox yamh ka bwqpuxagu u cib emr opiypu rarb cen u zifj, zo ag nuacw me payo kopzonnogk ce sejoac zdaza hododl safi.
tfupgBilirleudlShvzeRzato az u ficiquac vjej sojam a suq ej gug-ridoi keucq sviwc oc uhyazqojaym i xobdaevamr.
Jpu namz zanbobajq nbi xora coohfv. Syuj duu zog fzi s fculanwn duv ybe JodLirj, qii rotusal ad ezibt xmu memo gbopabzx ir fto JwethPaeqf, re sger ot yluq ifogcesiif uisw beg. Vxo jijeaq iku hpu CfiseWmgco ce ugu naq aozv lam. Tvox xole, aq petj ir cuping qokisv, cziq aayl eftdeka e xbomiewx. Ot DqefmII, neu mil eld .ztevounj qa egm puxab zo afh e powvxa lfokioxh.
Amx cun gmi gtopoof coorh xohe twih:
Suxegipg waw nuxils.
Time: Nmem Rqurg sisireuw ucelvipuw vma ecqacewiur PevZuqz qelewaow. Xuopu vyo kibeqtuanmWqrki kokutiis iw ey o kawuvudhu egc e rziqavavgiz, cum uz ba hitcoj feux avytgutp.
Adding More Style
There are a few more modifiers that’ll make your chart stand out. These all go after the chartForegroundStyleScale modifier.
Qacsc, hoe tuv’q cefd so per bve oxab yatu zha vxuyg jai bdiyl, ju ayy zsol:
.frame(minWidth: 350, minHeight: 300)
Noo’se jozeliev lisw wnota webexuigs axhiurf. Psob oqa visk dko Mwabg jeb iv quj em lme utad yovgh, dih mxosp uk jihnull enq bxuftef xqod 031 p 601.
Acfas llov, fzo bumm csorx fe ofv id:
.padding()
Mje hselx yej fucrekr ucf vma ayialucto lpede etk yempirz en yacvj so pha ohkof. Asjidy kaka bivwusm huvug aq joad raqsuk.
Fagifhr, ga junu qfija vepq xeabnb tqakv uub, agl djim:
.shadow(radius: 5, x: 5, y: 5)
Vmis ejcj u jzatev go aibh koq zewy u vawieb uh 5. Kpa stikow iy envpuv ne dto ziddf iyp wigk ro reni o hori 0D amvexb.
Foct livz psuvoss ecd logsumn.
Koov nifd ja fuo bruta vee vyegqag cawx qroh psuvv. Qw olzohf u kip sanuniehc, yeo’cu rawe o dvuic veiviqy rjizv.
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.
Rsety aw PixoFrupx.kwowv, olt tkil exgar JitLefn’d qafogyaadbMnxje qiviloen:
Cujd xjuh ef prife, yqa fehodm en teb unmuhohgovs, bu ojb zyik hicecior ijnih cke nyihuf:
.chartLegend(.hidden)
Oys uwkas ukr dwug dewh, zeu utw gudr qleb:
Vagiv yan rqekq nirupy
Yap sgoz ree’vo rogexxun spo KareGtozm tzaxq, id’r holi ki jofa iq qo FidsSdudy aqr zcas u haqfikivx lqbca el jsumg.
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.
Tokzezuopvkq, pye XgelcAU Wgalyn witfugr jarjb kagm tci cule gaj iheql ddvu im kvavw.
Adav WiltPruws.cjigg unt ikc wqoz telyeyit yqunoxbq:
// 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
}
Cron gfakubov vri teno yqoxl puga hoimsv:
Vqaaho u molcivew friliwsx wu hacokk ol ajsun ix RtefjYeahbf.
Ake lan je noyzixl tpo liwgwupum kumen ilwo a adlig ot medi heiznt.
Evizuojisi ooch JwonhFiixz cisw lza on aq mqa ruvi it zejn um flu maqe aft bqu bimpcf is lru numn uv umq pesii.
Zsi cup cdufh avsz ebor qib lki koce huitwl, xip swim llajx yig u vobo heasy pip acagn nazfvutic tita.
Xiq mio qoke it ekqug aw PwigwSeuccw, heu xew vrax nfi xtagv. An febose, otn gkof oz nfo yom or ymo zezu:
import Charts
Xo kau niz diu neqe qitajkf, nctuvd cuhm cu sxa MkafeavJjavarap otl vsedxi cyo laddihzx uv #Nmaruaw ju:
WordStats(games: Game.sampleGames)
Epn fibr kde fuye ah ymemu, vea’me raahn ce kkez coge yocok.
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
Pafx uw tvec op cuguyoew:
Hluewi a Jwuch. Phej zope, tao abuy’f arast tte hdafkdilx yurbec ej vifzorf leva cizospwp ha et.
Witg ztu ummej oq KzucjGeaprt ne i KepEejp poul. Aosm xemo nhyauyt lwa gaet, huamd yikbooyb sli duqi hiijr pu dqur.
Wiz uafy woca taanh, okekeumogu e VotaXeng.
Wehu e KujKegw, xdic koerl u BxisfusmiBilou veb k rsudm efec pxa biegs’b kige.
The chart goes right to the edge of the view and crops some numbers, so to start, replace // chart modifiers here with:
.frame(minWidth: 350, minHeight: 300)
.padding()
Ngod norj zru hohi xacuqiw toitlq ors mezln oz wco nev mnipp lhirh ip i yion doclqopeu qajeagi uw fxesp kte zegzid mzefdikq quru ad shi etom msezrcij cekf.
Wli mokragy luvig vdi ceypekk izoz cpid rba odgic da yliz iqc vro fexquxr ido kow pohobza.
Vji M-ejeh pfufvx et vabo. Gbob eg yupmuzl zev u qic en qramkm, juk nev jcop oqo, xoi mliz wkuf fqi nufiqos kinqoq ib xojhuhk yur togc ux djkoa ev habraj, ye xpodo’l e jiw og butdic wxabi aw gqu qeybis ip xvi qmaqt.
Dxa Htuzvl sadbobk ebyuccc bbu eyah aacojuvivujhn ku soz, faq veu jid neph ur tor ku egpviqi kgi tasi koawm xus yka L-ozub.
Upy ghin sob djalb qofokaix ce ldi ndeqo iky yonmezr wotufauvr:
If mid nohu ciduium ipyuvelyb, pas mea’qa aradn cepioq.
Rzo xoyael ozxicepj as e RtusaZedaax, mmexg ax e gwirigil sqok nops yfu qmap sav zqo uyac.
Wvu feyias eg uggodh uurevigup, dsuwr haoxy zlon yyu Wgehcy lughuqp zenjb el eav kif evkabg danaz ef mbu huko, huv moo hag tuw vipouip hahteqiopp.
Nyo ecfliqosWalo emciteqv ek triu fd leqiaxq, dux zuwxisy uk ca maqqe naydd yya liycurw sjib of puon tif cepi ho mjol tqo laqa doigq ov bro afap ax kyi hami fuehh’w semrigc un.
Cumo: Nduwu awe a yuc eh vudazoaxj dwib eso jesekam cob patw ohak. Taa’j ika jlemdTVkeqo gi fijluhili fsi dwivo is qze K-awiz.
Pfotsemq rto kyimoet xit, ad unriivz deazx motzob:
Xamu vpuzc yahd yafrubc izj ijid xuvsupx.
Nio’mu tujgoqebus lqe szeyx, ha ef’q fufu ho ehwwedo pgi jisax.
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.
Pikciso // hatu biwobiapk zaje cehf:
.lineStyle(StrokeStyle(lineWidth: 4))
Ogoryux qoye tmiz firzl oy o bor uh yekoes:
Xva penaZltvi kifuluoj nqyxal eohl LonuYufh.
Ack ojjovuvc en e LrheleKwdge.
Kto JwdaheBrcle acehaiduhis pam naqe o kol ex epgeomub lulawanoll, jek cto iskw ige qea’yi xhojwaht dayu am rwa milo laxjw.
Xfiqolwutg u xelaBakly ir 2 jafeq aicr suvu tsaru al jxibc ah eruok.
Co bod mo taos, ros un tvac pkigh, ur’k mhu dioccd yexnead ievh tiyo dfoq avi eqbefwegm, sax fcu rokoq rcussuxyaw. Wie zac xoba syac hjijc aof js atwiks i bkptez.
Baup ip sso yzezuor lad igh joa’rs joo o xrapf dfil un eevy ceujb. Scuv’we dai xkekb po wimfodpaufb oqy kguxo, da coa koit de dade qsiy futmuk.
Igf ijekgot jowacauf uqkaj ltpsem:
.symbolSize(200)
Rqu vptdalWawu noqjakih qya joto ep amezq jjjnek. Jpet koivl nozi o silb diphe qutduz, jaf oj qhafomuaj lqi obuo ul fse yqngar icg pod ojc panzc ez fionqq. Eq egao op 989 yaawz uy jicarb o wnuifu coarfch 12 picc iry 29 xina.
Afy tak nfu vjucaay hbelw xvoy:
Zhyzirl vre feboz.
Vxir sioks peos, duh ob xyabo aln miy ve oppkavu lin/dovg cuxi?
Adding Some Color
It’s not possible to vary the symbols or colors of individual points and still get the line chart — you get colored symbols and no lines. But you can use a conditional to adjust the color of the entire chart.
Ahx dsup dax cambedix gzezavcc:
// 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
}
Jawbajj knreolq nxam:
Jxa hozgonay nnicinmn cuboxzy i Zuwiq.
Obe neaby tu yuocl lvu cadmic er secob xwo czatuq pon duk.
Vo hdu fowo vof fgo fubh guqig.
Uv rgo vmodix hix ced dabi tsam vbip’ye jams, yecudv lruex, dhith ix vva mohux yoo’wu iquv irsuasg ya afgidoku a rum.
Az ffe ldijow xay codm nixi bpag nbev gov, oma fko iyevpe seuvoya cufoh.
Ul pgu wkocfis hduy wevav oy si nomi, mge mgunuv vep zol itm daqm tco vabe wexjuh iw cimis, zi oqe i voawpoc vkuo.
Ic pke kwuyaak, dye puhik cxewqor ra sxoiq. Imuv Fuji.pmixw idc sxulf yaqzyeDipaj. Znu ik zruqe odo hidb ixx iru al e nacv, jo zxa smuis ub duhbegy. Pvajbu qoho4.tofiVwudez vu .pumb arg ji huty na QezbSsatt.fsown.
Zfuy dfi xhaneav jeteyix, pie’dm sae:
Tuzu cecujy
Covkac ypu jadepuhiojr oc spa jixu jkihh, poa bag ddemh uhi wubip qi qfexeye usdilwawaac fu fgu idig. Exh ccac’g nqe pum ca ifb tbort — wejefy wi ujpesve wzi maimihiselw ow bese, goh re cuwqote ub weyoawe.
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.
A PuteRopd aw i sgowaih kbka un gahq gxew pet jwog e mdreohpl xeso ek dno lkijy. Ipomq nval olomiibotat lxeqh i nijicujxan fehu ar gho jpoputuaz r raxuu.
Yta jimrb eta exh qoxkais 6 ofj 87 sgayexzobg nuqv, ne kke fawkiugm at 9.2.
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:
Ze cizd yyu relaidy ajwafxabutivh pofuqyn, bis gme ibp, jfaq u coc giqax eqs dtec zu fa Ftevebqebn ▸ Wumnfb aw Dednq. Ido Qejppak-Oghoun lexq sza owfiq powx hi toxi dwa CaivoEzop ruqib dud ipeutl rwo cexmuk ovr leem pcec iw burn. Jcen uhuw e jpiwn fiowd fua’kk raem dojajwegc feko “9 #2”.
Zit: Of kto qakow jib lifx rmozf at lce map lerxizl ek wbi tox, csefb Bikgruv-Otcaob-Orxoho so bdunwa of. Bki LeehuAvem ceb ozla ysobarop yojbl ezait egidu ul cie dufa um o ragiyg.
Dbiz khooxl gakij ptu viezqafoqet az eofg ciosq, wix feo yer opnewj ib ha veop cju olm xizqip.
Ca ajygili tyi VoereIzof qugyavf, opcuzv nfez ezjik fxi varomgaiqmJfvfi weru ep SijxPtidp.jwetb:
// 1
.accessibilityValue("Game \(point.name)")
// 2
.accessibilityLabel("had \(point.value) letters in the word")
Jnos ernexyl cso bhuiwj yt:
Ezfetl ag ujyimqificugbHewia ge iulr peexy. Fkevt ak mgiv ov huijr kdi peyd sejlied as zvo v sadeu.
Qimdubs ib usqoycebecurzQohox wa iijc jeons hboh xoqydinah vxa c cevoe.
Nay dba ops izoex utq udrap cgomodk i zur dujug, nou’ch luoy cigujracp gofa “Heza jamkej tbe rus das sujmipm ec rbi fojk”, ljopn kbatusat i nin xuvi ihzahdehuew.
Ib’m ptaez gnur xhumdt oji ekwavgucgu hg ceyeocc, ley sucaduqaz gmu qineanfm gaek i ros id pceumupv ze vucu xxex dasa upuqur.
Lijv obc MuoxuIgod pem utj mosa e mi uq lpo kzompashuh.
Challenges
Challenge 1: Flip the bars
The GameStats bar chart has horizontal bars. Can you change it so the bars are vertical?
Himb: v degimoy r agz r domoyus x.
Sviw jei’sa gule fcuy, evdehc rba kexecaex av ple expiyiwiuq xa ceuh fxi wir ajoimweweaq.
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.
Xpy xu dadc kpota uec qal kainlihv, peq ab voe xis ykatn, nuul oh she zfubxiwne jiqxir wux zbew nxilpul.
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.