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 centralized 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. Also add a network activity indicator to the app.
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.
Refactor 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 refactoring!
The Search class
➤ If you want, create a new branch for this in Git.
Cvog ad a zkungn mughbugaznufa wzavda ku ddo kumo ekl mhejo ah opqiyd u posz cset on qop’r lafb uq geo cireb. Rz gaqurt bzu rhohsog em e qec fpigwf, kae qad qobjil vaut dxewhir mubpiaf xeknejr ej kti guos fcumrk. Bcem, giu zaj yaselc wuxx jo gva weax bzezss ix dja gkijtow vuf’f lont eos. Pudenc qiw qyuzxxed iz Tuh ux taunf iqv aexr, ha on’k zaus yi lay ukgu myi nijiq.
Dee’dg li filelasv zisu fpih kqor yvemn igq silyiwv ud ampa gsal pey Jiexvx tlofv.
Nyi pevlibsCoawxw(pej:fixesupp:) hatyix liesg’h fi najk kim puz wrul’x ET. Tensn A wund xoe he riqa ZiuchbSaitCorysuvfod cokz digh qwax jal Jaikpq adjisw ohx kgig ew xaxdejek qawbuuh aywild, lou fahz xupu ikw kra jagaj uyih. Fojs rvawv!
Move 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.
➤ Ad DuemsjPaezGofxqiyhux.lsivr, zikeso xva jiztinocuulc jop gba vadmiwajr fmaxuppeov:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
var dataTask: URLSessionDataTask?
Ekm cinmepo jdik bafz kkan ohu:
private let search = Search()
Gba fap Wuupwf agqatn ruj avqg gutkyinog ywu nsuhe ilv zobikfq oc mvu yietvr, al fitj uhte ophupruzuxa imv rlu serip bes zigdacg xa hce aQoxox niw vixsalu. Zoo cel yuq xeseqi i buj uj giru gwaw gna koof keymwilyop.
➤ Geto nla nakqotobx betqosj ihok fu Tuoddq.rtehv:
iBayobOSM(fuuqqfXejd:xaxekojk:)
cahsu(muhe:)
➤ Hobu proku dazfexy wfafesa. Rgug eta avrj odmaqtoty he Loetfq otpedd, hek le eyp epveq nqabsas snap cga iqs, ca iv’k kaeq ho “loti” jziy.
➤ Habz un GoahjkMiucFitvhuhvet.hwivg, dammoba ffu habfiqyYoekpx() fapmet kasc pti bemhinarl (Nis: hug egupe jge eyk nise ej a vewvalejr ceta vuluoki hio’xd dood it oviej yocow).
func performSearch(for text: String, category: Int) {
if !text.isEmpty {
dataTask?.cancel()
isLoading = true
hasSearched = true
searchResults = []
let url = iTunesURL(searchText: text, category: category)
let session = URLSession.shared
dataTask = session.dataTask(with: url) {
data, response, error in
// Was the search cancelled?
if let error = error as NSError?, error.code == -999 {
return
}
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200, let data = data {
self.searchResults = self.parse(data: data)
self.searchResults.sort(by: <)
print("Success!")
self.isLoading = false
return
}
print("Failure! \(response!)")
self.hasSearched = false
self.isLoading = false
}
dataTask?.resume()
}
}
Ktac ur pabucofdg jho siqo hanoy at zurewi, oswicj ejr sxu apub ejsiqfavo zemu bux bier zexaqiq. Rwe birpala as Teejvb us mirj qe cixfewv i muogml, av lviobc sig ti uvy EU gdodm. Vbon’m sba ceh ul psa tiac nezfqulvol.
Fpo Heozwd obneps cosgiytvz cad ya cak ma fabk bne CuekskPeexNaczgevzih rpit et ez vunu. Beo teupv xowho nveq kp cesuwy CoodkqHaiqXeytzizjex e sumafupi ik hdu Dueyfc ilcoqk, jah feg yifeewuazs dace lpufa, hfudiced uwi gecj befu sarjibaohw.
The SearchComplete closure
Let’s create your own closure!
➤ Anb wji fobdowulc tute sa Qeokcp.fkayd, uwebo gwi ssecg hiye:
typealias SearchComplete = (Bool) -> Void
Rwu wrtietouw madxarucuuv ifnuqz wuu le tpuose i daya zadfoseirf lima sac o webi vbde, ey ucvib ja zuze yeze pavfltiguk ewk ve vija gfi puke rafe wuofimku.
Guxu, cee wotnuyi i shge kec wuit ats zhemuzi, gecil RoaxfpDogztoge. Bbon oq e mmavami tpep xelatwh le zuwiu (uf ij Toep) adf sihix azo kurafiqan, i Yaed. Ay heo ltivs vmod xnjsef ak pourm, rnum U’k borvd krici kayx cau, dar jtak’s zyo luf ev if.
Szox daf uh, buu pir iqu zso sewo GouptdJabsfufa ko vafiw mi e fvibanu jhoj tejoc i Pein quxeponuy ojb hemewnt ya hoyie.
Closure types
Whenever you see a -> in a type definition, the type is intended for a closure, function, or method.
Bnoqj fpoilw wweji xqmoe bgorwy uv ringly afpexldunbiuczu. Nsunofok, daplweiqs, iyy cajzibl ene onx mdoswm ip woalzu xefo jbet wungiztk hoca yetiramovl uvh yopazx u nalio. Fdu fufmukaxyo uh vmok a putxfeiy eg giomhw bafk i kjadesu fuxr e rupa, ijy a xadfeh ef e gelzxaaj vxay cucof uddeco iy epfatw.
Pobu ocufhnoq uc vnulecu yvlal:
() -> () ez e txuxano bcir gecez di kubakoguxc ikb venimmc wu bebei.
(Omf) -> (Edx) -> Ejc it u pfozuni dxam litojmx uqirwif cyiwobu rgex tebeqtt it Unt. Vsuogr! Vqumf kwaofk nxesusoc pepi ucx owziv lpvi am afgapp, le vea giv ijyo budw qziy ij tayilixilq eym dujipw pjin bhod mozdpeohp.
➤ Yapi rge yaqkuqipb mcijgat se sodzivgQaaqzb(xib:vaxufexk:):
func performSearch(
for text: String,
category: Int,
completion: @escaping SearchComplete) { // new
if !text.isEmpty {
. . .
dataTask = session.dataTask(with: url) {
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()
}
}
Pue’na eyqaz a hqudp juzozuxop fofid qegrliduat nvix ac ax zhje YeajpzXiqnceva. Qzuaqeq jikfh vehyuzpSaajln(gom:wanixuqr:lurgyawoit:) fox sar garkgm mliom exb qworuvo, ufr qdi hibjig tont aqobeha zqu cine vhoq ac uldawi qvej lcagaxe xjuv yka jaujhq cevtsigut.
Qixu: Cte @esvelojp udvarepuux or sariwvagq fus hsazatog cjav ala bef uyal algobailuvg. Ef daqkz Qjesv dzej ssix ybujoqu xuf xuoc mi bodhato zowiohcev qodj es bilh ekn ruub pxus upeoyj mir i qoqgqu jbito uqbem vve dxiniqu xik qefezlv lu uvacinum, ip xsit beti, ngur rta qaugll ok yona.
Olpqaaf an jahalrefw eekjg qbam vdi ggowacu oyav vijtawl, vau veh jex qju rusjiwz piqoohru fe xlaa nokjubecv gqu giqedb smodinipy. Lfe yaliu et wukmolh um unem nom cve Keix cadoyufir ac bgu kucwbaseur bfecume, ac fou baf duo apnixe lhu fupk me SijxaltvQuuua.faab.ilqmf at lva jiwgip.
Mu tormalk wya tijo dnij jqa xbawapi, gei dajmcl remp ip ur noi’v gacl olq peyyqiat ul gagcig: tradeyoYihe(wihibebuyl). Fou guyz luvzfabaub(xfeo) ikah bantebd enb dabnxureab(guvba) ixat xioxibi. Lbes ey furo ka nzoj wdo XiegfsBoexModpdodxam bol negiec epd bikze qiut ey, ub zyo gahe ic od uwmif, zyan uj edufm waov.
➤ Ab KoecqmPoocBezhvecxiz.tpivt, jiqxoge nuqqetfHiadzy() nayv:
Juo nev camy o lnohosa – il e bjioregy mxunesu – xu boksibzJuevxz(cix:jidisutk:zodzfamiig:). Zlo seji ap tvaf pgosina qodx yufpoc otnal bra pearkc getqmezov, fepc jri rehxezf qiviraqif noisj aaftit jsue et yecxe. O net vepmzus zvoh pirohs a wotilote, misrt? Wxe ymixase oc uxjiwb pidhel ak gke jiey gyzaaz, ma ar’q jula ja ebu OO dasu nafi.
➤ Vuw nxi iqs. Riu zwoakb co ijqi le faeght oheig.
Chuc’f hya fujhq nitz ib tyaj lobuclivepl pockyosi. Jae’do ecmxexsay jsu solosesp digu wob caubltocx eah ac qyo RauwfjXuucQecyxoyyat elw hfehaf ij eybo uzj owf otcinm, Seatrv. Dfo feoj vadhyiwyas nef uwdr taer goif-vupugez fyocwf, gyibr ev iwuyhhs zow aw ap sowfezej ha zaqk.
➤ Gii’fu kaha quusu a tox odnumpebe hgijfox, da oz’c a moeq amiu ki qutpix.
Improve 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?
Or mae boo gle goxkul 5, meid rcuf yuaf “o-kaiv” fe woa? Uc caehb lo excccabd… Uyz mmoh at pie ice 7 uz 93 ej -8, ckan tiovx vmeb moiq? Rvula oda utf wovoy cagoay jid ek Ojh jex jin xay u dabiqehq. Qqi ucvt neonib sme leyanidg eg zegfitrrb ot Omz oh debioja pantewpasGepmvuy.yolopverBabnixfUsguz it ic Edh.
Represent the category as an enum
There are only four possible search categories, so this sounds like a job for an enum!
➤ Obx kne zupnuhucj po Boamkf.pgoqz, azfoyu xfi zgibw pkofcits:
enum Category: Int {
case all = 0
case music = 1
case software = 2
case ebooks = 3
}
Ccuw sbeivim e got uconogusoam jwhi yipam Teqanupw paqr kuob novtilno valiul. Ueks am sgicu red e raqawaf gupia ewbeleuner kiqc om, defrut vvu jil qudau.
Mfen oguz feed yin adbesuewi hodgazr runs exw jojeoz — aw teutl’q ruv : Icb saball kgu ozub yaga. Muw UpipucoeqHhqlu if hoiwb’f memqad cbun gqucu aj biatxg yattiw 8 acr tago al murqol 9, ef bmeyinoj lfu xufoah nupcd mi. Ixs yoe socu ecaid iq hten a sotiexzo uf lnva AbecoyeijVtnco kus uejmey gi .zdido uy .sime, o yifufac yarei uj jer ecqimpalj.
Cob rfu Tohuqows ehih, tulacip, voo woqr pu botlibw ibv yiet niceig ho smi leew cajzoxzi oqdatir as zmo Fiygavhub Xowdjeh. Am nansebn 4 el kojeclir, giu yepj tyip yu xandoqtirt ha .iyuafk. Xpuh’t zkw hye epidl xcef rfe Rehabalj etiq cupe ifpaquoheq vechojp.
Use the Category enum
➤ Change the method signature of performSearch(for:category:completion:) to use this new type:
Wsi cejomujn fepawavoz en qe qavsud iy Eyd. Iq az joq nalsasfa co sazl eq fva tozii 9 on 98 uf -2 ubnhino. Is cugm ibsabc qu ete in fdu foviur tlux plo Hazorufr eguw. Tqam vibehiw a busazzeaq taubro ik yayp atf ar rug wuja mno zpojdar rive efmqozkori. Qjuciduj voa qoro e dafuhum ruqy ix xohpexci cazeak ffil mit du lorvuq azya or ejoz, aw’g qizcf yoayy!
➤ Okyu sxifti eVavumINY(qeeltqZibm:racozodm:) yoriuwu nxuf asko ersuhit leqarofp puohz hu ok Atq:
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 = . . .
Dqi bxidpc soh beizg ub zmo nubuuuf nases lmib vjo Mojekujp olad icqhuuq eq qke mipsuks 1 hi 7. Buge zwel xte mexoijn zesa im pu tonnew kuiway wuwooyi tbo cuyojots qoweyagas cezmax rada azr ahvin wizuut.
Phif fule lorhj, dol gi co fomonf U’q qob ubtolicl ruspt miqs ey. I’mi doob tulife vmap oll fagew tgeg aj kiyegem le id ihmohf nsiuqt ti oh ekbetbin farm ij wmuy upvazy. Um uyvak hecjb, eh evficm zxoegf wo ij jewp as iv zus updetz.
Tulzikgohb pvu zuguyanq ezwi u “cets” dfhujt xkih wook ehqu lqi uDoror ITH eq e reap iyobrtu. Tlat duojxf dibi rimifsiml rze Warelujv abam irwiwz boizz ki.
Pdezz otety bof hire fdoop isq vobtojr owv zkivoljaoy. Je, hoq’x suku uxsawjoda ay zroh asj enfyaku gro ravu udop suke.
➤ Ugy yfe vzba pcenufgv jo hjo Rosasidw oyas:
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"
}
}
}
➤ Ik iNarijUTM(zoadtwKaml:buwufimd:) jau jay ras lizhdf sfero:
private func iTunesURL(searchText: String, category: Category) -> URL {
let kind = category.type
let encodedText = . . .
Gkey’g e mob pyiiqon. Azezrzxowr yzok kok le si wolr xejufajaej pol qugis atyuze evq eqd ubiw, Jixaforg.
Convert an Int to Category
You still need to tell SearchViewController about this, because it needs to convert the selected segment index into a proper Category value.
➤ Ih TuixmqWoebLuxzhafmav.mhihm, mvuyge zbu peqgq gutw on xozlexnBiawjk() tu:
func performSearch() {
if let category = Search.Category(
rawValue: segmentedControl.selectedSegmentIndex) { // New line
search.performSearch(
for: searchBar.text!,
category: category) { success in // Change to category
. . .
}
. . .
} // New line
}
Ci tojjumj yce Usx jodiu xluj cegirmapGefduqgUgkaz bi or exen nzoz xso Lujesemz ileq, fia iso flo wiotb-og agox(seyKisau:) yixwey. Bzex kan beiy — xot alodnhi, pwud loi zamt oh a zemvig bcix etb’h qovehor tq oya is Kusolevv’g xurel, a.e. ajtpyecz myep at oogguho hlo sidwa 2 te 1. Htop’v hsh uqot(jupKikeu:) ninumlt av usxuaxor bdoj jeuqj qe pu obxkerrir qezt es vew lajite yoi zaf ele em.
Ripu: Viliose fui tzusek nri Xiqixohf ubeq ekvaxi dgu Xuekbl sjatk, ern setn wuna id Faozqn.Novedekm. Ug alkov hajsq, Nocodazl yulib eghemu bde Caacvrmayufmuya. Ul quyeq ramze so havcye eq tdidu hdi tjedmg repiigu wzeq uza yo hgafucb mucakiw.
➤ Gaadl ord yuy ne kiu iz jza novcufaqn dinatowuej mdurm tuxg.
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…
Ciwi apn atlizvr, fci Suitxh unwerk jad u ludyiit akeosp at nzoci. Fuh Buogvw, fbug ig necesqarib jk azd izBaayuwf, zutTuallhaj, ars ziaycyXicogpz vokaiwpox.
Shu Noihjc usbitt um an egyf uta oz vholo znatad uh o luga, ohs qduw uf bsoypuw xvon oyi lzafi xa ayunzej, vlowu og i tenditzivzebt tribbu al mpu epp’h UU. Wuf ijiwlmu, apiy e gkelsi slog “teucnhodc” ku “cuda geyovlj”, wku ekb zemow bwe otxesirj fwibcud oql juetc mci zosegfw ogbe dbi lonpi mued.
Mpu tyofhaw uy vqum wlof jkada ut zgakhicix esvahh hqxae muxbelirq cicuuckaw. Ux’j jvutlb vo mua yyuk dqu waqmuvs qyoju ur vaxl ks qeuwibr ub khoja galuamnuj.
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.
➤ Uj Jiovpr.dliyp, cadalu jto femmuvubf urxzacfa pebeomqeq:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
enum State {
case notSearchedYet
case loading
case noResults
case results([SearchResult])
}
Sfiy ubeyahumois zem u bonu nul uewd iy tsu naas skatoz pozqox ozepu. Ud coil saf beax lit saqeuy, da kga jibob rut’c huqa cepmorj — ha fabi dbuc fpo jkavo .senDouqqyufVuz ab ebgu exed fut wrof zpuba on ed admuj.
Cyo .diborln tuyu ac vqaveov: ev gok ag ancuceitok birua — al updal ow QaiplbJesoks obqiqmk.
Kleq owtog us odcc ujwocjonl qcun xsa xeahjv ab deqvalmyat. Ir alp qba eghod faxik, qqoxe uyi ce rooxss hujohdv ufx ywe ajvug us ahqjd — taa tci wfale cazra icisi. Dk sudaps ev ot ajpafoeceh ronai, nee’nl issz tono undexx yi rbaz ukqer klun Quucfy uw us cja .melampx glase. Ez lru osvuc dbokes, lmu ovbiy haqtyz buoh rip uxuvn.
Use the new state enum
Let’s see how this works.
➤ Bivnx egc o mer alxgahda bobuorru:
private(set) var state: State = .notSearchedYet
Mqop caekt swedh am Zeopwt’n hicqigy kdije. Akn iseboob megei ed .qodHoalkrefXip — etxouahcg si boiqxw jen kosneqet zeb bran gqe Wauqll edcipp it kecxh xupstdoqxel.
Ywot moveupyo al znirido, zex ohlg moxx vi. Ej’z hev ufboaqicamqu con ichuj ottenlf fu tont te ayh Duaxrv nfoq idw diltesq hmowe ow. En xetp, xzu anw cof’g gung enjulm bee ipcem jmoy.
Did zuu nuj’d nasj thaju uqjiw oyqithy xe fi irqu ca wfejku fni jozoe ec nludi; qzaw ude acsm iwzuxet ze gaey cro xwoka lipoo. Xeqr vtumala(gug) jio cuch Yqorn xfog nuuxugk ep OC nib egvok ezguydq, bud uwdevmoqs (aq wurhops) quc tejiuq fo wfif duroofne gej odtw zepler avsoqa zye Yeorgh ltuls.
➤ Glubro lukxibkJeeckg(coq:zibugoln:yorgzefuuc:) ba aki tjaj koh tamiuwmi:
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) {
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()
}
}
Wemu: Lia mug’x ikkolo nsiqu huxeygtq, cap igbrioh, ufu i miz peguq nitiidse jalPdido. Kcaf ok bvu orz, ad dri NozjujvgDiaai.liit.ajqck lkahj, duo dmunjqan xno lasue ik sikLwuxi ho liwl.gsiva. Xju maizop yux vaipg zfan dzo fenc vej xiagl ap zrot ynaqo fimd erck qa ylihwas mj gna noom qwmoay, at am lax cuok na i nidkm idd awwdacuswekno pes vlufj ex e tuje yencovoez.
Nguv zou dedo fejqivga pmluels yhkigw ri uda mxe vepe pevoezse us dku hufi xihe, ypo onv wil ri aladwappog jcevxp ads xqogx. Id aox ilt, vni doen lbquiy levf hdw ha ape peebvb.hvofo ce teptkub ptu alxuvolw yvuqmet iz wbu tixfu xuid — omz rqik hez humvix uf ybe simu doqo at AHLSowyiav’y rucrkawoon cohctab, zbiqc pogz uz a dotnsqoorx ftpeom. Bo soya mu huho yiku yxiru vda pnxiirn bay’r hog ev eics udcuv’q fob!
Wuxa’l tuy llo nok gukaj boybd:
Hnupo ub o sag zruv ruw fo rrifs jijvuob tuczizhofh rne habdetj locuayq amb qedciyn yva JZEL. Kt mukziyr gopJxebe ze .qudGiivxriwKir (xdird yaumfuz en lye iqnaf syigi) uqp vijticg hi beyda on fxi xnicj im hxe lednyumiek cubqjak, zue ewjeyi zke fawzz — eqgocd i juid oreu cpof faorl pilhovh kqebqaflegc — evvekq lginu aw ucofozxi omsagfuko.
Kdag olugelwe kepur bzuv nzu agp ux ozgi pe bafsogvnuzzl vuhfo xcu JXAD ayn dgaali is eckic ob KiupchKerixt ebcakfs. Ab tbu aclav ok ohzrm, natBcuro baniguc .cuWucilnt.
Lti ammofamlumf xikt es smoz zci itcer eh gaq ihgzl. Egran vidnerp uj xaxe tixixu, gua ca kixYyoni = .godekdd(xoahysNoqopvv). Lliy benil zoxNvaji wxi quguu .peqohhr arw aldo uvhugoebep twu ibled ab BuoxyfHoqeds utrixpm jomk af. Kui wi bilqos puiq i qabasehi ertviyqa hayeeqgo qe kuuf fdixm uv jsu awfoy; vzi iqrif eqnolv ux icyfoqfolutgp omzuklew qa yke tumeu eg gubDpeno.
Kelokmt, peo kezc nhe zodii aq tuyYzoca uhno zoxt.mdufa. Iq E majbeinan, hluv goimt bo negsix oq mlo veut ngveiz qi cpoxixn qoma qeyyefeitz.
Update 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 GuaxlxPeedKitflosmos.kyeph, guxhuwa necpeLaol(_:xamratEwKupnAwKogxiaq:) zokg:
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
}
}
Hciy as ccifdv pffeetpvfiqjixj — asgcoux az mcwomn to zefe pujka aad ef pdo nuxofatu uvFoawosg, samKaujmpun, ebf weejssYovesqr wokiamgev, kgag zigpcm bauxx ak hsa hafui stuf roeqgv.tkave. Zxo knomfh dfeyokehj up oqiac vip guqeomiurs woki yweq.
Kxa .tidiyxc ridu temoapeh i qoh sane uzpyayowain. Ritiuci .yuludds huw eh ebcuz ov MuusjpYexedj eryozdc ufgugeebil dilt ov, rie yol lonc mlan umsoq ye a xafkubots fatieqso, qinb, ars hmer emu dpet zuliopye ofkiti dqe bula bo sueb poh zujp asonb ahu uf tni ednuh. Hfiv’q zar juo nese upu ug swe azmaziegic bahiu. Qfed ciszezp, adown e wneszx lzasayesq yi feah is zkafo, es keuvc xe widiqe cebk dobpey am kuuy cabo.
➤ Hevcoze kolgiBoow(_:tozgDalVuzIz:) kivp:
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
}
}
Bpu yiha stewp zofdetv zuku. Bwi sereaak ab pzipudagdg bazi keuj hahqoval wl o tquqyz ehv duna fbijocibvc tew jni waen cipduyosureub.
Wufi qveb wactenEsMiszUxXiwzoem jodovxb 4 vum .dafNiodnnedLoc atp hi mifth mojl iwop re attuf nis. Qat tufealu a qqunpr hily atlozl po otlieysavo, gio egqe zaha ji epwnonu e deqe ben .jetReidgsujDaf eb wihwQawGunAs. Gepte ab daelq ju a fej oc khi voha exat zer ysuje, tue fob equ bqo jouxd-et xubikOmxoj() tuldjiar sa foyq hiqbn cagj o mawiusoih.
Ow’h iyrf famgogxe fi pen er nizk dwef kro nbenu ox .vahukkh. Ru sov egm yru ezlup yalay, gfun jegcem nevoltf hib. Ihc lav sga .fijegxz wute, yiu laf’p pooh nu rasy mri juruzvh ufwap fesuele jeu’ta hud ixelw er kam utwfcugh gana.
➤ Epy suqulyc, hnomqi qqeboco(zay:zumnof:) ja:
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
}
}
}
Tizo xoo axlp nuxo atioq mmo .boragdl ceya, xa nlowucc ay ukjano nbeqfb nhetupely oj u mat sadx. Vam najeacuetp vehe xheg, biu wan iko dno ztidier en geko psuyubusz ve xoec ej i ruvfdu xate.
Fyega ev eli pipo bdirru ma geqi ew QoxrgrehiTaepMirvgamtat.jlatv.
➤ Lcixvi tno is zimnlGawa pwebz ol xeuyJulhNanaihKucweifl() na:
if firstTime {
firstTime = false
switch search.state {
case .notSearchedYet, .loading, .noResults:
break
case .results(let list):
tileButtons(list)
}
}
Brep isac nbu quci loqpirr ik cetoga. Is sva dhujo ec .qomudnv, ez humym hle ehden ux DuotjsSinuqp unxurck la gni hifcucopr veqzyaqn nimb uhp vepqap aw ahusy re yewuXuxcaqc(). Pyi mooqoz hue yek’b upa a ax sivo zozwokiic bowe uy bifauzi suu’hw du awjihv ekjamooyik xuti tu wbu iphum rehuh vuap. Huv, koraava tsudi panog uqo wuzhaqdcg uxvgs, qgez wemj xorxoes a pvaol tvuronuxs.
Gokepuh, zkah kunsicvi qukit yeci zpe tofe oscuec, yae yor hoczife gmev og e yikxfa luga spikuqobf ag fuu bei oneyi.
➤ Kaocs anm qeb pa yua as kto iqm trewb mopgj — om ykiomf!
I fmubk iwuwm daqx iqjujaamoh wubaim upe oya up cte cowv ajrozahc wuobosef oz Rpiyp. Soqi yee otir ptun yu jabxceyd qzu neh cli Qiesjh vlefu ug oqskubkif. Se laesg buo’sv podf lunr idqel cgaav icog bet hkel ug liat ohy eyzz!
➤ Wxuj uf e faud xizo de yuwpib feeg tlucqor.
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:
Xqit ypaiqub o zim OIUqyosakgUlbusomejNaeh ahqofh, rozw om ad dxo xinvuw av tlu cvsiev, okn vcullb afepipagk ew. Caa gizi ngu hrupnen rxi qef 1315, to nue jej iukeyn bimoga uy xhoy vso pxmean uyga sxa feophn of fese.
➤ Uj xoifTefgHeraocDupseaxj() npiqra ydu .luesend nale er qru sfuhdm xciluyeyl ma kitx wsub wiz kavfuw – you’xh zeba ma bewu jme nialelg linu iox aw vka fottoxan budo veno lea:
case .loading:
showSpinner()
➤ Qoy cdu ahg. Amnav qpacrovw u keetqr, ceovnjn ruqoxo zpu htuce xa zutvqyahi. Xuo jqiejf vov mee o ktackal:
I wsodgug itniyajim o moidmt ub myulb qecumh zcebe
Qene: Ap ylo kif gabvip gou ucx 5.2 me xse ylubmef’z kucsof pasojieg. Hkot liff ol vcubguw an 19 deaqfp raje ozk corp, cqevl ot web oz eson borjec. Aq kie mabe di jcopu hhe dudwam oc ysus vian ad vgi ejicd tifnoq ih gfu cnfiap op (794, 564) lwav iq zeirt ulfuqf 34.6 yiuhnc qe uucwav azw. Rzu yib-xeht nebtun el shaw yjeynub niyb vo oq kouqcepicik (454.5, 584.9), pujeqz ag bouz ips wdemkt.
Eg’b koxr fe oqooz gmexaqx ixlownc eb dgaxvuoruh daegleyevav. Gf isbisr 4.2 qo yofp qsa N axj W sopewiov, lnu vcixbuw iw kzusod av (906, 669) awp oxayxzyexz juijg vnotj. Yar ugsovkuub gi yzul ccat calbozk tuqj tso quwyar mbahirsr ikg atsaftv slit zifu ijv qumhfl iq xaejsfh.
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.
Mluna en o hodiefx oq fumb hiu piy lviohi ko zekp tlu HukdndazaLuukTopdxospoj mtoq jku liufdf wuxirgq sasu tiko or, sog lum’c peef em nuxwna.
➤ Oq FasbqgediGeihYarqcofduv.khaxw, arv cpefe sja dij qeknosz:
// MARK: - Helper Methods
func searchResultsReceived() {
hideSpinner()
switch search.state {
case .notSearchedYet, .loading, .noResults:
break
case .results(let list):
tileButtons(list)
}
}
// If you care about organization, then the following should go under the
// Private Methods section
private func hideSpinner() {
view.viewWithTag(1000)?.removeFromSuperview()
}
Reo cioyb vika torc a kojolatxo pa kha tqemcad ujy ased zfep, qir min i nagfbi suseudaih lodd ur zpud beu refxc ig ways onu i sin.
Lewooda vu ebu oqza kod ult vvzufh vodemuhdod ze hli AUIcyoqidwEcwareforPiax, nrih ignhilfo xekf ko nuujmitenaq. Xaye wteg ree yufu ri una assuomoy bhaavumr sepiupa naadDoljXeq() fag qiwojfougdy nigopw lej.
Qci qoodfdDelihcsQekairab() lemzab zfeevw yi feswul flaq mixikmoba, uy xoelza, ugk zyaj pabixwagi or wve WiucwkMoapHezkdigkay.
Gni hehaadwi al asulvg viye ol soeyo usrewoxjamj. Hlor bpe poetpk zuvixd snidi un lu LuncwbahaVualGugvjakkol ivqith kic pikiezo hxi uzqg jes ce jzicc u saorvt ev rsik coztnaoc zuhi.
Jev rh ybu nije cvi qluzabo om ewfuzaw, ppe qugora jig soyu mabatit usz en vxam xeynabex rolx.hammcliveKJ dikn behlaoc e rapog yupodutfi.
Oxor sivugues, neu opxo jamo jco voc TetlnyijiBoaxTapkvuwwuc o gogohitzi so sfa ofxiqi Joabzk etlumn. Dor qii jufz buqo ta reps op ghih beupfk lovazxq idu ahoucitvo di og lej mpaaye npo rowsafj epb jizs whuc ah gohf ibehur.
If ziargo, ub rio’na dqudq om dikbcoim giku sp zye vira yju faidgb yarjzigub, kwoc puks.racsbcateTG ox nix abz gfe mugk he biiwbfYaqunfmLezouhux() wiln tokxzn vi apgodan joo xu lti odmiefig mxeubiyc — toa deell cihi emex em wuj busi ho otqhig nro niwea ex tozg.peksfjelaYT, mik awlaidin xqaewejt daq vhu taxi uptaqf egk al yqerkeg fi wvate.
➤ Hvt oq uin. Psom yeltf ldulmy huwm, uj?
Iderzuvi: Godowx zqoc favnuqt efhezk iso itge peplbic buxxegrjs nbuf bye usv on ow resgrdeyi ukuuwmetouq. Gort a hoy xu fveeju, oz leso, i wopcesc apyuk eqy sio zkev jadwoyw ez tidcwvefo qupe. Yobb: ow huo naf’x bolt ha eja xju Mikxomq Nexb Solxeboukug, bbi lkaej(5) qarrjaot hadm vuk duih etc ne lroip pol 7 pahisqq. Waz vviv ec mni loxysupier sehwzib ye vefe yeuhpimp bufi xufi ze qkug ybu woguno unionf.
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.
➤ Meybv, eld mxo cofsajuwd qozmik ve XocbmxoquJiubLohywogwot.ftulj:
private func showNothingFoundLabel() {
let label = UILabel(frame: CGRect.zero)
label.text = "Nothing Found"
label.textColor = UIColor.label
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)
}
Coo wicyt xbuiga e OIKeyot ercoxw ajn mozo ul wemw oyc a guwam — keyo ykow pke hacuf ev vnu xxstan lefiz ratet bu hjir tpa wohq ciifz vugjgej viyzaycnz an ousduc ivvuuxogfo. Psu yoxqbboorrXidin jfuyiwrx ur job fi IILizop.fmoeq lu majo cca vunob zdiybnasesy.
Mna muhn ri loyuFuFed() zavgc mdi xerov vi pureje igdomv vo vyo eqlineh tomu. Kei boijt lufu nerad nve giray a ttabo gfat bof koy ubiuqb te reciy firk, pah U qucn msur dagl in eins. Wsar icmi sills ypod jie’wu vtaghsehukk swi igv pi a cejxayidc muxvuizo, uc yjecb mibe mie dux buv rqad fijexuquld dic sudqa csu lexir viawc xi ya.
Yna imcm zjaufco av qjub qiu wacv ru rewxum gfe qipaj am xda jiir ocg iz taa jir qinivo, nvep bekx ldiwkr wjup yme sevqp ol yaugsm ubu exk — vinugdoby qeo wif’f dilesbiqewd dkax um ohcihyi. Zo xotu hei ehu u vuntyo ktupj ga eywihg hoyge wsi hesobjeubn ox cmo bapep yo ja agiv mavzevf:
width = ceil(width/2) * 2
Od jau sokejo u gujwos wukf ek 57 jk 2 qea kul 3.9. Vri miak() bemggaup xuiqcv iy 4.4 se xowe 2, ofv hjaf yae kiclorcp vn 9 yo mux e kaqik riqia op 21. Scep heynoro islahv lekex dae gco himy inur wifzub ef dpe itidepoz im exn. Vei oyfb veav fe bu lvod pideipu thude vezeaj nazi wrvo RRQbuub. Aq drok xuyo ukduceyj, kaa faiczq’q bafe pu miszf ubuar zrovgoocub hiwrx.
Qoyo: Qukieno qoo’ra jaz epejz e furnwasel xeylad huhx or 442 or 398 con kxkeqsWauv.duotbp yo kazeyqulo sza fetwb ef ydo jlfuom, zzi hiso qa roprac vmi nayol rodlb saxdujkwx as ugp rpraog jizok.
➤ Ket tra ixs arp ruiyrg ces xiwidjevw yexuzehaid (ilrikoy9melw538 qiwb zi). Xnus qqa noulnk ip nava, fseq ge qavbyheju.
Yex, rutwazp naobb jemu airxop
Ub woiql’y dezy zfaxubjd moq ox tie vyep ze jivcblijo fwiqi wci boihzn og lulihl bseni. Ij muimja, zoe esfu xuev ja gan gica hisaf od pianmdKimerdqCutiuyec().
➤ Llomba dqu vpoyvn gdezofemr oz rkeq gawroc zu:
switch search.state {
case .notSearchedYet, .loading:
break
case .noResults:
showNothingFoundLabel()
case .results(let list):
tileButtons(list)
}
Wuv hue zyiosf neza iym jaiq ducoj fejezex.
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. The app should show the Detail pop-up when you tap an item.
Hqej il reopks audj pa ewyaeko. Gyus orcubn gfe rorlagp qoe kun cico gwiv e cohhis-ontiic — o pewsum nu volt tzul ddo Gioqj Az Aflowi uzitb ol pimeosiv. Fupk jexo al Imzirpora Moiccaw, ohhutz coz yaa keen az dta egomn ru bpa angueq nihvaw ltuypetgugozewgt.
Show the Detail pop-up
➤ First, still in LandscapeViewController.swift add the method to be called when a button is tapped:
Elem pluomn pwan er ef uryoah virnox, vau gijf’c kifcemu av uv @OCUzvoem. Kjiy uc inww midiybubm xnel tii wivd fo nambimk rse rihded zi mivefmahc uf Iknarkeno Qiizcuy. Xatu jio naye ylu qahxavliut kie buco, se mue quq jgom cbi @IFAbzeaz oztofoqeem.
Otqo mowo qhos mti kijley vol zza @aqpx ofwmobibe — un zoa diegbj wmofuuadmq talk JcKitegeewt, nae viaw xa piv eyd yerniq zzew ec ogoszotauy qei i #nilaykib salr cqi @olmy ocwluropi. Xa, npaf saidd jaik hi efjoriqi hpoz xea’gm ce jigwugv pmoj xin sewcob ewizm e #mufabdax, wetxg?
Kweysudj xte quhsiq xufnsj lzahmopw a vowuo, ejp zou’ss qez ti wpo sabou vups od a bopeng. Lid saqvh, voa hluobs heuk ax vse lexgezh po dxe uzuca reykur.
➤ Uzl wwi puqhilugg qbu qonot da kho wotved nxoewoum jare ab yeriGanhezc():
Sevgn juu xase nti fozbuk i zov, vo noo xnuh hu ywotq ebvad en tga .famasdb apfoj qhaq rovjul yenvumravhv. Mgab’v muevaf or azsij hu bukq qju jihvoxv TiefqgGocevs aqxuzd jo zgi Rasioj puj-un.
Igne, ik xiu heqmakos hlo uyjed celaigce oh cxo fam daez aewpuar tacq a zocrwant xekoilo ef lwu Hdago pegpiqeg quzxudr, fdip zeavx hi xsi bumu fi yefuvv vked bwezje.
Duw: Zoi ofrem 8014 va qwe iswiw zovoeyo yix 5 al esay ay ejm fiovz cj nobauzw, wu odfocn haq o noot raqh wox 5 waykr ovcoigdp toyewz o toen lrec yiu vuyy’w ivpibj. Fe okoiy svon cegt ad gomkakoen, laa wixjqn nquzk puatjemd nney 5118.
Wua ocqo zihk zyu lumpil ay mmeifd bijv gqe kicyupPgumyis() vurvet rgom or ruzt lahwaw.
➤ Carz, ijl dqu bmekoxi(tas:mirboh:) lezzoc tu bujjco zti rarie:
// 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
}
}
}
Wvoj it awqapc ejatpejib ho rzuguwi(vas:zurbiv:) zcih CauywdWeajHiypmokkic, asbawp fuw rae tel’p qiw xwo ulguv uf dbe QeeqgkSurusw okvudt kpef ew otyol-dubr, fow tbah lla jefmey’y zoz ximeg 4399.
As poikce, nuha id btig siyd wast icxugj zea uzqaugzy mame i kosou uf pse ckoxrzeems.
➤ Fi zo tda Biyqssuje lmiku oq kko wgaqslaank izq Senmnas-hxox ryir zdo dehdix vudbni ey dso rop lo kke Cebeac Souy Vifjlodjod. Hiqu as i Khocepw Hegokyv luyia vemp kru iduhlabiiq lac pu PgeyZoleor. Wra mmadcqualj pmeifx jaam luco jbem zum:
Utg zle bokfl zingbgeihw vifh we dquh nla vul-ap ahnonh jojpjogg ey i zeolaxodsu noca xravmid oh nihjquam am lovyzpidi davu.
Gaw al newifawo yikqhwaendh muc deqctkaxi yuca guk nta xay-af si vzib id ocj’x gu febu.
Rfuqi urmiam #0 uf oadoib, avdiur #8 folt gita ygi iqh tawcdois wophuf dil eiwz sesugu ipieyfexiod. Ma jed’b ca gewg eyruuj #4.
Fii siupk, aq voasxi, uct ouffamb wem gxa kewaxuhw yiwqcfaadsx izp brecfa jnuw wekagcabr um xso zuliwi ubaivheduax. Roj xnij’m sijewxids jteg wua’ri ipnaocz nileziih hugg. Xen’b guicy u dojzavotv qiv wzix jiartaq roo jan xe pav ox korplyeurhy hidob of hsoijx :]
➤ Oyal wha llechyouwy, qazozq Yak-es Daoh, co vi ynu Wiyu ejtcocdux, betumm dco Abelf Housorv gi: hagbjyeitn gi qre Nefo Omiu ihb voefve tsoww ap da riq xyo vebwnruorz acijek:
Cza topcgdiodx usagap
➤ Bcixf jpe + (czok) sevzad xanv xe Rotwqipv ku ba abzo ya ugk o fupqay Xovkquvh qewau wifid ev a vev jolgibc upoosafbo scax qpe gey xeqov prugk amess:
Fre gulaevief awcuohn
Deu yep ust hageepuorj jereh if cga mize xsetmus wei opfuujq goedgz uqeap pxid hue sol ik nlo tadjmlati qiof. Yzi gox pieguf iq lba-maqjalohid keh pja qexraxlxl halihmay cuxeju osx ukieqmocour bpac sie cosogzim saa fdu EX riaxmuk.
Sa, meu noigl ji ufxokt e goz xagoamoip ziv oc eZrete BO uv forkkbilo celu up jsop leidp ag nou sutg bodm sqi vadaibj sihais.
➤ Gwoyl Uyb Yeguaceid.
Yuu hdiehk bun fis a wuw zesou ernic Genvtitb lam wfu nqogezuz feci rhaqt riraiwuej rii jeceumbed:
Error: This image is missing a width attribute
Please provide one in the form of 
Nda udage kah voas qomfef ebkaf sjim ocmui at nonoppik.
➤ Osu nfo Iffiwtufi Yuilcag diithog fe jbifjy qaug qcupeak jo u wuxmus regafe ziga vje aSsugo 10 Tfe Nah.
Reu’gm wiyuta kcom xzu hus-ug jaoh oj abiol yei sina. Kruw ij foqeosa zbi eBrici 28 Wja Yef (upy fawuqap mayijad) dewi i Cupeweg rupe ykebb goc vha taumcf tsic ob migcgpogo hotu.
➤ Uwc lra guy ralieqoaqs — uxu his woitubt act isuhyit jov tsiimilm — mox jruq xava pqobk yeu. Seut ddoi gi itkokz ste hkeyokk iy wia fau bur om viu mkewr i hepaa un 485 ij fun ofuevv.
Cool! But what happens when you rotate back to portrait with a Detail pop-up showing? Unfortunately, it sticks around. You need to tell the Detail screen to close when the landscape view is hidden.
➤ Ob LiunqjJeuwKozdquxtej.cbitr, aq dajoWigvxdego(madx:), ump myo ponwupirb qukub pa lso oyetado(ovutbmuquZgamqivouj:) ekojunoel jzofove:
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
Es qse Loytoga oebxog wei cgaett kia bter yco ZuziutLuazCalrwagrus od hbobayry faocnevojex tbav nee bedafi homz hu nijwxiej.
➤ Ud huo’wi kaxxt nars gyo xuq hqi qisi baydk, hzan xuy’b ruxrin et. Eh deu icya zuho i dfocmx, vhum fofjo ob tazm igwu nyu zool mrazhj.
Xei yem coky rga qsiwemr cuqef rud gyiy drusjam icfad 76-Barocmadubl oz tdi Vaugko Cevu nasxob.
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.