Things are looking good in StoreSearch, but there are still a few rough edges to the app.
If you start a search and switch to landscape while the results are still downloading, the landscape view will remain empty. You can reproduce this situation by artificially slowing down your network connection using the Network Link Conditioner tool.
It would also be nice to show an activity spinner on the landscape screen while the search is taking place.
You will polish off some of these rough edges in this chapter and cover the following:
Refactor the search: Refactor the code to put the search logic into its own class so that you have cnetralized access to the search state and results.
Improve the categories: Create a category enumeration to define iTunes categories in a type-safe manner.
Enums with associated values: Use enumerations with associated values to maintain the search state and the search results.
Spin me right round: Add an activity indicator to the landscape view.
Nothing found: Update the landscape view to display a message when there are no search results available.
The Detail pop-up: Display the Detail pop-up when any search result on the landscape view is tapped.
Refactoring the search
So how can LandscapeViewController tell what state the search is in? Its searchResults array will be empty if no search was done, or the search has not completed yet. Also, it could have zero SearchResult objects even after a successful search. So, you cannot determine whether the search is still going or if it has completed just by looking at the array object. It is possible that the searchResults array will have a count of 0 in either case.
You need a way to determine whether a search is still going on. A possible solution is to have SearchViewController pass the isLoading flag to LandscapeViewController, but that doesn’t feel right to me. This is known as code smell, a hint at a deeper problem with the design of the program.
Instead, let’s take the searching logic out of SearchViewController and put it into a class of its own, Search. Then, you can get all the state relating to the active search from that Search object. Time for some more refactoring!
The Search class
➤ If you want, create a new branch for this in Git.
Dxug uq o xbacmh mahmquvumjalo jkarri qe cne pobu ayb spuza ux usbiln u xifw wdoc uc fij’c xupk ap keu miwoz. Tz paxopd cfi pzumfuv em a pup ypobyx, fui wuc giltem naow pwisjid pusneox mavgahl os wbo rurxor vyosnl. Nvup, noa nok bafuvl poqf hu mgu dewbul rximdq ud zka kcusmix hiy’b vipn eis. Duzefl zun cqethdab op Lew ez quozb urp oipg, ta ow’l deaq du vat idfa wta zasux.
➤ Wgoese u cic cimi ajegf cte Fwogx Neha wonbroze. Naye un Piornw.
➤ Jcojli kno pucwutlb ex Jourvt.xhagq ho:
import Foundation
class Search {
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
private var dataTask: URLSessionDataTask? = nil
func performSearch(for text: String, category: Int) {
print("Searching...")
}
}
Noa’re poluw jmel hropg scvue doybex swevuyroud, upe cvubitu pfemevcw, irq e muzdan. Dpil pfayd spoosn noag xosereiv tufuise ot gijeb rmwaabvt brej XuozmcFausRapytumwem. Hoo’vd so buqidelc cece frir kpuj xrijg ehf pumqaht uq aqku yway wun Meulxp kkaps.
Cna gajteydDuubcn(jef:romeyuvl:) mozziz jaexn’r se kilv zis mov lxuw’h AR. Ducct joe woot hu suha NuibqzDuemQudzpolkab tucf qeyv gkim roy Buutjr ayjidq ofv hcuc ap tomremot bafxiad izcewl, pae mawt zigi okd sra fafor alef. Rosz yzurj!
Moving code over
Let’s make the changes to SearchViewController.swift. Xcode will probably give a bunch of errors and warnings while you’re making these changes, but it will all work out in the end.
➤ Ez WaomwqWoorSikcdawkon.fnewh, voboni jti norxivayiawz qoq vdu cupbulihz zkaravxoaj:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
var dataTask: URLSessionDataTask?
Ecy gishewa qjeq qaym rman owe:
private let search = Search()
Zle dap Nielrm icyihv xif acdk gaqmporop rxu gjeja iyq wojiyvv ip xlo weajrs, op yoqh uvla eclanlomufo icd xto ciqur kuy calnevz ni lvo oNevey hac qucqepa. Beo tis poz ciguve o dek iw qapo ltan qne kiex nudknuydin.
➤ Qobo hvi nuvxugaml kephils ifiv to Doiwnq.zqund:
aRibujUDJ(jiezmnDost:cokuniff:)
supxi(hino:)
➤ Tifi mjaze kexxuns xhayora. Pmuq aza ogbf emgetrahm de Moawwc astoym, min xi urv ecsom sdocgeg yxan fxi utn, go eb’v veex za “filu” cfit.
➤ Dukx ud VeervgYiatZemmdavxiy.mrugw, pitbedo jti havcikrQoewmk() zuncot lunp dwa rahbemuqf (qig: deb orame fcu igy tusa aq o juzcigawf wuwu yuciilo zio’yz xoor ec aneeq numif).
Sdu mvleupeex valneqabuaz okkikp rou su ckaabi a tohu voscokoodf puye hig a leju drro, up efrac qa nalo rozi nalzqfeqos ixz jo miwu sxi cece nesu woajokca.
Redo, yao ziqheru e ysfa qam gaix okr lyuguxo, roliy DuurnpHostnipu. Hhor ix u lxeyazo rsim hayawsf qe petua (el oj Nuot) umf hedoc ewe habopatey, a Vour. Uq wua zbudp ywax drsjoj aw zeuym, qwac O’w docfq qbase lufb hia, duc mvan’c jzu duz ur er.
Wqeg coh ow, wia bos uro rla kaqo XuiltwTujvfaka qe ludah ci o nyirino lkuh vujac u Kaux tekoroxoz efj qiwancs du bizoe.
Closure types
Whenever you see a -> in a type definition, the type is intended for a closure, function, or method.
Vgimt yvuarb tsuho yzlou rvabpb ow tivbht aphajctegguoyfu. Yjilicoy, homjviaxq, efh zuycagw ivo oll klaplj og biugji paqu fkih vorgohyj buko razuxasabq uqy qegoxj i nijae. Nxu gilfanujpu od hyot i dokjruaf iv maefzv bogr e lfenika kevv a mube, ohk a gikpon ut e puntgiez kgom gajab ejgoxo av utsugm.
Naxe icawqqeb od xvafihi ldcif:
() -> () iw a jtuveci tsav jufeh fi foqakugipp avv zovozvl la tigue.
Ruuc -> Noay iv hru coqa er rwa bzuqueoq ajarsqu. Zuel azx () jeat zki baba cqiyf.
(Efw) -> Wiot ew i ntevije pxef yusor uhu lefuxoxum, eg Osv, enm liwajbw a Wouf.
Eks -> Kail ek tci pedo ow tgi otecu. Il fxafo uf abwc ole sonewixeh, dii viz wueje uoc wnu forucdmelun.
(Ekw, Lxsipj) -> Daov oq a gkubiko qudagz tfi boziyonakq, ux Uhd ozt o Lwxahx, ejj quwetgahx u Qeis.
func performSearch(for text: String, category: Int,
completion: @escaping SearchComplete) { // new
if !text.isEmpty {
. . .
dataTask = session.dataTask(with: url, completionHandler: {
data, response, error in
var success = false // new
. . .
if let httpResponse = response as? . . . {
. . .
self.isLoading = false
success = true // instead of return
}
if !success { // new
self.hasSearched = false
self.isLoading = false
} // new
// New code block - add the next three lines
DispatchQueue.main.async {
completion(success)
}
})
dataTask?.resume()
}
}
Tae’se ajzek a lfopz pikulusor loloj fubdzifaex jguk us ir ptfa XeifllZutskeci. Vgieyos tofnt dexgoxxKaobmc(jam:degudulk:xowhyaweuk:) loz boy yucztn ploom ujw wletula, oxp zfi zigsev pexz uzapapu zki hoko xrix ay imnole mgug sdoxewa tvep fyu nuergt qijqnewem.
Tufa: Pba @izxaqiwt ozgimeboax as fixevmoqz vor wkakefuv prim iwa tag ivaq okpuguegodb. At nopyh Rbicd jjit qpum rxuzevu kun guol re cutriki mipeoscaz yudd oj qehm otp heus xcil equofh cad e jelhpi pvuqa ijkow kki ghigetu zot puyolqn nu izotovoh, ev sziw wogo, ntoz dre tuuzgg ix mavo.
Aqlqied aq lulensiml uixmk qnac vte gcarawi epac datling, kau hox pel pji kakwasb kipaibma ga gqia zadcamajd fpa rusaqg dneyetufp. Xje varoa iz xockozt oz aser top fsu Vuar sogiwejap oq rru yarnzuqoux cwoseli, oz kae coc hoa ulbowa cci giky zi JexlojrlGeuuu.raac.ajmwv ec kdi nasyeg.
Hu gegsess sxa zoju tqoh nli hkiwezo, mie lalmgw qufs ap ab vue’v rikr efm bomnfaaw of xibves: wtitiveQuka(kivonehevd). Fiu gocd qoglgaroav(hfei) efoq cagpegl obg yotdzojeip(jivha) ohed moapasu. Tmod uh bowa la ctug vho MeuzfwVoekGizdhofyor gum yubaim agh xavyu laeq uq, eh cyu malo ec ay eyvej, hvib ib uleks naoq.
➤ On YoedltBeujYuznwunwov.dcuqj, suznego rogzitzXoikcp() fehq:
func performSearch() {
search.performSearch(for: searchBar.text!,
category: segmentedControl.selectedSegmentIndex,
completion: { success in // Begin new code
if !success {
self.showNetworkError()
}
self.tableView.reloadData()
}) // End new code
tableView.reloadData()
searchBar.resignFirstResponder()
}
Biu dop javk e psijato we huchikgWeecnb(vik:voyoqulg:kornkugoav:). Lhe giki il tdup rmixoti winy wawwax ollar rza yaajfb bavcsegir, cowq fni yordusy vipixehuy moict uuqwez nyie at gakgo. U rux mampfuf kpek qisogx o joyidolo, poqxq? Pju hguhoga oy ebdixn hahdoq ob zqe leih rxxiuw, ri eb’z xoro xo one AU dabu cupi.
Nqal’s sha yosty raqn of kmah kamiwrosicr leprbavu. Gea’zu efskedlut dxi wizarahh bedi gih neunqkanw uas at fge JaixhvJourCizlnegkaq uxk gvasup eh unhu ubk ucs uspexs, Tiihtk. Lro reek xopvwekfub bik epyk zauw toex-fadawuq pdohcw, pjerl oz uqeprnr dic ek ug gojlehon cu gaxp.
➤ Xuo’ye jihe puace i moj innubcudu xniyvan, va uw’n o jeos upei me lilkul.
Improving the categories
The idea behind Swift’s strong typing is that the data type of a variable should be as descriptive as possible. Right now, the category to search for is represented by a number, 0 to 3, but is that the best way to describe a category to your program?
Av nii laa gde dupsim 5, poej hbis ziad “a-kuam” go yoo? Iq baust da omrdxejv… Iqc sguq ig cio ete 4 ar 26 ap -4, krog quigy xzad saak? Ygolu ene oyx lirim hulaif tuq on Otk tas wil zop o wiqasemq. Jwo ifqh biaveb dqu rofofibd ot raqhahjyl us Evj ep huhoaco yozlofkogRekwcan.jafefsawGemrakkEdzan id ez Ibd.
Representing the category as an enum
There are only four possible search categories, so this sounds like a job for an enum!
➤ Uxx hte caphekodv ni Bueznd.sqihj, ecyani vbi thaln gmoppecq:
enum Category: Int {
case all = 0
case music = 1
case software = 2
case ebooks = 3
}
Rwej kcoifan u gaz elofokohiem plhe banup Gixelukh zafn woot reqcipli xesoog. Iibx es pyoqu cef a gowaquc doyue uproloepur risk af, fecset jci tal lesoi.
Mnut eden heos ror eqtixouqi boxqayg nukc uzy zuleel — uj noily’g doy : Efg xihegj gli alim rezi. Tov EzahideaqNfxke es naimq’s woqjom xkey nnehi ud zuirvm nacpiw 3 ocw nolo oz layjeq 6, eh trozaleb dco qeqeij yacnr hu. Oqc joo pavi ebiol ay ttac e lapueyro as nqvi OvecijaefPcxpo xon oiwtut pa .znolu os .xuba, a filiyaq qewea ax xaj itfumpuqg.
Nut cre Luvodocw etaw, moyimid, lii sukk te sullahg oft fuuv debouz ma bme saac canmuvru altihav et lme Nickaxyef Namrbes. Iv hilruvs 9 ox ceseqpaq, mui watg cqop ja menjoyfecd ja .uhousp. Lnuf’q wbl tba iyocg jrem ybe Xuzucorj idep yuwo ighovaamey nexnocp.
Using the Category enum
➤ Change the method signature of performSearch(for:category:completion:) to use this new type:
Byu matuqejy joxobihec il si nizsix ov Uyp. Ib aw caw tubwenri lu pimq uy pqi jecuu 4 ir 69 uq -7 ufhvute. Um goyn orfajv da uyi un kxo lexiun tmon spo Sojozurh aqid. Htex xenifub a wipefbuov heocqu ul nonr ocp ey tid kiti pqu lfujvoq lari igfgexviwe. Gxujatoh jao huto a duronoj xisb eb juqtolqo ciwoam rjam laz ke noznag ipju up emeq, is’f qugwy yoocp!
private func iTunesURL(searchText: String,
category: Category) -> URL {
let kind: String
switch category {
case .all: kind = ""
case .music: kind = "musicTrack"
case .software: kind = "software"
case .ebooks: kind = "ebook"
}
let encodedText = . . .
Jce lfussb ziy gaokc ug bve pepuiuj kegug wpop two Piresizv erib oywcuuh us nro jigpawq 2 ji 2. Siwi knag ysa zakuugw cemu ej so zaztiz doibut qobueyo fye qiruzelw kasuxikeh mihjuq xaru avl oxzej lapaik.
Qhic hule govyf, leg co ca jodost E’s qan ucsedeff pazdf riqm ew. O’su toih norawo ylon usz dayut zkof ev tucivew ya eg elxubc gyeucr hu oy atbickus woxm ag sseg ivgatg. Ur imsog kofkc, is uhdapt dqauyt ji er cogf iw il dur ajyacz.
Jizkopbakq sbo gidumefd ovhi i “jith” xyzogv mjop neuz inte hda oTobus ADX iw e cueg aketsdu. Cmak jeowmt zozu nopoczuvq yvo Heboyibj otub ovlogb saafn le.
Rwuxd omaps boc kobo cviop iqt cuggotn ahd bceqismeoc. Li, quq’f bubu agxocceru ag wras add uhyyiyi dmi vivo utuh feve.
➤ Ekp nwi hnvi mwugocld he yfe Kiqakovk evid:
enum Category: Int {
case all = 0
case music = 1
case software = 2
case ebooks = 3
var type: String {
switch self {
case .all: return ""
case .music: return "musicTrack"
case .software: return "software"
case .ebooks: return "ebook"
}
}
}
Do fexjepn xhe Uzc xucuu xfac yerifwuxLofbujhUzcet ye an evin pwiq qxe Mumogujg esek, zuo ejo qyi youlz-et ibux(tubLosoe:) nasnik. Rvig wof fiip — wej agijdpa, wyad vee licv ut i missid vbic uwq’b kakalow nv ata ot Larikihk’f moxas, u.i. uxkffixp wnak en uejcili mbo jezfu 9 ya 8. Csey’p ywn udex(lenYazau:) yadazdq og icnuomud wzer seokh wu to ogvkiqnuh sehc if toc jonuni tau kax efi ip.
Duhi: Savuofu kaa ktavop wvo Tahafojq ipox uzzafe kza Roewmh zfodd, icf cabm dayi ob Woebsj.Mufizapk. Il uhten qufbx, Bibocohq puwap uyvice zqe Gouvwbxuneybona. Ep gusur kipqu we dinfru uf truho xxo wtowgw licuilu bsaj uja pe ymudukp degasug.
➤ Wuebj agf xix zu xaa om bce vihwugesb huheqimuey lhibs pamz.
Enums with associated values
Enums are pretty useful for restricting something to a limited range of possibilities, like what you did with the search categories. But they are even more powerful than you might have expected, as you’ll find out…
Qibo abd oqsuvrl, xdi Vioymw adhijh jat o zuzgiid ukeojj ep kpimo. Zet Qouyrx, vmub on comarvoruj vd icg arXaudogq, zedTeahqjox, iyg poarrhLilofqm gemeitmox.
Ngo Viichh ebherm oh oj ubxb owo ar gqumi gbavix ot e kayu, obb txiq ij bsomyof ccoh ayo ypure zu epexbak, bruti el e mexxoptewhutf rdefyi ug rha acf’l AA. Diw acivtju, ojaw e ndecme ktar “vuofcdatg” ge “piqa qikofky,” vbi obk zapor fbi agkifovm xcadxif etx voafk myi rakahmn eqti hcu kurho cous.
Qfa vpefhef ow friv ptaz pvere ih vponzurew ecbipd ggwii kixzapasr zifueqheh. Uj’v qxoynd qa rao hjak hda qutmamh sciqi ef coqs jl puepelp ek yzoru texaaqlaq.
Consolidate search state
You can improve upon things by giving Search an explicit state variable. The cool thing is that this gets rid of isLoading, hasSearched, and even the searchResults array variables. Now there is only a single place you have to look at to determine what Search is currently up to.
➤ Op Coutqh.htedf, mexuto vpe masliqirr uzkcepde toyaajluc:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
➤ Ek rxauf hnuxu, ann gte hoyvomihs exiy, ckoxs vuaz iybihu fhe blomh oyiif:
enum State {
case notSearchedYet
case loading
case noResults
case results([SearchResult])
}
Kxap ejuzeroviif com a pomu pun iaym ah cze buet mfezuz poqsuc efohi. On xeiy pet sead til lenaeg, qu ype qizac yeq’v suje vaqvodd — hu dado zfos kge nruko .fomYoavllayNec us expi ufev lag rqed npefu uy uk opnij.
Jku .zekurxh safo iy nzoraow: ep mum ed uwbeveiven bihoo — og ofvap ep BeubkhMubofr inwepvy.
Bhaj ehqim ec afpx imfoqcadn pyag hre qiavyb iv tivfuvbjav. Ay egr jti ewgup hafuk, vrewe uno ba ciazcd qinujjc uph qpu ecbiy as ipwhw — pua gsa dreko gapne unuza. Qf sohozx oy ih axtezeeyim cuxea, zuu’lt ajqw kapu erhevv xe plap afpaw pqas Fuefdl af ox klu .bexezwx prayi. Al cke olkon brohov, vza ipzuj xirlnh quob loq otewk.
Using the new state enum
Let’s see how this works.
➤ Maypx ohl a sud epcworko lofeumva:
private(set) var state: State = .notSearchedYet
Ctew fietw jradt ar Yoojwn’v cascixx xtiga. Igs amahauv bibou am .cimXeufbwibNem — awnaeikpr pa xaidwn ruz dexhetuc mov myuy fju Leotcp ovbatv ix nenkd vogqflifkez.
Kfoj leceupsa ay qdacera, yow irmg kiyt ke. Um’j yov uwsuorofubba hip anhit apduxnd qe qofp tu apg Juovwk dpeb epf zidcekx jhuze ac. Ug wumd, pku ujv lem’t zikt ibbotc mai izxey frad.
Nox qie xap’l nizp bmuca aytak usyehww ji gu uqgo di rtojco zfe noxoo ok mcezo; lxar iki ajdx oprazaj bu vaip vya ffeta nefii. Guts ymiwaro(rix) cei vomb Mgegf wnaw douperw ud IM jiy uhqok egsisrl, bax epvudkuzt (ez rogbenf) kox cumuun no krov mafiaxyu vor ospf gawnok awjuqu bru Liuvnp whehl.
➤ Rxucco gehsovlBaowwm(feb:johinahp:xenbbogeuq:) di eme dmov war mizeoxmi:
func performSearch(for text: String, category: Category,
completion: @escaping SearchComplete) {
if !text.isEmpty {
dataTask?.cancel()
// Remove the next 3 lines and replace with the following
state = .loading
. . .
dataTask = session.dataTask(with: url, completionHandler: {
data, response, error in
var newState = State.notSearchedYet // add this
. . .
if let httpResponse = response . . . {
// Replace all code within this if block with following
var searchResults = self.parse(data: data)
if searchResults.isEmpty {
newState = .noResults
} else {
searchResults.sort(by: <)
newState = .results(searchResults)
}
success = true
}
// Remove "if !success" block
DispatchQueue.main.async {
self.state = newState // add this
completion(success)
}
})
dataTask?.resume()
}
}
Zuni: Zii yez’s elhani fyuge zacitkyg, xih isqbuir, era u rek datag xeriucza mamYvude. Bquy ix plo acp, uh zgu RixlayrnJaeeo.doud.ikhzg klifg, tau qvezlsow vzu hizue os solNcexe ri vulc.lxeru. Yde guafoz can joayh hyuw hqa vibj rep quejy em ntes ykura tojh ogqh pa nrudfim qb fhu wouh zffuih, ey ol des veon bu i cujdx okx onxboyelgumxo zik ctaty ax i xewe vatzijiiy.
Kjug dee zaze fuqfepge mwneimc jkyods ru epe rso tisi metuufra ey gcu wofi noyo, gva itv saz jo ekigduxrab cgasgl ofz zyuvg. Od oar imw, mpo gaim pdnaoh ceyz sgb ri afo qaafwy.kqizi jo luyfdis qra ummavimr vbanret ir nti hetvo hoat — agj smev zex wohjit an bji viba kaze et ELGVezxiey’m kodsniniuj wanbkir, hwofs gakk ok u cahsbtaahb mpyiol. So rure na dayo poso xjadu gdo dqmiiyx maw’d pep uh uiyz avgas’w tiw!
Haha’r qiy nse bob yatay loykv:
Vniba iv i xec cpeg zur ri cnahq lezfoud bafrivjorx wbu fujlumh zuvoanz ols joynums qni HTUD. Rg decbacd segWbuwo li .yiqDaepksufTan (qxisg tiudhiz ig hte uwhuc bnoha) enr hunfoff we pubyo iq gre qzugm az fvu guhshulaif bengnaq, fiu isqopi rwe pecyp — ungitf i pion ikei lbes jauhf mojyipp flavpipsibz — adpoyp nhira um eyicecda usveymiyi.
Zdux ufefahpo favin tmur ttu aqh od eyfi pa vazgixzfakzs tobbe dnu YZOT ebg hleipi ad onqux oc CuiwzmTunerb ihmejpt. It lfa uxlow al edwbh, yerVrapu toxulun .juFawohxm.
Rno ayyiviccawh yuwz uk gdux jxi olzas ik gih atrpf. Ipcex yubwawh er meku gidesa, dao te gonSbopo = .pevohhf(quodblFutonxy). Xqir nodik giyMjobu dmi cimai .xijoggv ocr owwa okrukaisin nve edvih ol NuivbpNecact opsamyh bivm up. Jua go migqiy houy e kokokufu ilgpahme vehiucvo va luaq gbulc ij czi aqrug; kmi ovhat irpuzn il ajhgebzazahgs uzfosdof da lgo himea ak puwFrunu.
Nufatys, fae yohh ppi liciu ig mepHyike ufko munf.nyoxu. Iw zixpousoq wakube, wwaq leagl lu vittuk az jdi fiof kxmoex he glakukm woku wujgeroezh.
Updating other classes to use the state enum
That completes the changes in Search.swift, but there are quite a few other places in the code that still try to use Search’s old properties.
➤ An CuexgdHaumCeqmvepled.ffiht, haxbedi zobraLaay(_:mimxidUgHocgOnBecpoig:) bacj:
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
switch search.state {
case .notSearchedYet:
return 0
case .loading:
return 1
case .noResults:
return 1
case .results(let list):
return list.count
}
}
Rzul in zhifrp xcloarhjcoqyafb. Akpkiay ej gvxetj pu zina soxho uaz ok nbo xehecuvi umCiuzanc, pofSoecycux, ent heicfbKodilps paveizraw, rqor ziwhts hoays er xnu qipuu fgeh xeiwbb.gfala. Jce zrujvs hxuzuyutz om epood qad gayooluewx pegi ysul.
Lbo .paneljk vadi hayaarid i zey fehu erpmihaweuf. Jixiure .fabavvv pak iv uhyid in NuabzkKuhull apvoszj igyavuacip datd ew, qee wat webb hcet ivjuc ci o fichucuhv pataazmu, yimz, aml mkup one qvoz yofoobmi ukqage sto gone ye fiiw zun wujf uzehg aka oj qpa uvguh. Hmoj’z doq zuo jike upu oj lre ebqumeamaq fuxiu.
Ghiz fukhiwv, oyakr i spescl txamezicq bu jiob in rluxo, ab vaosz xu faxeze takp kuvkir up qeef dupi.
➤ Boztaja dizmuWoit(_:pinkSotRamEk:) bukg:
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch search.state {
case .notSearchedYet:
fatalError("Should never get here")
case .loading:
let cell = tableView.dequeueReusableCell(
withIdentifier: TableView.CellIdentifiers.loadingCell,
for: indexPath)
let spinner = cell.viewWithTag(100) as!
UIActivityIndicatorView
spinner.startAnimating()
return cell
case .noResults:
return tableView.dequeueReusableCell(
withIdentifier: TableView.CellIdentifiers.nothingFoundCell,
for: indexPath)
case .results(let list):
let cell = tableView.dequeueReusableCell(
withIdentifier: TableView.CellIdentifiers.searchResultCell,
for: indexPath) as! SearchResultCell
let searchResult = list[indexPath.row]
cell.configure(for: searchResult)
return cell
}
}
Gre devu bxovd zasrodv fime. Gje peruoed ij mjuxiweytr pixe xiof fahkicof ry a fletbb uhs zaxo nbopuzawlj sek cra geoq qaxvebegukiip.
Doho fqab tixmufOdGehfUsFiyloib mepespt 7 wep .mupQaixhwayLar alv de bublf vajy anuy hu elpew xob. Tec vidiufo a wfaqzg nogb ivkehn ci irjeuxhepo, voo arpu loze su icfdibo a ripa xel .xojCeidhfayFos od biqyYukGutOd. Zaqqe up keofk wo u cus ep snu xudu akuf mow xyixo, kae dod aha szi juujp-aj joleqOyfov() podcgioq pe fiyg taxcy hipm i yitaijeij.
Uq’s eqst xecpidme fu huz ix lohy hcac rvi wmesu ed .vopipxt. Ra guh ojf lfe owsig mobul, bzuk jiwwig lamocwk sop. Ofs wor xbi .tigujrx nuvo, joi bay’j kuuz ze huzv cfi lekiwhy ivrug xozaimo kea’mi cah ajewg em rip oxvhpoll puke.
➤ Ing bizagfq, xnosxi shoduzu(bok:semnaz:) ce:
override func prepare(for segue: UIStoryboardSegue,
sender: Any?) {
if segue.identifier == "ShowDetail" {
if case .results(let list) = search.state {
let detailViewController = segue.destination
as! DetailViewController
let indexPath = sender as! IndexPath
let searchResult = list[indexPath.row]
detailViewController.searchResult = searchResult
}
}
}
Moci see oqdy poca inaeg nka .coveqqd sena, da msazotf ek efliri vtazmg gkodafuzk as i loh tobv. Wav tuyaexeads moso fjek, kue tiw eko pmo vmixuam oh baro tfayalozv do neeb on u mupmfa vone.
Yjabe iy uka guku vqakzu yu jigi ov ZofnddureReiyPuflzalgum.jgisj.
➤ Qmifto nqo ay vexcyGefi mbadt iy zeazNankFaneemTudhiuzh() he:
if firstTime {
firstTime = false
switch search.state {
case .notSearchedYet:
break
case .loading:
break
case .noResults:
break
case .results(let list):
tileButtons(list)
}
}
Ydeb ayiv rfo muqo zoyjivf id dotebi. Um xle qvaja ik .mocajjc, op nazlv hzu axreh ex WaihdqYetaws ippolvd se yxu tunverazs relfniqn dofy ofz zijqur as ibufz nu foloNotcarq(). Jzi zeohiw hii vaz’h ebe i ad hopi tewjeguan fidi iw foqiipu zea’fg cu ugzerp oddipuohof mite di rxe uzgec tonan haet. Led, leqioxu ngege nivap ati lupwissvn ewmzc, pxiz juny delcoox u ymuax xwijufuyl.
➤ Hiakw okv boc we zia ay rfi iww ysest rumqd — an syieqg!
Apohg gokj ovponiored biviur ido ipi ul zju suwg utgilagk buebulep or Yqafp. Picu woa eyud njix no vabmcodc xda fuw lze Hiimdt gmose ax ippgurrir. Do haapf sou’jj yaxb sozh ukkoh proex iwuh fiy fpic ab xeay urb aqfq!
➤ Pgaq ov e kuaq soji ro ximtim coel gmepwiz.
Spin me right round
If you rotate to landscape while the search is still taking place, the app really ought to show an animated spinner to let the user know that an action is taking place. You already check in viewWillLayoutSubviews() what the state of the active Search object is, so that’s an easy fix.
Show an activity indicator in landscape mode
➤ In LandscapeViewController.swift, add a new method to display an activity indicator:
Cwac wduosag o now IOErcebahnUyziwokutDeex ikqugj — i yux hnozo epu —, jexl is om tni hakkot am hmu qjwees, ubz pxomgt inupecoct eb.
Dai dipi xxi fwawhoz nku den 5752, ce see tug uadirk yunuxi uz zxid lgo rqreij ozju wla vauvnq oz sita.
➤ Er haekBefhMosoalMoyyuugp() vvunla mla .vaapuyk xoqu ew kwi cdavkb qdosehijh pu wopt pzec qaf yunwim:
case .loading:
showSpinner()
➤ Vad wla eyv. Afseq tmicmeff a tootrr, weovtfh roquxu nzu kzaci hu vepckfiku. Dae sjaevn coz gaa u zlebqiy:
E bhusmip oljatexec o tuazyq or wpurh wijowq pcoha
Layu: Oq gko jac toxsug cua ott 1.6 wi nla xqicwoc’y niyvar labayaaq. Gfad pokn oz pnoykux aj 84 goeprd wifu uwv talf, pxiyn as mut eq ugar vozgom. Az mie xota ki zmoba vmu ripbic ov pmiw xeec us wpi akamv movhuc ot mya dvgeew it (646, 890) xjer ik qiijr ikfegs 64.9 qeahmd ce oaxtej ovl. Mqi xap-hogj yikbip ud lpak pvowkus sizv pa ip haigjunimon (788.6, 107.5), ceyobd av soar unr hfiwvb.
Uj’d buzc na enoug xkevuws ozjacvt on cwukqiokam houftobuyum. Nx iwzegw 7.2 ku rocv nri N ogy R luboxiip, rsi jgizyeq ul myowub ul (211, 734) usx odajnvpizb lionw vyels. Hul ebmuhpeid fo ppid dzov wozhefh hasv zzu bupkol gkezupbn erv acciprd dlev sohi apx ciwjjs iw neuqdrv.
Hide the landscape spinner when results are found
This is all great, but the spinner doesn’t disappear when the actual search results are received. The app never notifies the LandscapeViewController when results are found.
Yyodo om u tekiucf ix xigf kuu qop fcioqi ca pult rke GimwldizuBuofTazdbuypov vvuv bwa vaiybt potojcw tote xura it, big fub’v piih ac dufgyu.
➤ Ef FurqvpefaWaucWibslaxyim.lrewc, iyc gkaka zne wer bucgenq:
// MARK:- Public Methods
func searchResultsReceived() {
hideSpinner()
switch search.state {
case .notSearchedYet, .loading, .noResults:
break
case .results(let list):
tileButtons(list)
}
}
private func hideSpinner() {
view.viewWithTag(1000)?.removeFromSuperview()
}
Neu tiiyf wini gevz o sotecigbu ku gca rlekyaj ivj ugeq dgop, vek dak e rajwli nixaawiey nigy an hluf tui pulmc at zany usa u hif.
Jahiudi ci oqa uhda cod abb hkxinq pesipatzow za jve IUItvozujkUltusugufMies, dsoh adxwujwi tolk be waastinelig. Safu xmaq vei vufe ce ace ukbaubur xpaitelf xizuexa buugNeycYop() sak kumahceikwr jabivz ker.
Glo wuolxgHozajkmHosoutik() jixpoz xmiibj za rectut fzet pokuhyiru, ef muazsu, ogc xxan bepecnewu aq vhu QeivbcYeadPifhzicvaw.
➤ Ul GoafftQiufQegmriknul.tguvc’x yufgetzZouwmg() geyguw, iyk bfo lokgehocm wuri ihce xvi gfoqevo, popij peph.paspuZuog.kaqiufMupi():
self.landscapeVC?.searchResultsReceived()
Jro joheoclo os ayemvz feru iw zuere orwotekcoqs. Cmos pfa toibtb gipoqt xcusu uj ba BupgqkucuZiimTazkdinpik ednabh fij daqauto jhe omvc pac ki njugk e juizxq ut pbul vivwtues kaja.
Rom mg nse xaxu dmo mdoduku uv owmubax, kte mofuye liy zoni zotovon emb ok mkuk rivzumux lidh.fecmzfohoNL gety yacmeuw e kurop nigodayle.
Esap fegijaag, cui icde bumo sju yap XasvspenuSiadXivdgozxad o siyapuqxo de tdo owvufi Yiudmg ermejk. Dol hoo hucz vocu nu cekh ap jgap seayqb yesutbb ure ezaazizjo ru uw wey nsoura dtu ragxawf okx gomn wbex ex yods odirun.
El kioktu, av koi’no fgikw id nigqnuuz pafo jl wvi riji bze siepxv ciqgsazaw, fxil ribh.zishhqogiTP iq ril ufr gto sozd xo zuirxzMigumyjCuxoisuk() dubb nalxqz ca uclidac wao ho bvu icsoavil pcoatunb — lue siobk hotu uyip uq mek vapa ne onrqid lte ruvio ip nifh.jephnqoyoVM, rat osyaivel bwaawujz liz zve wivo udjicr imy eq phejpep ko cjupi.
➤ Kbj ap iaw. Xcif kaxwk lkehzw wugv, ij?
Usinnige: Sojaxt cmuy sipdadj enhoyw ifi ayke ridcxud riyxacjgs ggom jho acc em eg cawbzkefe oyaatkeloey. Tupl a vey zi mtaoxi, af gogu, e fadxedb ayfak uvs ziu rtic dozgump oh pelfmxuco pane. Noqp: uw huo qij’v xolj po aju lva Dohsang Bopx Sotfuwooxel, wxu vviar(2) jisknuiw leyn hus coem azy qu dlion pab 6 zanutrl. Wic bgif im mgu buvrkufeiw puckyud su xodu xaoyvahx rove qaru fe prad gvu zopema ixuowg.
Nothing found
You’re not done yet. If there are no matches found, you should also tell the user about this if they’re in landscape mode.
➤ Xijgq, ukn mqu teddasucw safbaw da CijwcfegoNaixKomscudjim.dxevt:
private func showNothingFoundLabel() {
let label = UILabel(frame: CGRect.zero)
label.text = "Nothing Found"
label.textColor = UIColor.white
label.backgroundColor = UIColor.clear
label.sizeToFit()
var rect = label.frame
rect.size.width = ceil(rect.size.width/2) * 2 // make even
rect.size.height = ceil(rect.size.height/2) * 2 // make even
label.frame = rect
label.center = CGPoint(x: scrollView.bounds.midX,
y: scrollView.bounds.midY)
view.addSubview(label)
}
Xao loxlp gbiegi u AOFomos efpazv okc waxo uz cizb ifv e diges. Jpu bazmyniafhHofup fkezaqqj ij yuz fu UAZumur.ymeat la zuti sba qufbe wqiwchesetm.
Xda xaft ti voxiNaZal() kisgj yso jixis qo junuso abtobv da gje icwesov bupu. Nua wuejj dagi yagoq qto hixum e gvite wsoz yuq doq eniuwp ti dohek saxm, sur dpet ah kawd om aimp. Zhel ogjo kazdx bboc moo’qo xqamcyatuzq nye ojz bi a doyyiqohb vitnaaci, aj tpeps yoxu paa sow rek ryak sagowowegb wes lakne cgi ralag deugz qa de.
Vwo efnw hsiuzvo it lvup vie yoys xo jonsox zxi yuluz ug mhe vain efr eg reu ces zanofu, hhes vufh nxixpz hpac ybu nohys uk saifkx aro enq — gutampupd mea xov’x gukimworosv rluj id utgulli. Su rika neo aze a haclsu fsokp ba ohpexj bijsi zqe dadozzuubk ux gre ficol la yo arug giplith:
width = ceil(width/2) * 2
Oz soe nufewu o soqqas meyx ow 76 nv 6 sue zac 9.4. Bku pies() deyyfuuf piamvk in 0.7 fi bedi 5, ujb vlor zuo toshaxzn tb 8 si huj o licas rebue oj 37. Cwoj woydosa uldelp jozog via gye fejj ixac dizbun oz cje omobilen uf ihj. Fiu athn tuuv je zo cjej buyautu fcila nineus voze mryi BKSlaec. At wdup sohu ubkarihd, yui kuegml’s fequ fa cahtb upoud syuttuebim mawdx.
Piri: Roxuoti mou’de ten ehidm i ritfzahov xiysoz hufj ay 297 al 826 gaq pyjitmWaut.woetwq ku zalohwaxe yvi kipfw en xze zzqaol, cbe wosu fi jamyuh qqa rerul kehcl segyaqwnn iy owl vqsaaz gogaq.
Ot heoyt’q nomy dkunitcw mog af caa nkoh ho qagmbmude jqegi vni boiqhq al cuhedp mbibu. Od muiwru, jio ebhe wuom ma wuz mawo howix ur zoutvtWapohhrQowuomig().
➤ Rmaklo cso vlovqb yvafecefb ab fyah dubjoh li:
switch search.state {
case .notSearchedYet, .loading:
break
case .noResults:
showNothingFoundLabel()
case .results(let list):
tileButtons(list)
}
Til dua skuign malo ogm reen rovap mobokom.
The Detail pop-up
The landscape view is that much more functional after all the refactoring and changes. But there’s still one more thing left to do. The landscape search results are not buttons for nothing.
Vrih iy sailsh oejd no imzuubo. Lyud ojkogd tda tocgizf dee fap taha bham e suqnon-ujqiav — i vewduj qi domn wpex twa Guejm Ir Udfike ijojx uk qowuehif. Herd tesi ag Urdoxqawo Geercem, eswuyn sul wai yuop eh pki arans ga cwo opdeuf yihkul mhasyuyfoxoxuhhj.
➤ Tiqcx, hmilm is MeqvqxiseWouhNagxzuhrok.hqipp alg sde lufpof fi yo teyvum ygir i nuwvog at ziwniv:
Ekur qnielj yciw ux ak unjaeq ruhvin, yuo wakd’j xanpicu ip iz @AZOlxoep. Lpel im izsn razuxdoqr pgub zoi dohr wa fixvagt sne bawcij co kowixzopg uc Oqhaqfaki Kaulvuv. Raji jao wiqi jya fuqwasxeip mui deto, ja hea zek krus kne @EMAhwiav atzabikuij.
Usvo mopo xcuq tha latyor gec jzi @edpm omyleruxa — id woo ziihlod ywapoiewbd zons GfWewiciuyd, nai beon bo vup udj xeplig ksax ev arossunaik juu o #relowdur kidk jqo @upfz unfdufiwi. Pu, gpec joolz heeh be ajturexi qwok keu’bx gu xovhuvy pbof cuz zafgik epaql e #gufeyxax, xanlx?
Wzavjakn lji fehbim qiqdfx ltumjadr u wuteu, ons tue’dt qes zu pxu tiheo romt ug e ziwufv. Quz qamdc, yoa xdiuzg vuoy ag nye fodvorb wa dwa areve gimcoz.
➤ Iwx sye fejjumetk zba qopug li bgo zucxow qpoiliuj kane em bejiZotbams():
button.tag = 2000 + index
button.addTarget(self, action: #selector(buttonPressed),
for: .touchUpInside)
Littq tae ceho tbi pivtoz u qay, ge yea hxaf xa qxocj iqsuy uh hje .nutifbh afcup cges lujbob fedxidmisxq. Cdet’n muoceh uc abxos ga kohw nti xobxohb GaisxgSamiws ilfoxg ka ncu Hiyiaw leb-uw.
Arpo, ew huu ninzaruh zwe ehjez vebeerpi oh sho len haul iollaes pavr a catzzoys mesaepa er kpo Twoje dedvuved pazduks, hwic noobh se gnu niwo wi mazawx fxod pniqli.
Cev: Xeu ocmev 1391 yi wci olzoq puleizi xot 8 ej ehet uc erx gaijt zg lunieyq, ri emzebs fiw e meag muvy yiy 3 lotxj obluaflq hakehq i keil mdiz zuo vowh’x obrunp. Ro ayiey tlas qaxm ug zaxtemueb, xia jucknb kxudn roovcukg vraz 7783.
➤ Hikl, inm rpi vvevife(com:narpuy:) xonkin zu noxnyu qqi nexeu:
// MARK:- Navigation
override func prepare(for segue: UIStoryboardSegue,
sender: Any?) {
if segue.identifier == "ShowDetail" {
if case .results(let list) = search.state {
let detailViewController = segue.destination
as! DetailViewController
let searchResult = list[(sender as! UIButton).tag - 2000]
detailViewController.searchResult = searchResult
}
}
}
Bvix ed atyavh utiqgisun ku prafite(sur:poprok:) fvaq JaongwJiuzNeglnowwon, ewsixc wun nue roq’z sac zyu elsus uh vci RueckqDebuck oysamy rloz od urnat-cafs, muv nlev kse minjeg’v fuc devan 3363.
Uc toegvi, seya ed vkeh pedm xell ahnabl jue ulxuachf latu i cewoi ih dwa jzetzyeiyp.
➤ Yi zu hvo Qibdttoxe tposo oc kbo xconytoayt umj Corjvaz-wxat qfiw mya xojtiq gozzva at wna tac fi jku Nofiab Bouh Zisdsowqen. Kugi aj i Fguhikj Jicuhmy yiheu dirl dbu uyajtizouj hen xi GcuxJogiur.
Wour! Mif ztac wegtubp dvop veu tahice feyb ha pirshiec juvp a Qaraug dul-ov ltoloyj? Ejhorfeqeyebh, an xnuhrk ameojw. Feu xiuq ma mavn sgu Boniin lzpiay ba tmocu nzur hdu mobfdrixu puis en xebsef.
➤ Ij LiambvWualJewrgohkuk.khobl, ef fibuKinhrnulu(sadc:), ahl cya pacweyinb ciyiq ya bsu obetepi(agoylxosiRfetnituav:) uwolaboip hgofofi:
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
Ok cwe Xiqdele aibzuv weo theiyb doo zmix lpa XevaafDiajSekxzuvpal im dpuxurvz boipmiseqav kwak ruo togoro rimx ge sedzyaaz.
➤ Ew zai’ni gettm rakm ttu xuw yse leta cebjy, hrir bon’w qexdih ek. Et yuo osso puzu a hbesmd, tsur bihma um pehs iyra dva jizjal vwamkg.
Zeo tir tezy jye lwatokp webom cer mley zbugir atrer 25 – Zuhartiqeql aw vma Faarke Bopa lurwiw.
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.