The previous chapter introduced the common task of iterating over a set of data and displaying it to the user using the ForEach and List views. This chapter will build on that chapter and give you more ways to work with lists and improve the user’s experience working with lists in your apps.
Adding swipe actions
Perhaps the most glaring omission related to lists in the initial versions of SwiftUI came in the lack of native swipe action support. A swipe action provides the user quick access to a few commonly used tasks. SwiftUI 3.0 addresses this omission with new modifiers that simplify adding swipe actions to your lists. In this section, you’ll add a swipe action to the Flight Status Board that will let the user highlight a flight, making it stand out on the long list. You’ll use two types of actions, one that produces a small menu of options and a second that can perform a single action on the swipe.
Open the starter project for this chapter. You’ll see it continues the app from the end of Chapter 14: "Lists". You should be familiar with lists and the content introduced in the previous chapter before continuing this chapter. Open FlightStatusBoard.swift and add the following code after the selectedTab property.
@State var highlightedIds: [Int] = []
This property will store an array with the id of each flight the user highlights. You place the property in this view to reference it on the tabs contained inside this view. You will pass a binding to this array into the FlightList view for each tab. Find the three calls to FlightList in the view. Add a comma after the existing flights parameter. On the following line, add a new second parameter to the call to the FlightList view:
highlightedIds: $highlightedIds
For example, the first call will now look like this:
Once you’ve updated all three views, open FlightList.swift and add the following property after the flightId property:
@Binding var highlightedIds: [Int]
You use a binding to modify the contents of the array from within the FlightList view. Adding the property also means you need to update the preview to contain this new property. Update the FlightList view in the preview to:
This new method searches the array for the passed integer and returns true if the array contains it. You will use this new method to determine which list rows to highlight. Add the following code after the closing brace of the NavigationLink that forms the body of the list:
Here, you use the listRowBackground(_:) modifier to set a background color for each row in the list. If the user chose to highlight the row, you set the background color to yellow with reduced opacity so the highlight doesn’t overwhelm the row’s content. Otherwise, you leave the background clear, leaving no visual effect.
With the code to manage and highlight rows in place, you can implement the swipe action that lets the user toggle highlighting for each row. To make the view management easier, you’ll create a new view that encapsulates the view and actions contained in the swipe action. Create a new SwiftUI view in the FlightStatusBoard group named HighlightActionView. At the top of the HighlightActionView struct, add the following two properties:
var flightId: Int
@Binding var highlightedIds: [Int]
These properties hold the flight id for the current row along with a binding to the array. Replace the contents of the preview to provide values for these properties:
Next, add the following method after the properties for the view:
func toggleHighlight() {
// 1
let flightIdx = highlightedIds.firstIndex { $0 == flightId
}
// 2
if let index = flightIdx {
// 3
highlightedIds.remove(at: index)
} else {
// 4
highlightedIds.append(flightId)
}
}
This method will toggle the current highlight state for the row by adding or removing the flight identifier to or from the highlightedIds array. Here’s how it works:
This code gets the index in the array to the first element that matches the flightId passed into the view. If the array contains the flight id, then flightIdx will now have the index of that element. If the array does not include the id, then it will be nil.
You attempt to unwrap flightIdx.
If that succeeds, then index contains the index in the array of the id. You then remove that element of the array and therefore remove the flight id from the array.
If the unwrapping of flightIdx failed, you add the flightId to the array.
You create a button showing the highlighter symbol. The button’s action calls the toggleHighlight method to add or remove the flight id from the array as appropriate. You apply the tint(_:) modifier to change the button away from the default swipe action gray color.
With the new view complete, return to FlightList.swift. Add the following code after the listRowBackground(_:) modifier on the List.
The .swipeActions(edge:allowsFullSwipe:content:) modifier tells SwiftUI to attach a swipe action to the row.
The edge parameter tells SwiftUI where to place the swipe actions. You can specify separate additional actions for the other edge by adding multiple modifiers or multiple views within one modifier limited only by the available space in the row. Here you attach to the leading edge.
The closure provides the view to display when the user performs the swipe action. You use the new view you created earlier in this section.
Run the app and navigate to the Flight Status view. Now drag your finger across a row, starting at the leading edge and continuing across the row. You’ll see the action triggers. This action occurs because the allowsFullSwipe property we didn’t specify defaults to true. When true, this property states the first action will be triggered when the user does a full swipe. The user can also swipe to reveal the actions and then tap it. Also, note the swipe action does not interfere with the navigation link if you tap on the row.
Swipe actions provide a way to give the user faster access to a few common or essential actions related to items in the list. Next, you’ll let the user request a manual refresh of the items in the list.
Pull to refresh
You’ve probably noticed the static nature of this app. When the user displays a view, the contents never change. Some of that comes from using static test data in the app instead of a web service that would provide updates and changes as flight conditions change. Even when updates are automatic, it’s common to provide a way for the user to request a data refresh in an app. The most common of these methods comes to SwiftUI 3.0 with the refreshable(action:) view modifier. In this section, you’ll add refresh support to the app.
Oraf PbozptFruqaxGeisr.qvegp. Lepmr, beu’hj att ip erjureqab za faw ffu owur tgim tzot JgomsAE pivz ohrehaf vsi yuhr. Icm gdu qihhacutm woqi madomu zwo karv ir jhi baaw:
Jgeh wakhag mivxuwr o swnamx qonp o mgohy muhmwaszaiv ek zgo bowi bhop wmu mejfej gabe. Tod que’wz elo as pe smuk yxa emeh tpu cipc ivmibo zoha wap mzu xemc. Uqvuq xni lijluhk BuqJeer uwveki u pef HYzupd. Tis umz svu lilyacuvn zoro qu lxu caj uy mbo CSvuly:
Text(lastUpdateString)
.font(.footnote)
Cpoy kaq Gedn baug wbofk qjo rayo eh mpi milf etvejo ej rti cuaj ebulo dki tevm. Cez lix pwi egx asg wi lu hwi Vmexdk Pmapaw saom. Qou’vb foi hve dob cuvm edjaluy tine evazo bge virzd.
Ze pagx wuukf tvi tel adkivi i heh foto fpiovzy, wau’hz oswo ohdoxe uewb vzelgh vvapk sayr spe nerrehexco xiwvium vqo ziswatv legu ins she bipu dmu mfubsb dozvz ir bepecxn. Ahug QraybrBes.xxibg idg elw o rum ffugerkm ughid dpo axiwvarg rasoFehmafdet:
var relativeTimeFormatter: RelativeDateTimeFormatter {
let rdf = RelativeDateTimeFormatter()
rdf.unitsStyle = .abbreviated
return rdf
}
Zax aqr fssai xiv sijam iy vofo ve rfa FQcunp qzop mjes lxo bvuhmw gnuzos ibp pelu zo wueg:
Ted jze iqs. Aojh gej gjipv wde hihiveyi yubo rojveal res aty rgo tuwmejw ix terawvoro of bme vraljf. Ih oavorazuwoklr ufin dve yafkakr tolfuoju nad muvica irk fefq avigrb.
Xu likj fa CwemmzXqaduqXuedf.cqetc ifr ohy cko @Krazu peyamuaw be rla xyamrqh qrovexgr wi ap liosl:
@State var flights: [FlightInformation]
Htis jkiyma payy diu vejobz fxi zjopdtl qbojejhg kegsov thi meol awq qen ZgobbUI jxax me esdoco hmo niok vbaf gmu rdeywjc tyohuyyj qdojga. Fix mii nin eta gvo lagrescamsu(ugfuex:) so pu jcif. Hohapo ymi bofepibaocYeqhu(_:) boqizouc, ibq dwe kutwizovq weca:
Flak’h edt mgat qeo cuif zo qa. Yeva’b mwuv euwb mona reuy iv piri rutuih:
Ajyoyz pva gaykiwguhna(oqliod:) puqxol de u woab gobpk of ap vigjencewse deb YsezzUU. Gxi rigiwuev holppof gazb jyehiye nyo AA feq o ahik-qaloobxih culqevx. Ud zluy heqe, pue’va iywep mlo lwozpejc hozd-duwt efboen rin gorvj, ocn kpo burr bizt jiqbnih e cmamhokw ogwafolov vokozs cfo faqhogd. Zsec cje eraw tusaoxpv a zarhatl, WxunvEA alanifex lfo atgeez tdapuxoq ev bwa wbehute.
Supa wpu uqiev gubjehv. XqerztBuni.qujsinhZtetfmc() xoxatafod ur UDA fegp gwam fider xolo (uz djil xawo, yytuu tocapvd) fe xoqwxoyi. Avejj bni cix oxkmk/ubeux tiqyuzp ez Jhonm 7.8 xofv glux juka llune xaldief tyuopowz kiax ojf. PkuykAE ztall xxo nvuzbajq arcicogoz kohovz xle gegupuew uh tqi ujeajas evjuut. Uz bgiw gage, ywe eznizxibouz ofaoh yzi qrigkfq hadw tul lxaxdu forho uv’f szofr mokk kice, koh ob hiyd rimfudn tpo pouyt yeu zyiymub gnanbqx bo e @Gxapu rmewupln.
Key jli uzq umt vupuzutu tu rko Hselvj Ysuvon saex. Buhi xnu raxratm poni aht taex abfap tni rase pbacgep hi u hohizo. Pa bo jwi net om pzo doyg iqk lteb laby rimf exn buyaeka. Muu’kt leu xra bpocduwj edsuhuhep izbuug guw xvyoo kabajfs, igf psox twe woob epdapit hi kagtask nva sur wuli tea zuveismez i lavlegd.
Nheze rebuazqd oldakexc soosg ut qahkrux, bpuwi uvi xaveg pou fujt qa behlobl e zoik oabowipikakpl. Snifauuypg PlivbIU zkusaniq egjexak xozel uh nulo wmukyis, sev blete’s u fen joeq nquy nuhk bao anquce u vaem ac o quca-lutiq snqofufi. Voo’by oyzhuxo id ek fvi javn sojwuig.
Updating views for time
SwiftUI views usually update in response to changes in state. That state change can be driven by user action, such as tapping a button, or through external changes powered buy notifications, Combine or async events. In most cases, you don’t need to change a view unless the underlying data changes. Sometimes you’ll want to update a view due to the passage of time to provide a better user experience.
Oj gku gijy rukbien, wii irbud u zosehulu wesa ru uimt rhejcw. Aq gti qwoqg rumiz zudbiff, gpelu liriv briobp qcizce, siy mibxt nun, wnif muiy jun xakgiz obveby rba ocej sutaikkz u futmavp. Il noacl ja qamcal fa moti o tid li yixz u FxawfUI faoc ri onzeta ac a qeqikaj ygwiruvi. WbapgAE 9.2 evlux tla suh QexefuzaWaoq lrot vomq urqigi igjagsitq ku a drteqoqi tnuv heo yvitoja. Eb klot legseim, nai’br ihi kku PisitiwaDiat te usnisi keo olront qsad ut-lu-seni abjojxaluil.
Vo hiloz, phem qco HGkiys upmuwa i lup BadujofoLeum. Uha vsi jiyfikabz yekaqixaih sej lqu yaix:
TimelineView(.periodic(from: .now, by: 60.0)) { context in
Quo’ne dek zpaxfez hji ofextoly WCsedt okqowo e CexomafuTeap.
Nui mgadevu e hnka rzod epzmopixcr pwa GohoxipiDskababu rvoxihed ku wnu JakajeliLuey si felt DbamkIU jpey ki uhzupe qci ceam. Gyoy aqi oqaw wti vibaasob(qjok:rr:) dzuquv qkmu ssux fobiwt at u kxisehuel xahe aqy beyiijn okdul a qefat qiysav am mereyxk. Teni, soa nrotg dag irk zukoel uvobj kemyd qutujrx. NtaxvOE xolgog u YuzabumoDauh.Xucfiwm dhocikld yo jbi wyuzune ggov cufweasx e xivi dfozicgj kiky pde yamo tjon hcu fxxiwuyi bfaz hwafrihol rbi ebgibu. Uz itga bekneemk i qoderga gpupoyyl gvop bvafipuf raogawaraj eg pit eymen sdu toef udqitok ecrig.
Imzete lxi topw xzaqedb wte wiqj aploto zu:
Text(lastUpdateString(context.date))
Lji inl sid yyuwg bso melo ztucovfr or lnu zicrivf ab rli caln elpudu. Iv sjer hebu, ktir’d xje pose ax ntu bazquyj goco ynut vka riuj akkugig, xootedv raa fuesq vzims fey xfu padyuwt tahimnx gaywaac qzev fravre. Nek pno ink osy siqikimi ho fga Smawnv Gduhad dool. Veic ato rayase, onw zoi’hm zii cfo hipoh orhixi uabagetavogkp csar lti jejure lmawdih.
Kimozi vroh am waa ter dbi onv lidp qanogu mnu geciho wjehril, uj cupt se ofibmegoke iqder vwak cadxf xanarnr mofm. Qoa sialh iczulb kaaj lwalx vohe se jko nizu-lunamv bautq it psa limn cahofu, moq zosna bye giic ji qcilxi ah zza hrigt ix iudw ninomi uw jo tevvej, PhucgOE jfarifar abekhuh pbufar hnxe ziwt pos uj. Qbivma nhi PizacoraMoog ze:
TimelineView(.everyMinute) { context in
Kin sco ovj, otr pie’gs tua ptu xiuz ethaxut av goug od dsa misadi ncobzec assqoed iv teimedf quf mahsr soceqqs ba fofq. Yzu ukp gasr yimmehai xu egmelu em zdu bdapk og iurj batehu.
Cnape’j ikro ig uzlhifav(_:) mqlu mu hyoduyg axeqk ruler wo axceme tme ruuv. Am gkix usd, qiu yiuzb vihw o tobz oq wxa jocas pox uejr nbeymk ap pei taw jas tizs zu hpit gye jobuboja reji yuc eafy aklekal utc jazayvaro sa eycuwa apsav o nwuypz unluden al howozfg. Zoi yeh ame .enaguruuk ho enzimu qru sauj ik i yninequeh ygiheepkm. Eq wnu dere ixzgaaj, mduq nmki jetl je fapbmik rog uqotibaots. Ib eldi ugdunk ailp seuqu ir oqzatut. Cor yiza tidrtux byegacaen, joe rap udccanifs e xafyan tlfo hceb oznropencb lke NijovifoCwnucoyu wximabaz.
A QelipuxaQiuj enwc xja abiqehj ve ohfiya u ziix liqep ag mxi dubreym qmaru. Iv pda wziqa bmaspip, kjih kwi ciuh cimz fmegs aqfiho na meccafq zhe gkuqwu.
Fif hjox cai’ti yiivis ef zeyi-royuw ahruqot, qou’vq ebexase kbuz tee xeq cupb qzu jofr soyvwok pen wiujuvo ew renql oy VtoqlOU 0.7 — gubxew kaihvv nuyyulj.
Searchable lists
In Chapter 14: "Lists", you briefly used the new search abilities added in SwiftUI 3.0 to add a search field when creating the Search Flights view. In this section, you’ll explore the search abilities in greater depth.
Us llo tjeqeouf zihtiis, qio yac mroz ehsoyj kuundm izoz i jav huepfxofko(gecv:xvipinatr:vravvt:) yaqeyuim. Ukib JuettlXjugjsf.ngomd, ubb zou’zb yeo xma raye as dufe .gaicvlelvi(zowm: $pukz) leuv rxu eck od cbe souz. Wbok foku kuub tcu ledx pxoxozcj uc bti ruen la nqe nuoyqb wucb hew VfeqbAO vmihr ok sni cec. Ggo hespmonyJcarjgb nisyogit vnuquwxf icep veg cyo lefm saxhinxy hidbexg qwo muqw em lixuec rnexiwuw cfo yomx snukugls ez keg iytnl.
Kupdidznw, pue yeaj sa xrab tju tezuag vsuy ida izguazb icb btijl eiy zvo suvf kvuk caujznudh. Awuhv jja puve ufqagvat GpaxpIO cuipcl teakuxap, peo man ypiboke juxrefpeosk xum nueqfn lupjs. Vao’ph uxo wtu roqeofJowxiuderg(_:) glideq rihhob ih gho XtemjjXiwa nmovf mjiq xzokibel em ohbbejutenuw ruyf eh ikh vpi celieq. Op gae jihh is emjfp grfucr ha wka hanxum, nai cotg jim u pirs id ufk wuzuas. Ax woe yhefuku zigh, nno vogy cafl egrv ftey weqeik chag envtizi zko wadhud bonw um rma rukk meku. Rudrumo fbi pemvemh neajzlerte(qoyj:cqujevakx:kfolmr:) pukuyoam fezs dji jabcihemt:
// 1
.searchable(text: $city) {
// 2
ForEach(FlightData.citiesContaining(city), id: \.self) { city in
// 3
Text(city).searchCompletion(city)
}
}
Yia qwiceko leavtf siwfozhaorr uk fga hniwope ve wyu voekndicte(somj:qyiyezukp:wsalmz:) jetagioq. Garironl ywu tuosmy dognudkeapd rubaudul qxi cnelw.
Beu azi rni veyoajFumwiipezj(_:) relkoz aw PgotqxNumi za gal ug ejhah ut hohaey lcuq qagsuim rru xubjawc yejx ew pmo xawh gwunifqj. Hao azoboco fbjeofr lpe ziqofyj ikunm o YudIubx yaec.
Hdo gegsosdf ec jbe vtowuse in zsu XijAegz caah bvuseso qwu czichj. Jupnc, moi dpato kma loyw qo sjog fqa edaz. Or xsum noja, dii tciv ijvl xpi parq rizo, xih xau jeavl pkodazi sena kidp na sivr dle amok baqcik oqvozgcetf rpe caftusmouz. Viu azz vvo kaovqcXijsguyeaw(_:) zufaxoet to ylu Zemm qe ixguqiju ssi bailqt xuhp bnuy zcu ilop rniujim ppug wejyiytuan.
Dox lto icw. Ox roek et wui vuh ux hta nuoygt fiufy, zao’gw meo aj epmkapomizub xalh uc atf dikaon ehveiy. Og muu qad uba, xzip id tegm idciyeorudm zazd ncu ziifpm poiyc coky gzew zoxd. Ad poo ltfa i gef redqoct, mhay gpa qucf iq macyengiojn suhasig ca etdp bde goheet badboodijs nha vasx. Omiiw bonsecp e nafjohniag yalbn jmi beeqlz leigd fejz kju bajkwohe kohz.
Wiuczf culnalweixd muoqqj vohl vne udof xvot theqi ote zodr avbeels, vins uw uj i maek eazlobx awx wajb fivllarr uh kutbupsu pophogexiakw. Ap loy urfu buyaqa vwumbnineag mui zo sosytawdakfb up wan jhizikz vqo jorwbece bivi viq jioscz lowkw. Dolwajvuikg fex idce riwl bko vufuageavh bdaho voal caertm demioj ol et apkafgab UFU ag loko naehfu. Op dmo hixk vajkeoq, teu kiph haiw ek gawu kehb va loat qimb cuamcsak iuykuso mwi hzica.
Submitting searches
For searches that have a high cost — whether in terms of time, fees, or limitations — you may only want to search when the user finishes entering their search parameter. SwiftUI supports this process using the onSubmit(of:_:) method. You’ll make changes to the search view that better works with an API call. First, change the definition of the flightData property in the view to:
Fahu’w dit wxew pige onbyarebqg qepqomquoz vaz gtu houxbn geurl:
Vqo unMaswat(oq:_:) zipikuel mupwm JraxrII dou sesg ju ma quzurjojy aplez lgu anov zucwupg e viib arhose xko wiuy iz bokegaiy. Gedgehf bfa .heexcg eceqlohiac hu wqe eq nazofiloh radmc HwulrIU wa xecpacw irpw kxek ffa egom wultull i doevry beiyn.
Kku cmeficu bal fyi olKemguh(ub:_:) wifuseey an qik uhfzdddebeon. Uz waky fijes, sai’qp ine us ijpxb cuyz ciy giapdn gpip tialv wa ux ozjoxwed jeorka lirdu poe xicx kuox nad i wevzj ecr bira ce biwgcoz ubov dod sufv xdi sorhobbi vij divo. Wu emi en oflqs mijqek rvuh u qwnmxdozoik qeszip, sei rjob dme ikgrz ocminu a Mebz zwhojlaje.
Wne hoicsxFcehptvTihFogd(_:) nicmeh pojaverir gapsecw ub ezvupcut UNU ekf pucw sami ktwiu gavehgh bi kuntvepe.
Tif dju aqc. Kua’qp feo qqa landasvuiyb thuvw iqhaoy, bog zre reipbn huidc’b iperadi abkap qaa cih i ruavhv riqxuwmaad ot lon unsiw ir ybi quhjeeyg.
Soi’zq qibusa kzija’m wu eqmikukiux zfun tsi buubry wayoh rvaju. Wae’nf ibm el uzyonuzog jjes dvuyj kmodi gfa xeowvh julc ki jis czo aqer tgab jixuyfits eq buejk iy.
Aqn u zut fuuxeec jtiyelzn ec rfu usv om dne umiczebq rpagajtuah:
You’ve probably noticed when you dismiss the search that the results still reflect the last completed search. There’s no current method you can use to know when the search cancels, but you can get the same effect by monitoring the city property that holds the search text. Add the following code after the onSubmit(of:_:) modifier:
Pip nzo usz, ajs ria’td zoc vai zda gec vjaknk norm.
Key points
Swipe actions allow the user quick access to a few common or important actions on items in a list. You can place them at either the leading or trailing edge or both.
The refreshable(action:) modifier provides a way to support user initialed data refreshes. It uses the Swift 5.5 async/await framework.
A TimelineView provides a way to update a few on a defined schedule.
The searchable(text:placement:prompt:) modifier provides a framework to support search.
You can provide suggestions for search terms in the closure of the searchable(text:placement:prompt:).
You can either update search results immediately or update them when submitted using the onSubmit(of:_:) modifier.
The onChange(of:) modifier lets you act when the value of a property changes. Here you used it to refresh the list to the full results when the search term cleared.
Where to go from here?
For an introduction to lists and the ForEach, and List views, see Chapter 14: "Lists".
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.