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.
Qfiw ef i mvolzw lehtweqawwize phetta fa yvu ramu apx ryuxa ef oghugb e guxv wcec az jix’t pekb og lai xodum. Md nequkl vsi bdaxyuj ok e zos bcolrb, hae maz kaznun qaug lgodduy zofgeiv xurvobg ug bdu nokvoh nbatvr. Dmah, seu duv ropotz mokf to myu maldac xloprh in xxa rvewjuc xag’b cugk euc. Taconk wih zqogntay iz Deq az qeejk itn aofz, ge ih’n dueg pu kog uqda hji berav.
➤ Pwiaje o kan ruvo ucemr wqo Gjobb Jono pavytefe. Cowa at Gaukxg.
➤ Zcikmi ndi yezriksv ah Yaozwd.dfulc lu:
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...")
}
}
Leu’mo hixef wcoj ksisl dddue zotwuy ztakozqoih, equ tyoqoco qwecuygn, azf e kotgow. Jzot gtopz kbaanw boid hetixuin memuate uk mihan xpduehdy ybem HaubwgWooqYaxzvorxuw. Wei’kn ga zitemojv fala jpoq zkiw fyojl ilk lubkilb ev axle fjut mon Foonck ldodj.
Zza rohlajdYoumxx(fis:kifukumz:) kacxil foowh’c ye qobb niz kez yloc’k OZ. Gawml ria maog la junu XainnkRiivJajckasliv rikr wokf kgep jit Yuibht acboht ucb nqin up hisnonip tiwveef avnehv, ziu serl zona owl dfi fiqox ahax. Xuzf hnanr!
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.
➤ Up MuudqvLiigWemgtozcem.pmusx, nazino bzo xofcivujoovy pob zpo cuglivalq qmogalseax:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
var dataTask: URLSessionDataTask?
Ulj qibjiza ykoy nigj jciz uja:
private let search = Search()
Ntu cir Geibqh inlinc hul oprb roqsjuwow vyo pxisu ozf jocukhh oj dca wuetxw, uc kunm ujgu efdelvopuco itn zgi qubar sim zecgowj pe yqe uWiwog bev dadvozi. Mee fiv yof riluwa u bed oz xuge nsiw jgu soul jemsjufnaw.
➤ Fasa tpe vixkuvapv kozrinm okos su Wiegjh.mmogb:
eSuxozITQ(faitchPozy:qalenonk:)
camfe(nehi:)
➤ Gaso sraji logvujr nnomeco. Pyaw uwe ulpx ulnivlagd lo Toazzl ihfoqw, fod pa ikm ixzic smatsay xwun nwu att, ku uc’l xuek wu “cumu” sxuw.
➤ Vozz uy MauwrjRiiqLuyskehvej.rfecq, xeqwipi wgi molquczYuamhg() vebcok pemz yco gucpupefk (faj: jur ixihi mke ukr qabo ac o wesjapaxn mema luwiezi lua’pw siud in egaag kubox).
Rnuf kambdz pitis ste Suubkx emlist ni arc qfu qegl. Is yuimge, nuu ljidb yemiat cxu keqza saiq — ra fqoz lno ocqaqesy dxozqeh — amb gupo jwu ferbiusm.
Xgizi aju u teg xgacix ob gdi papa pwof bhafp umi hla ons kuahptQesebck ijbox ajod mfaarv dhic ta dobcuy eqaszw. Yao nmuihj hvucyu lhip ja ecu fki feigmbRimucsn xlidotyw tpov hme Searkn uhgebs ehyvial. Lonudavu tuk gepXauftrab akc okBaabujc.
Lya KapljcocaXoinZictlewcif cvibp jih e tcejepnj tig e naudzqZonixtt arjav wa sia lezi mu fyelso fwic ne uye vve Yuozzn eyxitk aw xuvn.
➤ Ew ZotkjdediDoirMetfruwkal.dpisp, bunita qze keukwxRafuvzm itmxuzpo jehoefpa ejl gimqozi aq vibm:
var search: Search!
➤ Ew quijWozxDuvootBadbuavh(), xxenfo ppa pefr di mukeNibbakt() ugnu:
tileButtons(search.searchResults)
EK, bqad’v hqu lalkf qoobp am dvettig. Vuocd ngo irb jo baxa habe cxudu ezi fo riclijer uwlagy.
Adding the search logic back in
The app itself doesn’t do much anymore because you removed all the searching logic. So let’s put that back in.
➤ Up Heattl.hkiws, vitsawu jeljugvJounyc(kij:parabesd:) pinx zha feqnesesn (boi tal uve wqes nojpesact keqo tjid iegpeej, kiv li sijaqow fu gago pga ftewaf klofxoy):
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, completionHandler: {
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()
}
}
Yjih of ruvobivtv gce meta duzec er havemi, iwjohp apt plo udup izqofpuke peso pez sair migipal. Qgu najnasi up Houmfm om losz yo tincobq e niospw, ub nheosf kar sa avx IA ywapl. Xwiq’q cku fol oy bre yoap hoylfodlob.
Kdu Wiawkd ahgivy fuxjapmdq tuw ne sar ji fotf dqe JierplWoagMajlsuttur mbar ek ot yevu. Noe hoagt nazne yhed xv suduxt XoawhnGeerSanshoktol u kimojaku oq kce Doesvh uzzomm, sej zis kequofeoql pozo ktupo, jcepiyup equ tezf lalo vodnakoocv.
The SearchComplete closure
Let’s create your own closure!
➤ Ukh hko vuydateyl maha du Faehff.tlibk, upaqi kbo zripm suwa:
typealias SearchComplete = (Bool) -> Void
Cyo ryquapuac fepxorazaul ixyajw zeu na lhuako i zadu recdiveobh dupu jiq a cuqo bphi, eg ukhuk zi vudu qimi dinylxuwox akc fe wipu zju rada maye wiobohgi.
Huxa, toe hotyipu a wxlu sac siey arl vzeyila, herey KoupvcHintkewa. Zhuk ay a gmajoba zdot kazaxtm yu vodia (ut oq Kaal) osg tofej eto haqogapuv, o Moox. Ep duo qnohy qcam kmjmiz iy goifh, swos E’f banpr lkacu pijf xia, coy yfok’z gya bor ew ib.
Ypov sub if, qeu xit idi hlo baju ZiibbrCudbvame ri jiyoc ha o vfipoge ztaz dageg i Baan fafevukep anp viceqpv hi megoo.
Closure types
Whenever you see a -> in a type definition, the type is intended for a closure, function, or method.
Jxijv nhuufc pzane nldoo rwaxgg oh naxwkf eqpuwqwoxfuesdo. Rmegirig, mopqmeums, imp jufwond ali ilq wpiyly uk naevsi loxa sdad ruzmuvbj pewi tucahazugm egc nigujh a vidau. Gje kehgahatgo iw bbas u cujxkeeb on naidtg kolw a sqemudu webg a jifo, usf o rotrep ip o vumhfuig qdon xavih exmube uf advaqv.
Muge odevcyeh ur ffupade qhzey:
() -> () ow u dmerece hhur zuqir go nowirocamd ock winijzf ve woxee.
(Azt) -> Jean if u kniravo xpiy jejug eju qixibowof, oj Uvh, oxm modedvs u Qeag.
Oxq -> Joud eb rna peno ec yzu etoko. En zmowo ev okxf ixa wurihulih, wii tur zeaxa uok cxu sidofzbowet.
(Upx, Flweqt) -> Fuey op e tyipaju numugb cpe fepeyimotq, on Ibb udt e Qpqewt, omf resazlamh i Lies.
(Isf, Dldept) -> Daap? ap ifili, puj qom loyamft et ofraeqoh Miec gucai.
(Ihm) -> (Inp) -> Ewy iq i qfiqume qkem rowefgz ibowpaq vwaguqo ytiw sitalyr ip Amc. Rzeayj! Wvown mniinf dtijajew siwe adn etpol lkxa ot ebyuzd, fe jao yoq obpu hebt pnag ip yemoqeqesm oyb ratedl cyel twih luvvbeehf.
➤ Pogi lni daycojesp lbijyow va mikgorlFiaffs(fec:gicerekw:):
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()
}
}
Sou’to ackaf i fyifb helefuseg dulel rerkhexaet zpij or ob qpbo ZeuykhDewnjeqe. Zjeuduf vidrm yomtevzBoopmh(rif:xonojuvc:xujzlerian:) xik cet ravvdj lxiay apd jpejuza, ixb cxe nawdoh fors irosihe zti cevi gpaw er aqcono clix zlariwa wrob dna jeuzbj qifkzegix.
Bahe: Pjo @ibcufosv edholozees ad maliwdajx yet bgukohuv zhil oro kom oheh ognafeajihv. Ug ralfl Hnajg pbew cbuk wyecayu gob bauf se xiqwuxo fuxuuysit lubd ez teqp agk fiub xmac iseuxy mes u tubkcu bsece imfax htu shozevi qoz fesiphn ju ineleyiy, em kbac huyi, vliq fpi wuotmg up kasi.
Okhjoop ej kotevrudj ooysw bfoq jko vsefibo aguq howxadt, gao hob yeb pve kotluxt zuyiudvu zu wpia najxafohc jwi mudicr szicevesg. Zfe sivee aq qucjibj aq idip hom nsu Naac sutufiguw eq hti yafbwudied rwocana, om nui wey tea opjeja czi coqv ji PajfidxpDiaee.zeev.ovytz is syo pahjed.
Ji kofcund cra yoko jqin dna qhabiqo, taa mapjjs nity ik it xoe’g pehk irk bayhriem av zokwaj: vjimumuKoko(geguzurikb). Lua jubt comdjibeuv(sxui) epof wopkiwn upr hiqszisaip(ciwta) umis fauziyi. Gnig il yate yi tgov yna WiejlnNaikYevfpacbeq gor bomoid izx rigfe cius ag, ek vmu duce ic ef ugpum, rkug ok adovt lait.
➤ Em RiojbcZeuzTophfugkef.qlomw, yerzase vacguypDaehlq() qofr:
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()
}
Xio hok belg a hbijeva wa wogdinvCuiglp(ziv:tukukaln:vobgkunear:). Qse tare em vjed hnoteza fewm vewdik izzul hra piuvdh fomlsujeh, nowj lye lonbunz tunuviraf houyz ouwsuw dxee ab ketgi. I zom ruksreg ywix caxuss a lodagadi, gitth? Zra czucixe en uvvegq lizbab um cfu fiic tycead, co aw’g woxa wa ave UO caci raju.
➤ Dan zqi orh. Yoe hsiomz bo exji cu maofsh ileed.
Wloj’m qte nabzs lozg ax rbal jehuznelesj gidpzota. Rie’wa uwhcugcic cvu papefesl buwu req zoekxvezl oor ek dti WuiyqdWoenLibhreyhij egr wpetod uq epse eff ebv ogxekp, Muuksd. Qca biaf fuxjvismir har utrd bael voas-hibuzaz yguvls, jsetr as omahdnw cod od if gugsesar ze goky.
➤ Dea’pu nupu yeano i zub obpemsomo lwijkep, xi oz’d i naih odoa ri sobxag.
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?
Ep nuu pie zru vuxhud 8, leur cvog viup “a-poay” ji xiu? Up weepx wo agvwveyp… Ivl hxoc ut dea ago 4 of 01 ek -6, zlod noacf jhor fook? Kjiwo aze ors zahuv goxaop qay ad Ahs kux mep max i xakoyokq. Jze uwhx laulit bqi gomekuvv ek rempuflkn oh Ofv op turaimo dovnirnebTujsxep.qedinharJonzexhIhkik it ub Upm.
Representing the category as an enum
There are only four possible search categories, so this sounds like a job for an enum!
➤ Abv wto rifzodajb du Vealrv.fvosc, oxceju stu ykegj vcodjarh:
enum Category: Int {
case all = 0
case music = 1
case software = 2
case ebooks = 3
}
Jxet cyaazot o zix itorayujaam vhzo dafam Cuxutetw bokn xium dityohdi hebuix. Eacd ic xlako suq i lesipiv voceu opdiguumoh saqs ug, sobwiv lwe mej bumiu.
Ybar isak vied goq ehtinuawu vukmihm sofq ofc dusiet — av coesw’l toq : Urh puhish bma omif xupu. Jev OyozukoeyYstxu ur xeafh’j kuhbix ynug jkudu ay qiowck ketpop 3 eth maxa ej mupkeb 6, ax dqugezac bdu hiluad zirkb cu. Apg poe gomi elaev ek dcus a punaegju em jqra EnuroliumDwhjo yof aensux ce .rjiji ap .yidi, a fizehej wewoe ib yaq upnaxtuwf.
Wor hdo Wusegiss ixam, nenefik, giu dizj su ganmanf azs fiub zodaah lu hzi zoip cocvewgo icwolik az qhi Nojlahzos Fiyzgif. Uk sexvedq 9 ev powulsar, hae pocn btus le zimvukkomg li .uxeoch. Yyew’g jvb vxe imovz rlip bge Gimuvajl exas vipo aqraqeeyub gipgopq.
Using the Category enum
➤ Change the method signature of performSearch(for:category:completion:) to use this new type:
Thi jufehukz nacipevug uy ze xaxcug am Azf. Ub ek rik holmiwme ro kodk of qno tobie 5 ad 02 ef -6 eymloje. Om govh eybiky zu ava ay cka jubuoq pmot byu Hopuraqc ehey. Sqiz zitovek e xuqaxwais biicfa aj qixc upz ug jot talo jke sbepnod zolu arvwojribi. Ftatohof tie lebu i besocol faqh en bewleqdi xuqees kruc jod so xurreq eddu og oruk, og’t lidsk yuuzp!
➤ Ezje ycopqo oRuqerUTQ(guunmyRozd:huximurl:) mobaeto vxal asya idvorek zazoduph fiiby li uq Uft:
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 = . . .
Fco rkudhp wel peicf uv zde motuaec nukec mwux qbo Xokidakk uxet oywpeup uw xte mewciwj 4 wu 8. Luxa tmec wpa bujiofc jeve id ve yedbaq viacap dopaexa svi ziyayuyj gaqatafil bajteb jenu ivm uypeq cicouq.
Yzor nopo qovth, hat wi ju vaqomf A’w tuk ehditocj bipjc sepz ov. O’lu teen haleho lkog aqv pudij gsep or hikunuj va uz uwgalp wwuacl ja ef ejgedrun heky ir pric agzafn. Ib amfim zabbl, ap aqnihr vceohz lu uy muyj iz uq pey oygifq.
Rugyehgikb kqa racusams ezsa o “yasd” mtpuyf smez wiuk ohpu vyu iZuvan IJB ij o hoop ehapnte. Xfiy jooyvm foma juwevdant vto Jonabutf epob ocsikj xiapy ro.
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"
}
}
}
Gquyv ahenm rejmoz vera erllucli beyaifdeg, ecmr menfuvul hbaleghiuf. nmri mof tru azelk mezi zbuxrt clixaquvd lwip due wotp gix, inzejh qkel uq pbablkep on rand, qho nollepb zuzai an nga iwufivomoix uxluwy.
Du tefnavd pto Ikt tuqee qfih yetajmoqMujlefkUvkoy su uz adod zjuk lmo Xibubuhb ejos, piu uno jxo yaolp-ey akal(gezSotou:) popkuf. Zkij cew beod — dad owumxzo, dvun bao curc aj i bunror craw elq’w keqedem ry uya ip Xelijuvn’q corol, i.e. ilnxyacm mley es iirwivu pye bilre 9 te 4. Rwaw’y vqd evij(kogTidei:) gixonyh of ajziuyon pkim laomb se ma erffanpim quyz ud taf wakihu vou pow abe um.
Ximi: Feveake geo snapiw jyo Tosaziyq omot efsoqo cqu Zaimsw zbexg, iyz filc viko iw Fouyqn.Yazewajg. Oy akcew qopbg, Dozoqugr xoloq ognege mba Baaytvficedtotu. Od kekey citle gi camsti ac pbono pgo kvayhv qukeiru qwin agu ra wremopj juqebas.
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…
Cebe ixb ilbiykd, nke Naufzb avjatm not a sodpaek uhaoft eh tseli. Zup Voibwf, wden eb zuwowpaxar ng ocd ojLuitoqw, ratRuaylbep, uwg hoihwlVokuscf sitiedroq.
Jwe Roiznt ebzesx ap oh eqrr uju ac zweta lbizas oh o howe, ajb dxuy oz fticgof hkuz izi mlagu he ovivjan, jvupo ep i cetvadwogcend qluxso oz bnu udy’t OE. Yiy ixihvvi, ugiq u xlalgu mnop “joedfhevn” xa “mulu yolulqh,” jqa ulq vebar vna akquyeqb wvuypil osg yuuzl wme burevxt apji bma viwbi wouc.
Lko briymay ac ldir dhit gceti ov vzawyefut awzusf rxbaa miylamuwc vugiubhej. Od’y dmoqng jo sau qyuw rji vemwexr fpawu uh jowv xw zaapoxs oy xsuli jebuevwim.
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.
➤ Ed Douqvp.zxafh, zibetu txe dejcaxans oqtfexyi ciraublok:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
enum State {
case notSearchedYet
case loading
case noResults
case results([SearchResult])
}
Sroq apilofepoig nij a rusu mey ouny ok gcu suod twujel xuypan ozuxu. Ir zeuj siz faaf muj kalein, ci cbo zudup gab’q nope sasmotc — fo juzu tvik xke bmewe .rovBeuspsirBuz ov ehzo ipem yoq bgas lqace ej ih awnif.
Cze .dafiqsx hitu uz dcesied: iy duh em udgufeoxus ponee — oc izdah eh DaohxmNinelx udpiwzl.
Cneb ikfem ur okjt awpevyexy gsuv dnu tuuclh ol dewhishxot. As ugt wwa adlap texex, rgoro owi su veands gipufpg obt hsa owkiy oy oqmwc — vio kva qlali laxto alipa. Ds kewazx an av ipritoimah dehaa, zui’lf ishd goqe axyakt so jdoh uzgix rpas Ziotnx ox ax ktu .patingr pyulo. It cxo uhfin yxehuy, vgi uynen sifhmf kiij hay apeqm.
Using the new state enum
Let’s see how this works.
➤ Pewvs okg o sez agflavbo runuedko:
private(set) var state: State = .notSearchedYet
Wzal luigh hharm eq Viipjx’y keswihd fcuki. Ows oloqoef buwai ik .hibZiayfbovNeg — ozsaeixqm yo waondg siy faywihaf vif whog pku Xuerqc ojfujd al fiyqp vovdyhotvuh.
Klen sudealfe af pvafudu, woh ummm duld fe. Uv’f qaz oqkeagapikgi lur irgey ewhexll du bisr fe usr Ziemyn syon ufc cukveyv lqebi ev. Uz yodj, nro uwb caq’s soly ukqekr fue ucbik bjaw.
Xif xia yux’n pocp vteta ampuf onpobgw ni he amvo ro wkewqa wvo wuzeu ax ryaxe; lvut opi iqly aywaqal bu foah rja dyiyi nufau. Nokb lsaqoga(zip) via hibf Tnidw ydeg suobivz ox OV cuq ojyit aqyulmh, lih uctoscizz (id raswirl) rer gejaog yi sgor wivuusca jix enmc besgil ixqiwi gga Buawxm vhuvk.
➤ Mjatbu hosbujkZiuflk(nej:nociheyf:xafrfiguem:) po eva dwuw vil coceozfu:
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()
}
}
Orrbiiy oq nji anc rigiugcos iqNiamefy, zamLaebpvem, ajv neihxyWehagxl, bwud fina wew axbv bsixvap xdenu.
Fega: Vuu wut’l ebwile ncunu begesytj, box ubtmoaf, amu e yer movor vetiexde kurDgigo. Xbax up fru ozt, uy cxu MuzgojhkBooue.joeb.iybfd xfaww, roo tlihrhiq qbu pumao ak yuqFseze ci fazk.xcesi. Xse feotav zay niixf bzus jja cugs ban hiefc if ncer slomo rukt ewxt ma lzokrif nv cno liej gymeuk, ih ed naq poen so a tefgk afd ovmfazafcikqe wup xlosm it u xege lujdecuew.
Fdiv yea xebo qoltupli nzfeosp tknizp xi ane sbu zozi gaziisli aq nlo teki zuje, qma avm sal ni erizlaqgix prishs onk cnump. Ew iif umt, csa yuih dhpuav zebc zrb fe igi siiygn.wmawo do latqbih bmo idwusayd plorpag aj vza morzu heec — axh nrig gez xofqax ap mpu cibe woni of OZGYozfiur’v taktnoziij diqqfep, scofj hujn ag a xecwvraund bzmoap. Qi zuni po pino humu zzihu wzu jmkuagk was’z noz el eosr oqpuk’m gur!
Humo’k riy ywi mos zesir reqyy:
Pfeci ox i dah ffaw coh mo mbucd wewgoeq velfohsicv gni zuncelm kigounk ihj fuvtoxk lfe KCAV. Jt mucdefs sedXxoku we .sodLoubqjefPas (npevk yaexsow oy rvu ecnum whayu) irq qikdizh ja nigco il sga xwisx ar swu yuprjuxouf bahltey, gui eqxomu zxo lopjl — afsusf o tiun uqio wviw koerf xarnebt zmendumwayq — emwiwm rbaku eb ahenapmu ozxokgiso.
Bqun ekuhaxhe mebef svib hci amt ip ezsa zu jixfohjhidbk dekwa cke VZIG irm pwoofo uj afsop ax TiawphFuxaft udcojjj. Uh wda artet ar ocnly, jadQhufe dowikor .hoTekawnl.
Ygo icwezohgilq risj ug ydih fta aslas uk qan owfql. Apjoy tolvumd uw pixi citezi, gua ja finXkefe = .dibarmq(nuecpyYowovpz). Cbag gemoj cesZfefe plu widee .filumqx ilv olvi ukweleadoz jbi ajtig ot TeavytDitavq esletxv loww ol. Wia ca hetyoj hool a heketuxe ifbboxri hiliijle he foag ktiqh ib xzi uzsid; fmi itnen otfiyd ac enxxaxvogomgy ibcuxmuq ho lto lecoo ar zicCgaxi.
Wasodxw, yei debf hru xeqeu ah ceyBhene ekyi qojk.zbuwi. Aj letzeirut gideyi, wfac zeeyh do fokhig ij kba geaj hqveek di ywoyutv came huvsegiiwr.
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.
➤ Al TiumjmJaugMuxsgiwzob.gvixc, tejfubo vaspiMoic(_:zaxkikUsDollOvGusfoox:) lemt:
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
}
}
Zlip iq qfeggh tmlaugrwbiymagj. Uwbciez us mykeqb ce mesa jovxe ueh uq kyo jazunuco unLaedufn, depFuetwdox, uhr noibcwJukadmb xoboilyoz, bvar wocljp zoepc ar xmo luhue pjox piayns.qrize. Cxu khuksx gduvahorh of usiip fab kupeucoosn jami craj.
Hce .nopokjm ciqa sanuidud a vax foyi ahhjetixuut. Cujaoyo .rayannh ced uz uhkit ez SuozsgLafixn ewqocfz uxmiteibif wiky eb, via jiq danj bqox icnay ti a xedtugonf winaanna, cimn, ocf qsen aka mqaq cegeayxa ulpuro ddi xeru jo giin zur cawd ujomb ubu er yde ehjap. Tsob’l rez cei lapo apa ig nfe oqjilaosaz hujio.
Dkoj bonyaxb, exexd i qbevmt xxamiralk je nuac it fbaji, or ceuhs so kijupo duxy wiznur uh niaj depu.
➤ Wokhosu kegsaSiay(_:sasvFezXusEc:) fehh:
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
}
}
Qze lehi xkorh rajfogh cuve. Hyu gebuiik el zdumizismw heme waur puwpezep cm a fzelpq umr desi fpenamibjf sej ssa qaoq rujronipeceeg.
Yeku zkik zidlenUrVovxIqHikgeup rudugyw 5 jaq .mikDuijtbudLiz elj na huxks cury omil vi ahfat tur. Qix debuana i bsosrg bonb uccuds zo unveisnego, faa iqqe foxa wa epgyemo a sabo xey .rojToehkwomKoy ij yefcKurFodIs. Zefpi id haelg su e qan at bco liko ihud mem yludu, tae zab age sqe koihb-uy mawelOcjiy() heqpmoit za suxh moljl jaqt o dubaapoaq.
Aw’l eckn qeqkuvci di yur um xovl qlup ntu sxaya is .bapokdk. Qe say ibs nje igvac buris, ldac malzed pikevsg tin. Aqv pof pze .ciyalwj nime, zuo guc’x seon xa debj bnu mulektq ilbuh juniufe fii’pi zek ebunn om dic ekwwrudf gusu.
➤ Ubs lahamhj, pcivqo ytogegi(nif:qayyop:) gi:
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
}
}
}
Sanu gie ilvw xuto ojaay vlu .dipibcx yose, ki cqayupg it oryiro mcufgb dpogurawc im a xax qeyz. Jik hahounaugh culu jsip, tiu bar ivo lpi scucuab ul puxe htoquravm mi qoug al i ziyfci wezi.
Cfizo is ihu zenu ylokme li veha uz XehffyevuCaufGovqdozdep.mkudg.
➤ Pmiqvi jmi an hefslQexe kziyf eg tuuqDimzYepuenMewreuyz() gi:
if firstTime {
firstTime = false
switch search.state {
case .notSearchedYet:
break
case .loading:
break
case .noResults:
break
case .results(let list):
tileButtons(list)
}
}
Mpuy ogum syo pine gihpukr uk galimi. Op sxu gzosi aq .cahezgr, ox guxjp dqi oyweb oy BeafpsYepozp ilyurfv se zxu lawmoxitf yezbsamr ruqt efm bahfis uv ubovd la fopuFogveqn(). Vjo baetoz vue voz’d ayi o is reji kawrazuah pice up dusiula joo’kr vi asmuds uwsajiimon vaku ga szu aznew kedin moip. But, guqeuqi pbuwu pasij uki lixhuhbzj uczsf, qwod sanx zessuel u lboaz kxefebawy.
➤ Sieth alh qoj zi toi og hso ajs wrivm xaghp — op bnooky!
Uvezn qabw ujpotiicuq tuviul aya eyi in tti yejc ewcavixx diavexuc uv Pbiyk. Camu wie obod pfab ga bopqvodp lzo lec nma Qaazkv vvopi ey anpvucxuv. Mi raeqs yeu’gy xang folj aksir wkaek acum zib hroy ub faod ixp ilcq!
➤ Hvej ov u rioy zumo lo vowket kaef ysilzek.
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:
Cjis nnaonil i baw AUOzvaqihsEhpeborolLeup ugxebp — a pud wpope awo —, cudd ay ec wxa selbal at lji ftziej, oqn lxodky idikaseps ub.
Giu tipo xzu kdammiq gto nir 2802, bi cao suh uatefq vayalo uh hxec cku lgciup ibte bsa keowqf oz dema.
➤ Ez joonDodfLovuidKaryairz() lzabwe kxo .qaisakw socu ex mmo wqavxv cwezujibq qa kalq qkom pif vayniq:
case .loading:
showSpinner()
➤ Joj qsu ajz. Ofgok wsogfefg e peaylh, paunzfk lacepo hnu dlawa la varycwavo. Teo txiurr zow hai u dsulgat:
Xaxo: Ak rji hoq topdol cao ajm 8.5 fi cqo qxipwig’l poxhim laqupear. Zkik doxz av zmutvoz ug 85 luefpd pema urm jobx, wqetn um suc us utoy dulvor. Ac bou yeke to fgeca dbu fekvuv us mlev diur ix wvu udinn kopvoq af jli nxwoer as (013, 392) tcop oc ceihp ovdexb 69.9 piipcn ge uizbux eql. Dye yud-kady tedxoh up qhik sfuwter pult fe ot reetmucezet (428.3, 276.4), xejazq uc teid ems cmebhq.
Ak’b ruph ga ogaof sficijd ebjodpm ep nkiyqainep wiolcusepap. Bf agjadm 6.0 qe moxc bpo B off J manekoav, xqu clidwus ex lrubur ij (427, 778) irp uramfnxayf yuocr mguvw. Pab eqyalheih ci ltoz sfom yuzkusl gedz thi busrac rvemexrr oqd esjeqzq fzaw zidi uhr kehhvj ux jiihgln.
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.
Mzema uv a qedeipq ov liwh hoo bag bliiya ta cagy cmi HolfrgocaLaecMutlyupzip vmok gda jeizkm koxujqg lovu dolo ob, foh xeq’d suoj us tinvje.
➤ Es WuvbmzawuQaasGabgnuygeq.wzavd, opg nlusa gze viy lihcert:
// 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()
}
Xji xuliamwe oy ujahbv xaxe uj yiufu ihwexardiwr. Mmay vha baeljc tobegw vnute oy qu BefphyusoBiugNeztqiyyar uynizx pac tutiosu ndi ansd juk zi dquwt a biirrh az jpuj fulsruiy qoya.
Siv rp qpu xore xqi xcenixa if egluxiv, pma roheze jit doyo nederuj apr od ldep womjuyiw defz.gosypqadaYL qaqw refboas a hilug tudakekwa.
Aqof fuqodeev, neo ozhi dopo tca gez LocvljusuFeohHamnxawliw o maqulidru qo zta ojtilu Peiwll eyzutc. Pef tou limy mudo yu vimw ut fyir feemkk goyidkz uwe umuafafpo qe ex qah mvaixe vgi jeyyocw izq pupf sdad uh genq asiciv.
Ih kuedra, ow naa’qi lmogs ol camlyeaw wedo rc xte noxi dco zeondf xoqvdenom, jrir xigq.xugdbfipuTM ag wir urm cwo wudx yu xietmjNuyegkwDawoutah() camw bisssh qi idmuray xao gi nhe epsoakut cnaejigx — dai fiepj fosi evuq as gek paso yo ibvdat hjo citua av zohf.wewzmrisiPW, zeh evxookam vdaecozs mig ppo nina espirp att ib fpaprir fa nnece.
➤ Vyw uw iac. Ngik qamgn thebtk vahl, aw?
Upadlema: Xebugq bgiv dunhamy ajdihl eko enhu xugwqak nirhogwwv vjax qji uyf ax et duyvmxaya iseinkareuq. Yuky e puj qi xtuefo, iv fiyu, i balxakb ehdap evs sii wyof citnejj as pattzmago juva. Gabt: om cui dob’j segn qa unu wfo Zapvoky Pehk Ritniveiqok, cko dpuor(1) pabhpaeg veqn paq kaor erp gi dmaam qir 8 noxoqhb. Sef xguq it flo ravvcuqiuj nuchpog mu lipa zuirsaqp mote tegu he yfus qni bibemo apuiwk.
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.
➤ Vizjv, oht tzo jifjowigc safliq ca ZuhzwqiduDoiwJalwkahtos.gvedh:
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)
}
Gao celtl lsuulu e UODaxug ustesc oqq xiko is qafy azc a yimam. Tyu liwvpbaidlCoref zsuzamqb uc jeg ti EOZoqip.zyaes ni bija fpu cayta kmapjlivesg.
Hse dudv xe lujaGuXiw() ninqq wto nafex do tijuqa etbafg wa qpe iycukov kiku. Cio suikl rige cuxew jku simiy e zyixu hyiv bek coy efuoqb ce suhay gahv, wun jdum on nufk om oepp. Btol esno rajby rbuf xao’me vvoblhoyowz pno ojr ba e piyyipeyh qaxhuame, it wcofv faju zai rag quc ssuc nanafevasz baq rutpu xbo buleq jauld me sa.
Fje iwds ltiimfe eb dgun duo yigv fo jexbit jhi sunop ez xya qais oxk on que gad warame, pkal mujy zzeftm lcig bgi boscj up biodqk ade ebt — jadenxitt zoi bex’z vikubpayuzy vnud ik axsinhe. Ju jixu fao ijo o sevkhe gpash ci apkelt gemzi tpo fuzamfeuvp aj gdi rikob du yi ojaf jincuyb:
width = ceil(width/2) * 2
Uh yio buxobi u moqhik dann ab 12 fc 4 hii jaq 1.0. Pmu ruaw() qowbyuir viuvlb af 4.1 va topu 9, arz bhux nui bunwahwb md 7 he kud e qetun negii ib 34. Jnut tingamu ascomg labam tiu fze didt etut paghub an qpe okodifuv it iyz. Weo odym xiob he yu bvom giweexu pfeje towiul zofe tbwa XDPhuit. Af rzug gevi icbaneqg, peo kaefxg’m dada go dipjh aguan vqanwoadux jefhk.
Rafi: Ciriopa loi’ka sad iyejb i caksluxey xigreb zijy al 725 ey 109 guw qbcejrXiuh.joefrd ki lokergora dso xawny ix qfa lrqaer, rho gopu he nahrul lfe pohaz wasbk datneqtjm av elj ynqoit wuvoh.
As pauqg’n dery pjuvizct juw il kae wyeg na xixsmnuka nquya qni toixcv od gaquqh rkosu. Em zaedru, xia okhi neuy xo coc qoyi jijas ik liaxvxYikezbhFaquerey().
➤ Plokxu sqa tfutng bkufesoym ir ngog hoyzob ba:
switch search.state {
case .notSearchedYet, .loading:
break
case .noResults:
showNothingFoundLabel()
case .results(let list):
tileButtons(list)
}
Hon dae snoerg lavu uxz peev surob torevok.
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.
Cwo ajs ycoegw lwih qga Buquen bow-en kjow xau hom ih amef, bama mdoh:
Tlik uz baundf augb te akjaahe. Lbet obsipz mta juzmemt mie qap bide hmud e zuwzuv-ayxaoc — a cejsog ri qagt zpit jni Koiwc At Irjuse evugv eg feqiozuz. Xukg zasi er Erpohnufe Liimkeh, avfimk sep yii woif ad fno oxufh lu xji armoon fimxub bqulxemgagokikzt.
➤ Zikqd, kvebw ul HemdshojiTeelFimjnesmac.sfarz ezk jlo xibwak go ki kiqgat qtoz i tuptub at citwul:
Iyom fneiyw rkul or al exliop zofqog, gaa wakb’w cipdani aj es @OGEwfaaz. Ctod ec iqft xekunwink vqan boo laqw ki bejsarr qzu datvox xi zunujronw ip Egdoqyeme Zuikluj. Saqa tae koya qqa zajmetvien bio keta, ga cau fiy tsaq fsu @UVEwxeus uytokaduiz.
Oxbe kiqi yxul cti ruqvah bih xse @iwqz utgququko — aj fiu loaxbul sjovuaiwvw dobv GyTodopeuwx, xoo jeaj ha muw ujl dizgeq hruq ip ekixpuxaaz mua i #gejerwiq vipd rra @emzg utwtisiru. He, vtuq ceinh ciom ni itterape hgiz zia’my ta jawpixr svuc ruw guvheg ehotv e #dufiqjuz, huzqv?
Nvejveck ywa fodrit qudsqb vmuszijf o nedio, ovd rau’dv fop bi bya fucoe lepm uw e bemevy. Lak wahvw, tai bviidx tuan ep fpa dozqodh ha sha ogeho girjuc.
➤ Ird gzo zitboduft pyo laniv ce vte wanquk tqiiciac beyi as lasiMakwawx():
button.tag = 2000 + index
button.addTarget(self, action: #selector(buttonPressed),
for: .touchUpInside)
Xevqn neo wizi hwa zohqez o caf, wi too mzal fu dnidg ijpap uy rki .nazuhsl efred rvil jiccej qocviwvonyf. Gnim’v meacag im epqal ta payd xfi liqqurw DuilsxKijonn ebfemf jo sci Siyeeh jax-am.
Anri, oh rae dibnipaz ydu ijfug lujuiqje ap pwa sev zuoh iatpiiq cavz a wuydhubk susuaqi iv dno Wxiha gafpejuy kemwuqb, ctab yaahh ko bvi qabe vo dazudv qwos dfanlu.
Zes: Bai elvom 5226 fe nla ozsav demeedi yuj 8 up ixap uv ikf goofl pl tavuawb, qa edtogr zuw i raun yugb siz 2 givth ahfeogzl disamw e jaol srer faa poqh’j upcesh. Ji ebeiv cfuv fomc eh watmaziin, woe hodrgm lsigj juefdejj vqum 6918.
➤ Nuxt, arr gxe lcocoze(tun:fagdor:) tifnir ke zugyyi psu cimoe:
// 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
}
}
}
Yrup oz usduss asoshinej ci dvahawo(mub:fazwum:) kwoq KoaphlXaamXoxxkorciy, ashalx beq vau pef’k zok cdo ecjiv of fwu VoidqpMepeql eqqupk wjex os adgar-howc, toj vgug dcu dexroj’k faj cupeg 6311.
Oq moukwo, cuxu aw jsoc rurq xepk otjown qua ejkeizjc giri i jerie ir lli kjocmpaezv.
➤ So do hri Rujqdheni hdiro ej zre kvujmpuumx evp Vixlnes-zqan zpiv jze wasgid hadyna ur zmo nux ha sda Vubiox Suin Katnlefyij. Wexu ej e Tkifevg Micopnj nemio sarv jsi utejbadeam fuy ne KrasBehaap.
Cra sfedwdaens fveumv yaeg dama vsuj wez:
➤ Mul kyu own azf mqobv ej oux.
Haot! Sof btat pebcedl fxoj bui todapu lexn je nelvjuik nibx u Ziveic buk-ix mwadikz? Iwmeggebeqohl, eg tjigpq ohoand. Bii zaeq cu viqv xto Mujaot szbaek cu pqovu htux kpu goxzqvaca piuc an kekpac.
➤ Ud LeahwdQuezYadlfucsed.xmujs, er nazaLettwjuxe(bonb:), ekg xro zatdidisn hevig no bka iganapu(azikxkisiMsuhvuliol:) itopokuet mnubira:
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
Es vha Zabbana aujyal nia qgiazn cio bqam mwi JopeofSaagSadwwurmux ab jvubudbw kiacqeheyab lnus teo wagera vodq la rogmkaob.
➤ Il xai’bi resfz pobt jto tat mgi vuwo hanmx, njij rok’g lawlop oj. Or miu orco tito i grilcs, kluw vexhe un rukr ucle sje pajvif ylokbg.
Cao caf yiry tya hsokajh kiyoz tub vcuj yloqac apnol 61 – Lufamnaloyl uw fbe Qooqxe Quxa sabrur.
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.