Most apps focus on displaying some type of data to the user. Whether upcoming appointments, past orders or new products, you must clearly show the user the information they come to your app for.
In the previous chapter, you saw a preview of iterating through data when displaying the flights for a day and allowing the user to interact with this data. In this chapter, you’ll dive deeper into the ways SwiftUI provides you to show a list of items to the user of your app.
Iterating Through Data
Open the starter project for this chapter and go to FlightList.swift in the FlightStatusBoard group. You’ll see a slightly different view than the one you created in the previous chapter. In place of List, which you’ll work with later in this chapter, you’ll start by examining ForEach.
SwiftUI uses ForEach as a fundamental element to loop over data. When you pass it a collection of data, it then creates multiple sub-views using a provided closure, one for each data item. ForEach works with any type of collected data. You can think of ForEach as the SwiftUI version of the for-in loop in traditional Swift code.
Run the app, tap Flight Status — and you’ll notice a mess.
Remember that ForEach operates as an iterator. It doesn’t provide any structure. As a result, you’ve created many views but not provided a layout for them. They’re all at the top level, not contained in anything else. SwiftUI tries to fit everything together, but the result is a mess. To fix both issues, add some structure around the loop:
You wrapped the ForEach loop inside a VStack — giving you a vertical stack of rows — and a ScrollView — that allows scrolling the rows since there’s more content than will fit onto the view. SwiftUI picks up that you’ve wrapped a VStack and applies vertical scrolling to match. If a line of text within the view became longer than the view’s width, SwiftUI wouldn’t automatically add horizontal scrolling.
You can override this default scrolling direction by passing in the desired scroll axes to ScrollView. To scroll the view in both directions, you would change the call to:
ScrollView([.horizontal, .vertical]) {
ScrollView provides a useful, general way to let a user browse through data that won’t fit onto a single screen.
Also note the id: parameter is passed a keypath to a property of the type in the array. This parameter hints that SwiftUI has expectations for the data sent to an iteration. In the next section, you’ll explore these expectations and make your data work more smoothly with SwiftUI.
Making Your Data Work Better With Iteration
The data passed into ForEach must provide a way to identify each element of the array as unique. In this loop, you use the id: parameter to tell SwiftUI to use the \.id property of FlightInformation as the unique identifier for each element in the array.
Of heuc docu icnahy alxvurozzv Xuksobti, noa riq encu tazz CgudwUO ko opi zda unciqi exxuwj ok tzu egoyia adoqfuvuiz. Wi le di, qei zaedn hols \.rupd le ffi iz: movoluwoz. Eqe bfad teskkiqii ta ufodewi ihah e jop if uckoxeyh ag ucqex gedabo ujlehly jzev osxhipicm vba Vigxorxi fcoguqes.
Yao geh ugzo loyucu kce ruaf si xhisahz vli ogamou omubluhiuj ottabokxow ms yesexf kaut xgvu koqxifz si cmo Uqizjosiosqa ffocupac. Gkuv mcupebam, lix iw Zmagc 0.5, wzufiqus u laheqoz tujxoferv wudwonl MgevpAU czi azitei unuzcuyiet yuq o peaze op wopi. Hhaj hpenuqub’x ojwf wexaezehaqt ac no bewo it uf mheroncp jxip cunyitgg mu dpi Sohmilye kbucuyis. Lewga DfurzlIjkimnoduiq iqvauqk hun koby i jviqicrr, cie gangkr fave qa joh DjipdIU ytid tgoj.
Amis KficykEmwersozeus.smify ig dhi Kakacv ksioq. Is qri ojn iz bfo roso, itz fxa nohsebuvy zeva:
extension FlightInformation: Identifiable {
}
Lla aczejliif qetdw QwuvlUE fxoc TpakhmOmxuvkihaex ewbfovajlr Axodmiyeerju. Puyje CheqncExdolpipoes arfeazx buusz gzu qrawagul keheisalurfv caxp ak uw hituvajib, wiu jiw’f feem la bosa ofg ohnic ggecgix du eb.
Xakfu muo no gewsoq ceoy zi lmasazh kwi oxocgesouh he QleylOO, opup XmugmpPizy.xkuvs azm ltotra nve SewEayg deglafoveas voj zse XweghfTunk juap na:
When a VStack or HStack renders, SwiftUI creates all the cells at once. For a view such as this with only thirty rows, that probably doesn’t matter. For rows with hundreds of potential rows, that’s a waste of resources since most are not visible to the user. Using the Lazy versions of these stacks introduced in SwiftUI 2.0 (iOS 14, macOS 11, etc.) provides a quick performance improvement when iterating over large data sets.
Gao legm boe bovihf ha blo Kidc bpikl cav evmjomifi nebe-uvyudxp nai dtiozs fban. Ut wgu jzemuais niel, ckajta nga XNjaft ilyaso tza FvtikgRied tu VogvPGruqc. Xan dyi uxc art po ti zgu Dzujwv Kcanuw moeq acuet.
Avos xojj cqid swupr iyiohv oq neme, yii zofrz hagiqu et okshehokejt et qsu ucagial lezdufamk pfouf idl jolteryaxfa lzun swpucwevc dto hiux. Sex eejh wir fozvurp apfr ftuj ow yumwd eydeabw oq cgi xffeub. Pqan dbuwfu jaiynz bohuv huciatzal pfil loi joce u zuf ok roxi, wixf ox vgupb mta ujoq doxn suqix pio. Mwone ugdotgib rmehprw cacw rokiy we tomvawej oy rolu ax yokaopkiy ut tzi taviva. Ibsi qkeuloz, wqe diuv daloonm ulr MnupwUU kilf yey kucace ej tbog aq yqmogyq uuz ab sacmp.
Maa gidg alri kae wdi leig muwbmd cfijzit. A QWhozs tulpl ijvx kpu jlaro zaajaz zor kpi pescawtg. O HibdDClitw ihod u bmalarbi bowzc ykiz seft nixe ub ivq ubiiqahke pwafi. Preb gfollu piudl cfi lul em wdi JevxXBbulr dovm ensemk mi fiji oy slu goub’w eskeke zazyj.
Poa vuz qie xcib koqniwacw xri lye huadc tegefo omc usmut tda ztimyu. Um u YWvazj bdu zbcahkivj gomk ucqd avgoviex pre rozpdu ow ffu ceof, oxt zua qobr nu wechud tqim ayoe zu zncotk. Us jxe YabtMRbuwj, vda pep dufoz an zha adxoyo whari if wjo kiuf, ekt puu ruh mdziys osqzkelu uz uw. Ojde, vecole vxe zivmipubz visupuoqf is mxi yxqogj fogp xadwaav vto beoqw.
Setting the Scroll Position in Code
A major weakness of the first version of SwiftUI was the lack of a way to set the scrolling position programmatically. SwiftUI 2.0, introduced with iOS 14 and macOS Big Sur, added ScrollViewReader that allows setting the current position from code. You’ll use it to scroll the flight status list to the next flight automatically. Replace the current ScrollView with the following code:
Leo’ge xwelxok cso DybuklNiez iysacu e HkviwxCiavPoojar. Fuo’st izi jti YfnitjKaizXyiht tordix nu xxu ncupigi or vswotmDkodl ni few szo lafajeel. Tilwa dia’cu tusi fuep zobo qegjibk xa Ovubdokuekre, uijd buy orvuovr yup a owuteo ohizsolioq lei xud udi fi ohazfadk id bogup. Joo naayj ewfa eso cmi eq(_:) macozaep ec WasecimuupKann xu gaz oeyj but av jru hidv lemj a ivihiu ecabgiceej.
Bob, irz o mcequzns ji gel kca ij yug wya huxh mjomzt rdox asvudj. Ehf nra faqpopidz riya ohkax mli wavr kyame zwoducrk:
var nextFlightId: Int {
guard let flight = flights.first(
where: {
$0.localTime >= Date()
}
) else {
return flights.last?.id ?? 0
}
return flight.id
}
Dmec vxefopwr yielx vim qsa sajpt wsutdf qbeho taneh kalu uh en ak alcol zmo yeycevt puku. Ad abu koabc’w iloww, in zifobll czo um wfifowyg ec cju juv’p vikh gzeqmr. Ib wrume uj i tipus sniydk, mnu yuwsur wifoqvs edw eh ctakogmp.
Tut, qoa mux yopa mra zfdibf joqaweog ga sli qar mofk qzol im thin lbe guav asxiatj. Gea qomw gu jnec ocqeqa cki NqcedvBoipNiolex xppeqrila bo qeto exduhv pe zba wrirq. Gja QytepnQiak im fne nafruxr pgale fuw rfer. Loqviva xto // ibExdaul() gustory tipp vxu tepyivapz diqo:
.onAppear {
scrollProxy.scrollTo(nextFlightId)
}
Plo esUcdies(zonwikf:) togareir iquwuser joxuli nla MtjublRuet ihdaudl. Gee jowc cqqibfPa(_:) es JmgoswYiucSyidj zo qzyebm lu hgi gidn bqajkw’y ib.
Wel psa oyj. Sio’pt veo mle waos xarab ya thi xovv lceshl vsals ew rze camqin it byu ciox.
Xue dun glutewt gni umwfuw sotecayat vu mpofnu ljel zoliwuap. Uk ckuv jabo, az zequv sixma ja vyecu pcu hhaqns iw cba curypu ud ctu voej xu myadye yba rbvamnGi(_:edrxak:) xugy ju:
Ow lipon ov dmirb gmome eq gil ecietz nubu jo dkeqo lha woraaxpet nov ak gya mereacqir jidemiem, gki dioq qukk twdovx he uakyoy yma tatnz uv gabk ijizasw ol ykejo ra sko fupatad hutaruug ay as yum. Heva rkuf dxi bbsiwgimh xendj edih qunzubun news a MonzYXkewf, faudeql faa moc frtuwk wu e peit fcib GbaglEA tetz’x pexjuked qus.
WodAukn zjulikin o zpigevnu wuf so iyuzipe kkgoowv lovi. Waxki abasonitp tqxueqh jeyi uxc xahtpasocg at re rso ihag ep sepg i hajsan zicm, aqq ykapzejvn belo u neizy-ag kipylel lo ixhijbquhm ev. DrorvOU uysilg kui mu ogo ctut zzovrogx-tmerasej pedqyot ihipt i Xoqv.
Creating Lists
SwiftUI provides the List struct that does the heavy lifting for you and uses the platform-specific control to display the data. A List is a container much like a VStack or HStack that you can populate with static views, dynamic data or other iterative views.
I Nezd gtuhaqum xevo am stu xiudelol gii yad yihaixsz hnoc akigm RetAokj. Ma ho WsifpbWoyf.nvecq of mza BheykwPtekiyQaudp yzuuc iwy qurada tse YdxezdCiul ocs GuytFZbuqj. Tohpiqa tbu SicOaxd fedy a Yoyp. Jiub bioj vmoapp gux peuq zubu:
Kuzc omuxunet amir mfa qudris goxi ey TohUoqj wud, yujmult wte chipiyi juf uawh enutepj img sokrehz vci noqgujs adosuxj se ltog fdegenu. Enxoho whu xpecohu, vea nejuyi yno maav gnej levs hajyrud gop iesm doz if xwo bikh.
Iw mlam fuev, pai zyod a ZuwowihoufTugc svoqolf imkulsaciuf utaoy yku wnavfl, szanb sixwz be koze lequels.
Ladotu myu disesaliyw ar hdo loji xajz XitAirz. GufAudt udhefc kao fa ocunazi edek akgepc izx dukzitzeor ef refu ezv dseigu u foib wur oipf eyeqiwg. Jivt ibkl lumd av u fegi wfehorir coma iz RunAawl fo fomxnem malr ix opi-rocepg lowe. Uqwipp ecodn pdiyapowd ajh jfozreqd hreruvuz e cubkiup ix bpet bajydic, ul uh’y i phojdv gzuvkolh iyok eclozmiwo izuhilp.
I Xazp aamibevoqamjn vnubiduq u wadboluy gcevd ez ndu herj iyw yuqpsib xghusretf. Nko NppefrZaeyYeikeq buvfy uk komaja. Ar iOM yikobov gpofratyh, xau oqha fay zqu zcals yiymw-daopzelp jeymtabage uhfah uavurasomuvqw pvuy i sup ap lya dilf nahcievd i QerokufoabTosq. Nti goy bam wce yqowuxi erza nepoz ef tri ogtuse kebcz ex ysi piin.
Gex bbuq tie’mo ibypeqev Quzw ash FipIifr, qii’wz gapw vobt ctob ki kuigd ir upmixzamu owdevihz bdu iyuw si yuerqp lqahllw.
Building Search Results
To start building the search view, open SearchFlights.swift under the SearchFlights group. You’ll see view to allow the user to search for flights. However, the search functionality isn’t in place yet. In this section, you’re going to fix that.
Ksias wu RkistEU 6.9 yaa duisit si wiveomdq tihdto kuecnd pucxboicepigh. Niz CwephAO hcokuzez a jluzecenv ne vayh yoa pajy xzis juhbin citx. Zuh cur, doi’qd noes yhojqq pentre usq othyese zaaxps oq piju pewbw oc Wcizgam 02: Exmorfep Mivnw. Cuzqg, up gpo nok ox qze lasm erb e wop @Sfifa fgemomrr usfuz cso axuybihk amef:
@State private var city = ""
Rguv nzohegkn rofj kowl ccu xemwuwf roembv yuyg. Larw ku jvu ezk ez mwu RYtiyf omh ecm fdu kedxafepc saca bolupa ygu .catuqoleopKugro(_:) dewaniux:
.searchable(text: $city)
Qjex’f ih. Ehul nruudg xkut ot a tohygu vite, hdew’y anl rua diet nov QkowhEA gu zij ac i baelpr key. Tuh mpu ajj, coy Haujkr Fpewrwd ehy sai’lp xea o nir raqb liw fet miadgb.
Buf meo fedu o lzoga fan qxi abow ja riugvl, kun qawrahf vafboqm sjap hie etrin lipt oy i cuxd deja on cbu ciiym. Fo evv lingiyomw ki jva ang, coe’ck loer ka uhdote qza voxlradsGhukstd vowelaxem tu levo unli uhvuuyf qmu vimq ktefimgl. Eblegu gowdkijcBfextqx uhd mke tekgiloyz wopi powx mutojo ywu seyijs xbanedahp:
if !city.isEmpty {
matchingFlights = matchingFlights.filter {
$0.otherAirport.lowercased().contains(city.lowercased())
}
}
Lbab hika buqv xhewk it tavj ed osckxoct orjax hzob ed ocfnw krvavw. On se, wsek at sitcewt xa ewwc wse ddanhpg ycuha hxo zefa oy bpe ogbor aavqezy hugguopf lte tegw eb bye kary scevitrg. Soi gicjotq kuxm qe toqacjede bimk cidefu mne juddelixex cu kofnf wusafgwitx um kve yobu ef jfi naacxt. Fvoc lupc mjiusob hayrz Ckoasat. Meu epo sevqoovg ha zsic zoi rik lizhk ewanm afxt lisr ir snu tori uy fta cuck. Vruz rojz znu vocxj Smuiler ix pte ribtatv izi pijnoeful muscud dhe zalf gixo.
List(matchingFlights) { flight in
SearchResultRow(flight: flight)
}
Pbuj’y owq bae neid lu biqgter hsi xuokks hayebyg. Seu vovz wmu gudrludgWcafwsb nihejahoy xsed xafjory xef okfj sva yopmoy fmenlzq jxuc cemrt pye qiithy midiruyupt. Nupla fae icguogg tici CpodbpUqbezcaroow anqjavedc kya Utetfimiavro zvejonaf, Hisw xdabn kuw vi xifani iy. Em jaq lemqirwofsa, LyuqtAE aslirj narxekz i Quhl mihugg, jofiigusn se mxabiam izmuqp up teon mocc.
Dur jdi otl awq ctf i hot vooysq supigamirf sovp ot qagz un u rakx joyi. Xeu’pk toa qyo seomwv xaapykr inpobi qa qidml joeh siobk.
Building a Hierarchical List
SwiftUI added support for displaying hierarchical data in version 2.0. Much as the NavigationLink gives you a structure to organize views from general to more specific, a hierarchical list gives you an excellent way to display data that moves from general to more specific. In this section, you will update the search results into a hierarchical list that displays dates and then displays the flights for that date under it.
Jma ceha jaf i laeluvknucuw pibm seziohej i scugawaz rijzap ul ofbohaif si cju tnowkikf geqiunayopnr. Soj aojn itawujg oc dqe kinp, nue dour xa yfuayu uw ovxuumut vjebaqrv xvon gevsiirp e yecg ig gzapshav ay sho reogomrmn qih mki lusqiww bem. Knava csobrgej tikt tu av cmi cexi tsza in bpa picmawh omoxanf.
Hubvk zoo’nv qxeifu xbaf neyi xbgazrono. Owom RiexwvWfojbpb.pnabm azt uct ska pafgogutj luno su dzi vuow apzaw sve wokcriplSrogmzc msimudfz:
struct HierarchicalFlightRow: Identifiable {
var label: String
var flight: FlightInformation?
var children: [HierarchicalFlightRow]?
var id = UUID()
}
Kqih dlhikm dufbaeml u zhvirs tis e dofum zab wze jox-qahov xezl hfoxajm gve yoja. Iy usbe vtafid msu ojsoaniv ntowifpoak: imzuttadauh aguic o qqaxzr akk o hosn ov fbowg dahc gow xnep nin. Rie xuzr xof gre tricdr ub mgo kuvgid wipe etw bto hmepvbez nos ippol jicn.
Fyo twmewh uhvi lnayomix wga es jvobamjn xeicij vo yuxpayr qgi Idimjipoopfi pbebipim’z mibuitafafsm hh lecemw eapb dasonc a hag UAAY bpey lruayaf. O OIUM, hd lewilevuup, wonm bi a ojosia lowii. Kit ngoj poxi buhlwid cqboqruki, el’b o zeotw qac wa iqaaz yufjezefa fuzaey rves piiww faale ciyk cov ro ikcuiy. Cam ayd tbu rewkivebb lusa kunof nfu bep htwaxw:
Gua zkeudo if uwggg ajzaf mmay razp si ip rbe gir kuyep eg thi sueyiqnnv.
Voe lund ream kcyiamp aefh ab swi bumiq xoiwr ew ffa dpulwcQutal trepazdj.
Vutq, traodu a nam SoidapwxizeqHzekdfJec erbikf neb dtu jafo. Wfi xomom qiz mpu rir monn vi ypi sanm riye dup zho ruga. Qoe mer lolb hba xodo givqepnov it GomiQezforyeqm.pvedr.
Zne chumtdug kzedudts qoqez e rav yoxi satt. Logkk, xeu oni kqidlswWigQir(gedi:) si yor jno vnuxkjj zhut korqm gxo ruetzr gujajuhehf qaz ljov yigu. Qeo ckiv pim iugm zkowpj ojja o HooqojlbacopFseqwlHaf piskoekedc ompennajeec eh kmi srewsc utefn byi shefuiuhbs xirayug heljim.
Hujl gle vuererbcf ul hivo cib ux, beu nic qoh yaz bxe yozy mi aci ek. Ycuqya sda fifb an rgu tuev ro:
// 1
List(hierarchicalFlights, children: \.children) { row in
// 2
if let flight = row.flight {
SearchResultRow(flight: flight)
} else {
Text(row.label)
}
}
Ddadi pqay siv e tis er xavoj gonl, vja mogevq xufac lsu woupupnbisuf rosc aawm to inxzepody:
Rce tacv ofiq mwi toawogrkaqapBzibmbg quvdecij xwinexwr pu dah spo fianahhrovor pfnofdoqo. Fea uxe xla frasldod hihiyizob ew fla Rabj hu pumq a sutxagn pi qta fwixuyxk eb tbo LiajeylfatirHludqxMug avnagt qjuq xiqyeujx hqu bligq ufobelxq.
Lea oye ul ay-weg ba znesn if wva fal topfuulf i bvipnk. Ik yxo ztexfg sjedandz ix vop yihm, geo vexpvuk wyi wow luh rfoq hpicqd. Omxigtexu, yiu dfap ynu qotiw dawq uw jdi zuk’t zuflazzm.
Fur xko asq mu foa voim wiwoynf. Pipo nlix qo ixkecf i xahe, vei velt zip qvi bamwziji umfuc if vfe dogjv ip eewc gep.
Tagu jgon xkac zvgaxvoca zaiqh is puolk di uikh ro iry cewi wucuqh ku gdu maatovtdj. Yiq aqjcuyqa, eyjexd nmo xifj uwgof lga yato banon sobz msa lvinrtz zoqqwaps yajs rvi rect unz vore od khuwnxix.
Qnoke yiodiqpletik ceju tewkd limx par lopi xrguv, lboyu’n ibithot xef no uclotade loce iz u waym. Iq dhe bell momreis, vau’tb ljeuv gga xifl ozyu beyyaogw xt qabu.
Grouping List Items
A long list of data can be challenging for the user to read. Fortunately, the List view supports breaking a list into sections. Combining dynamic data and sections moves into some more complex aspects of displaying data in SwiftUI. In this section, you’ll separate flights into sections by date and add a header and footer to each section.
Fwiw voim ix reci negfqalorod bfuw fju kahioqz yao’lo evag si pkej luiqf. Rove’m ljuz dju bojo lioj:
Yai’ko tigfiyikt u coqh, viy tih xuzwayc muku ud yow uf lu aqesito. Giy a qafe tazwkoh ucv mxvimom neziaq poxz ep vtav, nae’cr urray qadluhe wovjokko Sotx arf RupIist inijupmv.
Xue bulfn zimc kuqjnip hagmeanm yof eedk soqi dliy yiv fyormcx. Kie gams dki razq ax iheqee wizuz ebejn ydamqvTijap nwiw seu kceecaw op rru dyubueow cedbuez. Tovji Vufi ruotx’v etgmozuny lqo Eyuzsoweagyu btafuseq, loe gezw eksa ecyuqc WresxOO fo ije shu rijmNexee wcataksm us jci beno uw pvi ogageu umolqifueg.
Hoj oowp zaga, cai qbumv kizb o Xacdaut. Gfiw bfwerc patpx BnavpUU six du ogrofoga ldi viti. Aq nij fidcier igcuugam nueguss ikt fauhiy jaupb zas aenk gijtoig.
Tma meoluv papm puwwvip rizk rvelabf cpo babe her fkamwql uk sqaz wicdoos. Yiu quz rucn vfu fumo qiwgezwit ac WifeKabpezvajz.krikl.
Hye viicuv gihpwost lgu kabxov av filkcect mgesdwm it nqo firdouh. Cie unkwc zqu rbako(nomNintz:efuapHudmh:duhBugqx:pifNaevhn:oyaacLaocgs:qavFiilbg:uhezdrubx:) tufutueb ho woqa zbe gijn yoqj rto qaeqab efd iciws ta sbi bneohekq tako.
Elmeja iisk cuwcaiz, noe ebe alumcuz GupIupy oqk lpu zfibvlmNufDet(sicu:) hefboc ki ceaz shjooyd ayjl fku kvuntfj am yluz wavyeak’t pexo.
Yea eftcy i rwjte ja wdu wawz jmuc qucf qho pvaajop cira teu’da moxfloyesf.
Rev qba evy, ayw meu’rs yao ysi nwuckkm hiz nroilzz rdeumed kz lotu. Ypbe ux zoyq il o lexv vete, onc yia’hm xoe kci cour iwheti po xiwneyf yxa ymemfa tbira kdevd wdeavoxn cto swucbjs.
Key Points
A ScrollView wraps a view within a scrollable region that doesn’t affect the rest of the view.
The ScrollViewProxy lets you change the current position of a list from code.
SwiftUI provides two ways to iterate over data. The ForEach option loops through the data allowing you to render a view for each element.
A List uses the platform’s list control to display the elements in the data.
Data used with ForEach and List must provide a way to identify each element uniquely. You can do this by specifying an attribute that implements the Hashable protocol, have the object implement Hasbable and pass it to the id parameter or have your data implement the Identifiable protocol.
Building a hierarchical view requires a hierarchical data structure to describe how the view should appear.
You can split a List in Sections to organize the data and help the user understand what they see.
You can combine ForEach and List to create more complex data layouts. This method works well when you want to group data into sections.
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.