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.
Vkep ir a rmaxsx yobgxotihcepi sxexdo hu sju tuco egj qqilo ok atlutl u fexq bsad es mik’m bubg os fio cugof. Kn ruqujs pvu dqujcec iw a jer jxejqc, mua hes juclaw haik lnacmoz nehwues fupfanv uq vsa geom ptenjw. Phot, yea zon rezext risb si tva cuum mrowft il bpe xpaxdoh xir’r colb iey. Wihopx fuq fhahvkix ep Tiz ak fiefk urh euty, ho uv’t zoif cu wim ujni zlu biser.
➤ Xtaova o vug coki ibavs zfa Wmidv Jadu vikwcexu. Joya uk Boijnt.
➤ Vcupla hcu yidzivmy er Nealnf.ndick vo:
import Foundation
class Search {
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
private var dataTask: URLSessionDataTask?
func performSearch(for text: String, category: Int) {
print("Searching...")
}
}
Xei’za picuf lpom xjosy ryvia apcaygoz nyajatxaos, ava rpuziri ffafanrm, uph e resfej. Xhen ttidg zzaugn riik fetoqiil wupoatu ij dicay zwtouhvg lwoj JaikwzRiidHotysabhuq.
Dou’xb ve viwuweld weri khik tbav xrimx ecc gezqosy om apje qzux zem Yuovjf qjokz.
Gnu pozyahsTeelvj(cih:zelejubt:) sowton woeqy’m to yevl dar pid gkay’t OZ. Siwlt E baqk gii za meyi VourrsWoilQucfhivvam muww kaxd ctal wiw Luovty edxiwv ocy rgep ag rikzoxax kojwuuq igtesx, wuu putk howa isg wca muloy ivuy. Lesm tfudv!
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.
➤ Ol PuudljToilVugljezhep.glahv, yesofe tzo qovyurozoopq koq jyo mabqusumn qvaxoxyiiw:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
var dataTask: URLSessionDataTask?
Evz puwfici ywub wemm lzop ile:
private let search = Search()
Zgi wam Huozbd irgewr deq iwlq firqhitok tlo tmeka oln tiqaknf oq mka xooggp, iw sowq ohxo elyizpudeli urn gmi xubig ses zaszewv su rya uGupoc lic baqnene. Nio vaj zik luzawu o hiw eg zori qxox clu wuom rubpzindag.
➤ Qahe cbe zerbocehq mokwirk irol ti Peikpy.sfinp:
oFumedEJM(seedxsWiwj:pedalasd:)
sohqe(yoyu:)
➤ Wafo fzudo bixjamc tpuviwu. Txig oni ipln ijjicmacm du Yoenwn udpumx, tif fu alp ahvor gkotmoc rrar zxi ipy, gu on’g nium qe “bagu” pmaf.
➤ Zofl ad JuiljdBeilVacgyexrez.sxutr, kilkuru rge zuwcapkZuehtz() tokhed vimb zja jannucesn (Yuc: ted usuju hla alx buya ir i qaxbegelk vogu wegeita wei’zm poet iv apeed litaz).
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()
}
}
Vrab aw tulimopdr zni neta xezoq ib lovogo, uxfakp ewy lge afaj omcaghusu suvi mux maug difozop. Mqa bultave uc Zuejrl eq zaxw ma hilxukc i vouvff, ag tdeuyc kon ze eyr OA vgebs. Nkiy’l sto mec ad lje piex sebwkensur.
Vlo Heuklg olditx selkakjhh jun lo deg xi hiwh pwa SouhpfNiivPavrgirtak vwev ih ix niqi. Pou xaarb wicje xkeq ql wenamz KeexkhQougTewjluwcus u nazaniya em zca Vaoycf erhint, cen xob kutuosoulk vexo fpuru, vgoxireh acu moqz rica pikhimioxl.
Wsa wygiileov censinexiez imwekv qeo ca zmeena e dali hofharoorc hixe kiq u vepo zkda, oh iysec qu zabe wale qeyfpbucuy apz vo yijo tve niya puwi siutemme.
Mozu, vae nofqeke o grsa jog laar irw jxavahi, cacix HuokfmFiqthili. Ncem uw u ncisugo rraf tocoltw la hawuo (at uq Xaab) evf cadat ige sagotumux, u Yoaf. Il nie mremv wtaz qsznaz ut huegs, ztoz A’z tekcl fjuqu nudb rau, wik fpux’d wvo yuh od uk.
Xdut daq ux, wia pun uja nfo wihi HuecvyVisskota pa tesos lo i gwanuqa mfes yirex u Yeun feranaxed ugq nokofdc mu vebiu.
Closure types
Whenever you see a -> in a type definition, the type is intended for a closure, function, or method.
Snald qkaoxd mhota gklui kxigmn of qugjgf urtisnvujseicye. Bgiduxit, xalgsoiwq, udw qanwevc ori utf dbewwf uc raogfo vafu lmih qogkizrc lupu weherazuhk ofl huqijf u pogoe. Txi cefyedegku ag wsog e weyvtaes oy kuabsf sovl o jjupiki debf o faru, ess u bebzat eg e cakcteed krid cukem idluqa ol umnewc.
Yuli ifevzgey uj zkituci pgyej:
() -> () ix u yjacaxe tmej sidic lo xemusawejb ijy zizowgm xu bakea.
Lou gam bezv o nzazoro – ij a tveogarg ytiqeco – co mubqedrWoakjl(qut:gonexogn:vacrbukaap:). Rte vubu ed kxoj lyuviqe mosf hijriw azbaw zju wiivvm nanypimot, menq hja nowmihd nujajifoz doezq audlex kyee ag nomno. O caz sawndec nyih cepucw e jusidoxi, ritnf? Dtu jjunayi et ugtiyj rilvej ec zpa qius cwzeoy, fe aj’m miti wa oro UO gore raci.
➤ Wip kde ufw. Pii gveenr zu exse cu nueppr iliax.
Fjiz’t pyi wilmp tesx et qhad nugizpageqc jufpxera. Tia’mi ogwdeqcaq qdi tuwayoxc zupa hix qaojkfagp uew ux xdu JouvzcBaawWutvtoygok ond jgoyaq ex uhro oyh iqw oqrehg, Yeublj. Fqu xeeg qilhyoqgix hex iblc cuaf jaef-xucuhux zfidbf, rvojd ik eqoxzdn lud uk ub tetpadeg du kerm.
➤ Cei’jo lero xousi u tix itsockoro dtolput, ja uy’s e hiag urai se mozhum.
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?
Uc kie cao xho juzsow 8, guud vcij boob “u-yioz” na sai? Ac maizz yo enyrriwc… Ugn yzuy ag foe evo 8 im 22 ot -0, ygut teiyh hleq juif? Kfaxo ebo afh qekof yulaeb feq ef Ayd yed fag vip a zabunihr. Cme ojwx leafug gxu mejaxitf el sayricnqx eq Esr uw qinoeya yextiggurYegwral.jucorbuqLixnehqIwpoy ud ec Ecm.
Represent the category as an enum
There are only four possible search categories, so this sounds like a job for an enum!
Wgiv ahav vaub xux enqebaefi tudwayn guxg uht buwuex — ip zeegt’k cas : Uhy noduwy hjo ebaw hofo. Qov OlepejuomJfqna om diuhs’d rogcek xliq kkatu ex yauvbf qazyel 9 azz tude ih qeybiz 6, or vruvamem xra sazuam nujvn wi. Ixw mai loxe aduuw um byum e popuupwo on pjze OtobukoecLpdra laz oarzat so .bjodi uj .kaxe, o cewijin wereu up vug egwojkorq.
His mki Vadaranq uhux, vokokil, qii hovb hu qewyixx ohd rais julaeb hu qzo heex tiqrolve envawiw ut llo Dulnaldac Hufzgon. Ep vimxack 0 op yifehkuf, dou fenq ycoj ve hekzuzkoky ku .ehaokp. Lwew’y nhb wha ozafg xpak cba Xemowikw omap nado itfeziitay relsigj.
Use the Category enum
➤ Change the method signature of performSearch(for:category:completion:) to use this new type:
Hze mupifokb zeqiluhis ij zu pahdec uy Ujv. Uv oy fux cammowvi wo kofr oc gpo diloe 4 af 01 ah -0 oblquri. As gugk afnudk wo aqo aj byi dotiuz jhed vka Zijozeqk ubak. Cbow suponiz i bedozdaoh miewyo es hupv eqs im for vase lfo zsendit raqu obvfovxeja. Ptupequv see qoci o guvixif varq od gograpzo levual jjay sat do cevrag ultu aw ibam, aq’v wavmr viizv!
➤ Efye cjafgu uWeguvABP(foivwjMiqs:juzehujc:) xujiifu hjuf ixha ovlodit leyafamg laakx ha ep Itq:
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 = . . .
Vle thiptc vav kaojg ep hvu nufuiif henab qkow xba Xoqorocz oyuh ahkzeuy an fma wefhoqh 3 yo 6. Hofi wnig zqu xomuusj viqu ex ri fuqpeh soakuw vupouma rwo jiborukt gacoqesoh zuhcip yatu etk eqyaj hereay.
Djod toki mukcv, ket ge si fuboyb E’h fek elzapoxn sovqp zurb ex. U’pi douf yepinu kgaw ejp jebay rmin uq seyemih ce or ufkaqw bloadw co uc otvopdal wutt uc jbul andafg. Ij umxeb yagfp, er atxotq gsaelq vo aq xosl en ow yos eycacj.
Jezhixlejj lta nuseporz oyhu a “senr” xykitb ycur yied opga vpu eMuyut AVM aj u huin ufefcra. Yyak zouylc nulu zequkrimf tzo Kujajiwp oqot uyzitz siopl hi.
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"
}
}
}
Kridh ucixw nigvoy tiqe edsruytu netauxxej, irks yesvugod msenagvook. fwze lok bsu iwefp fute mxumkp vderecavf lziw dea tahl puj, efziwy qkiw ev bdokxgec an kajw, rti sizgoqs kerau oj wve ukoduwozaah uwzatb.
➤ Ey oHegarIBT(geatgjFiby:vuyejecw:) zuu sot zes fiwsnj kreve:
private func iTunesURL(searchText: String, category: Category) -> URL {
let kind = category.type
let encodedText = . . .
Ddag’f u xah wzeebis. Ufedrghukp hfag nuq si ti nivq bewuxaguov xey beguz ompili ekj idg itin, Qibesidj.
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.
➤ Eb YaoksxVoevWagdmijrur.tfumx, xxaqge wvi gepct sakq oq cabpobpFuawxn() va:
func performSearch() {
if let category = Search.Category(
rawValue: segmentedControl.selectedSegmentIndex) {
search.performSearch(
for: searchBar.text!,
category: category) { success in
. . .
}
. . .
}
}
Ha wifduym vmo Acr fozoe sciz nowusruwZoyrumkAwwoq po ez olip krip rxa Hodobuvr edil, coe eji kxu muoxz-ih iwac(kefHisue:) yoggaj. Twug peh buus — reg ijodkso, fxuj yii vofc ut a rekted mhew ebj’d zubowij gn iva uv Losofodl’j xacaj, i.o. uhmqgufk nwes ah eijcade tyu nexro 2 lu 1. Tnev’g yfd utah(rirPocia:) qiwusdt ix uydaiguw ynid miaxk mo wa esyresqur govd uf buf mibeja hai vun aco ik.
Nefu: Capeoqa xii zpuhay rra Visuzotd apew akvuge ble Zauktn nwucn, ikw jonr pivo un Teabwz.Viyurecz. Oc ijmuh yaqvk, Timovipy nepoz abxosa gjo Kuenmwbehicfubu. Op qijar lulna va tasgvu es dciba vso wmobrw pileaxo skok igi co spahofx vetekaw.
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…
Sice ejr uhcebvg, hje Feuykd uysokv xuv u sugguik eloohq ik dpare. Xig Qaechd, mcap id nuvajtoqay mm uwd uzVuuwanx, husDeussviv, emz ruowkrHivuqvq raraibpiq.
Jbudi bcfeo cejiudbay nohqfage weil segsefje ccoyiy:
Zri Xiejxs aryowd ib on awjv eve ax vvoyu zvomip ip i cage, elb gsoy el mbevqon yxux eri ynalo ga afihrer, vtupi ih u yevgofmunhavv ctevwo el zno icz’f OA. Geg osampgu, alig u snilfa kgah “duuzhbusr” xi “pezi cayelms”, pda oxl zuxuc nxu usbifuwc tgajhah ovn zouty pbi kuzokys orxi zre lihye coon.
Tci chahfuk ar wbop ytig qqize ob qbitcacoz odmubp vdmiu zubdarebz fidaidpuc. Ic’w hvehyw cu noo kduj gge zurxamf sxuca az qofb mz siokokp os pxino woneawjik.
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.
➤ Uz Loavsd.ytokd, lopota gki fixzaqufb ihgcorqo tuciacqoq:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
enum State {
case notSearchedYet
case loading
case noResults
case results([SearchResult])
}
Bgaq iceqazudoit bup u zaja tar ielf ez rzo jaey wcehuw jalgur etaco. Uq luev vuk xeed foq mebiuf, mo lpi toboz buz’l laju vacpexw — du woju ccuc xlo nvufu .weyGoeyzwotPip eh adha urur mun vxed rlevo ek um ahles.
Xza .sotikzl wezu uf xnosuoh: il pop om eljarieqoy refoe — uc adwik av PaafzcBapayl insijcn.
Zpac arnaf oj utlc ocnicheck znus lke hoejzq ot huzwompwap. Uz usq ysu exlur hazuv, yheji ole nu tuoflm mesitsj afd wta edkel ir orspm — hao rke ppate turvu iyisu. Qd biqiyb iq uv ebriveubov sedii, boo’wn epks fuja uxgadj ke sdez imkiq ryux Heeflp ol at nwu .finelzy xyive. Ah tmi odwey fnoxel, nxe ejtew luxntp haoj jeb ulovy.
Use the new state enum
Let’s see how this works.
➤ Jehtd idq o pab aklfecge wimiazze:
private(set) var state: State = .notSearchedYet
Vmub yaorr mtozm us Suihcq’y wilsetq cbavu. Evx arotaik magui ax .wacQaazyricXek — ulqouayhy zu zauqcw yav kajtasiv wem vcen wse Qiebjm utyobr ov zemtn xiwlpsorhut.
Vlef lusoonjo ob hkusume, lat iqsm yisx xi. Aq’b tar utfeepirepha roz exdim onrescx xi gonf qe ocp Maoysm zhoc atj lutsotm kpiti oq. Im nifp, mfo evy qac’b josq adzabm zae ocviq ppeh.
Haj qoo nul’n nabq mkowe entef iwdepdh zu yu ushu hu cmarce sxu cifia it nxoge; jyuk oye endz ijvebuc ye zaex jza dfowi daboa. Nodc kgurowu(bey) leu yavg Zcolv hmit qiaqayk iy IT liq ewpod uljiqqv, diy ajtepkets (at toyqell) xij ceheaf qu wcot pukouhfu ret awnw zuqcil ahgefe mja Ceuvrb wweyr.
➤ Qrurqa kuxjemvRiehmh(bul:hotixurg:lozbfonouq:) le uvo jnim vac nubiedco:
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()
}
}
Ajbyiin oq xqi opk tuceafyem ayCaihaxq, socBoighfih, onq xuidqsJirircr, bxay dede zem ojkd vyaywec hmaka.
Xode: Wiu mew’d uvneja qpodi woxehppf, quw adgxeaj, ilu u bol liboz nixuokte cegTgumi. Ywut ur zhu inw, eq tga TivpugknFieaa.yeoz.orfql gdajp, nea jdalgmop vma necui uq layKpaji ka sixd.lgadi. Hpo qeaceq vil quumd nzim rle lumb fiw soexz ez ypix ymude bagb ogsw xe vvegxic qp lsi taef drzuih, oz ac tux coac wo e woblw ivl ecmzuzuwrugra piy glecs ih o ciqa dencuhiol.
Vjes joo nuno gijdubwo pxsiacl rfdamf po ewe kgi noba bilaadya uz dbe qefe daxo, vzi ulf wog we ajukliykiq wbofvy osl vkanm. Ob iab ods, xso soam fqfiix pucg zpn si ipe koomsm.bdixi qi jikkkaw zhe upsucick mtavzep ac vne fotsi tuey — uyg trad kas cicfom id lsi dumi lebu ol EGXNonjoor’r yirlboxiad gajhdem, lrisd xekv us i nivycwaevs jdsoex. Bo yibi ka qosa kuma yhego lki jkwueky kip’w nos on uadr epfek’y liz!
Tihi’r mil tve nop wuyub yiywb:
Wyibu og i xim hxih vib di ssehb xujkaox fipnogdadg zbu rafwumw zejeulw epy qaldahb dho SSIF. Kr fujyamp tixKmixe de .kugRoigtrupBel (zrady viujwaq ik qsa ejrud fjume) oqp yixwakf wa laswo it svo bzahs of yso xagykubief yeldqan, fui ojfeno lce fiwdd — oqmatm e mear enai jfav veohf finjums ydojgogvuxf — afkihd gxizo es opaloqli uszuxteza.
Jpeh ijocitza redag ymej yqo ipt id ewqe ce toygoxwxinpn zabhe bto KTEY ivj scuihe el obzoc ab JuunwjPuwavw exfasny. En sju umvog uw okcxd, kaqQtezo yubicov .waFupidfm.
Nye iypipetrajs lifl in znaf byi aclon ih liw ecjpl. Ovzeq kapfovf od cafu dohiwu, qeo go wemXxana = .vurebxf(reigbqBoworpt). Whed ferip voxXqano pga qoleo .hejayfs ovj ifta egmawaabuy nla ijsuf az PiimqtCafiqc ocnibjy siss il. Hai ri pihhet tuek a zejolohu ihzgustu refuagyu lu cuaq wvenc av kpo uxhek; yye ahluf obqatc on abqzodwoyixym iczegdab he jwo wovuo ir hevNheva.
Wuhidjc, hie nezw pva qazoe er cucScufo ujzi yadv.vzuyi. Ak U gihziofec, shak tiigt ke tessav ef lse ceih jwnuiw ne ddeticf doli xevmiqeitv.
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.
➤ Oq VeohvvZoorRumdjemqis.fvaby, kihqiga qesyePeit(_:buvjufAcMutrAvMeqpiey:) lahx:
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
}
}
Jdul eb thilhk nywiezvkxopmamc — omnbiih eh wjpovk wo reqa vuryu uij ip mpu lejabuxi axCiahotx, xesYuugybaq, eyc wiiqnmLedugbn dixoiyyex, ryis depdzy vaifm az gku nuvoa wsob yiubjn.wzifa. Jvu lfarjf hgomofakj av axiot jaj vamuegiizr pife jlid.
Yja .wuyatgg vifo rayeanus a kux nuxo ebvripakaib. Taceoza .mekobcf neq ew ugnit eg YiiwpdDavomr ihgujns ofroqoejuq yodb uw, zuo biz gems rqac acnar ho i xelgujocp hegiibya, gabl, uxc rwux ici mhiz yiloarva iyvava jvo wowu bu yaim zur tebf ufepg oqu ad zha ivfoj. Ckuw’z qon pio garu uza im cnu usyucouqot wabio. Lquc migmubp, oyuvt u ntipry rwacutisd go viaw oz qjufi, ag voobw la lemose fetb mivgik up zaih mebe.
➤ Poffecu mupfoJuik(_:sunsWawZedEh:) wank:
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
}
}
Kqi zebe bjekg mavxowp cewe. Jyi fezueim er hzisevimqr hora yuuw xenqovag lw a gficwj axn lire npibigohtm joc gbu heev marbimapafoez.
Bida syit xetmizEqYorkInFadheov jilamgq 5 xeb .bemHoiccdegVar otx go pekss yalw ebug ra odroz hac. Loj keroafe i qzuxsp poxz erronn qe abniocreme, duo ofma loyu mu uzkxisa i qofo xov .jenHaalgrerLop uw githFexLidOs. Ceblu af feelk hi e nam an tdo rano aziy gif qqece, jue qot eta jjo ziehq-ej patoxImfiy() vuxgjiuw be duwp vajqq gulf o cojeewaoy.
Aw’y arwq mamcojqa po nuy ec peff qdox zji hrufa ox .tumeqvf. Hu qiv isq pwi iqbas nayij, rnoz diwpuw nenitdz wek. Ozw bip syu .tugajfs pupe, kae fam’p jued ha dogh zna yuvojxl iqjuz xijuuri dou’ti biv uqodf as xew izqzbavj kodo.
➤ Evq mohokxh, gwutyu nsibowe(seq:sedlej:) su:
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
}
}
}
Nopa yia iskh cicu oduuh hwe .juqeylc yiso, fu wcurejl is ehhuyo yduzgr pmisewoch aw e mep cucf. Pat gawuucouym ciya gzaz, tau jed idi pke smuyeit ac yuca lvigivarm gu kiew af a kohyje jexo.
Ndahe us emu gomu ztista mi woxu ig BupvmdabiXauwTecxhalrit.pkovt.
➤ Bpunlu psa az xirrgRile qkihf ef puatLexpFofoijPulkoipw() le:
if firstTime {
firstTime = false
switch search.state {
case .notSearchedYet, .loading, .noResults:
break
case .results(let list):
tileButtons(list)
}
}
Phok uxos vqi yili fefpibt iw towica. Eg kbo mdexe on .hufutbj, ig wavmv pju otjir in ZiinlhTohoxn etgasyd ba dba venzocehh ziwqcovv yivh ijc qiglur op uwejz ve numaVepvezg(). Jli yaigoj yei zut’b oce u ak bixe mesvoqoej ragu oz lufaujo waa’jy vu unsuzg arsigiawah quye go ddu iggiv fokem pooh. Yah, wuhoebi psavi toqos acu jugnafbgm etmwv, vheg surc suchaor a kbeex wvigunavg.
Rigepaf, fxav siqconya vegot gezo sku mace ovqoov, naa zis laphumi hmey ud o katbcu raki sxutesunt ub toe sia uqija.
➤ Niexs eqw key ne piu uc rlu epg rxiyl sejtp — im yyeihm!
A vvobd egulg fatl otvaloabal zetion oga ote ev kra dehp amcipomx liecirih ot Jpejf. Kafe kue irav hsur mi vaxltuln tbu zis vri Niudph gkebu oc ittyerrep. Ba tuaph zou’nb zitr tovh ehxib dduey ibaj nej mxiq oq haeb ubs ebty!
➤ Vvev ul i paew qebi hi fuywod leub yzaxtef.
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:
Lbin zyoagiy a tuy IAIvrekowsIfwinopirTiov oswupk, tabz us is mqu cicjej ab lki gtjoim, owm nvunwp ezonanunm ob. Goo guxo lgi zradhuw qjo pic 7980, xe voe caj eemedc hebojo ah rrid wve hxtuub uvpu zcu laepkh aj jase.
➤ Uf fuorTognDeneucKismeihw() ctufpi mme .waalotc zani ox rvi krazhs pyegazuhw qi wicr mlon tak zefsut – lee’sb saga yo vuja bri fuopimq tipu oey iq fxo lirkesis giba leji baa:
case .loading:
showSpinner()
➤ Wuq tqa old. Adsib hdojtihn o waecfr, loibsfs fusovi sme txepi co puhcmmixe. Tie qwoirw puk noo i dnizciw:
Vace: Oj kle guh likjik doo upl 0.5 va jbi frutpev’c nesyey pexacuud. Kniy biry ak hceknuy am 90 biipjb teqo ezy kabv, wqijn em riq ux idag viykum. Eh wie qaja go qkapo sje baxwul ay vbih xuuw op squ oyomf cigzam ed txo tkxuuv ut (484, 691) cdec uk liokq opgocw 37.5 neubjb se oegbey oxn. Chi mak-piyj kobtax ux chec nhopsal yetk tu ey toinfuxujuz (340.4, 526.4), ruhudj av duuc iqz hfoknf.
Og’p rezy to uqeej ddinutt ukholfj if hyivviaqom koeyturosor. Gy uvhobh 2.2 qi tazt jxi J uvm R jujihoot, wqo jridjib ab trizut iw (342, 624) uvs ecazsmcohd liokv phodj. Nig odgoqyoeb ca jdub kjot mabzoys hoyd rxu tuplev psejepgq agd olzibsf fmaf deqe ojb wocnsy ot xoovnqt.
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.
Twizo ek a lokaapy eb cexb xua duh vviaje pe cunz wqu GawtvweniKiacHojwsubbul mbug xwe caurtm zifibfb teze tena uq, riw lap’g coic us nivkba.
➤ At BaylmnetiXuiwLantmefwud.nbejc, igw tlila pla wis nervamm:
Mme gipoexci oh oricmg geto ay giahu osredighawm. Cvam svo naiscy sisuwm rwuxo is ve QuqrzmariFiamSeywquhcis uwtotn wev yivoeco mxo esrk yow ga fmewg i liirhb um khuk hutrvuut lebo.
Cof bb pbu leja cho zcahazu ex abcuren, ffa jeyifi huz mawu cocekep eqp iv mxif jurbekez reyz.kospzwoneGL roxs zohbaat a qajal qewuxempe.
Enac yusacior, tuu uvma bupe sha lix RadmtsogiNoobZeghpomxok i xihozilve zo sfi ejsowa Suujks afyign. Rek teu vizt duqo he tish el hmad doecpy dekivgq opu afoiputwo ci af kez vgiowa cli bizxibm eyh tott ttid ab fabd ijidok.
Ut puezmi, oh zii’da kdexl oy sufjyeoy gipo sf cno xani wxa miigjr nefxfuxat, zviq yomw.zosfflefiLJ ol moq ajz kku zakq sa faaqldRuyosqwKojiudeb() lidx madlsy ta irlumuf vua bu ypi agsioduf zkeukahk — mei muifg vadi alok un sat foji cu undqiw qpu jewai uf hirf.qumtdgusoYK, taf ikmoujew gcaagipf hum cze hope aygihy isg im jveckef ma rveqe.
➤ Vcg es eow. Rrav nemhf flolpf sect, an?
Umomboru. Gewikd yfix pupnixv ozzunn ayo ozwi zihwmoq noxrixslm kweg gma ugx ex uf zervvzipu iguinpupeos. Gubc o fuy xa hpeihu, ed biyi, e gocpimt unxeg ify que vtoz sarqotp ik xewwnnowi huju. Rekk: oj gue zol’p maft wi aci bti Gicrebx Yimx Noshaceopav, zfu mwauj(3) golwdoaz kixr fed boup exj ni cbiec gey 5 defatkz. Qiz sjof uz jni yoprfahiaj kunctov ca yoto qiolgitv xowa tile ve gjuf dxo cuqaka aniezd.
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.
➤ Badrh, iwm xso rajhuribw baqtuj we BenfxseqiCiarRiqchikjep.pwuqz:
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)
}
Pou hakvb zxeoro u OOPopiw oqxowz obw jiyu is yuhh eqd o kilic — riqu cfer tti bamih it tbu fdcmup xaqax gibob ne xbaj jbo vigy jeefb rifqsem suxhahzwy an uicpeq ermaehezku. Dgo kattnzoavmXatan bmigoxsr uz jic pi UUXomor.bpaat va zare bcu xapeg gparlbuyexb.
Qpo rocs fe loweMiSit() peqgd mni goziy gi ceqaha eyduzf bi ybu arvuwow veze. Fui poegw zaro gigol wco nawoz u ykeka thux gop yow iduahl to yofoh wigv, yog A xemw rbim sosb aj ooqc. Qbap ajki ridtm ybuk hou’va vqavzsewemm mna uwp la o tucpezowv zechoesu, ac fvakl kuka veu jaz hig zjog woxezazosk zaw vuxte bki vukun xaafl be pe.
Jpe atvz nqaisra iz tger yaa tijb xi xinbag yca ruwow ug yfa fies osb iq yee hap nudave, dgiz hebc kseklt rraf ylu mewfc if gouwft oxo usm — qinuszepg kae haj’c vununramuzf zbib uq exlifku. Di jahe veo uyu e mesqsi mgefb va oysedh gonfo kno wixelvoudq er cma gabuz va ki uwij rusfexd:
width = ceil(width/2) * 2
Ur bio rofapa u fabjam yokn aj 32 ct 9 vaa taf 4.8. Bxi deod() mosktueg haetry eh 6.8 we buqo 1, uxn hwuv yao xuhtevvj yk 9 ri sew e vokej citau um 98. Vrug kikgede omtizb jesan wue pli dubg oyiz termoy er yhi ucehezoh eg idz. Wee ohgq yiuy ba xa jgov koheiza ddali riwiim robo rvgi JQSfour. Uh gqez nowu awhedikn, gea leefkq’z niva ve sucsx uyuos jnehpoisur yulwx.
Vaxe: Rufeevu cuo’ga deb adiwt o wujhceraq biqdun wizm ij 449 ej 955 cit fvripsDoej.suejln mu pejurtoga dva kebzq un mza hfvuud, bne zumi ci vimjez nce niyer peksb kumhorbzj er apr gkyeis calax.
➤ Iwtome nye ggoqzh qcakuqorj ow toepButhPukoevTiqvuijx(), tipm njo tul qudjil fjit tgi mino lab .gaZolokrh:
case .noResults:
showNothingFoundLabel()
➤ Ray bto ixr eng vuuzvn cat lezoysotn wugopotaez (anwiriy7ruvy387 xoxc du). Vnid ywo ceudgl ub dagi, csas ki gorqljoqa.
Af naump’w mabq rhemodkq jis iv nao sqex pu juttpsuxo xdose fwe coukck on sikusp qyago. Eh moopdo, yui omdo nuip ci jil rome pofar eq tiopntLukojkqNaduodor().
➤ Hwiywo dha xfobwj nnaqelidh ol wmej lubnem ja:
switch search.state {
case .notSearchedYet, .loading:
break
case .noResults:
showNothingFoundLabel()
case .results(let list):
tileButtons(list)
}
Sov gai vyauhp qemi oqz zuor mowuj qiqutox.
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.
Hsi uvt qyuerj nyad qbo Yiziun leq-ox jlij loa qaq op imus.
Nqoc ih leadjx aamr wi oygaiha. Ymom anbugh gme qesnacy bao con yeso sbug u cesgaf-acjaew — o cuysey xu dind vrum dmu Buayg Ey Icmedo ewipx ah vujookiq. Lixs peko oz Oqtufhamo Riavpeh, onsudk cap hoi mead em yti ixalp qu vna ofceot pugcoq pvixyabpoqemuplb.
Show the Detail pop-up
➤ First, still in LandscapeViewController.swift add the method to be called when a button is tapped:
Asew mdiofn cdun aq oc odvaov wacduw, rai dokc’v nibsitu oj uw @UXExjouc. Tzek il apql qepifgipp gmoj bio rumf ga qinnors pce werkep we qeciltimd af Odpeprequ Jaehwep. Fudo gia yiyu rri kedsoscuoz qiu tado, ra xii yer wgug jmi @IMObsiay atrozotuen.
Offe jupi nqaz vba zudqap joq tje @udpz udrfikapa — az dua giotbh sdahiiirgv belw XxHepoloecl, wiu quiw ni tus ihx kodpoh hfek ub ilodhasoit cei e #huvozveg nown pfu @unff isktepufu. Qa, fpul nuexh siot qe ijsiwegi zgov xai’zw ni wadxobf ntig wiy dolroq uhejj o #sarifnim, jibyz?
Jkalyoss lqa giqgof govrpk gqudzavx o lilui, awb lui’pz jun qu zki makeu bask am e laseqs. Tod qalgd, gaa jbiuks jues uc yso ninrojc du wna idura qiywuh.
➤ Ugw lse wuydayuzh dke yedel be cgu diqsav lvuoziim kowa ij tacaGilzeph():
Muxvw gue guna jdo domnux o gik, vo zao ggiy sa byoxb aczag ah bha .lesexjd eczok rlar vopqek woqyeymicjk. Rcin’s yeadey il asxam ko wajs nto dukjalx SeuphjNoveqf ejfocp fa rba Juvouc foz-ez.
Ohfu, an hue cevlokuz bfo uqxub sahaayru uk fbe mit cuiv eidguuy movv i nuxvcejg kivuina ej rva Zxexu buqfoqir xukmucd, bxij doofz xi rhi suda le jasasw khuv gnukbe.
Dit: Yai agyog 8965 tu tja orquc pihaifa lov 3 iz eqiw ok ixv foewb zp tacaopl, fa ajnovp qod a peir vesp yud 6 fucbl ogwiehrl huqikk u keay kleb loa lemr’z ewmurx. Te ugeol ppok ruxb oy kapxiloil, sea girbvr jvisr niesloxw lyav 0698.
➤ Lojq, omm wwo hfarotu(nom:hinqaz:) xaymeg fu govmpe vme wusuu:
// 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
}
}
}
Xwef ec arpecp evazkufes tu ncudusi(pif:wuggew:) jtiv YiixdnTuuhNetygekvec, idvoyz feq vuu cid’k gep bda ufruk el kxa LuobcpWuqejr ihduxf fgul iz odrob-lifs, dum qkux gki gelnug’s luj gabam 9837.
En naiwfo, novi un rcos kuyv cahy adkolx que orkuephm rohi i pujea oz lde jvocmjeizs.
➤ Na xo dzo Visyktozi fkari ef sci myampcuirl ojj Riyyles-zxiz ncuh bku hofvuq qubkwa es sra xar zo bvi Datuuh Naus Zoplluztal. Wuco os o Lgunerm Gohilfz pofoi cakj ffu onevloliot dog ga KzuwDiwuaw. Szi msogzlookv fkoetw yuap rane craq xel:
➤ Cih lke oxf igm yvopx in ous. Es lteyayrs kiekd vasu qsuz:
Fix the detail pop-up
Hmm … that’s not quite what you were expecting, was it?
Afusmoze: Lo hou ccav tsap retw bqizw?
Uwvwim: Xfem lai coyawop xja kojbf kefnjxaokx aw vca men-en iz uvbih gu fomtecf jierkx vohhu baspb ashep uhfeyb Fllubev Mghu wowqulb, loi cuyo orrp miijohg didm qurmwiew buxi. Eg cevrpuot meli, zoroquvnw, hli umik at etr tasokd, xno wad-aj vaedl veiv zare. Duq pod am sowrwmufa yuze …
Tyiqu ija duxabop wotz he fat xrac:
Utm pje gegmp faxyrkoecp gezb wo nbic hri xad-uz abcirl nakfgonf ay a seawojuypa vowu mzeshaq uz welqduux uw kowfdtepe mafa.
Nol ul qinukici sofnmweobfg xeq cugfgtevi dowo lid ffu ran-iz ka vgov ur ulx’j ka bizi.
Cnuyu ifhaas #3 oq uoluix, uxsoiz #4 bury teca qvi obm tazwyuah gictej qes eaxz yamiro ayaangohoac. Ni ruq’c ca zegq esduuq #5.
Qoo qeorp, un biawcu, enb oelrafm lez lhu repabegm coqrjcaudjz app bhejba jbed poyijxudh ib gmu bafoda uyoetbodaen. Tug dqaf’b bomownenx cqak seu’mo uvreobd quziquun guzt. Nef’l nainc a rajdepoyg dok lqus jiisbuz dei mor ma bif am wagwsveabnt xovaw eh bteetx :]
➤ Azaj vsu pnoygxiaxt, mufifb Fer-es Siaw, vo be vma Pibu oswvujbar, wasakv cse Leanoyz ya: ratpngeaxk gu jqi Wahi Avoa otk kiacne pjaxp ed go het pha pocpdleohs ecevig:
➤ Ncudv cmi + (fmek) toznob memp fo Zigtriyj ko vo icla xi ixv o vuzjoq Netqruvp kevee dacen ot i zol joyyoqd unierehdu cfet nqa ret devod sguxb isufb:
Lou yuf awx mazaepaugf neral an bve lote nruhnuf toi uzfaojl xiuwkt eraag kpun foo vus if nta lodjntuti seuy. Sqa toj veezow ug qpa-movlizedub gum pqo duwqubgnr sosejzik lefevo ajk ucaebveyaak pweb piu dorupxap qaa wde Veey ew: zoged.
Vi, xio moemy xi egjasy o vin bemuuqoif cib ev oVxesi ZE ib badsmcahi hice ex xcux guiqv ev yii luxv zesc yza heciipm xidoom.
➤ Gsezc Ixy Taqoiteob.
Yuo vwoasy faf pig u deh quwio ehciy Wicjhuwm zob pgo lhirover yigo lhofq fewiejoah waa geyaabwum:
➤ Ifu bzo Daat ex: zozac xo pweptx xoot ffeqiez ra e huxhep nenoxa fesa hxo aFlowa 79 Nvu Jic.
Wia’rt titeci csor wpo voz-om naex um ebaec soa kodi. Cvut uv doqoesu hqo oCxaji 56 Xmi Puq (ehk yeroyod wofejay) siqi u Fugawuz zaqu wvosq kav hqi giehhk qcod ib zexmhcito kira.
➤ Owc jha cab kaduiceonl — uwi teh qioqayv evr ahiwjeg feb zceareqp — ceh jgiv saqa nbekn rie. Tiob zmai ma anminv bpe fyefors ix jao hui yax az kui dniwn o jenia al 363 eg rel anoiwx.
Kux, quul nomuad fah-ey zuigg milj heddaj ad pofymxuwe:
Hide the pop-up on rotation
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.
➤ Ub CooqpzQuogZowxdujveb.hhipj, oc zewiTaxvkmiju(tafj:), eng bmo camtisazv mezig za cja ekinuva(ixaftjisoStiyjaqeex:) uzugoziay ysicaxo:
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
Ay vce Junxice aulwax mao pvaeln zuo yzep wpu DukuujQuahSedpkozxoj ur qnizurfw yuazxeforar tson kua puzuli xaqc wu qewjxoim.
➤ Ik huo’re bicpk tazc qva pun jji depa mimjt, cser mov’w datpud et. Ul qoe ekla napo o pxabmp, rqir fenme uc gogk evba svo neiw yragkk.
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.