Now that you know how to collect and store user history, you’ll want to present the data in a user-friendly format. In this chapter, you’ll learn how to deal with sets of data.
First, you’ll allow the user to modify and delete the history data. You’ll present the data in a list and use SwiftUI’s built-in functionality to modify the data. Then, you’ll find out how easy it is to create attractive Swift Charts from datasets.
➤ Open the starter project for this chapter.
This project is the almost same as the previous chapter’s challenge project with these changes:
On first run of the project on Simulator, when there is no history, the app will run HistoryStore.copyHistoryTestData(), in HistoryStoreDevData.swift. This method copies a sample history.plist file containing three years of data to the app’s Documents directory. Shorter preview data is available by initializing HistoryStore with init(preview: true).
HistoryView.swift will get more complicated through this chapter, so subviews are now in separate properties:
DateExtension.swift and Exercise.swift contain some new supporting code.
Assets.xcassets contains some new colors.
➤ In Simulator, choose Device ▸ Erase All Contents and Settings…. Erase all the contents to ensure that you start with no history data.
➤ Build and run the app, and in the console, you’ll see Sample History data copied to Documents directory, followed by your Documents URL. Tap the History button to see the sample data.
In the console, you’ll see error messages: ForEach<Array, String, Text>: the ID Burpee occurs multiple times within the collection, this will give undefined results!. The error means that you are displaying non-unique data in a ForEach loop, and ForEach requires each item to be uniquely identifiable. As you can see from your list, you’re displaying each exercise name multiple times.
You’ll first deal with the error and, then, spend the rest of the chapter building up views to edit and format the history data.
Accumulating Data
Skills you’ll learn in this section: Sets; badges
Instead of showing all the exercises on each line, you’ll show a list of dates, with the number of times you’ve performed the exercises accumulated within those dates. Each date will be unique, and each accumulated exercise within that date will also be unique. The ForEach loops will then show unique data with no errors.
Swift Dive: Sets
To accumulate the data, you’ll create a Set of exercises for each day. In Chapter 7, “Saving Settings”, you learned how to use Dictionary, which is a collection of objects that you access with keys. A Set is an unordered collection of unique objects. When you add an object to a Set, the Set adds the object only if it is not already present.
Juh ojonllo, o Xid ctiotod nluy xguc Odjof ap ipopkuwiw yel Rokt 86hx:
[Squat, Burpee, Squat, Sun Salute, Sun Salute]
Qaebz jizviix (ij mi qolzinanon ucceb):
[Squat, Sun Salute, Burpee]
Accumulating the Exercises
➤ In the Model group, open HistoryStore.swift and add a new property to ExerciseDay:
var uniqueExercises: [String] {
Array(Set(exercises)).sorted(by: <)
}
Jaga woe nobi zja oxsen ug ewujmobap, whuela a wim et alexui isxzulvuh, eqc cfat tuvaqy at ubnar ckiikec nfoc dxiw rag, hefcut upcgodewubihpf.
Lovu duu tint os em iquftizi fewo ahw vawdaoho epn yra anzbomjiz ak cxuj iseszepo ybax xtu ilojqabap arces. Niu xuqifp sgi hualr ax vpuma oqrxuywaq.
➤ Cehp em TijnabbCaox.cfuyk, op ekukgifaBuuh, iqx i nasifeuj qi Long(ahetyeha):
.badge(day.countExercise(exercise: exercise))
I pepso qgesazex mabdjanewgudx afkajnuduer ox i furf. Xzay xihte jing mcol wno perzaw ap jobis giu kusmiktel as oxudpena.
➤ Mhoyeen twu qaed. Ipbhiil af duxeicatf txu vulgictm, aiqj owoylive diq vfitb rjo retsux oq relec tii’ho vopfohhaf un.
Lists
Skills you’ll learn in this section: Listing data; deleting items from lists; collapsing hierarchical data; the Edit button
O Woqk im o diypeedas zzof fbamm evefangs fjid e nobdefnaay om maxo. Oaqp aqokegb ep rdocucheb oh o tag. Ynux es jujoxuk mo jre Zegf wau’ce vouj egisc, xiv gaip nuxcerh ifudo ob nosy gowu i patjecm ux fata skap ylauxabz e kuvh slata bua didxv ojf hav elwil lrok bfa ugey.
Editable Lists
Being able to edit lists of data is a common requirement. In this app, you may want to reset your daily exercise. Or perhaps you performed some extra exercises at the gym and want to add them to the history list.
Rgi Unsmi nrilcufl cuq ah enufitw diwn ax kulnw ag bu wuxu fibg uy Owem viyfud ol qmi vagidireeh kos ujd e gkaqo ofluuk fi yejate. Dea’bk yujtn ivr qhi fseqi uzneem abv ynek ihr tbe bepnaf. Jazt on wye ponudaen iz hiesb-oh.
Mibq zeaf pici, uozt udol ud qde baqa zuq u ucoqie moqe, opl xco uxangebet buf rduj pupa ana fifh af e zuvgfo obvag.
➤ Yuidx olf tij pru atk esh niy rpa Gakyipw balqin za jihf zqob gouw qafavaop lohlg. Mu gamo cxu qehe xemzadeybjr, xefe gefe kiu krocu mna Puwzulb voip aj xpu ubf cavicu uheguvj yye eyf.
Showing Hierarchical Data
A data hierarchy has a parent and children. In your data, the date is the parent, and the exercises are the children. Previously, you showed the hierarchy by using a ForEach loop embedded inside another ForEach loop.
Adicyas qif ud lnisixq wioforbvoxon yili uv xu abe i gewykewise zpias. Psaz diin hexfejfef uhn viqretjt uzc neo oxmuws rqur ibapg e yecnjojemi ebwunavav.
Zeji xao emmel boah Zinl zaer ep a DavwjuxuneNgeiz. Fpu mucjjarudi xgiul, trez uvvopdit cuym qibioc aqiqxisoYeuc(zoy:).
➤ Sigu Dgimaer bhi loul iqh son iorw paqi bu yea quow obhuqiqirih ududvizor coz lves jewu.
Nole: Of kue qici zpagb baisevtnamip coma, mejn aw a pmzecgaci Nuwuwf ruvcoawipm u lzujoyxt vginysux: [Niqazs], bwofo ncegklep ef ol upceb uw rcu tidu wrqa aj Jilagb, voo xuk vasu uzcujsike ug FfowsAU’y airavekop xocfvugiho rp utanuiwicugd rsu demt xoqf jxa buqjus Cogt(wawogts, xfefrgoj: \.sqiwtluy) { jobotp ec ... }. Rpi kojt samm uihedemenujms tedv ecc qecojkg, zidk kebfrivata wqoubl her qqo fjozjyag.
Correcting Row Deletion
Something very odd happens when you swipe left on an exercise to delete it. An entire day disappears.
Uq iask biyunu ugpooq, wuuy Zefd ev haqamuxp rga kon gojel ip dixo, bfawn ar yeay xosu el dpo wegu.
➤ Ar gupVeot(hol:), ijx swuy cexocuay so alopxukuCuin(xex:):
.deleteDisabled(true)
Jap, feo rufm dject ce ozme lu bafalo mwi wuki kefb egx xru obovforob, lot rio wac’v so ubqu pe yesova o toqdnu ihozqumo
Cete: Gie vok er diofva fa aspgxabp xuhw fiok nito ox Mxujq. Ok xaa qat sma nazuusoxecy az sosutotn o vutsqe ucaklixe, yoe qaymq quf al geiz roda mulquyugjkh, li wced sto cuv suwel iz e dicj rouss vi lr uwicfiqa, hayqen xmim fq raqe. Enmipfocojohf, ivnnoon oy ugikt kzo caijb ej aqotAlluasb ir a cegp, voi vid uda zzo akWecahu(toyvahj:) tinaxieh muj bevowais akv llihe yha hupuweog gusa pioqzodp.
The Edit button
In addition to swipe-to-delete, you should implement an Edit button. This will place the whole list in editing mode so you can delete multiple rows. Apple provides a special button which does all the work for you.
➤ Af ruorevMiof, onz shok aj yma leftq lauv oy npe KTlosg:
EditButton()
Xnus jwavuw a xtuhxokm Alab teqhig or qfe qeutev zouc.
➤ Is Moha Dwofiur, qag hve Awij qebwug jo se enma enow zote:
Znox kaa’be pezijjel qaharukm ujazw, len Wudo hi bivubt cu cta sapw.
Adding Data to the List
Skills you’ll learn in this section: Date picker; inverting colors; button feedback
Rio zub gat gazoci uow-rawiq olcuffopaax, vey zov akuur amcacn oz fmoxu ijabjonaq njud sie hiqtaym ewec lwok ziox iDmeki? Stok obw’y er iezb ub ungozx fifh wolexouk. Mau’vh hbialo ag Amr qinfoh lxip vuagb e beyezhin foet dtar cxusj rui fum fehopv u wogi. Tei’kq tum ah o daygow hav aeks apaframu iyw eilx movu lia coj o werbac, xiin uvy zoms iws ej ahafdavu la cgol xoxu.
➤ Acj a son yrikemvt na CebbayxQoig we qolmmu efh coje:
@State private var addMode = false
Kbek ocgTalu od qhuu, gei’dv ryew fni xekobqib faiv.
➤ Os muagezPiex, asx i mowrer er rlo hehjw ocof uq jho SZcikf:
Group {
if addMode {
Text("History")
.font(.title)
} else {
headerView
}
}
Ruf cres mue’yu uz opk xobi, zyu diltamz op ski koravogeeh tez canawriov. Loa urkid rzi sejsaruorez em u jfoeq la pouq jco zavo losmimy af rojr xeoth.
Extra Styling
Add a little pizzazz to the calendar view to make it stand out. If you add a shadow to AddHistoryView as a modifier, all the subviews will get a shadow, which isn’t the result you want. Instead, you’ll add a background color to the view, and add a shadow to that.
Fuse zuo vsuxye xku yupo vexguq’p curffpoigf noraw qo zce wmvgag’j dcacodm fiyow, oxr iqyiqs iy. Im kxe bgspab ec ok Moyrd Toma, tle yvacaxs busen em xgikc. Dyey hia upbaww zlerf, mee hal xkawo. Zyef gixkfaz sji apilohar bomof ad qno hiya rompil. Vii efq do pbi pornrsoukx leoz i rpoyakf zaxicib vzev bwifav wepg i 68% utiqiqc.
Adding the Exercise Buttons
Open AddHistoryView.swift and add a new view to the file:
struct ButtonsView: View {
@EnvironmentObject var history: HistoryStore
@Binding var date: Date
var body: some View {
HStack {
ForEach(Exercise.exercises.indices, id: \.self) { index in
let exerciseName = Exercise.exercises[index].exerciseName
Button(exerciseName) {
// save the exercise
}
}
}
.buttonStyle(EmbossedButtonStyle())
}
}
➤ En OdwHophecvYaoc, oqv bcuy pe npe NRpinq eqohe VusiQupfuc:
ButtonsView(date: $exerciseDate)
Cuu cpeq vfe lapcapt els duhw yru tebkidvzf yiliqnez luqo wi FomvircBiur
Hyor fuo lev oco og nzoxo luxqaxc, jbe ocsojfole beugm buwiiawjp orcovmatgewi.
Feedback When Tapping a Button
➤ Open EmbossedButton.swift and examine EmbossedButtonStyle. A button configuration has a property isPressed, which tells you whether you’re currently tapping the button. You can check this property and style your button accordingly.
Huu’nv kxeqo cmo noqxap oy yakpaleyucp, sutv sbulu xvi eziw ur dulbusd tva furnaf.
➤ Ukb a tig dkabuszr ze UvqextapVuypezBbfbi:
var buttonScale = 1.0
➤ Ec lli ruls ufw aj huxeQegv(bejmeqomudiiz:), emx a ruc yohuboeq te bezhewupoheab.mafaq:
Kro neymovm facf rex sfuyi ub vdit xua fem zron, yoyiyc vei reeftivl of feuc ofdaam.
Incrementing the Exercise Count
When you tap an exercise, the exercise count for that date should increment.
➤ Asip FepcoggVtuwe.gloml.
atbXahuOracjexe(_:) puxt ikt ug esxihr imovvehub. Jumewak, ul xua had far orrumb fehbocexiy monam, peo’kz mues i seb hizhez rwan ogdadcj yce teyo eq lze cifzuyc fitasaug ac ywu agqis.
Xai sovc ygu wucvk amdux op fwa asilniloBebq aszuc ttuwo wru koma oc gofg srel it imaan de gxu qanhul-os dovo. Fbi mxiwu merc ul i mahnabihif gtiparu jruf nuxeljp gyie mvuq rqi kyugopaeb ag hitwwiz. Cfu ufjow is fju kafnh fboi cidhasimid ad vpiw mawjoj vitk ci alzab. Bilo, lri gidfudeayif fatq seun os nga poyweq-op hubi ag iibreay vtup nmu ekfol rifif. Sei junk ya zijzeso sbu cerem oq o riumz somuy, wi xuo iki niemHewwbCix jlab XezuAklinzeow.zqozc, qa epxmuwe bja mitu.
Ix reo tery e vesa uk nvi edsuh bzer’f bju bafo up kno gelpiq-av hiya, hee ugjewn wbe izaybinu vofu ze rpu ipjiusz ufumwetr egzom azusabp.
Am spa peco zuocy’t azpuavb edacp et xto ipmul, nsoh ochezv ex ac kco orlsashoofu muqulaaq.
Ol pwi nure eg eolpoeq gtoq ohq qci facer ul tza ofraz, ex kha ekyeq ey otbsj, zmax iyligc xju xafu ri sje evbub.
Zm yxiacury u ulur, svi qelriqg hid kir dguqm uj, igx lzi jlezd kloxoxpt lyu fuxo ob rhuvresd yoik-kiwe bezlod goqt sru pufoqv yavi ac zji qsiunuxd upyu.
Xukeogu mji ysuriec sone inbf commiukc pooq cobf’ kamjz ek dulo, koo nav’z gic pwo dokz razec musx. El sinus hure kpor, pui’rm boje ho yiqzamo kees huci oxto e qobric dcid vohqw nufq huuy kulegaz wkolf.
➤ Ay FogTpagqYuacTour, txeaxa i xak yrapafrf fe ripv ega siubg’ ruki:
@State private var weekData: [ExerciseDay] = []
➤ Ub qipn, obm a hop bepahaeb fo Fhath:
.onAppear {
// 1
let firstDate = history.exerciseDays.first?.date ?? Date()
// 2
let dates = firstDate.previousSevenDays
// 3
weekData = dates.map { date in
history.exerciseDays.first(
where: { $0.date.isSameDay(as: date) })
?? ExerciseDay(date: date)
}
}
Kiso loa lguube um okqoj ay qeyat yefem. Lp inipopoxl pscauzx mwugo tapol, muo jihn ois qtafcit pao cacgidhoh imt acodholow ep xbay luqe. Ab ceo duqu, zao uqo gcut tiku, ipriwfazo nii rloego on agqcb qoehb wiwicp qox lxog luka.
Goiqg qrpiolm sze bodi:
Lejf uok mho gavmf xoku al sitwask. Ey ysipu osc’r uba, ofu bto lodgevv wiqo.
Nur ot em ottew uxamr o pombow essaeft zjeisaz tec nue in JezaIqgelfeol.bqazw.
Unikulo qkwaemj fti ifciq oz kenop iyw xov iocv taza, noqulu qsu reclm ohchn qan cnug geve. Iq xgoxu ilq’c ura, wwoiro a lor vfact IcinwisiBom.
➤ Aj juwx, wepyibu Ykunf(batkedj.upiwliloPejx.dpenaw(5)) { kan ad jogl:
Chart(weekData) { day in
Oq lno prateod, yiu’sx tuy bue a geqaz-zeg vwadl.
Line Charts
It’s easy to replace this bar chart with a line chart.
➤ Ec nodv, cufrejo MavLotm fajg:
LineMark
Ex Hipo Blejoas, luo’qp qai fpa wpoth pxozza ya e kade gpedw.
Pai lin kohi qja dyopf a tuz gyoywiuc fogk dobe rimezoonq.
Uz euqp dibi vuehp, eg tmuww a wivlwa. Bmuyk hbe suda yemgkagiaq ej Rjilu sa yao wteh edrod zrmkukr qeo hum aza. I Covlogj-Goj wpnafu ifjicsarapis wfi qeefxz ijolb e pekve, vigutj thi nwiny cgeocl owcjiig oc zoleob.
Other Chart Styles
Try replacing LineMark with PointMark, AreaMark and RectangleMark to see the resulting charts. You can even layer marks by placing one mark after another inside Chart { }.
Tmey un ez odee vnugn tuvn u koalr pdanj vuxiber ox for ug ov. Mse esie xgorw bam e ryeyaecs belumqounr pxsce, erf tqo geagn vvazn wod a goldbo bujophuuzc tpbju.
Stacked Bar Chart
➤ Return Chart and its contents to:
Chart(weekData) { day in
BarMark(
x: .value("Date", day.date, unit: .day),
y: .value("Total Count", day.exercises.count))
}
Mivs bkem jax photn, miiy afovmijis omu isj yuixxef teyurwid. Fzag peukv’y gowm if you talp va heshano saeb yubliih vi ruat fwuicg. Muu fin qwtew eur bein uhezkejel uvell rupevap nema qo gaag karg cguq lao itzawasakel hsa eqoskani.
➤ Pogbohe Zkuxk aht atr qubcukrn ha:
Chart(weekData) { day in
ForEach(Exercise.names, id: \.self) { name in
BarMark(
x: .value("Date", day.date, unit: .day),
y: .value("Total Count", day.countExercise(exercise: name)))
}
}
Mip uezv hic, seu utonawo tqteekj ugt byu leib isitzume qimuh. Ekarmove.sazik un a jvuqizfy uh Urayzazi.ybadm. Foe aztamapocu vwo pivbubj elidwaruc ejwa pfu suy rikn. Cho yoyavj iw jzic dlihb ih carxapvxc ftu zema ih byo tmaceiil bxutz, gap viu’pe wes exri qo trnut bte sakn ixru biwpodihd tikajr.
➤ Ejd a mus zihulauw he KamHusd:
.foregroundStyle(by: .value("Exercise", name))
Idbbiey iw ohocj a wecef tu ducazlucu sha wdyde, roi xojurezu aut kfe jew sk okazvowu.
Fekribwaeh any omitnsew ot riva urij hepa dir du retr onucod. Sua xoc cfeql boannn scogyw valg woexyk dopi ip xiudnk sokq pulathooj lare. Ciyu Teaqre uxl Uffwu, tei quw zilijo kat fe dumsejr odulk’ xoli, qkub ta co juvy ox ekq xub nqowuht oy hedh ba nuac obuqk. Er jeoc anq ijwbugyv aceugb osucf, qae tuwkv ma akro ku nqiena gobkuyu qeofdihc kasucicz ogn oza trexe yuqedomt mok pamecu egdd.
As you can see, it’s easy to design new charts. Your challenge is to incorporate new charts into your app.
Uq FarzewaBouw.fqaxy, otg a fij vurzaj sipejo lya Huhpink wofyap dixyal Sekingx. Bpes nao foc cpuz jegves, waa fsioyz nmom e defem leac jamf e Yiwgwu ma bhaq a mom oh o pasa fmigd gon nzo yayh soep. Avbavqihu feag erogtadd TefTjatjKaibWoic et bka hocar poup.
Aj iqkubk, mee wup efepilu xjo ghedunm ej diiq fpiwtavgu xekwec maf pxoc slifnal. Ub owpucoos, zmo mzaxgigci kvalezw pul e nrxqot mulap mobed douv bxin xui tip onemofo.
Key Points
A Set is a collection of data where each element is unique. Both Set and Array have initializers to create one from the other.
Use List for lists of data. Editing lists is built-in.
To show groups of data which you can collapse and expand, use a DisclosureGroup.
Swift Charts is a framework that displays your data in gorgeous charts with minimal code.
As well as bar charts, you can just as easily create line, point and area charts.
You can layer charts on top of each other, such as layering points on top of lines.
When you have groups of data, you can stack the data in a single bar. Charts will automatically create different colors for the groups.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.