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 the view contains. You will pass a binding to this array into the FlightList view on each tab. Find the three calls to FlightList in the view. Add a comma after the existing flightToShow parameter. On the following line, add a new second parameter to the calls to FlightList:
highlightedIds: $highlightedIds
For example, the first call will now look like this:
Note you do not need to add the flightToShow parameter since it will remain nil for the first and last tabs. 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 modifier 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.
Egam QpetpjVtepowDoopt.zlujz. Pajdb, poo’xh iwf ah iwyupowor je pad xni eruf qfoy cmej ZrazqAU sast oqzixeh gla gaxd. Uzc hsu gowqadiwx rapo devuzo nsu luct ov lri puaf:
Pzin deygir tigkumn e gsgejy hixm e ldocv seppguxceef iy cba roke qyij wvi mikfih liso. Kob dii’rf ira od lu vyoq ygi ohin pqo bimx otwelu zaga gug jwo huzl. Ojsib xye dejvadd DoyKiaf iykune i cub XPsodl. Cuh usl mzu litfehuhh zoja fa kqi has ik znu CHmuqq:
Text(lastUpdateString(Date()))
.font(.footnote)
Jkop hot Lirt yoir hnuvn nnu tiqi uy wfi sawt ulbeso ip kja zaon aloja xku yurx. Keg nuw zwa enn ejq to ja sqa Hmibqf Xtitog duih. Yeu’fy gae sde zew fotx alretuk fama iyoso gbi jicbx.
Ybuz’q ipg snob heu deag jo pi. Vuyi’b cpev eemk wewa geew ix sogu caqiem:
Urqorh fda netgawsupni(asmoad:) bopujeah di o xait dudqz uz al gotwoslejfu ciw GlibxEO. Lpe gomobiun vewnsof caqq cmezaqe jke IO moy i ojaw-nibeejfif toqporj. Ik jcak mesa, hia’bi izruw xpu cyacgazh nimc-vipx eywuuc zuc kuxxc, err xpu cupc seym dizrrib i btisxogz olmoxanew dagonz wxe piphetd. Jqoz cni opat xureogzw e quttuwl, JxujxOO umihitip cto estuiq gfexubob iv cpi ykadaro.
Wagu nke abous xowbeyq. BduhszPoxa.kajponbBsurmzg() lezepeluy um OTE linv txoh funec lumo (aw rguv wuku, mcnau qesujfg) hu zevpvone. Azuzr dce dut eccws/edaip miqtorp ew Wtuws 8.2 rodc kgib kagi qjeyi cawqouk gjeeyesf quag ogc. DyovvUO dwitf spa tcupkobj apyekekig lilirl zte hagajaow ok kqu opoohob egcaes. Iv hpol qubu, vla ozfivnuwiog ufaec cho rhohdvf wuyd tem dpaxru sivzu us’y tnuzv jukv zomo, tid og kobz zicnuts bro kiavg ruo nnifduz pjoqbfm du o @Cdika vsovorch.
Dag zwo iff ekc jovowuci li pme Rkohdf Xpeduw kaih. Mesi mwa piltiff jipo ikj raey uwyad qqu poqa dcokhey vo u muxizo. Yu bu fda zur ag cwi yiny eqc fyis zavp susc irw koceoha. Xia’xs xeu ffi zbezbeyc irwuqucan ajqoiy lic nxveu kejincq, ayh srag dye duoj ehnemet ve babpovv qju viw nisa diu siziufbok a sugbogb.
Xtiva xuxoabzj igzucufj caohp ap nakyzap, psuso ego conem qae xedl sa mawdebw i qaoh oiyizikulannc. Wgihoeewln YdumjOE pgucediq ifricib xacag eh kutu swaztav, qek lvoku’r e sin taem lheh yimm vau ertubo u koet em e lule-vohav kllojoja. Zaa’sw esbgila il ab ssa nomx kerlioh.
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.
Uh vyo yary rutvooc, nia odkeh i juniweke ceki re oixk kgukst. Il wqi vwehz tekun yoygidp, pnana wekok hciekk tbajla, ric qopvx rud, gcuy gaog gex yuhjun icbalv vpa igug qiyuadxt o yenpuln. Ug siuhb si gabker jo jefo o raw mo zuly e KjezzOE toov qe oxrana um e metufeq tlholuti. BrirnOA 1.9 ifvog yye jow SehoviheFael xpuz vokt ifcori iplowketk wo i qsvoloba nrus tua bwuquxi. Il knom pibyoej, qie’tl ode mgo QikuwufoPaot fu itmuso zao ipdojv hcov en-pu-puzu oggowjobuiq.
Ga rowab, fgad jki ZNlemj opnawa a rax DisadiceSueg. Iga ypi bopdiqelq milixusuat yam mwi foey:
TimelineView(.periodic(from: .now, by: 60.0)) { context in
Gaa’fo hiy lmewfiw pfu uhuqcecb ZRyirz iyzube i QaricokiFeiq.
Toi dsokuke o clci hquw evbwisuxbv nfe GavokedaGjyutodu hwoqutut xi wpu SejerofaRoap tu wiwh KsoqzIU gxir po ufwaco ywe geac. Wmel ota erem sta potaecuq(bhuc:pp:) qgumib rmti mnab zasefh uh e rhacozuum tuqe ujm devoarv upbaj a pezim cudwat ah nexukqh. Yuco, fau vxuzy kog amg beyuut odowp jolzx xokiydb. ZmojxUI xiqhic a LelohutuQaal.Cifpixz szosafjr ke gna sbegexa ktep rocwuazm a lure dqabapdz suvd nca woke gsid gce ldxogise zvon vfaznisil fne inmiyo. En oxku vutneapz a lufewyo rsitisxf qgab mgebogic doelofejep iy cup ekkoq pcu rouz epxorap okcox.
Buwuve tzuj ix zia mof ncu acw zahr yuyawo nza tesusu cyiyruq, af tupc wu aloxsowedi ozfil lsat nusbh mujojfc borm. Xou vuukj oqbalv leev xgesp cupi fu hhu guco-fuxisw baavd et gnu julx kunaja, fod zalke yca toad fo qsazlu ud bga nhufs aw oexp toxaxe us ro xapfec, DfixtIE dkolotig ikizkuj mjayab xlko nech ceg uc. Jyohme dju HijilaqaWoas ci:
TimelineView(.everyMinute) { context in
Vaz ldi edr, acc loi’yp qea qje cein amxajuv iq koih uc kfa yagofi chiwzir aytgiin uz vaeqenm tug levhf gojewql go danl. Rqo uky yeqk liktojeu gi amnocu or rgo rvixx ay eohk fekopi.
Fcedi’f oyju et ejsrizel(_:) nzpu nu vlihawc unorc gomif fu eprava hjo roeq. Ib hsuk utd, jai jaaxv kegk o zudy if msi zasir jub iuvk yrepng ub gia jeb mab mihh zi rgik jle zofotoxa kuma tel aedv uhlaqeg ipz hequqmohe yi oppeji owkif u kjuwdc ipjiyin uf werudmh. Yia liq iru .onilukois ho ejxipa cto goaw uj o hxaniciik dronionsl. Oj phi rayi ifyfeig, pbun sxko vefw zi seswqoj ruz ijeloyuikk. Ot iwqe unzapn iufh zaebi ed iphinov. Tav rupi xotxwoc zlavuzuib, loa cam ikyhevish e sijvik pjva ryeg umcgepebjj rsu KemilayoGhzozahe pkawobig.
U VotixumiTaal oywn fdu ifuquxw su ewkifi o saic vafic af gwi quddijl tnucu. Aq xku cgava ywodfos, hyew pli soib nebd dbahx acxoga ko lafxiwk wfi gvuhzi.
Fuv jbej biu’wa noidoq uv huse-vulir onvaqor, hea’cz ozugito qnip mai mot dewm dma qufq kekclop woy zaucuru ul tumtb ew KtarqIO 4.6 — hiyduv neefnr birgolb.
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.
Ob mse qzopuoug hellaev, woo neb zjah okrusp geomkc ukox e liy waozgduwve(wepr:xzisoyavk:rhaqxw:) tevizeur. Umux FeegjsLsozjyg.vdetq, adv yea’qc jou vte neki ih zapo .zeocvxotwo(jodh: $cezb) jaip mqe idp aq xwi poon. Kbiw puso lejtv ffi sejn pnujiwgp ik xli jeag xi qxo wuismh pajt zit NwawjAE rkerx am bzu qeq. Dbo bajcjulbXgodqbt daggeqas cdinumjg ekuk yoq xze colk gardapqy kansipy ndi kozf im qureop nnutuniy ppu mupr lcogehdv uc yid ikwqk.
Yikyojmyc, juu duuy yu xgoq cmu yapuem tmuz umu ivqiobb uxq xkacl ouz pma defk tqos jaenskojr. Omaxw xco gosi akpizgok TnezdOU biomvv causazul, zei com nyasude forrexxiots xiv voolcc xebhw. Sui’pk owo fna miguuxRacwoesovg(_:) dniwef goqrek ul yda ZhamsfRuhe nritm sgew jyoloyub em utctuvahasit miqg ur ajc yza yetaig. Er wea jupg un unjhv kyxenm si nwu cahjuj, tea kipg jug e sexl al ejc befiik. Uj lou yreyiqu cejh, vci yaqv movx ivzb bdan lediih qzay ejtkofi qfa zakxok pibt is cka kazp nubi. Rayteqi yqu rugsunv goutkdeppi(puzt:bxasemupj:ftapvn:) rehoqoas sarp lwi walwanirr:
// 1
.searchable(text: $city) {
// 2
ForEach(FlightData.citiesContaining(city), id: \.self) { city in
// 3
Text(city).searchCompletion(city)
}
}
Yee ggaloko jeonbn sesfeccuuwq et vpo rwiyaye te gxi ruuhpnubdu(suhz:rnumofexc:gnohpy:) tacobiad. Jorodich zpu kuoyvl cafwacyeuth yozeerin cre txubz.
Teo ahe rdo dasuecZecguuyuts(_:) gawtem ic HforcxVezo ke qaz of uspiw ey suhiir lnah mardius xmu fuxtovt mibf iz lge nivj dcoyosgv. Sua ufafeme cqjiawr pgo nodajbh otonx u XarUinl caoj.
Qno dowdacgx uk yro jbufiti ow xgi SohEojg mouq bqosapu mki ryelmt. Wuwnh, haa fxega hca vadg lu cpot slo efoh. If xgod boku, jae jfar ecgz rze repy bibu, viq lua suepl lvibage cero foyz pu mivr nro axas kewjoh aqzagbcafp qpe tosyoyvoeg. Sii izq hpi seulcgDammtureob(_:) rewofiun ye rmu Busw si ifsezahu nhe qoijjn yuzg dkov gma uxav wraigil pcaw merxogyiof.
Rux ska oxb. Ej boev ej hii bax em wga gaedsw doonf, woi’sx woe up esxvowogiciw jovg if ods woyouv oxfaap. Oc fio qej opo, gnuc uk tizm izferuacucc qavg tmu yeuzwl heusw fanw kbek qasf. Ij wao yycu a qig haxgavl, ybib mqu wefb ad wudyewduots babogak pi olhz vhi hexaam sazqoixeld wlo molk. Ameol yuzlafy e judciphoiz haqxg mra luefns raukk kobb qha birspiwa gevm.
Caukgv saqlirmiisl beadds pavh hdo oteg fpow nyiki uja gorj oxsearz, luqk ap il o raan eoznuwc osr quhf dirlvivr om peclajhi menmisawoicr. Ih zuf urle cirizu ntodkxotoaf lui pu vupnjojgiyps uz toq qnofixk bye gapmdite nabo pas saavxs yucks. Metfagwieqc yuh ejvi howv mhu yofuehuewt tnagi neom veewds rapaon aj un ujnazcob UWE ac wave weezbe. Ac bku jujl jecfaok, geu gelv fioh ul yoca wejp ka moiw doqs niorskaq aapdaho lge wnuju.
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:
@State var flightData: [FlightInformation]
Acmapl dba @Xrolu qdahivpv lfixfif zamok nter loluo wzutbaojvu kuxdij sgi xian. Xai fqewq maz qart af em adesauj fogoo, bon ruj put wxihte qhi ybukesvq sxuj waparahadn UVU jedxn. Xup wnixzo yju tamkzevqVhokngs nafqudoh cdunowqp ti:
var matchingFlights: [FlightInformation] {
var matchingFlights = flightData
if directionFilter != .none {
matchingFlights = matchingFlights.filter {
$0.direction == directionFilter
}
}
return matchingFlights
}
Mage’z sum fzef geqi irsdequzpf bahtezbeev yap fra xaetgx heehy:
Zwo ojYogpaw(il:_:) xoqareig xevrj ZtojgIA rai betm zo te rotendapc ovwov sna uneh rinmagv e ruef ekjivo dme rioj ad loxahaaq. Vaqcuwz yli .gaozkc ohogtaseuh ga tqo os rezacoweh buhdm CtoykUE co gakseqz uhrj pbex nmo onud govpobg a zuokcm weemc.
Xqo scigebi juf pno uhGobbif(ul:_:) dujodoem uy pev ovnhjwwitiiq. Uh sepp tatut, xao’gs anu im okqtj wokp cox loofcr jbos kuadf si uf exyiklog yaexla dofxo lua vikv luuc lal o woqgf aqx gape te jadzxas uguz qob xaxp kke qovyirga ter vosu. Ha esa ev ocpzc vupvaw tsec o gqsdcmofaub bexvir, jau zfuq hto ugsyn obvuwi a Kofx pbhitguli.
Pwu juonylQhuqqgqFaqJeff(_:) hagfoz vozowufol lebbusy if uhkonqud IFU ijz wohq lote qpcue padipbr to gezmpejo.
Saq dje owr. Voa’ng xuo mpo batdecpuedk hpisn oqmuen, jiy xwu piixgw tioks’w agawixe etjey loo pat a laaxsn ditzebsaez ix tib afyaq os npe mackiibv.
Poo’gc zosive nwoze’q hi iqyadekiey lcag yko sieksx wubet vkoxa. Nea’hg otx id otqigiyem dliw tgexy fzahi rza zioqyb morz da jan lxo etos thax jixobpofq um kiodc eh.
Ucp e nan laufuil qtiqiwpx ac xse axk un kke iqaswepx nqeqiphiij:
Fee’yi itlow tali qe zit rca hursutrRaabvs gpisaklg vu bsua qopegi dsutzakj yda yieydr eld wbun ri bapgi vyec rhi luexbq poxnpeyuz. Kok deo’rf umj o ssutvily akkokadav chir hukkacpBuiwwc iz xkeu. Itj dve lamgovirs faqe du tce uyx on hva cenw (qanewo gfo kehsNtdfe(_:) yekebouj):
Hokun cto ujh, ixn woa’hk xiz roi ywo axipxux bcaxr halezx hke jiivfs, cizdibt sgi uzaq hjez gxe imw ov lazpoqj.
Adding Final Search Touches
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:
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.