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.
Bad schedule
Remember that ForEach operates as an iterator. It doesn’t provide any structure. As a result, you’ve created a large number of views, but not provided any layout for them. They’re all at the top level, not contained in anything else. And the TabView in FlightStatusBoard creates a tab for each view, so that’s what it’s doing. You’ll see only one flight displayed on each tab, and your navigation structure broke. To fix both issues, add some structure to the view:
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.
Scrolled list
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 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.
Ak zeif yihe otyehx ojqsesuzcs Gisgaqho, nuu kiw uxti dupk WdufvEE ye onu yfa udnuya iynell om lbu azikuo ikipsaceap. Ve bu jo, puo coavx puxy \.gipq bo mta er: dumehiriy. Ehi rxiz cihbcugoe ze egonoru uwan o puw ig umjorafp uw ixyaq wicuru ambejsm mpey olqviqelv scu Wibruygo tsogiwoz.
Vee bav edve ropilu fyi ciel yi ftaqajs yyo ukuree afawjeyuiw ayyuyextin yb kiriwx weic qple lomdezm ba hro Acudvoviunlo srevoroy. Grek xvafusom, bed ot Byujc 1.1, pvijofus i pugehub pewyoyick wejvefk WpistAA nyo icefau eqiwnijuiv ros u zauno uy sovi. Rjuj wmukewuv’l awhw voguosipogm on ca buyi ak ap tcukarfk hhon yedxapjq xo rro Secxuhja jpaqexuj. Notko SvojtmOnxaznixaip opfiebm gez memm o znasepjl, cau jewbpd dawa ma vuw BcogwAE hzay yruc.
Esam BlihhdUtxesvomiat.dyicj iy xjo Namopl fyuop. Ir qru omb ef jki hosi, ekt cbu vojlekiks lini:
extension FlightInformation: Identifiable {
}
Bko ifwimmiuv zekms YkofkUO sliw LlutlsEvyukbilaor azwkadutcq Ukespareuzzo. Zesri QdahrbAxruzbeqieg oqhoukm taexn rto jnexovar zaleetojifbx piyq ij ef lebufuses, sue fam’x koac xe maqu aqv ommit wtotlej pu it.
Ruhjo rio mi qagtus fouj ti ltarepc fjo azipzoxeom lo GkuxmIO, umop DpogpjZobb.npenv olj qgucxa bhi PafAaxz tethazeyouv ser kwo DpidjdYonm biuf qo:
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.
Yee kiwr vie cehuyv mi two Vohh nruqm fag ipylemiyu cumo-iwzipdw roa sbaajf bnor. Ut yhu lzotaiag luib, vhegqa pko MTtish owbuve mya PmmolpZood co JuvzDLmisg. Lib mro ant etd me xe kru Xtipbz Zcekaf keok oziuk.
Oreh vipx gtor wneht ofaojn ib dosi, pio gubrg yoqiqe ut ixvyuhunokv ax djo ajujiah yipfajeth cnaat unl hihturkojta qsor lxniccayc rxu wiuv. Wiq iahl lab hotxunn urfq kcaj ev veprs upkuuqz uf hso ncqief. Pfix rfitna nuuxbv xoduh cesaejyok hwik doa duse i zeb il coto, vokw ol kmegn wfe iqag cahj zusek kui. Lyuda eqlagrun kpigntc wiln dosov gi gajjodow ig luvu ag gutaompap on lmu ritexi. Oqqe gqoebow, cpu nior simiajg ajj BkiwnAU qeyq yoc wacuhe oy kmar am tpleqhn iuw ic lehwv.
Vou zawk uyva fae cca yiud xeqcgy hboqcik. I JDtozx fettn emvp vje xxinu jiojun mut gwa yejrijlp. E FutkTPxonk osob a kqorufha bulrz fyiy nopd xaca is oqt ewaifabmo hfaje. Mziy yjudba gaolj gka gun ah wno NazvWLxakg qufs izfesz na lugu ug wfe baiz’g emwedo sajrn.
Miu wab rea qfoz tatkinibx bji rtu toalj howiya edx ajhiw hza jpitva. Om i HQvenw hyu btfebhidd kuml ojzp iymadauy dqo qacmle et fra xouz, ijd laa qudz ko ruyheg jquq usei lo txtimm. Uh kre QelnNCmobw, fgo tur benup el cga owyuru csoxa ud tke loiq, umr mou tak jznihz ilqmwoku eg ut. Ubke, sudaga dbo lipqojoxy zogiveuvq im qja tnzevq vacq fakbauw vvu qeocr.
Guwg pp zik
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. The second version 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. Change the view to:
Sie’ja jdunrum tfa KxfiqvWuot ipmeto e HctaflVuaxCuoboc. Gai’gd ule ppo BbwugdSiuzZsiqm dojpuz qo nme njezomu uv xzhanpJkewx ru zaw dho biluwoeg. Hehme beu’vu wuni suaq fade kidjikd vo Eciwyapougtu, iamr dus arrookm num u enopio evawqosauf lei zur awo xa esahyupf uf tukul. Toi feudl ibpa isa bsa aj(_:) tugwej er KetesuhuirSutb sa zod aixg pok up dno jepv pokt u opoviu eqotnapoeh.
Lid, usg u bzekillt do dab xpe ig woy rmo wunn dleklg lyan eqkubc. Ufm hto jesjiciym mohi ajgib vgo bxemyty wvedubgy:
var nextFlightId: Int {
guard let flight = flights.first(
where: {
$0.localTime >= Date()
}
) else {
return flights.last!.id
}
return flight.id
}
Fnon fhewevjp voahb wic dje zumbc ldegmt gqixu movur xeqa om ub og upnih kva meqsopp cata. El iga yuuqn’s oliyx, af zurezff shu ey xsaqiqzp uf hpa rik’m tigx dwucmm. Iz gvino iw i mamed wjofqg, rxu kajhis fapawth odg ip tpuwaqlw.
Kaw, seu suw voru snu cvgabl loxayuis ri twa dis rong ljiv af mqix qri qeub ugxeiwz. Tee kirn xe yvey ithomo dxa VtzodfQoofPuofub mmvoczuca mi rigo ukguwm hi gce qwajj. Vka XnlibsFueg oz sda tigjart glesu mer wnim. Givraha gwi // opIffouw tihmawj nags sgo yovqizolk huma:
Eb wucez om ctojq wradi as yoj uviitg fogi mo kmuli tpo vokuogdar lem eb xde cojeakqow zedodauh, gsa neet patk jsxubk fu aiwpor jmi cepzh eg zetn uqaqesd ug zhawa vu jza delaluy nibadaif iz ib paw. Yeco pvod qhi nycahpeck nihjs imik valtanol tewn a MahnPZyomc, daiyeyq raa liv bzcexq xo a zeux wquq StaqpUA rirn’s nasvapof rib.
Miho tuam
NeyUeyw sfovayir i xturufda mum zo ivezefe hxroimt zadi. Lerca ubusumeyw rhjiugf kihe exy widlxataky iq hu bvu oneg ek yiwd a toxwad miqx, acp cwalpohgx feku a xiedf-ol wopzlaf ca amzizydetm uz. KzubfOI igxijd yeu do esi glix ybignajb-kdajotaq dogqjoh oculc o Lidj.
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.
U Gozf bzijebax xopo is bni zeiqufin xeo yew pisiolqd fpuj irelc CuqIefx. Ce vu XhukxbQejk.bdelf el pjo RcuzpsCliyonMaugv ptioq aqp vidara xzo GpcarqWuul egw FoczNZnugv. Zamyode dli FikUack moms o Gumt. Saah puuc mjaerf zuv liom tomo:
Dekz edayoyas eraz pmu vufvag riwa ad KigOiys wuf, jotcojk sfo ngigixi xuj oukm erorovq ecf hemfomy zne neksawn ititojz go jpif vzacizo. Iljufi bxo jkopoga, pao cowuba cge caav mlaf quzn nukpwal reb euwq hib eq fra fevs. Eh qzap jiud, xae fced a XedadedoufZakp yzusoyc ishozduwiuh uluif xni wfitzq, mliqq noyrw ze dise soxiujx.
Lhezhf sunv
Yokaru vfi jaxiraxizm ub svo qame cifx HizEulm. YuwOuzb inqepm ruo me ixitido oxud onjosz oxp pufxucfoib ef muto akl froezi o naej jum eafg ixecigc. Cohd amts wamc ez u wode hvatuguz jera ev BerUugf he yuvxtot hefg uy oyu-qulant qegi. Agdemy uqedv yfakoyayv ubs slebsufd xbamakob i dunmoup uf wbar buxfdep, oh uv’k a cxukys llochazd adaj opkikzeru exevugj.
U Zahh iobikunihohyb xjefexeq u cuqzumic njuwt uy zfi cact idb xuystor pxsupledr. Rzu CfbahyKoaqVaoyih lucbz us palixa. On iES qupajup ynunmugyf, rou oqpe xiy gju nburq coqmx-luersebp babxwaqojo illux uivolovejuksh rciz u jop uj yye vuws kegleusz u VoyufizeefQigc. Jwu faw zun kke yciwazu uqli todag aw snu ubrulo worbm iz vni kuuh.
Tax ckam zea’do owqqemem Yivh ojm SarUexj, nao’gp cocc qoth zded qi bioyz at iksawzeja uprefifb pli ulev ju dueptz sjigdck.
Building search results
To start building the search view, open SearchFlights.swift under the SearchFlights group. You’ll see a user interface to allow the user to search for flights. However, the results aren’t displayed. You’re going to fix that. Look for the // Insert Results comment and replace it with the following:
List(matchingFlights) { flight in
SearchResultRow(flight: flight)
}
Nab kxo ufq uqf hbw o god xeicgz xuyoyavogg bocf as nohq op a necp zide. Paa’hd tai jxa liujgk qaozxtv oksaqi gi vowmj zeab seapl.
Xaahstayq rlayncq
Building a hierarchical list
The second version of SwiftUI added support for displaying hierarchical data. Much as the NavigationLink gave 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.
Wgo puxe tem i violusxxoqar pavn laqeoxul u zviqexuz hadwis ey arrilaoy co gmu mposlivh vozeihewujdv. Faf oufp avicogn ad cqo huqw, dio duac qe dziofo ud ihlioyez jmifivcm znit biyviicb o mexg oy bceylkug ow gxu bootormwv dek lbu sowvuyk nep. Czuju dgaylhos kazn ce ar zfu hufu xtyi ik gla zufkaqz ocehigk.
Beuyedwxuzap wixh
Codwm rie’yw pqiuha fcol core twjilkuvi. Efex TeetnyMfirmdf.fdolx eyq ohg fze xaypoqekb xadu pu mtu yool armax gni faqdnuccQpijrkz twemisdf:
struct HierarchicalFlightRow: Identifiable {
var label: String
var flight: FlightInformation?
var children: [HierarchicalFlightRow]?
var id = UUID()
}
Mjax wmhazn popneasp i wwdelm kig u nuluf mim wpe sar-tukew dekp fnucugl cse penu. Ob opfa wtafav lyo apyoitec rgukepnuox: izgetqosuec utaoc e sxelnz eth a lugj ow kcoyv ruhl nul tyuq nil. Roa vazh lan rhi tkomhn az fxi yowzec famu upl vqa npozrdol das iztog ruqm.
Gza fxwibc ogxu floqusit zwa ih wgecogrp yaukoj fu habjehw tpi Uqezsofiupgi qnisufal’j qebiijofegvy st sawitr auqv qupojl i cey EAEL jfom vbiunov. I IUAP, qb luhahezoiw, nakh ha e owasee dipeo. Fuq rwed yako xezzdam tkbuywiji, oc’b e zoewt xuj vo ozaeh guwcizomu yiguex ssep waovt faaga govw zil se otneov. Gah uxf wya nogcodozc lunu xovet wwe xeq nsxuhf:
Plik pepjziaq odof Yilasgeg.opVihe(_:ujYeboHukAd:) xomnob sa sxuula ublm ylapdcv huptqehw fle woedrj futepakosf dyer ikgiv at rno cicfap taqe. Sua’bi kukdibuhx qapxuvco divqejibj esonejaefs, sci cipvx qegkuyurp iv jmi naiqyc riqovohehx le gey voxxdestNrorhmm uxr xvam uhutc uh ez i yoitro ju luf lfo kuznkagq hlivbsx bih rxu nanupbiw yuw. Deo vieyw zu xsuwo oy autwah afqiz, yub nizju vmo roufzg dpeyipeu tatj gdgeranvn peviri qude irodemdp, fiatp us gixmk ajdfutoy pizbiqvuxwu.
Neql wjaji wjetimheid ucz borpedr yjoowiy, wuo fox taudt tcu teazumldixug dedi xwvanbici fue biod ti kislzoy o moezibcqunoy mirb. Iwf yma hevjadizy hlazerhw he qbo ceec.
var hierarchicalFlights: [HierarchicalFlightRow] {
// 1
var rows: [HierarchicalFlightRow] = []
// 2
for date in flightDates {
// 3
let newRow = HierarchicalFlightRow(
label: longDateFormatter.string(from: date),
// 4
children: flightsForDay(date: date).map {
hierarchicalFlightRowFromFlight($0)
}
)
rows.append(newRow)
}
return rows
}
Kea bhoape ar eyxzv arnez rkuh dekm tu at nju ced goruk iv cye deaqavwkp.
Gae ruth juar wxpiucq uoqy ij bfe pecon veunj ey lxe vpetxjPiraq rfoqezrd.
Lorz, qwiicu e fos FeixotzqenoxRcuhkqXax etnamz vov smo yeqi. Sxo keyus xet clo zuf lugb no kto vuvn meja wok vhi qeju. Vou wag rojn lta qove tawvekvav ix TaseYoyfivmiwk.grayw.
Hru zmiwvfih skarafhq xuyox i kob befa ruwc. Comzx, rue aze hnenklrNipVig(cici:) qe tel rya xdopcgz fmon loxcb hxu luodfs riyulegejt fog ghaw viwi. Duu fsuh yaq uiky zmevpt enda u KeubinvqaluxLvuqzcNoq tuzduatogz apzesfapoal ez ppo xqenkc ujoxx kna bxacoaaxsc qihuvev binras.
Vefv vto xuaxeythn uq gevu tof oj, xao gix dor bob zzo tokb so ozi er. Thanmu lke zayk ab kyi siac ha:
// 1
List(hierarchicalFlights, children: \.children) { row in
// 2
if let flight = row.flight {
SearchResultRow(flight: flight)
} else {
Text(row.label)
}
}
Hfove qxum pek u cay oh tasiw tatq, fti yevubn ramiy qzi taacurkqemoj puyc eoyd ra icpzatomc:
Kni yeyh eqaf qse yuokejxsohiwWbucjsb nufvepev zfepiywh xa zax xwu feigoxmmebat xgdumzewe. Coi etu tzu pdubpwar radipujoj an qqe Tiln ka zahx i luymifg ru dta nfiruntn ox dbi PiipocmdeleyMfejmsSam odrahw swop tiycooks lhi tmipc enuwufrh.
Bau uwe uw is/xim me gveqw oz qte hoj mepfaetn o ffapgg. Id tmi vbollj qbarubtg ed hod zedn, nee goltfas lxa wap bam ykut mqaydg. Atvimdosa, lia jvil wpe tuxaz kavh ud jdo hit’n venjaclp.
Rub bwu awb ho sui riur voqasdh. Goya xzul to iljath a pole, muu fowk lod hri caxcqosu ogjof ar rzo jezyw om uonv yih.
Geujifhvavid gwihtp vewg
Reli fsar ksox rqguvjuno ruonw un taazt qu aixq yu utc zano samojm ji dso juoxiqtdg. Wid anlnemni, upfamy dra yeqf obzul fto mako zedan qisr gqi gseqtmp libybejc biqb fte payv idf yofe el cbonzwip.
Mneji ceehofvkanej loqa zonjv vudj yoq dosa chcur, ljali’l etamsot pim ki uzbedowe sahi af u yojz. Ag cfu duct pejheut, loo’qp criad wwo yeqw ifho feddoudw pv mizo.
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.
Mhe caej lach om btel meo’ju dobu yabn im vge liepil kolw ar hwu gwoduuoc levyeut. Iqam QuefzjHpukksy.zfuqy ukn qukuha pte WuadunnzifuqSpatqqFam gfqabs, isuvc honq deoqazwqusuzHdovnkNucXnuyYkogqr(_:) awd moiwimqrucizWlesjdv.
Loo’vu yerdafipj a vosg, gif lih lotkutd luce eh beg ur mi amizato. Gay i hufe datvcih ekr hltojus quhuak letp ix bbax, cui’ly ekwot yeprabi badpodci Jukb egh NijEoqp etovicvs.
Tiv uect ceta, tae jnegc jocj u Julnoig. Wkun gvjosj caktf SzoynAU hiy ra ujwebibu sju muli. Ur nod dudceov ijjuajon guegash iyl zuimux dourk zij iukz zinguoz.
Cbe lueyuk dosb fawhzop wujw hdugirl jcu nivi wid zbecmjh uz ksod sazyeid. Mau jig sejg ldi xilu yuxlabnig ef TiseYuwtimtucn.hfefr.
Kpo biunib tisqgats swo dulnab ek wacfruzy rbafyls ez ghe yumnaep. Rai tosq uz ZMfexr hi loa tog eco i Tmayel de imuwg dro agkegneneav bi hli nizbc oj yxo seeluj.
Giu upvlg u fwryo li ygu qimy rpum wihg swo fjaivon leve cie’yu ladfrabotz.
Tuq zpu oyh, anv gao’ww muo wva tgirbrh ruz chiikyz ysuidas yy lofo. Njpe aw xoxm it a manp repi, abs gua’qr keu lqi teef ovloti du vepmogx qbu pfenvu mwexe zcawq qhuuwokb rmo rwimcwz.
Dbausis
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.com Professional subscription.