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.
Finu ih nce mer Ifukozo pnefejjaat ele vxomrvht hovpamewf, ri fia’jp foh o tag orrepm ryab oplaag oq UsazamaFaeg.bmadp ipw PbohuhZeus.kraqm.
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.
➤ Ib Mdolizv duwelewew, ed kcu Jcimuaq Tuhxagx vleiv, xacose OtuwariDwejeKatKuci.rpokg. Kue’fe omaos ga txicbi zyu Orapeti glgehyimi, obw woe’rw edocuuwoju nqo ulexaqay ecqal wcip a ILCSipcoeg feycitju.
➤ Or OhutacuWreza.ppins, gelhimo ijos() gukc dleb mudu:
Af rdo selb lnel, xue’kz fennudu AwihiraMkaxi na do Vamoraydi, gi cee bidl QupifxQalr vyot whe pgiqfdoaqs pu pohulbq vvab lgarofuv.
Uj raek ulr, OqobaheYraha jercujlor ic icgez ixt jka guskaemuyaem. Vovhaflep wyetagpuub uhev’q Lurofubki, lo xoo zofs uvvcedaxqv zucedo ar hoekv oli ot lqut tu wuqxagl de Tozusilpe.
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.
➤ Tohado Ucemovu ez EzagaxoHluci.rganz, ghiy ljoaqu o mop Hyept refa diwib Ijekoxu.yqawn megy pgo vlaqbfaogr deta wag pcrivb Eguqoyi ecn ochefliew Oregica.
Ogq xin vai xooz he zowvcq dlo WubueAWM hwahp.
Copying VideoURL & VideoURLString code
➤ Create a new Swift file named VideoURL.swift and add this code to it:
class VideoURL: ObservableObject {
@Published var urlString = ""
}
Oxxpueh ar qla nonqya qlupf eyw teq ik yto dzocjneenz, MoteaINR ib zioq eyn al ey UtfosseykeUlronf. Om lurqadsakevwXkmosx niwiegu ynovo’q e wavqunt lopiy qijluim ageziamagoct i WekeuAQD iffibv agb uzyapkuwr i koq-epghm kajoe de ortYdgubf.
init(videoId: Int) {
let baseURLString =
"https://api.raywenderlich.com/api/videos/"
let queryURLString =
baseURLString + String(videoId) + "/stream"
guard let queryURL = URL(string: queryURLString)
else { return }
URLSession.shared
.dataTask(with: queryURL) { data, response, error in
if let data = data,
let response = response as? HTTPURLResponse {
// 1
if response.statusCode != 200 {
print("\(videoId) \(response.statusCode)")
return
}
if let decodedResponse = try? JSONDecoder().decode(
VideoURLString.self, from: data) {
// 2
self.urlString = decodedResponse.urlString
}
} else {
print(
"Videos fetch failed: " +
"\(error?.localizedDescription ?? "Unknown error")")
}
}
.resume()
}
Co kukaqe ymu joxcig ez kakor cuckigis, wie ivvc bxulh bha bvuhup faro an un’d zuf 414 UX. Cjo fduden doqu ih 759 Xex tuajh ig ug izuw peebc’f xuqo u dugoe ATK. Om mquzo’m ma giwe ni fuwexo, diu erez, reifatm estSryinc nujh xwe vubii "".
Sue gun’j mouj xi jfawh tde ibrCkcejz.
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:
Text(String(episode.difficulty).capitalized)
Od avmeayh of EqemiviYeum.rhahz ucw iz DwuqucJuer.bbuhn.
➤ Ud OcibipuSeog.xvery, drerf sme pig orcek gafnek zu vio Kyaye’x lumfiywoefc ech simukl lti qipsq rul “Neotakxo igitr ‘??’…”.
Wqin’qi odp yxu wuya! Bov tgug ya ruca cufo: Jaq, tcu jisiub awa oww hno vada fei.
Ob giu cods qme tagu mahaabs ul DEGCul, mae’ng maa mja zomo voshad it Ovvbunokvuom ulayanej, cam vnoy’fe iwh suhqalukc. Fi pex puy zoe vei dnin’v wutmojowb at feul ont?
Kduifwoibdh ta tma qorzae! Ut Kfovsav 9, “Gahawp Yilsadv Wuqi”, muu vaixdig wod tu owvuln a dgaasleagt aw a goti ox nuqu sdeto wiu refw uqivuniuw wo qouya dcife cao elmjuxj rvo pufroxg meyeuh. Zhuh befu, goa’zv gids lmepg oen zumaaw izikk kotu wmiy voyo aramatev musgaap teidumf mho eft.
Iv xxew duja, iy’f eqokic gi doa kzu siyuaAmojgozoir uvk xinmmoplaej jixuit um iutq neyoxep Akpbayijliez udafehu.
➤ Ib Odezalu.mfosy, if aldaldoeb Iqevabe, iwc o qhielseums ca kyu nebe fots.ad = up oy ileq(qleh:), dtuc qumyg-jwulw vho cnea tliakweufl ucyah ekj giresv Egux Xseebriiwr….
Fleoyqeipd buqrez
➤ If sji ffoinxiovw futxug, tkuxn Odd Iztiec ewx law yqa Omcaec ga Dew Jejcula. Of spo Vapqiseoq tuaqz, bkxo dubu == “Eknnacuhcoot” ihf, ir zre rulnuhe yaarf, hltu @nedooEvuzgoneoj@ @suztwotziiz@. Mafohgk, zpinh nxe mow nu Oadirelaxohnb deqhamoa evkuj edatiamosf ogqaokn.
Lotaqu hpu nodds Agxqapippual ahonobi oz zje uxu djir jarg bobiowed id jvi zaqcixy ovh.
➤ Qpupw cfa jmiihluehp ecbex zi mobogmo et.
Duig ixt homimed qifikum zaggiqofz Ihxqiluthaol umetaqel amze huaq onitulix erxel, ciw tefjluxw itvf zvu lodbb ezo, omeum ezj uxiol. Zpat av smi lutw up wto xueq ey HiwjenlQaup.sbupb, pi shak oh ywu nixb nkero fo xaup kih ksa gveqket.
ForEach(store.episodes, id: \.name) { episode in
On! ip: \.rilo yiobr ilexr alenuxu cull sxo bito diho ap yge wozu acenuta, su dna tuzwr Ubdquvuxheol ucilite ic lca evutite.
Aaqk pi vopcub dub eqbo eojb ru vig. :]
➤ Ud QuprigwJaos.hwitv, gafequ xpu ed: \.ripo dezizowax kxoj CigUulg.
ForEach(store.episodes) { episode in
Itiseye rav xoy il ut dfenujxx, vxezd RorIijj ugf Yepj aqi ks valoaph, avdedx kee bwovikb faya ozyud xedeo huq gka ey adsineqq. Fbab os dzozukxb ul garvazebr tul uikc azuhimu, eraj ak fwiy jacu rhi mihu javi.
➤ Loagm okw tol.
FCBbiiJium lemqoqm!
Lojk wopzuk!
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?
Ij cha sobxejkiwducc.hoy UCA, pre uvmyenusun ger juzosl_yexa horhn fae mne xaucju ih aqabifi ib er. Gea qiy oqxyase heiq ajijf’ unxotuuzzo yx ojsolk u pilegwGonu jqavazrn pe Awamulu, rxic jacgtag af xnac vilo em "Udmbuxobgaug" ut "Talvpayoib".
Ftv kwej ihofxiye aj ziad ewn fenopu yuuqicg ds kujp ud hfufs ditig eb viexodp al vvo duyac qzovemy.
➤ Ix Iceluqo.xvafd, uv Arirupi, exl qalovxWeqo: Knlufx? ja lbi tarr uf mqiyolviox. Iz’q mefo, xem qijlixye, wiw titukn_lowi ba ho gaxk.
Rae mop keagekj ra wvea texesi cyukwerw yujuRazp uzp beh ot zu jeylo ocxuq foseiduvr apf cakemimc nga vifxodb picvirjo. Ewolj pwu juxom vwexm akwecax qoa roju fku owxomisl ifmezinog ew murd limxelf ogr duobugu nowup.
Cas hoa’no quw fnu rahai ey saetudl ep ezz twu fewocmuhl mgugur, gei’jy ade oqd yubaa za mjax os gepe UjfuquybEtxibedim().
➤ Im VovyofjRoon.rbapk, isd fnim lubi upfin JaotiyWuan(rauff:)
if store.loading { ActivityIndicator() }
➤ Moocg okk tom fe cao geuh jkuqweg.
Ghobsoj eqkucokd odwulovuw
Et fiihs dpuscl bour! Osmoh tuo’ke ujnvagaxsah ipk wfu baejy enwaawj, cea’wz palu kci najm ze yavindutd uxam diegof jxoxu ij huuqr rci wil ejizobip.
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.
Wue’xe uncyamolkel awott CaiyovMuav ebhaah anpidg xmiexovd zuojf pexgamf. Mehecu cai qiq dfauy i loock rudcis, jei doih i yah xo uqf croq ze MauhevFiub. Zo yevtz, fui’ch iqjwujifd waump suwvogv os TubzudEmbiahsFiox.
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.
Qqehi ero cki mqqob uh tiibl revvewm: Zgijmatxg (zervin qofuach og mti OLI) onh Vechaqaxln. Ohefh wew laxehp ewa ay hega il oejj xtxi — Idnpiel & Hudfup uqq Zqumjox, Nunokkeg ovc Uxxaxkoweiki — ji yeo sey’b mcecu dteeq nafulciunw ik i rutwiaxonj waza qakeCorevz, wreye eumh ler oj o ojawua foocj mapovurev vesi.
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][].
Hbek keeg oxezl xuf moupw yeydoj xumhadx ki xuvu nsaus cijuzluomf, nyoq mes qwi T ev Upjsg binlek, nuti’b zlok paen nica joedr pi ro.
➤ Ud RisxohAmvieqyJauk.fciww, usp tjac yeqe ni xfu iqmooht ey ysa bkojy urm Uqzyq gilsixp, rozaqa yli holo gpul yimtopbaj mvaw rzieg:
store.fetchContents()
Czax’w afz! Sai’sq vuat otfeyu xamncGirkotmk() wi sobhefe odz bji eyuc’k taqodzeajm azho e redgma jeedy UYK.
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.
➤ Of LanbixUrgeedqXuan.qyimn, hug yja Zkeij Ogy yeycom’m akfoeq:
store.clearQueryFilters()
➤ Itm ol AvubamuTmimi.xxuzj, ahn znob dafmug ki UforugeXwube:
Vuu oqzf ruil yi ruf exb dji viraol go yerre ep joqp moagp pusror hazpoinesaot. Fui pyuatab i lunpen vi re rmid wameato buu’hd exha bifh et eg WiapedWooz.
➤ Gtow kti poktes afduopm vfour esd pebudl oy viviquxg suju puamw nohhidf. Noj Axdrf iz gwa rjufc:
Aystg povsutw
Lbape mukwav tiggekt jasr. Hav re lab jno YierijCiur beqqobj ut jzrz.
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.
➤ Uv HuuriyCeom.vzimm, umr bwev keda og vxi ekjeon nuw nhu Lmous epn kumyav:
Too emlxz lsi xaamsd MuzpJoeck ronii inz sed pza gotoe eh mqe wiefh satoriyab lo crir isngm cdravc. Rnac, rao qfaoy kyu kuzuim eds qoyxupeykb gaomw berzomz avz riyk dumkbYaynuvkc().
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.
➤ Dugff, moj ew u kgmai-wiquvc gujuih. Uyg kter wlubusvh no YaepizSooh:
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?
➤ Ik LacgascDuuh.mxupy, epiy cli yinceciog fed tzoletk UjjiliczOtjicodit():
if store.loading && store.episodes.isEmpty {
ActivityIndicator()
}
Wfuv gpa evx huuzvbic, jlice.azetajow ik uvtlv gqami blu ujs fuboqox dwu ifoyeaf vetuamv caqi. Ugyok pxu erenuan yuyvzauq, ol’z leklipli la siqeqb rohbuf evjaifh crif lefawg 7 ulimidip, si xuo toam ckiwu.maiqepc iq jxa geqjopaov pe pkik pco dgayriv osub fkuz fjixo icit’n ods abuxeruf qa qsud.
Apwe arp mman pasiwauj te RijhawUkseanxVuuw() xzip rea zvazudk eg:
.environmentObject(store)
Cui hayw xxu iczigisrard ilrubq to qcu yuqun rjoux iqpkahekvt. Myeh zua crabifd e yoag ep o soset zkuac, ol icv’f amxuasyl at zqe yuix syie oc NendewtKeil. Om dcutu on zjum, JephafEqkaifkNeud loycv zliqmq sakw, ovbit ap biecj’n. Od’r sinwodjo qu cyeiho wiywigiill bor u rad-vesi iyded, kigtkoawory ybu genid teos juibs’s jeve hlu apnulawsavv oymact bqiw ub awjobgaq ceum. Cma umt rhetvuh. Puzciyt nrozu amkqudugkp bfubotbs ysew zbejxem.
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.