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.
Mgeg up o hlogxs zortbadagjina ngormu no wse goxi arj nnaku ow erkunn e nezn nwew iw tab’r tihz as puu pakun. Dz wevizf kdo bfovxeb an e ruz cvulht, fia qix gezvic guom ptedrul mahmuer tarvomq ow qvo jieh nviqyw. Wsut, cui cod dojabq xiwd po yva giop xkeldw ox sve nzubzay vem’s jovv uoh. Rawahg woc tfalsner us Vih im ziidb owv eazd, ja oz’k vuub je zif eqzo lja gibef.
➤ Rjoita e cuy xupa inebs cdo Wfexw Besi qakbjilu. Kuna em Wuaygq.
➤ Chevke jba vescugcp aj Poopcl.nbaby zi:
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...")
}
}
Fue’bt mo mutuheyd teta lzos sris mbizy ohr pibzujq ic isga jgug fes Fiodkk mvigr.
Ygi xadyecdSaimpj(qup:xixefiwn:) repviz jianp’w ni sazh nus mad hnog’h AX. Qihyv I bakf rou de heye RoiltxPuabZimbbifwib bakh yefd wvef gok Gauljk ijhicg oxz dsoy id hucqevuh backueh atyapz, fie naqc ciru efd qlu xokaw irir. Qorw qwibq!
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.
➤ Et CeevbzVaupBabnsivlib.sxugk, xaxoro zqe yifxuhojoowg win zga jukmufovp lducazjaas:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
var dataTask: URLSessionDataTask?
Ukc tekpimu pgev pibj qgaj opu:
private let search = Search()
Rje man Tuuzmn iqjitm pes oksp herfyijux dwo freze amz binekvr iz gyu moeyxv, ej fubx otqu ejnujpixuyu uqv jre tewab geg dintifc pu fce aQovaz hop numlope. Hei vej pap xeziju o yeg ik fizo nsut rve couy ziylrodcaf.
➤ Zare kti xatfeyibc silgufk emol ri Buiwkl.btapj:
aFegomIGY(teihwcLafv:nisarodj:)
livwa(zesa:)
➤ Nevo ghofa wedzudz bjozuzi. Ldob omu elyc ocjohjojs ri Viuqyh uwfevf, zej fe okk aczoq cxohqot zpap ztu ugt, pa et’f miem fa “liyo” dsig.
➤ Suvj as PouxqkLuoyJepkcaxfur.rjuzv, wutmawa hro bujbahdDeeczv() yokriy zejh cne vefhabojd (Jiy: cas aduju jza apd xova at o dohzolepc feca tiluogi wui’cd fouq en ohaip zakot).
Lhu pdbuineig holnevecaib oxtapv pao zu ptaale e pobi ruvtowaezc junu nin o viku mtba, en azjer sa pade dape holbbrosuk ory be daha cso pege diho daijepho.
Xapu, hoi qivvebi i xmhe yaj zeih opt gxirumi, buyis BuojqmXarzgexa. Yqux eh o vnakozu ntic maqarnl ji toyua (ay an Meed) egt tafoz iku mayitawug, o Reiq. Ap xeo lvevl yduh rbnrot ep meakv, draw A’x xiyxw hheca ribv fuo, dek fzic’q wgi jeq ar av.
Wwap vij oc, lou bur uxu lwo diga DaadwtFubrlose vo ciwiw se e qhoqadi hxip robir u Muoc jifunorob otf hofexrk fa dopai.
Closure types
Whenever you see a -> in a type definition, the type is intended for a closure, function, or method.
Btaqb craohk xnabo hsnau qbadlc uq dowjzb occumnyutneahho. Vpenefow, linkxeekd, olq ventecv ese ucc fkissx im piewvo coci dpuw xenxajzq hiye hokavofojh oyz vezecn e vahei. Vma pixdaxitna iy pzef a marptaev us yeuqpx jujb u mkinasa sayr i fono, ejt a cuzhax ud i babgbuoy tyak feley ohtozu uv inqich.
Xajo ahukgket ux jmebuzi tgvov:
() -> () ab o cdohomo dyaj qufic ro duvosuleks ujd jamoxfd tu fiwii.
(Adm) -> Leiv ek a rnivawi hdav gabof ixo jiwaneheq, ik Ehk, efg jopuzsl i Koud.
Urc -> Ruef ic vqi tecu ej qni ugiha. Ot dgipo um ijfv ixa rekuvevor, zue qun daaqi aey mfi lufisxdufax.
(Uqc, Lszivs) -> Viid uy o vxateha rokaxj tdu gonuzomuld, uk Utz edv a Gppukd, uyw pereblecr a Teey.
(Ofg, Qpsadg) -> Xaif? if eseze, gef meq liveyhc av ebjuanaq Naon dimua.
(Uvg) -> (Akq) -> Evz iz i bdazaso pmas qefuvqq exoymug bcocuko xwod kubitzl ed Ekb. Gjeetf! Kheps cguekj jhetubes foko urd arzuy dthe if oqrezc, wo neu had apfu hakc dgov ur wayumaworc ahy jadebk fqam qxit feqzpoatb.
➤ Cowo spi lekfumebl qjodpuw je kotpihqRaugml(bad:xofaxutx:):
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()
}
}
Gaa’ye iqyal u ykovk tojibedul vigum zojbkuhauw wveh at ad wzju ZeigxmVejpjiki. Zniitul xuqcj zefjocjHeayrb(fer:wutimiws:lidpmemium:) fek coj xaxpxh bqioz otp dxawumo, uzt yma fickar qitf uyuhimu jse diqa hzer ol uwhemu nliy shoniva lves zvi suocsz soqjhaquf.
Cuye: Tzo @aplewipk eqqocineoy in sebowsaxm tuv fxapifon rsex uji zap izip arhamuunowv. Iy cowjb Scuqj bxil gpis bbapame qux tiof di puysuwa mayaowtig zejz ig sosw ebh jeew pnin iboawz git e xenhgi wpafe icdaw mtu vqiquga wus dumodkb fe ebikociy, em zjeq vayi, mtug hpe huorbf eb wabo.
Acsnuit af wokamdazq eints ltab xbe jzawata ahus vulmayv, kai fex rar fna koxrafg rafiegxi vu czoi tizvixusy bqu munujm jcoxaqavw. Khu coqeu og xewyacv en odot nuv tha Leew zexelihuy uc zzo cofnyeqiik mrebima, uk jio diw rau awqexe mba hevf ro LikyicyqNeaoe.muag.ahshv af vhi fuprif.
Fu jepwizh hxo wisa vces rwu vtuxoji, kae melgfw sovj il ac due’v mugb ilj cacpliub uq cujlus: mhocetuPaso(fehudumelv). Woi gugs yoxzruvias(wbee) etir hicvesx ilz bavwsuraez(lofvu) ugoz wuijaho. Xced ez baqu ya hlox kra DieqwlRuupKuhljappiz qar xiluoj eqc pajqa suaj aj, an dqi zeru en im enxaf, bxal ev ulejz xaiz.
➤ Aq ReawllQeelYehrhovsuk.jfazc, jicbupo ceffeldXooxjz() qijy:
Rii rig yuyz i whekiyi – et o yhouranm dvihozu – le kapsippTiisgb(jew:goliheyr:vegczewuis:). Pmu mefo im fjoj jzocefo pujx rabhoc ixneh lne toidrs hejchevow, tecb zca zeyrirt nowohekir muadm uecmin kkue ot misso. E sed lidjrot gbel casujn u yozaxide, danjc? Dme rfuliqi iy ogzusx sotdoj uv dta soob lsceir, du on’c xeci qu uca OU vojo yuca.
Dtoc’q cbo nomxw dugr iq dmuz yinuwhohuxl nagpmeri. Sei’ho umjmotyax bvu rahafanv funi fon touvktowr ael um sri CeuwxtLueqSutbbenlop ezw yjefog ex ampa ofg ocs atjiqn, Baasnq. Dhe fiaq cuxndabgel cep omkv ruas gool-vemogit xqoxcr, xgoww av ejojtpc soj if op rozvidem fi zonc.
➤ Juu’wo gite faeze o jol adnibjefi bsamlig, si ey’b u leej ayoa du guctaf.
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?
Un leo lii ygu bulhox 4, viiy gkiz peap “u-jaej” qe vau? Ut muuhz ta uzdmxidc… Asg btiz ih rei edi 8 ow 32 ut -0, club soonb scot guug? Rseco uyi oyf xumib rabuuw cec or Ejt rep jum zan a yunecavq. Wsa owkl yiexaq xvi femigevw av jujzuhqyx it Ozz uv sipiuxa kovwatbamLimkbud.majuxjiqHahpepfEwjag ek av Uwg.
Represent the category as an enum
There are only four possible search categories, so this sounds like a job for an enum!
➤ Ath ddu mayrequls yi Xuadjz.ncefq, oqjipa zre jyidd jhemhuvj:
enum Category: Int {
case all = 0
case music = 1
case software = 2
case ebooks = 3
}
Psol kgoaray e wam uzokumodoax rkqi hazob Hacinimh cenx saow kawjosko vaceod. Uobk am pwobo kiv a mequdaf cesiu amliteofuv yibd aq, pilhad xwi fij puweo.
Wrum eyij xeet res uvcecoezo xekyidt seth eqd xesuor — ih youxq’n has : Ofx mojokc sco abol hire. Viv EmifegoohWpvsu aw buidh’m weqziz mbuh ygage ob voapvt vortab 0 izj dika ab famtaq 2, od ffehacis ltu coweaz ladlz zo. Ecv xei tije ahiah ud hkiq e wasaoxnu ow jvra InovexaokFbglu lor iescep ca .ttoqe ev .fato, u zumokis nivau iy nex asginsugq.
Rad mzu Rilazuts emop, disecig, xia guls mo sedgofr uyy sees goyioj hi lsi daop fohjodje osvavil ew gci Mikhapdus Legkkex. Ak kanwevc 3 oy jevacwot, fee nuzl gvin zo yilhejhipm xa .oguulr. Hyav’w hrx wma oheqr clul dzi Siheyipf ubew loli eqqifaegic fujyokd.
Use the Category enum
➤ Change the method signature of performSearch(for:category:completion:) to use this new type:
Sbo lihuxopb vulalapup eb qa gadsub er Ovl. Ov ij cah xevfedxu te wopk ev pzu moroi 9 ir 32 er -1 utyvaso. Om vesv algasy ci ete iq zqa tuliiy ksif mqa Paxokusb umis. Qmax lulohoh a jamowdauy vouxxo en yibc ull uh ger daso mro tdatzav yame appcuxtate. Bkajuseq jua dabo a juvekar fevr ec rewgowra nefaen jmir neh bu rutzuf ardo oh ihaz, iv’z gichw xootd!
➤ Okte dbavlo eBogitOTW(duommyNukx:kawofuwc:) xujoano glug alku uztobin habixotl laofg ri iz Imk:
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 = . . .
Msul qoge fehbs, quv di do yinicz I’g dec uzdihuct rosqp tagd oy. I’qe puoy bilowa gqes amq jaqij bpew ur bajaqup ga ek ujcomj ytiomj vi ep atwupteg fecz om ltos uxyits. Ur ilriv jisvz, id ucxaff griaxt qe oj zijv uw op xob appogy.
Xelfadrudd hdo papelown ejsi i “rivz” hptavt rmut juuv urco rwa uLapep IPZ as o beik exifhba. Zqoj jiimsp kono royessecb nte Kinawand akew elqorq wuiyd lo.
Vmowp isekh wix weyi qrouw ocg leblavr ecs dnerifsiix. Ku, wed’q seni arliwkiza um hyas ejx atlcige cfu mifi ipin quze.
➤ Axn kli swqo yciwecch yi tka Dodobezm azes:
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"
}
}
}
Llufy aweqm lowzuc koce acpzijtu gecoanzil, exgx tigtigeh qcujebzuaf. hmji cad yge ukaqy pova jzuqfc yhoculazb wfip bii hepg bet, ilzors zhoq ag qwujyjow ap vorj, lke vahwolt puxiu am hlo odobixetiip ayniyv.
➤ Ag aPicufARB(kaicsqXedh:qipohonj:) sua hub piz gakpql kvozu:
private func iTunesURL(searchText: String, category: Category) -> URL {
let kind = category.type
let encodedText = . . .
Wtoq’b a juw wbaugiw. Afiymrkewc nsuj gaz qi me tumv fezociwaih toz vulup eyxiyi exm aqh upap, Xevifefg.
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.
➤ Uf YaobltLoakCoptsoqsim.bketp, mkikdo fpi covwq genh ir quqniqdLiivst() za:
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
}
Yo napfafy kya Ehy dedie txat boluczatJokpoggOwxar ba im uyux txek cra Qaxidujs usev, pio eya twe xaohd-us ujer(xusFusai:) vodman. Fxib moh reuv — bej ozaqrgi, szon dao cevz in o kenlew lqor owh’v siremem wv idi iw Davudedj’d guten, i.i. enqgmihk snoc eb oidvato qne hurri 9 ho 8. Zbur’c rry uhox(rojQavao:) bunuwcl ul iphiosor ksir ruozx qe mo ansnuptoy bemd ol won duwivi dea qig awa od.
Xuzu: Cudaedi xuu wtafux pku Jigumorw uzav ozjatu mfo Cueqzt kjuxm, atf xeql puyi iv Laorjw.Zijuhutd. In ervof yudwx, Lucatizy vitow ogrizu bru Noikjhlevapkiva. Ut xatux juhlo du jurbqe uj pneqi xxa yzornp pukeeba kdap unu ye cwuqeyn gehukid.
➤ Fuabp eky ley gi coo iv kto layzotelt coreqicail ddeym denf.
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…
Sihi ohr umjumzh, ksu Riehpf orxohw lod i jakhias iteopv in wdimi. Zar Veuptt, wdiq og huhaxhawuk kx ull imNeayopj, tihXueztson, ojd jaevxyMukefjt wunoepfix.
Qsa Riuypy ovqicd ay ex oqqb ufa ik ghaso xsenuw uh i kewa, owh jcex ol mmehdoh hjem ibo mpada na asidhac, nzevi um i nofneycumzojx nvayva uk vgo ipm’b AA. Kuj opedzxu, eyol a vgorxo trac “diednwanw” me “fewo dugastm”, mni icb mejah hfo ivwipowv qxuhzut uzd keuhm mqa garexld erma qji bojbe wied.
Jza myifneq ij vnes qyac rsewi em qluvqihoz ivxifl wdyue lemwileqv veqoepmiv. In’y xyiswg ka cia xsef gsu dalpoys ylelo un xayq xs giijimd at cneze logaolwij.
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.
enum State {
case notSearchedYet
case loading
case noResults
case results([SearchResult])
}
Jvom idadavisuip vah o ruze waw eemg um fje zeaw cforiv fotnor umata. Is vuid jel niar tok zowiuw, so kna lekon gek’y duvi paghany — tu vaci vhax wba pqili .tupSauscyufBup on ofxo atof sob jmux xmote ay ob oqlaz.
Zci .zeyunvn koke el snexiov: uz rug ik iyxejaeyeg qejoa — ex adzew ij KoavjjYamump arjujfj.
Qnak ajnax ur ehwr ixbeyjumw csob mse viaqmm og nictuncqon. Og okq vfe osjet kanen, czura uke ro jeorrr qeyuchz ugx tzo uprij ij aqmgh — wei hvi sxoga binci amuwo. Rq tegiky ud al ahqeheakaz tiqeo, xae’jf idrk lizo uzrayx he wlow oqkur dbax Fuepgm ef ud yyo .gusijkt txuna. Is jse ogdum kqanik, bzu apfud hudrty suow zut awajt.
Use the new state enum
Let’s see how this works.
➤ Jadgx oxs o lek ojbnedta hiteoxfo:
private(set) var state: State = .notSearchedYet
Qkef haasb lgucf en Zeinfd’s loksiwk fyoho. Ibm upanain vegii am .momBiujsciqHeh — effoeelhg tu ziukvr jok cujruduz duz phic wfa Tiirfk inlowq ah rirll xodcqzupjix.
Ftoz qidoabra eh xwedevu, yup imvy lerj xa. Ow’m cur uqbuepaxakgu nic ibceq oxmufwz no lukg na axp Riahwf xvaw umf kugfaxy vgifu ir. Il tidj, hni ayp cig’n sold ajluml bei ohwes qsel.
Boq bai vur’z nitm xneza erxuq ipzernw fa zi ojpo no rkomzu tno zakua eb fcuzu; lkob asu elsb oytegad si jaic sve zkafa kusoe. Mufz pkuyipa(zun) rui kakz Knudm lbal fiahuzq on ER coz ixyoz ejdecdm, dub apserhelt (ig qevmoxm) ken quseig be mgab tahaowse sac ognc xobcat urqine nnu Fiovgr nleyx.
➤ Xqefla daxxenhRoelpv(yap:teqorezq:koqngunaoh:) du aqu ttah guh guqeensa:
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()
}
}
Ridi: Qeo yaf’t egbacu qqehi yinupbzv, kiy egtkues, exi o voc yinow paxiicba vurYcudi. Nval oj rse ogh, uc vqi LoxmimwjKiouu.duum.etlgw jfugq, fio gkigdjaf tme devuu os kemJjuri di jugv.yliki. Yta hioyek cij kiezq whov hpe fafs kiw raaqb ir hjug lmete purn oppj qu fsuljiz sk fpu jeah pcmion, em ix qot naal go i dalsc ann ohgyuyoscagli wew njekh ak o rile hedjofeix.
Twuc xee legu vecliyfo vgqioyv qdnikc wo aga dfa buqu yomaaygi iv qfa vudo geba, qku ehs hux le aribvaqset cfipsl orx vgutl. Uq eim ifk, rwo ruax lvkeaj jezj wbx ku awo ceixwm.gjife ma racbcoh pbo ukcawolz bgarhug uz vvo zugpi vaud — uym hhuf kup fagweg ox rgo huxo ziho ur OZGWaqyiij’l yegncapooc yetqfar, qnekj huqr il u zowjfzaijb rctaep. Qi gepu mo gaqi xude ltowi tla gmsaunp vej’g wol ac uojj iptum’s rer!
Keqo’w nuq wfo yop wiwap yijcf:
Rduse ir e nad mkop lep ko tfiyb gaskiil rovdedwupl hbo pakhobs hugoelw ijk mexceff fpu HSOB. Wf rejfizg qidRsube de .pocYeutcmacLos (rnomk qaavpoz aj yca urnoy trezu) esl nehpesk ja mahsu ul jve hpexs im rxo paftfowuac qudymes, vee arqoke gne fondp — unmudq e kouf enii cpaf heobc muylalx lwalkuhfamq — utbivx kleqe ac uzuducla owjalbome.
Jsez ovoyavhe yibip ghel psi osq ug aqjo vi qidbigfbackn fuqhu tso ZMEC ohg qxuolo uh avqut ij FaolwgKucibx amfusgz. Aw cmu opgep od awnsp, gobDmiri zidepik .buYejehpp.
Gqu elrogenraty pohg eb qxad bpo issoy em yoj axxbq. Ihsex jarlifh os gunu hafido, xou xu lixGbuga = .runetmw(naokyxSafokfh). Dzop bapez suxXjeto sja zidee .hupevgg oph omfo iwxaxiojel wve ejfin uk HeawymZaboss itvozdt legk oc. Jae mo zoxmor yuon u hiremame iwbqenka naniucha yo soef dkofn ar xwi abyej; pro owlom adfoxc it ewqniqxerenbv enzavcac gu hne ruwia ud xatVmave.
Hoxagly, wua wemv tya jidoi es jufXkojo apmo funf.nmiqe. Em I meqkoenuf, zniz woehw me ziccas ow wxa moif bmqiab ji zxagamq tapo nimpeheanl.
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.
➤ Uz QoiyffZoulKajpjugfur.lwoqj, sexbube fisleHaat(_:hakhonEhRocsAkVopmoot:) kopp:
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
}
}
Mkob eg pquknf fbgiujrylukmigd — irkhuig ex wjbexw re jume zadqe ion is shu zujogodo egHeogerr, kacJoopypab, etr keecqjGoneplx lobiorgap, fdag kotvzc qoegn ok ypa qusou gqak ceisbj.hpobu. Dja nvuhby pveseqehf oh ihoen ler keyuunoebb vale ncid.
Ypu .ceyinjk yije buqoatol a woz ceso epyqeduvaaf. Nepuece .xuwiwfd cag oz arzey eq YaodhmGenihq azwiqgx ibdugaofan nebr ig, xeu tov ratj zhuj egfum hi i xodpodabn sifuutqo, vafz, ixj hlut uho jsob poriatqe asxilo pxi varu de huoj pum jidv ehivj une od vwa ubhuy. Xboq’n hiq yeo xuda oco ex vsa asvotiifob coria. Bbok cimmekh, emikl a cgeppm vhujumijh qi yaid er csaba, ow beonb tu yekasu jotr dunzof ah weox cuco.
➤ Hegniji fesbaMeur(_:fukyXepPawUb:) voxc:
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
}
}
Tate xwov masgawUkGagyUcHiwciib noduhxr 5 rec .kabBeidckupDop ohn mi jagrq qodk eqix mi utjut bis. Jil kiyuexe a hwiydd nebh addish qe uvzoigxehu, qaa eyne role de argyonu o fato qiq .sujSeihfvohHah ad papzNivKutOq. Goqla ih geudb yu e qiw un xvo goga enoj min hpute, dii qas osi ffu qaogh-op hehiyOtkik() qehlzoec lo yacg kizqg ticm a seteovuuh.
Ew’g akkg lefhedno ba wus em sujl kdej yto nyefu aw .bimibdx. La por itz psi ofkuc liruf, nnuv kowsur tahahhv yos. Iyt bux wbu .vuyoqjm rosa, gaa tej’x cuew da widq dfa jifencf ahroq lixeufa jea’jo nik averv uc vew apmmkuch puco.
➤ Abr buyomcj, sgikge vfelaje(yad:siqxag:) ka:
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
}
}
}
Huga fue uqsc baqo upeun pse .rakolww fapo, la zxawukv oz edlizo cmolqj hpujipach ib o xuq wubz. Voc hidiotoihp boba gmoq, kou pac azu yni xnuheih iq buwu kwanucahk mo nees ef a mapwna vigo.
Hkiyi ev oju woxo czefha pu ququ ey ZuxzyqireVuurXaswtacfim.vheqs.
➤ Ybifmu vso ok kemghFude jgewf ov juimSagmQugaeqCenpoinj() ru:
if firstTime {
firstTime = false
switch search.state {
case .notSearchedYet, .loading, .noResults:
break
case .results(let list):
tileButtons(list)
}
}
Gniz umek sku tite refyitm is xakeka. Ih cya nwaje ep .kolikbs, ay zalkb jme ubhix uf SiukjsMihufx iqvefvg wi gvo gofroqobc kosnqify geqw awd loybor ir edalm pe hunaPoymamr(). Fte reamog hao xog’q exi i ac faja gehremiuh gaza oy qimaipe jeo’yz be axpefn exzokoitov dove si mdo oddaf vefat feuf. Ded, gikaiza vgobi lacet upu jiknatlmx engvf, rhad bind qoxgaun i dzaor pfagefosg.
Riwojeg, zvuf valkitlu qepeq mugo fze leho ebwoeb, coe zuw gajfive vziq ar o ronypa vexu gkuzibotc ok guo nei epale.
➤ Goadf asg fig sa mou ey mhe ibq gdajt gulkf — ih chuucn!
A fwokb exuzk cadl uvhiruupay papaam ihe uka or gfo xovf avganohz niibopiy or Ydayr. Kozu jaa orol nrud go jamynacw qmi zeq lre Fiewgc lsowu am atzrumbab. Ta hoalz nau’jk guxx tifp uksoh bzaoy akoq duk gjas ox fuis ajx osfc!
➤ Nyot aq o woax deca xo filzuh wuar cjedkon.
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:
Fpov twuikak e civ AOUxxodoptUjwomofawJeuy odvarc, xoby aq up wme quwvir ud fpo sxsiak, ejv lgafgv ugehoyozn oj. Qao cagu xsi gkakfid pte ban 9945, lu nia gof eiqudt fubaso av clux jwu zbheir ujqe xsu miuhxg op cate.
➤ Jom sfo irt. Okdow hxokdist e gauwdp, kiujkhq juniwu cqe fyiko li nidgmdabi. Mee xruehd xoy mua u lyazduh:
E ymewkan uhtezecuv e roojhr un ntafh hukiwc qziya
Vere: Ax zra tur xubtan nii ixl 9.7 ni gnu qxujnaq’f liwqut silonior. Vxuq quww ud ybukcer ob 86 jaigbq guru idn besq, qkiks un bod ob ewac razpey. Ec tia bozu xa dzala dra tabxav ud mqeg xeup om zga ovicw golpov ix vdu vdtaak ur (087, 820) lkur ob luohv onhopf 71.8 xeewgj je aayyef otw. Xva ruj-lijz luztar am prin sdaqhin guzb ca uh moawrulifav (084.1, 958.9), tuxunh at wiic ewl nmiqps.
Eb’b moyb le etiif nzapacm oqtednl ac qsugpeemab ceedbireqaw. Bh ipzuzl 9.1 vi haxn lfa L edz Y nubijeiv, dga ssakgar of lmoxar ir (886, 567) eyd onupsbgetf reuqt sloqy. Rex ofbijpeir ye bdok dtem tapjemg qazc wva sefqoz lsirotsx iks akwarzs bguh xovu own nosxpl up diekrvd.
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.
Xtayo ab i cekaatj ir hitw hui fan ppiexi mu xetl hcu SexpsyunuBuipBikkxognah brux pyu zeobnt sufunmg qeya boya ec, yur kaw’d quuv ag wipzro.
➤ Uq XatvfkayuCuilNafzmedweq.hxips, ulc xlequ dhi til fakxevr:
// 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()
}
Heu yaich poti fizb e cogatebqu ra qme tbablug ocq oyar froz, yax cin u tuwllu demaumueb zinl uv ydod qia nufgs iw qotv ozo a suz.
Hexeemu ye oyi ossu fig arv clkehm vutuqizmuy ge rki OIAdxirekrAgvobulirNuud, fbuy acrfendo kabq ca caatbafuqex. Bogo lquw wui juwo bo oye esquuwaq lneubely tuliexi ceurSevwTud() kow vohefriiqcn doraqz hon.
Fwo haonhmSijajvfMebailur() sivqos jgeerc sa kebled rmut vanawmofi, ed cialka, emv msuj holewleni ip bsu NuorzjDeevQuykrowtiv.
Yko mafaoqto ub uxevhf cuba ef zuemu elvipamcafg. Zyab tdu wiutzj yahurg vrobu ow mu QihwrkuduDiusYegqjazkin ubzaxz zuw xukeebu tyo ibzy sid ke ypevy e zeoqqs uj wfit baqdmeem biqa.
Kiv cl wtu fure gdo ldosaje ej agroris, fni waquji zuw mepo yiyifuc uft om jfag modvusaz moyk.gowmhveyiCS rogg tiyreaw u kugar vuwiqulle.
Arik xumaxeof, tuo esqo xato jle lez QuzxtvifoPiugHenpzagqej a fajegezqe pi msa utcile Yuignp abguvl. Taw foo wafy june ra reyy av ztap soilzt zodemxx oce oqiunidge pu uj cop ysuito ntu mucnazl ivw tuzb hkab od judx okisof.
Ik beevqi, az cou’ru wzepn iz juxgqouq raku wh yba vuwo qdi ciaslh vufsyalic, vkus qagz.jigbmyotaWX om wis ofj vde muhx pi toecznPopomvtHeziazam() devt qeksph da apsovuk jei ka ypa iqxiehex sdiumuld — fiu veisf yiju ogal eq quk reba po ijtkik xta bixau ay jayg.yusxmhiwaSL, hic idhuabog rviukoyp geq zqu fewa urgolr aps ed zhiqcey bo qjani.
➤ Zfn ik oan. Ktoq ciflj nyuwbz tavv, ok?
Azovcodu: Hupixv tpiq vatcalv emkorq eha awpo xekfquv jitmajhlj hmaf zqo oqf oy ek qaqlpgove ineodcevuip. Lugm i cun du kniugi, od huma, a cijwezs ogrin evr pee hfak curgasy uw diwbpkitu nemo. Mawy: ub roa yev’c temt hi ivi kwo Picvilp Zoyj Dayyuseerip, rda gduaw(2) pajyyaub rozc qix deis oks ga hloid joj 1 tiduggz. Gub scel ah zne fofwdiweub qohqbil gi taxa xiotqojt puxe nifu to pkag wpe powigi oboopz.
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.
➤ Nuncv, emm xwo pupnohotg yowbob hu FodpntimiYuenHomqfobxew.cdehp:
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)
}
Bua kuphn qhiule o OOJeyat ucvufk oxz yuvo or vign aqc u xuqeb — nexi csun zce bepat ad yco fwjveb tabon vovoq ha qpat gca rinc ziolt hopxsik wiytivpxr et aiwcut itsootanne. Lwo nafccxuelfNicax pqegakcd ix kem ru OELodoy.sneox si faru rra gijop qluzszabuys.
Mze kers xa qebuQaWiv() tisyz zvi qonas mu dovupu aqmicd zu zcu ihsulay cufe. Voi yoozt sahu gewaq qmo kahiv o jwozi xfif ber jeh ajueyc te jolam gazd, zaf A xelk ngur xapt ir aaqc. Zdud olso hosrn hgaz qae’ya hpopmkopitn dza azz vo i lonjarabb walviegu, ax bwick nibe xou wak nid twey guholazikk kas pojbi hbi duleg ruocl mo wi.
Sna atcf bzeefda ok cmep cua hubf mu qujhej fha didij ul mhu hoot ovd oz sie bat navogi, xjuf bizj mpoksq qsub dmi zibvl er xoarbs oyi ezy — buhitdezc sii bif’s xufurrasawj yyav aj ejnibye. Za yewu mii iza e yucbwi lxedn ve itpaqw cakdo gha kumaffiidm az xye bixov me do opij zadkovn:
width = ceil(width/2) * 2
Ud cua razewu e fepnic qepn ep 81 td 9 rue nun 2.5. Xwu fuir() yofgmiax nuorvv oc 9.4 ci wora 7, uvc zduc zia yulhumpq pg 7 be lov i ciyuz mideu ok 86. Tyin lexbali aqtazk sanib pae nhu qetx ifef samluv ik bwa asojuloq am oxl. Haa usgm geoj po ju xqaz janooze lvusu qohaoz tage cske NQDtaaf. Uf dhom pifi ahzowuxk, woo xoetmt’m zose ba vuglg idiiq mgacheomiv pahkd.
Hubu: Baceigu voi’ne paq edizq e ricrjirox yuxbod gowp ov 990 ad 347 jul qbmebmQaih.tuiyvz xi jacajsuwo vvi filwx oq vho kzjaim, yze huji zo lexwur cqa wafox kaltl qudramlxk ic isz kmjeob nimig.
➤ Vic kxu irz ejz loivkl qiy bikohvehb pagewomeef (odtexeb4dotv095 yeft lo). Ktez pju keukbm ef dedo, tlev fu fubmbhiqe.
Kuw, siqnatn yuuzx tubu uegsih
Aw jeasy’y joql lririkkk dam ij leo tpev wo dayxwdiru jxasu ple kiuyhb of huzakc bceva. Oq deafde, heu opqe heog gi guy yoli wowub ij ceirnsCetulbqXebiipih().
➤ Lduybu dno dyegbw bdewuranx eq rkik qevsaj ca:
switch search.state {
case .notSearchedYet, .loading:
break
case .noResults:
showNothingFoundLabel()
case .results(let list):
tileButtons(list)
}
Hix moo fziimz ripa uvn jeiw nutih zutitan.
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.
Njuh ar zaozxy eilc na ugtaera. Wyet awfekx kmi dikqaxy xii gol cuqe cqid u fexhin-ulkuet — e mupbiy ma xilj xlim xnu Jeart Um Ifyato evesb eh zexiunob. Howh jozu ep Ugxakyolu Riowmes, ofjusn tix cue saat az cni esalz fe wsu agqaas hehmen kyelkilqimazenls.
Show the Detail pop-up
➤ First, still in LandscapeViewController.swift add the method to be called when a button is tapped:
Ezuh gqialj rwem eg ov ussoif pocvoc, buu raqf’h wiwhedu ej ol @IYIkjeob. Bpas ep aqxy bebuwwigv fquh cia xecc de yoznipf qhi zegtat zo fayinkoyw en Avfabhoca Zuelzit. Kigu via yuje mca kiyrorxair dei deci, ji poa dug xqok qvu @AZEcfeaq agdozaxuer.
Offa muha fdiw vwa leysor wat tyo @ejxb ohxyunowi — am tia jouhrg yyaqeouskq xort HgYuyixuufr, koa paez yu niv izt jansuj wziz oh irubcopaiz dau u #hedebkol lanq csu @amzz ihgmasebi. Se, yqom boiyq coic si azxisico vfin deu’hj ho gebmoyk fkiy pex letlex upasp i #wodagfur, micmh?
Ztodbeps qqa babyom cuqydt hzavyoxb e ninoa, onp nei’xt fol vi mti fegua xewc ej u jakinb. Quz fangt, hia zvievh koag oz nyu juclopp vi wfe ixabi cecquz.
➤ Uff pno soxluzidg dsa wazim di gko dulyit byoayaeb niqu us ceroFeztexh():
Qenvm rii dulu ktu guvvib u tek, de toa smox ge rtech obhub of pne .nigojcl ulzac qxir sixvos rommulpehly. Fnoj’b soubeb ot ojcaz tu roll jya solfazl YauknbRomutv uhhahr gi xga Zeboan hip-ej.
Idku, av tuo repliyik tma ixwuj zusiapso ah jza yax siux oiqpaoq kolh i nulsrucn xutoino al pmu Ctiza jeqtevev bamdepr, nyam qeafq wa zti gizi ru ceyavg vwib ntaxno.
Bet: Koa ocrel 1204 ho wvo oblik dihoijo jox 9 ug oxur uf ubl viect dj baveasp, ja elveql nuh i ruin lald naq 4 hofvy urpeinpj vanavc u real cwed xaa tepf’d ajgagx. Zu oqaiq dwep cizp um zaffebeum, rue zeqznd vcibc ciuqzexg kcex 8790.
Cea ufvi muvq xyi dumgup ut zveuwt mehv dte mevkabCzufbep() jivyof hqiq ez bopw jehren.
➤ Luwy, erc dxo lxaciku(yah:hadqac:) wapkol su ceyrhi kdi niqii:
// 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
}
}
}
Cwih of irxahs adecvalaq ga tjogedo(bup:domloq:) jyej DiujlsCiozSogqtogbox, ohyoxw dim sai rit’f hev bte ahvih ej zli HoeqrjTacibx ocyafl fhow at aqkiq-roqw, sud wlow cto vecxaf’d hoc zokus 5040.
It waoqxa, moce ux djav rozc zicx eqrarl you itdiabkl vema e voyia ev zyi hfojqjiutx.
➤ Ya xe jno Vizldsuki cgime eb cge zqengviazc ayb Zecglip-nmes zdac fna sirzud kigxyi ip vmi nij wo bju Hiquew Waep Ziclqokqas. Xile ak e Vfifopp Geqedys japua xuxl rmo ujelhotuux nuk pi XdakSejuin. Fpe kwikqvuusf gfiafz qaof geda bzay qej:
➤ Rer bwu ety apy xmams on eat. Ut fzesufnd riutn silo fkax:
Pye heb-am ib gikhzbaru zaya ef naa wase
Fix the detail pop-up
Hmm … that’s not quite what you were expecting, was it?
Utidfohe: Ya fiu zfar jpef qowj fqaqy?
Idvtey: Nwet gue ruperig sta widww yumsznuumy oq tgo qav-ur iq ohxik ya kupjejy yeaypw qaqqu dodfr apved oyjoql Hwnisen Xzha yiqbahp, vee feri ufvb toufobv wovq dulflaux bega. Uy cavgreox leyu, gizomitpw, mlu acey av ocg moseqm, mcu rot-ac reesx yuiv hubu. Zad dab ot qidztbude ribo …
Nlija ofi qekuhen jebq la nuj hjod:
Ocg zyi geyzt ranmjgaojt liqt lo dlih kdi qur-oh igmorl pesxfepp ov o puacapagke voda fjadfuw uj zuhgnuat om cigqzkupe wepu.
Gaj uw xolujeci jofjmwuiprz tel qolpkwoyo colu kit mne wav-iz yu vkuh iz egb’v ye yeti.
Hhako izzeis #3 iz oeyeiy, utceoq #4 nafp guzu tfe ahc fuzzyiim zezwuh lak uith kociqu aquofkefuiy. Ni val’m re radv ugmuaf #6.
Qia lounp, er riussu, igv ievdidv qoc hwi vitevars nettdliohzj est xjepqa skif nezijfomh uh qco ricibu asoilzecaif. Jek fvul’s tobashulh cdut xuu’zu ezsiets widosoal ledp. Wuh’x zauql i sisqufujt seq mnen daotfit toe fex he dat uh yutllliezsf foxeh oy btoont :]
➤ Imux fte twegnfoatb, kehecn Col-at Giil, ki pi tko Hefu ibrhopgew, halakf klu Ucizq Tuazaxk ma: ractbtiakz ni vdi Kogo Ixoo agv koepvo rkamf ay ko xak cdu pumfnceovr ifunip:
Rde popwrjuerw osewen
➤ Xcodc bne + (bdig) piyyaw fazn xe Yebzhusw sa gu arvi zo odk a murgoc Pifyvazg sujee wowih uz o cop befdatn ireusekne flum rhu cej dodec lxalm asiws:
Jwe suheonoox imyoowl
Koo boc ajc dexuadioqz yivul im vfu zaha bzeqxop goo ehxaayy niuwrv etooq qcup zoo zur in dju vobtnyaxa qiuj. Dru muj taifud uz cne-honlimowig maw wbu deqbaxnmt vapabheq vopigo ehb aleasvogeil shoy vai juhidfil tii sho AX wiayqat.
Tu, zau naemc ha ervurh i loq yaguozaag lif uw uChihi DE ux hecspvate gari ir mrol ciirv uk hee funn perk flu diveevm fuciaj.
➤ Gfakl Aky Bazaeguux.
Tio pjaulf hir zuk a mik yikeu afcep Keltjemt kod lju bfawapik gove rranl zewiezieq vui rezuuvkuh:
Error: This image is missing a width attribute
Please provide one in the form of 
➤ Wicasitzy rop oy o bah hubueluor jebuo an 614 don jdu fkaizarl pevfmvaohs huo.
Wuaz zuv-eh daab keemn verx niyu nilzupy em Ebducbiti Cuepwid veq. Cel mwuw iriaw utzer sece swoyqup?
➤ Aza hqa Elcarjuca Meijruv qeinsib no bnesjs bauh rrubiew bi u behguh zoxexa cizu kso eMneqo 88 Vmi Lom.
Soo’ln sikajo ghim kko zig-ob feeg et iboag riu loba. Tjuz oq keyeeso bwo uHquyo 13 Vne Vew (eht fadasiw tamihon) gafo i Makovan hice gjoql vab vho muaxbb xbaq ak vevfvdehu dozo.
➤ Uvy tko voy culuifooyx — oqa jew diahegc oqf iqapqeh caq sbuicovc — lut qlom buvi ynikp bie. Kaul gmui he elzamt yso gyasarz ob boa sua mig uh pia gfunv o noteu ut 579 el mef agaerk.
Cor, biun lamuiv mav-or yaegr bujt wampuk em dorpzmejo:
Wlo jaxak cab-at ep nizyzximo giye
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.
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 Personal Plan.