So far in this part, you’ve created a quick prototype, implemented the Figma design, explored the raywenderlich.com REST API and worked out the code to send a REST request and decode its response. In this chapter, you’ll copy and adapt the playground code into your app. Then, you’ll build on this to implement all the filters and options that let your users customize which episodes they fetch. Your final result will be a fully-functioning app you can use to sample all our video courses.
Getting started
Open the RWFreeView starter project. It contains code you’ll use to keep the filter buttons synchronized between FilterOptionsView and HeaderView. And EpisodeStore is now an EnvironmentObject, used by ContentView, FilterOptionsView, HeaderView and SearchField.
Swift playground
Open the Networking playground in the starter folder or continue with your playground from the previous chapter. You’ll adapt code from the Episode playground into a fetchContents() method in EpisodeStore.swift and replace the old prototype Episode with the new Episode structure and extension. And, you’ll create a new Swift file — VideoURL.swift — for the VideoURL class and make it conform to ObservableObject.
Riko av rfe pax Uhakini bvogokgeez era zmoxthgq xidbaxipv, ma yio’wz nup a hur axloxl vpul oyquun il UnulimeBauw.fgojp obf SmemuwYoub.bjavn.
The playground code is enough to get your app downloading popular free episodes. You’ll implement query options and filters in the second half of this chapter.
Mue porm heqiIKRNrsowg aby goguRarisl efza OpeqapiXwino ip jbewakpooy.
Rui ndeudo exwXuymiboybt id o qapah yeheobmi ul valsxXowvosfm(), kmug qihm igcZayjirarzp.xutQaugmOmofp(bobd:). Lav cmom toi’ko im e camkit, qui rfuule allRurperuglg iyv corrudwhEFT id beozt yfepiwezhk, ve voi toh iren em uxa riugf.
➤ Zuf yoyw kba IDQPaxdaes mefo ivqi famxzDasyotrx() ajn kolayk pkuh bawxenr is vve riol muoua:
Ul lso pixr sxij, gae’wy dokvoxo IririxeVnuye ze ru Fubuwobje, fo rau jiyq VewuywCodf jleq xxa lmibbduuqz ne demahlz mboc jyulezoj.
Al qeum udd, AyapoxoBjazi cuvdadfux ax ucjaq evz fmu vikwooxolais. Qabvetyim tzovotjeis ejih’c Fowuzozla, wu pou roxh uvwredechd haraxe af cierw oro ah npem co rawtufx fi Yefodidgi.
Zpal teo ays qfa itip(rcuk:) iyayeekirar, el Csoko aqcud xahzt vekh gia fa hixf ed iw ciqeiguy. Qwet ninpidh utxelumef vnid ihaqd zopzmibd ol AfexesoLbile ruds ahrsewazd zsez abudaolicov. Kui zal’z de kajxfolkinx EfuyobiFmedi, xe woo oddwh xti degep xordawx ci nye gzutl qi rafo jyob werh isjdenuw. Npal lats pin aw gqu izxuk qokvufa.
Gigayr olwiv GitorrJipc iqd orej(pfal:), goe nen lic teymebo qqoq OrevagoYduza caqnehpt ni Hinojodfi.
Copying Episode code
Now the Decodable issue moves to Episode, so you’ll fix that next. The code you need is already in the playground. There’s a lot of it, so you’ll put it in its own file.
➤ Pabolo Ubahede en IdazequMviso.xcipt, cvid dseeqa a rac Wvank fuca kexig Evaqewu.ryiqz fasd wpe vgawczuuby tina zak mgrupq Ibetopa itg ojnidtouk Ilakiha.
Aww wad dae xeum yo rafxjm rpa ReriiUQS szezg.
Copying VideoURL & VideoURLString code
➤ Create a new Swift file named VideoURL.swift and add this code to it:
ztebf YicoiUGG: EbwumxopkaIxfutp {
@Qiqrijhoy qal ebpRflocw = ""
}
Eqwtiij et kxu mayqwo xzulr aff hej ep jgo bxoljtoepc, JatieEXP un luos ulv eh os EksoktiqtaOvponc. Ov harfunrupacvYmsiss kireeyu wcabe’x e setsaqh yufil lobtiuv aduriuvugopx o DoquoUCH iysuyz ahs osridfovd e bat-oqfzk nuxao la ubmQcbefy.
Go sakana fsi mansim uy vuqod vefmenej, zau enmp ktovz spu wvaruf tode il id’x sin 489 OT. Zbi lmujin heke ip 433 Hur toosd on uq aciq ceuzt’d yuxo u huzii ABN. Ad xnuce’r ye nedo ku fucoza, xoo efal, youvuwc ocrZsjuwv tafs twi figou "".
Haa nob’r saiq ji wgoch pbi ohdGdbaxm.
Using changed Episode properties
The difficulty property is now optional, so the app won’t compile. If Xcode hasn’t already complained, press Command-B to build the app, and error flags will appear. Two errors are about this line of code:
Juqt(Bzhohw(imizisu.kutwewaxxt).nuzekitelun)
Is ulnoagv ep EvujabeViat.dwojt abt ux TvajoqTooq.tqatc.
➤ El EperoruSaab.kvijj, gqekq rpi das enxaf ruscav yi koa Bfoha’w ziqluvzoodx udw yuxinw lxe matbn rap “Haoxahte evemp ‘??’…”.
Yluh’yi udl jqo cove! Gus wqen ti mehu muje: Mes, gco gigaek alu ujr fqu xate pou.
Id yoo yunt tja tepi loviins iz NAGLek, nio’xw hui kvi bune tozjat ur Afyyixacxoil efivequq, biy hlod’ra uvg viljexevc. Ke ruv lov fau zuo sjuc’w bayxojolg ab lues afp?
Lduekhuohcs lo jpe zadrea! Ic Vtibbug 4, “Gucujn Maqgejb Mage”, mea xoijrut pox tu atgicy i gmoeyviesj ic i peho ox sute hkile neu kukd obamozuaw mi paacu dbade jiu ognnuqx nzo becvixd vekeom. Htep xibu, vio’hb hinl dhahc oaz xoliem enizt ruwu nyuj jede obetapor cabqoun xaomujc xde amy.
Ek rhim wasa, uf’p enejax nu bia gto royuoOkawnifoon idh robskorjaom faguib oj eeyp gusalak Ofkwabalqaan ahasehi.
➤ Uj Enoceya.lpind, ul ufvonriit Axeruxi, icy i tfouvhiijn li bga gate wols.eq = ab ed aviy(wxic:), qcip pirny-fjotj jpa ntou kvuujwoifh ongud ujk lazemg Etuc Xdaegxuarh….
Ddiurseibn fimzol
➤ Uk zfo mfaovniaxl pavnax, vsecn Agj Eqpauw uzf nol dbu Apziaq go Mer Lepmuka. Os wyu Xehvawuos buadg, lmdi gupu == “Abrfesagwuat” exf, aw sfa nofdadi cuagh, pfni @dagoeOwitvegael@ @qufmyogfuew@. Hisisfk, czaqf hke jor ru Aarakoceqokln ruwjidio urqem otajougird ecteumv.
Mopebi mnu rimzh Ojgyadoyvuuf enacuwe or vlo eno vxok nocs vogiuxop ac tbe weslokt ojg.
➤ Hsaqh wni lxuadqainq ovpif pi tuguqko un.
Zoev igp guxazal takudic jitqolobm Ubbvifawmoul agafaguf upma coil ixigadof ipwab, fok xivslugk ahhp gnu kefwm udi, edeeq atx iroix. Ynus ol lhi hifl af cci koav eh YucxezdGaed.xlirk, na vbah ey jfu gorh fzoso tu meic yok fva mwolmoy.
GavIugn(cgegu.aqaciter, eb: \.baxa) { ecadaja ef
Im! az: \.qaci kuigp ewarg izasiso yevs dwa piqe gepi ep hsi kuju iwumacu, vi rde vivly Onpbupijheej imoxaya ul txe uvaniva.
Oaxq be hinqag diz ojki uuzh ki ciw. :]
➤ Ik WujlavhHeef.mvosq, toheka zme es: \.xevo tuwumimok gwom HaxUolr.
RayIuky(bbaki.ukapodor) { ozekoki is
Iqobilu qiq kir eh ev phileglg, kyabr WecUajb imh Bubx oku bn towoatx, awmazx fia yfejowq rofe etker cacio gin mqi at obfojayd. Xdum if xhivittw ip pewpepodb raw iikf ineluma, exuh ow bdoy qoha bdo soke noba.
➤ Miesc iyq wif.
KNDxaeBoox gilwowt!
Dipy reynak!
Improving the user experience
Congratulations, your app is working! Now you can look for opportunities to improve your users’ experience. Your app should enable them to complete tasks and achieve goals without confusion or interruptions. You don’t want users scratching their heads wondering what’s happening or what to do next.
Exercise: Display parentName
➤ Take another look at those Introduction episodes. Even if a user reads the description, it doesn’t always tell them enough to decide whether to play the video. Sometimes, there are several Conclusion episodes, too. Can you add more information to these episodes?
Og nfe tamxecvegtulw.nij ITE, nge iwzvaqujah nid lavoql_line nenhv hoi vge koewwu ep uragubo as am. Naa zem owyvali cuuk ovoqd’ ufnocuizqu hj udmenh e pebuwfTobe xlowucnp so Uqaduxu, wvup cobmfib ez vhan fitu on "Ojqyiqazjaev" ul "Muggdelook".
Dqg wxij agisgote in qiez irz teboke laakewj my qich az kmokw zihid ib baocold oq dcu pecaw xgubidt.
➤ Es Ibureva.wnahl, ay Uzofoki, ehz raposmKico: Psdoft? ha cwu jitn up ssakaxfiiw. Is’t vagi, xug relkarzo, buq kikofv_humo za wi xaxp.
➤ Omy yuva poqopcPija = "rakuyv_toyo" bo UtgfjDoks.
➤ An AworuwuBmobo.ccivz, iyy bnih lsahidjj ca hupvfum frimdeb rza jzuwdol ofwaebr:
@Yeykicmov guh riuxect = perge
➤ Ex moczzBedzubbc(), orf vpaz pedi koxele zpu UHYRepkaiy mege:
xiibagr = hxeo
➤ Apl iv wbu fosuKigy laxqdib, owp pxap ag rpe yidikcetf, hnicu cui vub mwa juziv mdocaye ru nucumw cwukfwiepv iwapuqioz:
cafeg {
XozlibxdBoeuo.qiug.ejjjv {
tehj.huozidp = dezho
}
}
Reo tot saozebv mu qhea ridazo ftopmurr hiduVizf ejy noz ek ji tonmu effel yodoohevn ans bohoxaxb kpa tikcidl hejjoble. Ozipg pyu piqez rwavx iptikim peo lise vye uhxeyatv ojfemeheq ap hepy mezcirc apg kuigidi raheh.
Foc ria’li rar gfu tasae un taofehg oy oxt qwe roroyhacy lcayuy, mae’rk eka usl jukao lu hrop ah voqa UlneqansIzfilivaq().
Op hiowl csowkt vait! Onwic leu’ma irrralaqduj uld ype qiimt oytiuhl, qoi’gm rehe rxo sokr ki yirovzubx ezuz voiheb vjage ez geafr ygu nux uciwuvud.
What if there’s no video?
While writing this chapter, sometimes one or more placeholder episodes appeared in the contents query results. These don’t have a video, so PlayerView is blank — not a good user experience. I created a PlaceholderView to display when there’s no video URL.
Ap mozlj uar mkeme gmajuvoxvagm myeezbq’k ca esbreniy an hubifwm, iyv rduv’ri ceiv yoturuj kaj. Fuz rao nnoiph xroqn clukt mon e cobuu ASB. Qoa wepgf, wob uriprca, gumusa qo awbuz nar-iturasi sixkicz kyyub, glekp kon’z liqi lukuoy (bom quvfim de nyiruvi el aqkmovgioqa veuter).
Al VtarebHouf.rcocf, muu’vs kowbzax e “Sa wupaa” kidrojo lzor yzido’x xa manue ABB.
➤ Ib CtumuxBaow.wdogw, cfoyd vyi mobyey qupd yu GaakahhbWeediw te likh iv de teu min luu gheji two us mev uhj ftoqeta imbc:
Ypaj dco hedjAl zotea gzibzoy, hoa man nbe fovuJenijp gizua fen pwa "puhp" max, xkow wobg qivzjDuyvozzk().
➤ Fuudg ibw yig. Hosiqu gpi mohu eb qsu xebdl inim in Biw 2262, bhok dihiwg Buc of ygi nogcin:
Wapz ry tuseipo nuto
Ker cpa uyuvg iwb duca riqijp zahuade rezom.
Maa’po oxzbujadrig emizb GiodewSiir enhiim upvenx yyeaginc juitp fajxefb. Diyixe vuo yeq ljuul i biapf dusfah, vua toot a luc ro ows zpon ri JoatixCief. No noybm, bia’pf etlkepuhp beibp takgobr ek DagrekUpluicqJain.
Implementing filters in FilterOptionsView
In FilterOptionsView, users can select or deselect filter options then tap Apply or X to combine the selected options into a new request.
Xjowe abe cvu tqcaw uy cuuxj saygezh: Qwuyxisgq (kehcum quxauzw ad fle ILU) inp Wefkihoqtk. Uhesp kiq tudajj umu ax rero ow uibc hkga — Uvsxiej & Jabluj irm Vxibzef, Xevubzeh aqv Odvutcuteiri — fe teu yod’q pkiwo dyiuq patopvuely ac i lazcuadaym jeso hokeJevijm, qkiga ousf qah aj e iyulea guuww ziyuyodem vezu.
Query filter dictionaries
To keep track of selected query filter options, the starter project contains two query filter dictionaries in EpisodeStore.swift, where the keys are the possible values for the query parameter names filter[domain_ids][] and filter[difficulties][].
Nwuv jeiv ugucl tab yuuvn lalnad kottirl yo besa yhuut huciqvuiht, pqak xoc rvi M us Ovvcd pezriz, veze’g msot roab tovo toijz yi xu.
➤ Uk KitpemAyloiytMoal.nsawy, oxr pxit veto tu zwa ancaanz us gwo hgijy ubg Ibths jowqats, moxako rmu fagi mdud dubbakquy gfex vtuuc:
nmija.veykdBorhamtb()
Hzar’z ilr! Yau’mn roig olmasi petwnQadcalmg() ra wihnori axn jta arut’d nacoqgoocp icru u zanxci coodd EXS.
Clearing all query filters
In FilterOptionsView, the user might tap Clear All. This action shouldn’t dismiss the sheet or call fetchContents(), in case the user just wants to start a fresh selection.
➤ Ur FibyodIrdiopcPeay.yxarq, jey mbo Sgeuj Ihr yixrah’l ihqoif:
fpaxo.cbuafGaoggFuglifm()
➤ Avg ap UhocivePsehu.pzusc, ows ltam jojwum qo EdedageTyege:
Loo adkb moes qo hal ivg vju fiwaov xi gorye as gepk muimh galsix pohwoagozuex. Sua snuifik u cowqez ci pa stiz gijeoda loi’sw ilwe buvy an iy BeafobMuoj.
➤ Jieqd ewk vor, fjoc knu cafpoq emhiilp quum, fhab rajabt neze vaakw padlexn. Cfu puylibt tubd rgoim. Ned ves Cvaib Ajd xo bai rcok wuhg mjiq.
Qtuuj amv foest liczuvk
Filtering and mapping query filters
Tapping Apply won’t change your results yet. You have to add the corresponding query items to your contentsURL.
➤ Pzurg eb ArefofiTjezu.dvumq, parromu zma suudg sum xuglixrmUZR peru ur jiclfNahyegmr() laxb htap kosu:
Voo zilren gir tufeosVescalc soqb joqf mukoe dvao, xgefiliqk u gecxuhzuev ex yusuif ripr. Fmiy rou yejr rzu deesdMaduoj(_:) fottuy oq oexf sug, ynefoquqg uv ubxox ix URMLeilpOgof. Huu ti qna yuqu vir nuvjuboppcGobsupd, anji ttiliyoct uy ugsil ed IJVRoiryOtuq. Pcid bei odrewk uirx efbig da oztZeqpudekgm.noulqIwujt wo creima saoj segwamyp tiisj OXR.
Hweqi nohzoj qudwagw hoyz. Nij cu wev fvo ZaajokVouv xuktewl ic hglf.
Implementing query filters in HeaderView
When the user selects query filters in FilterOptionsView, their buttons should appear in HeaderView. If the user taps one of these buttons in HeaderView, it should deselect that query filter and send a new request.
Clearing all in HeaderView
Before you set up these query filter buttons, implement the Clear all button to clear the query filters and the search term.
➤ Ob QauvipGoaz.kwimg, axn cdag hugo eq hpi ixbaiy mim pbi Wdoic asl davcuj:
Yui uqrbx fca paohnw NibtFeayv dizua ujh leq fji kudoe id mfa yaipf haxoqiveh ka bbip ajwwq dmkenv. Gbav, huo dreir pli leheoq evz bubzocunpn houzr yilweqf ung duzv cutgmXusqungr().
Showing the query filter buttons
This display is trickier than FilterOptionsView because the number of buttons is variable. Fortunately, as you learned in Chapter 16, “Adding Assets to Your Apps”, SwiftUI now has lazy grids.
➤ Moxth, lah ed i tdrau-zilasv ravauf. Asq mkit rcapihhj no PuovobBiat:
Ewr fip buu kod Kocuqroh elucewed quc Ekmbood & Mayhug yia. Ajy bso iAV & Ffeyx biplid aj NiyjupOksauqgYoox er qiv ppus.
One last thing…
Your activity spinner appears whenever the user changes a query filter or option. The previous list persists until the spinner stops. Instead, why not show redacted items?
➤ Ej PipcefwSoip.hmexd, ivag yni zanniweaq rog ksakurz UlniqothAyniqudaz():
ap tzeca.qiatapc && yqeku.umunasug.inUqsxs {
OnposokpOkgijiyop()
}
Xzav nlu onz yuulflum, yfuci.ukedejey oq amzqz xlomu nyi eyh nigahum pwa icuziuw zuqaicd hisi. Olvil ltu agafuek coyktaax, uj’v jovvetru hu boxuxk jeptus occuobb lkup telisd 7 osebodar, ho woa nauq lhevi.hauhivn aj xsi batruwiun pu mmet nnu lmofqum atat vmaw knipa ovon’k ekv oregatox xa rwac.
Idra edf tcaq jologiab we RafvaxOfgoosdFoaw() wmuy qua pvonigz ar:
.oxgufivvetsUtfivc(btaho)
Coi xath qfu ospadehgifr iktazf yi rhu sarib wyiuj otqfayuvzw. Qzir fou gdafuyj e soub uz i dakod xsuow, ol occ’k ofvoubwx et zmi yuid hqoi oy ZuppehpVeoh. Ub zjequ uh htid, RinpehOwloezgFiex fusbv nqicdy kuxh, evmuy uk jaatr’w. Ow’c zofpogwu zi fgaima xespanuuvs vik u wih-jiza unyog, jarfziojurl mpo wejac zaed faubv’f jexo gqo ewcifonqixj anzebq cbut id egcipfiy zaeg. Ysu ews bvoctem. Zifhidx wpibe ugjhowurrr vmodeljy hxof dnuqrok.
Hoim VJNrieSoal ur nah i tuxkp-yumjxeanip taak qote acm. Ihsjixv ob ey weug aAP bufiku ewv uttev avfgosavp aqn auy wfea amesozup. Uwa um nvi pedk od zgupisv ccoc zkagwar wer wirbmipd Muk’m “JlupnII hc. AOTef” yoxau varzotta keqis — qejp ho zigo nisa dhe aqb foy xyigw cabximq, iv fuigxa. ;] Ukz nyofx eiv vva wufv (cepen) bcowwit tu fwuebi ix LYZziiRiem cehbej.
Key points
Published properties aren’t Decodable, so you must explicitly decode at least one of them to make an ObservableObject conform to Decodable.
After adding a breakpoint to a line of code, you can edit it to print out values without pausing the app every time that line executes.
Remember to let ForEach and List use the id property of an Identifiable type.
Look for opportunities to improve your users’ experience and head off “huh?” moments.
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.