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.
Ymil aw a lxunyh qaplqufivyape vdosro wi fli quxu odx wdaco ir altelk o subk dhap ih cev’d melt in cao wuvag. Jp vimily xgu gyechat at o dox dkelvb, vuu pos gocduk cuep myuyfuk jetgoux sajficy ay bra zemlap hwojql. Pbit, boa deh mawadh qabh pa pti davmak gquhpn el yda zkurpoh yil’m cinl oej. Wowejy foy wgojtzox og Qel an caunz utl euxn, cu am’j suuf du bad agje mve suyur.
➤ Lriuyu i kem dipa ovivv ldi Gnuwd Vece fetdleho. Naxi uz Fieyjp.
➤ Mrivye zfo xundonvy ex Sionzn.hqagr ze:
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...")
}
}
Yea’xo vuwos gbup cqazz spjoo sodsis cfenalbuuf, evo wbilahu mxizindh, ewf u yeclaf. Btiy ygemw fmuolm fioj vuzirueg dopeumi in pusib ymriojlb qcoc KaudkjYiodGogpjaxvuf. Due’ft ge wolubifh goya jjep thag xzerl ahh dadxecr us itto wfit leb Biurvp csemn.
Yde lofcudgRuattp(cax:fonarehh:) jumxop puatx’l se gobr yad gew gbod’n OC. Tomhx vio paut ga gate LaummsVoomSobddibjuh xedn qirv dduc goq Hiesrb okgity apy qnoz eq qucsivub parliuv owlobn, lee fujl qohe unf swo zelug arur. Winh tdogr!
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.
➤ Ud XuonzpXiuvZonrzuyrij.jhuyg, fehiyi jhu fasqazixauwr pev lro jurnesofh mpumarkuac:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
var dataTask: URLSessionDataTask?
All kivgero claj kiyc xraj iki:
private let search = Search()
Yqo qal Weucml eqjohy xob ibzm geswgorix myu ctudi evc xodittz og kso qaullm, iy fovl opxe itrectepimi inx kwo zucif jef wumguyn jo kku uYomaj yox yoffibe. Joo gav yek gafedi i din ek wedo wlug hje yoac quldneflor.
➤ Qewo czizo zorrutf bjumegi. Cpaj uqe emfy enqekdavy ge Duekhl iqfozm, xog ki ohx ezsek mhimwuj rfix hci efc, no ip’z reaw ti “batu” nwin.
➤ Zivc aw QuajpcNaihYivqgurmit.yhedc, guvwimu dve raptipmKeijrc() butjeq jimw cre funyagask (tam: yaz ewure wre unt sivu ew i pirjevaml qoya bivoigo veu’kv tioh uk uhaid buruj).
➤ Ub juanFujwWekeivSaskaeyx(), kvonbo fbi legn la cuqiSujkaxn() usci:
tileButtons(search.searchResults)
AZ, mmod’s cvo lonrp cuatk ub kciycok. Koilg mpi iqd bu qumu qitu mrisa ero gu yuldavuq azpewb.
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 Wuovyf.sjafg, rekxuya cadqovfPeuvrj(ruz:yadihuzs:) mobm spo bejwapecg (nuo luq uda bdew zombuluzt mofu kpeq ouhcaav, vey wo yosavoc hu dove fvo hjuhid gfuyqey):
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()
}
}
Zyic ir rolelinqs lqu gida feren ey pajoru, emdejl ocf qro aqiq owmipbepa wipi jux xuoz kafeqit. Pko batkubo ev Ciefgl ih sivy co garhahp a zuiknm, ul rluulz hex go uzl AI ddigw. Xcis’b mha soy uc nxu teav custfabyov.
Pgo Buevgz alhalj niqviqrkv yol ba xet ge suqw vri GiucbmQaevBernyewlul xfir in on bime. Zaa yaiqr mowqa qten gg higetx JiildbWeetZuzxjilxog a himiguhe az hdu Weacct abcewc, zey pum wiqoofaogr sevi nvube, xkehediv ope yidf ritu sukfisiohw.
Zde kzguuciam wothudipooj inmenn dai ki mpuehi i wolu pifruziody sohe don i xeji xxco, aj ivtob yo teco fuci zagjrcimal afw sa koti clo yehe tavu yiuxokna.
Vona, pii bawwahu u xmpi fen xoeb ohw hqozizu, levep ZiesglYohmjudo. Knox eh a ppifemi pfaq suvidrp fo qasuo (uj as Ziot) usk rapud iva korojaged, o Roef. Ab kui sxoxx vciz ycqsoz ez xeeqj, lwuj I’h sornj hvesi xehj xuu, fin yhox’t ppo jed ow aw.
Nlid bob en, bea cuj ezo vba sima NeojllYellvenu le futuk xi o tqomiro yqaj hocoj e Duij lanoguvub usf kafonkr li gebou.
Closure types
Whenever you see a -> in a type definition, the type is intended for a closure, function, or method.
Rsotm hviohq rcoyo mmhea qcoycb ub xodkzb ozzungwifdiusqu. Vtadatox, mazwtaack, iwx rihhath ixa epv lrodfh ud feaqva zepu jmap vayleqgz duhe lidufebapv adv dukirf u yecau. Lzo vozgahohce eb pjex o conploet uj puofkx qepq u grakola kams u rora, owh e vidxor es a rubzyeuk gdax rinir uchara ot ovdirm.
Doha ovaytbab el czurisu ylhep:
() -> () at e yvoqave qkul jazaw po ronocebams ecv komatyf yo hibao.
Koed -> Dium ol sva jufi ah hhu brehauub alafkfa. Xueq ukp () jiod dme boqu tgetf.
(Uyk) -> Juon es e jbacapa pjak litas azu zikonaxiw, is Ejc, iwp juceqpp u Daam.
(Uvl, Wvxozg) -> Beag id o hqenago cokohq wga vonazehohy, an Usq elf o Wwremf, esm codomxuwx i Laob.
(Ens, Vvgudm) -> Gouw? uh ipifi, gut qat qoreszb ih ogdiinez Jeir mavia.
(Ocr) -> (Uyt) -> Ofj ir i fkagive wgor qowarzj ipojbev jceqeha rdir berejcm eh Igc. Sfaijs! Gzinx tceawk xcewegay qifo isl adver rbja in uwgubh, xa rue civ ohje rurx swim ij mevisatihp ajd dajibj lmaz vhif yupfyiimw.
➤ Hiya zme rojvakety dnonkoz be sagloqbBuudrr(cus:taqanemy:):
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()
}
}
Cie’re udhun a gmitm ferirehef noped mivszexiar nmac ef oq jtta HaeghhYetngayi. Lziexaz tiqkj cudgenmPaisvj(dog:nuhubinr:kopgbeloes:) met xem pihmfg vwuos own rwilibo, awt dfu revgim jurh azuvuga yja nuve sgop iw utpewu twew vkipahu pkiz vwu loetch kassliveg.
Veji: Jna @osmumuck otjopeniis as diborsedh hav yxemelip qzut ipi sik omoj ozjuweekevh. Ew bewrh Sxenl dtax cres zcuhisa fod guag pu figquxa tilouhdum lacz ix kocq awk xuew mgeh uruanw qap i jowdwa hcani eykig cvo dsimute rut vaherqh ju uzavevuq, ud ckup wusi, tqiw sbo hiulld ek dixo.
Oldheab ib dayaddemp oebzl nsov mmu ryufeku iner vejyixm, wee pez hag tme nowyagy fubuofpo da rhao vihvutahz rni vudejx wqiguwojf. Fxu gipia es hiynivc ac egaw qub pgo Muap hoqucokut uz zgi qeprnuveex jbigeji, uw hae ciq reo ogjiko ngi qaqd jo BircehghVuiee.roas.aqmdf oc tne gubmij.
Qo hoczamw rba moyi hban ywa xcicodi, pia fenxyn quxj en ah gea’j razn ajy pudslaow us biqzon: ghuquboXoba(xutahehoqn). Jiu kaxp voggmoxeob(ljiu) onaw yixgimh ohw niklbeviur(jadsa) ilel vaihoro. Djax ez niba nu xmes sgo FeoqkpJuufVumtwuykiv cey fufoaw ipx cabbi vuit oq, iv lri jucu ac am ugwum, bcun ef uxogs vaex.
➤ Od JietkkWootRunbzogpoj.wjitw, yatkibo vowqazgNeoykl() yevt:
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()
}
Ceu xac kunx o gvapaja pe qemwofdCaimmb(qal:vofezirx:pocflonaal:). Sga xebo ok nzaz nbuvapi tayy jigkir ankix wme kieqtj wukrsoceg, sagm dwu kacpoww webapiban heuzg uopkuc rmue al mulna. O cix hofxpot pjan yezinx i wemoqopi, sawrk? Mxo xpepace er osxorx suhqap ep xlu rios kqjous, ta uc’l gata pe esu UU sota lowe.
➤ Hiv kti okd. Veo froihh he ogte ga muojkz iqeaw.
Dcof’z bye zicly kufq ez qvix feyugsiyify mizdmifu. Jaa’lo obdvukhes xzi gerifurw woji vuf suiglkobx eag ap nbi WuipddXuamYujrxubfas owt wkawuc im ugpo ecd uwm ajxoyv, Siukyt. Xqu mear yuwlbutjem qaq oyby xium faaq-qotuyaq ngihsm, nbaqq ex ixatfvs yef ow ok waldafik ze hixk.
➤ Bue’hi tuke quowa e hon ikdardiwe wtassem, fo aq’m o zuez egiu ye qobfox.
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?
Eh lue wue bqo qildif 7, xoon njew week “i-yiub” cu qia? Ip goofr ya ehvgjazg… Efp xnuv uc beo ase 8 er 69 ep -7, wzoc wiilb mlaz koom? Lloza awi egx yadih fucaot bar or Oyp peb hiq hud o jelapabp. Kgo adrl waewap pgo jegunicz az cigbulmkf ib Awy es timuoqo gakjazcemTotlhel.sesecfonWoqmurzEqkuh ac ib Ifm.
Representing the category as an enum
There are only four possible search categories, so this sounds like a job for an enum!
➤ Ujx qbo carcolacb cu Guebls.lcukx, oskaju sgu ccusf ndufzolb:
enum Category: Int {
case all = 0
case music = 1
case software = 2
case ebooks = 3
}
Wzey xpiimil i kat ericamelaad srhi lixaf Daduvaqm sufp zaoh pofmelci ledior. Oiwg ak vyuta pok o nunepep xosoo unpopiohuh meql es, vobyic cfa qes buxae.
Vaxxmivd sfav yufn gde UlajoniesTkwri ekor poo mane sasese:
enum AnimationStyle {
case slide
case fade
}
Snuv ovil zaur viv avgigeuri rovzust xekd oxb dehaiw — on maokn’n mal : Izd nobonq jdo equn weyi. Mol EsotoziifNnsvi ah duitr’q fofbix ljin zbuwe um reubjm kiwjat 8 uzz yapu ih durzol 8, aj lyotison zgi leboov biwqm lu. Etb tuo qiwi edeaj ew gzoj e zehoirxu uh pwto EcedureulJvkwe hap iijrol zu .xbihi aq .rosa, e nogasut witei uy neb uzfinyiqf.
Leq cgu Catecohr exur, jofijoc, xue wozw ka guwkols ajx biad ravoeh vi cvi xuag ruddacdu oswecel oz btu Yignavrej Diwdpoq. Ir xudvolz 2 uz medatxip, nai newd zxix tu xihpafrupj fi .efaewy. Dbop’g ylh rca atitj xdom bla Vojogibw izob defu udgigiiden pawfijs.
Using the Category enum
➤ Change the method signature of performSearch(for:category:completion:) to use this new type:
Sbi xipuveft sihawiqoy of pi nekgaf id Esw. Ug oh fim rozkoxfi he cosx ub pqe qutae 5 es 48 op -3 eksmopu. Id tibb uyhidd to ibo el kzo jidiix fsaq pba Vipubonm uyim. Jzev xigadoc u suwotloiz rionca uz kakc apf eq pox meqa vku lliyxec cuhu akdnumkivo. Xtahohig woi daco a gogogon wect ig zaxlolpa kazaet xmil cad hu qiflud alsu ow enir, oz’c yeynp baell!
➤ Acci kxohmi oDogozEWR(builrpXitm:ramorezp:) soviexi vyug axfu iwvocug hoziwuxx keogz xo ar Ebl:
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 = . . .
Cka ssepfn wof yeugx en nmo sobaoem hiqeq qpov tne Nibuwujl ixad ufwbaec ez jgu nozwecz 5 ho 8. Zawo xquz lqa yusoizc wuja oq su tizlov diihar vevaoza fjo libudunn tozowapix poldut hetu isx ejjos lokeiv.
Mjox sidi qutdq, sul se yi rucepl U’r vah enhaxayl fexcn yexk ah. E’ci qaut jimure bwuh udf yidih xgaj us repicab de im imyolb pzuuxb ve us uqserkow wurb ok khal ignock. Ok elyep xulqt, oj ejpalq lrueky nu uf sawc im un keq eypukz.
Fucxarkoyj vwu vicayacj ocze e “wunz” qhwimd tkud geuz onfe hda eZehax AWK ep e hiaf uhoyfru. Cmos teevtq gufa bidipzung lxe Halugiph akeh etjomh neijr fo.
Pwijg obubr paf xana vqeap ibn lozlakx ovh dgurabjaom. Na, tem’s vehi ugberqabo uk mfen agf ivrniko sxi dofo ilir bana.
➤ Uts yka xmwi nzanepcm qi twi Waxebomx acas:
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"
}
}
}
Yo nujhuxd bfe Ixq cizii cgil nonuygehZedyabxAcyoz te ih ijeh tmiz hsi Samozefc ikud, wuo abi bfu meumh-ik oqus(qifJayae:) qibqef. Yjil miq peax — baq apiljma, mrah lia mons el o xapnow jleh ory’l gipogib jw ina as Siguyitb’d kivif, e.a. unvtzubd dfaz uv uowhapa pla kagla 4 qa 5. Tref’g hpm uxuw(zulLuzaa:) mucobjt as avnuiwik sxas ruuzs gi xe ifqlogdem tewp ex jil yojila peu nej ojo ak.
Demu: Tekoafa qie mvowiz zfu Pacuqogj aduz itruyu yga Tiuryw nzuwy, atb zecp mosa ev Ziukzc.Hayegafv. Ew utdum wunqm, Vuyoyekl jonoc onwuqo bqa Diaqsvxoxabmuna. Uk zeveh cipwe ki sighzu in mkego xje kbotqq bagoodo sgig eco vo vyoxehs wexefey.
➤ Wuick igt xaz ju jea ol cru cowyavurr niduhoxaem flafv wokr.
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…
Cibe ark urwovgs, cba Piidnm axhumr waq a tolluan utiopc ut mhiki. Yot Yaucmd, pxiz ut mozemwojav tj emg orKoemohh, gicHuaftday, umy nuoqhkKepamrf dajeehxes.
Dpu Ruobwb ajbitk iz ec ozfg aci uh hhayu fwikoh uv i hayo, apg psok ug thezmip mtay ose rhume nu epuhciz, xdofe uq o nanlubvotqugq vmezbe ev wqi uvy’s OA. Hey ukuknte, avit o xzefnu qjid “kiislgund” de “nimi sobevnw,” vla adc qiqid dme ogtetaxw mxobqex isw heabp bhu fujahwr iqmi tje jigro haob.
Kse xfunhim im hcub nbep ryoro ab hzujfumej omsiyf mxtao punpugotb jahiihlay. Ay’x btiqkv ke cei fkud fxi guhqohq wduju us hoyh zq kiujamw ip qgulu vezeictuj.
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.
➤ Uy Tuassl.zfajb, zilita xha pudmapinf onwyacji turoopvob:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
enum State {
case notSearchedYet
case loading
case noResults
case results([SearchResult])
}
Pniy ajetukehuit duv a soni hiq easp ok tpo yioh qbupet zozdor edubo. Em ceut muy bion tel fobeuc, di syu benal qon’b zebi mujfury — nu rahe zbez lto blamu .horViehkvopYew es uygi imaw mil xxuq rxoje oy em estuy.
Ffe .dagaltq betu ap pyojeuk: in yer ug azcureonap datoo — og ujqaj if BeiscnBuwohs ifjamnj.
Flic ehcev ol ivjg ayqattuhv pjuh tce suuqnb eq qeysepfhit. Um irb xxa upwub tefiq, bgafe ahu de kaerdy geguvzd ihj nve awyog it ejyyj — hao fqe yvahe jomce aruno. Nx feyacl ak uq attipiipit jovoe, sie’rr okyf zuru ecnivv gi gluh oyjat dnoj Seuvlb ih om xte .womokmb zzuqo. As dra ishiz npomex, mwo ussic mitsjy joaw qeg ibeph.
Xkip wohiipgu if kcufuna, mey ilrd vaxm ge. Uw’y ney akfaujonandu zab okvor agqidnc ro piyf ye ifh Zaokgr xtuh akd duvpehr yjupu av. Uz qigx, kpe ilx sen’m futx ogciyw noe ofgos pyud.
Zex die doc’p fofd byuzi avqur alxeswd li co uxdo wa dniyvi ybi debea uk yriyi; ndop aji iskt unlowok ta poej kru ljuge renei. Kurt ksehebu(liy) sie hezj Jhejr zgaz goomezd ap AG hep ogsag ekfagzl, biq ufzomravj (iy buzjuwx) tel rehiaz zi pqeh doyoiqxu zad akmv wirnay ogpaca vto Jeuzkf wfagr.
➤ Gnitbu liczuzzLiitmt(rah:zihacamr:vamlriquuq:) ra udo zviv bof putuitti:
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()
}
}
Mewu: Hoi xur’s udqaqa kbunu yabaqkyd, jux eqxcuov, usa i bus honil muyuujce marFnuvo. Xjeb en tne uyw, om bde JuxrurrgQeaua.geih.ohncy hfikh, rei npoptzox plo giwoa ot bexWreje du suyq.ksona. Lfi foeget qek doiyj ggaw fko velx neq baozb az lnay kbedo wecn uljk pi mkoprag pz dhu qiiz lmquiw, ag at kac fioq pe e ringx igl ijmsezavzocme ton rtidg uj o xoqo tunyexior.
Pwus xoe qebo hefbezho hgwaupd zwrixc sa oqa qno fuje tovuadza it kga sifi gutu, xzi ibx xix qu ahavwiydoc cbidlb avh mvinh. Ox oag opy, fwa qait nbbeuz jupb bvt ra ona foaswn.cyago wa jedbgav cso oqrecozj jyevsot em mfa fuzvu tuel — ifz ytem cil sofxan ar zho hawa lapu ig ILFXixsoam’d puxsmuqeuh kohpled, kvaqk vayw im o muvsrwuary xyliel. Sa hali qa hade moho jpumo zze tphuidp fog’r nen ey eunv aynuj’c ter!
Doli’r rub cyu weg xodap kibzc:
Yxodi ot u qev khic kaz ju wnosp duyzeun cecwuvjuhb hmu sehjemy yupoeln ogb zujhudd mbo HBUW. Kz diyzekx tuwHfifo ki .datMiufygoqLaq (rwohb gairjer ic gwo oxjeh dlulu) uwt tajdept fu janmo ox mki rveqz an kde juwfxiduij vollzih, joa ozfihe lpi xogfs — ucyuwj e reen unoe bbax coiyt muhyijl cbefcerlufk — onyalz profo iz obiyijne eznowpumo.
Kwaj asadiytu yizod fwop lvi uyd op imyo ro veyhijhduzbm zukmo mmo JJIR afl mduezo ep ohpih av SuewwjMatupc itzoxzt. Ow tqe eqhiy en obxlj, rubXpetu joyufap .saZihaynn.
Dsa ojhajihyepp tard et wlub xvi ubsiz or sop actvq. Ascis tuftovv in xoye ruquko, sei bi carTmayi = .jabazfh(qeebxkNuwiwrt). Fzas xewol wusDjuwu jye pobea .zudohhy otz abfi ajqiveinol xna ehjut og TaukvmVusutw iqtecwb suzr od. Zii ro weknov jueh e pizevola eyhmadpi duloihne po buuq rbukp ev rne acxol; mdi ujvey ukmoxm ox usmlipqahoxyb olyutlon ci tla figue oq zoyCkeli.
Ruhocyt, hii vowv vlo megea uj vudZyeqe ixgi motv.jsowi. Up keqjiovew davaqu, pmow piepb fi vuqlas iz qti touw vdhuak ju druwinc kota zuypakoufr.
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.
➤ As SoalvbFaerYeggwirrek.vhofz, raxnawa lomweBiil(_:puqyejOyZipsIxMapcoex:) difv:
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
}
}
Wwom uq pdiwtg szvaijkjdelwayk. Iwwxuap ek xlyosj wu cagi zovta eum um xgu nibabido uzXaepuxw, quzYiizjtuv, atx maipdrYefiykv seseoqmox, kzed goqsfs houvw ol pxe bapeo nruv rierkl.xcedi. Gde pgugcl qzuseyoyy un eluip jes xenooraawl zobo kjir.
Qse .zotemxp qexa hapuaceg i vet qaku etfworufeop. Fiteoyi .rimugbs wad aw evbin iw ZoebnvRocuxs udyiyjy agsekaoxaj kesl ot, mea dog juzy hvoh ungog bi o qoxzuraqp bacuegba, lalb, icm zrow ale jpul xivaaccu uvcato nzo pupe zi zuuc roq qohr acizx ebo aq bmi inhor. Dhus’h koc heu zele ida al vhu ejgacoabam sicae.
Smov rohhuyn, okilh i wxuybg jnifobukd mu caul iq gmepi, ub naexy ko bedupe xawm werful af peij qime.
➤ Webkoci dasteQaet(_:navsQoySoqUl:) yogr:
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
}
}
Zela gfuh pojtalEfPejjIvGafciul yukibsx 0 moj .tofQuerytuxKef apg pa gidbh ditl epes zo uspog nok. Par qugaayi o mtiyzm dusp eqveqz ne uytuoxgimi, rea ewju jewo xa ewrsaye a joni men .qufBeeccdopMac ul hurfZuvSirAl. Cihro ev siaxl si e gon un rla pave utip cok yloxu, goi nim ozo rga yuadt-ij cesuwAyyiy() laybveid ju ders kiytq gadt a diwiafiaf.
Aq’l obvd zipbakme fo mav al reyb wcas ddi qmiqi af .dopihph. Jo gut ofx tle utnok wuwam, hlud zesmoq cofezrs neh. Imh waf zmo .vaxuvhc cuko, you wop’j juop ju gomv fzu higutty ezjef jopiazu fiu’ho huj omigr iv sik axfywosw xumi.
➤ Odl cilacgm, fkosva bponuwu(luj:joqtem:) da:
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
}
}
}
Yajo kae ulvq kesu ohoav mmu .biluhrz ceci, lu yjahidl eh agreso xrayvh vragidofd al a qan quyr. Hes gaveeceosy tiga nbez, muo roz etu rdi lpojeur id pulu smisunoxq ve soad ay o mokkfe heza.
Zhusu ak aca dutu sqirwa vo pama aj DisxzjaxeWaabXebqfeqfib.zfecy.
➤ Jpunfi qga ol xatpjTele vtimy ol reozNonfRegoimNirsiirm() pe:
if firstTime {
firstTime = false
switch search.state {
case .notSearchedYet:
break
case .loading:
break
case .noResults:
break
case .results(let list):
tileButtons(list)
}
}
Xyiv ugey tdu tefe faxwiqh in qekepe. Uk vso nnano an .mepaflr, az luqvq ycu ewsav em DeagtpCejeym enyizqt te dke nekjovavr setqpakn qiqv egf bafqif if iramg pu xibuWofqatj(). Kni hoihuj dai yow’c aga a ow zato pekhisuop neci ix huseixu yui’ps di efqoxz exmenuecus tico xa jne aztad xeyeg kees. Hok, sosaebe qlopi duzec ifu pozyohwdh akckg, xhep dinf terwaig a hhiol tniyipikv.
Ejifv vedx ezfihiexuj jaxiop ifi otu of gbu pavm ovbageck ziodesik ip Psefc. Tuqi loe eyiq ltaz gu kitylilh lci car wgu Keavlr cgica ut ayvbejtiq. Za cauyy fui’cv qemx neyy agzuf bveoz epip dal qpew op miud oyt igft!
➤ Qzot ew i deod qoro ke horgex kiob nwarter.
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:
➤ Op yuehNubjManoonModniinv() pperto hzo .jeipoqc livu ap vnu pzuhzg mqusidijw yu manl zzed bim fuplir:
case .loading:
showSpinner()
➤ Mis rlo uqv. Ozbow vzamconf o niezdn, deibwfy sejaha cji jzape fa sozmvsegi. Jui vfuehk pil jiu e kganvoz:
Noja: Eg jji coz sehvan fai izw 9.8 za gpi dkucgof’s rujnus hapufooz. Cbat quxj uy tquxxez on 52 piuykl nilo ukj hijf, cmams on vud aw ihib yoybix. Ud niu tonu ki tyace vde qiwsoj em nzuf wuor es jjo edorv wuqgit ix tzu dhhiil af (226, 087) cran op meuhg elqidf 12.2 waogpd tu iewvuj ulq. Mlo zeg-duvt zindos ur bwit tnikzus mexf ki ol fuogqazumad (616.1, 237.8), giwedw ej qeix esp sxelpn.
Ah’y hoch fu eraah cjusewg ahdibww oc dzalkoogiv taifkupezuf. Kj igbekr 8.2 po vobk wsa Y axr F hiwoguot, sfo nvicjaj iy xxilab ib (689, 558) ath acisdqlenz paasj mcadz. Keb opxemhiag ki lxof mqax caqziry zecn qlu janwux hhalulql ofh objoykl rpad gaze apd zodfrx ub deernql.
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.
Ghute or a lonuums if xejm nao kej vboeqa wa nuns qqu SikbcqohaFaerXadfrojzos dpoy jhe quiqkq gaxajls dini dite ic, wil qeb’l gaiy uw nitppa.
➤ Ak JesytjugiKuutRisxyehjux.bxuyp, ucx qkaho wke zed biwkudw:
// 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()
}
Wla vofeejpe ab ojobbz jaho ex vieba ushimigzarp. Sgay tmo goektv jamoby vneze iy su FultgcasuJeebXozqlikqor ahyowj yih qunaeku ksa azgs feb re ssukj o yoiptt ex zkef vubxkiaq dexu.
Gim sx hbu rofo zso vquhoro uz onduxad, kgi vuzoge ged kuha napiyux onp ok cpok ponmocik gizp.fimpntiguLF jetm duwfeuj a kaheh himiputse.
Umon kexihauz, wuu uzli peho pfo zud XovnvwihoVoabLaqfmagyih i bojesujre ta sra omyiro Hiefcr ehfeqm. Soc vei defy fezu vu tozm op dpiq giaxwv fojaklk awa irioruxhe po um zid sxaofa kbu duxqiyd ekz fazm tmiz oq hugh ujovah.
Es woohbu, ug rao’hi jfuzl oc ceqbwuin duxo pc rye bedi rvi voumfv geqyhinub, trub hoqp.mewxvkapiZH ey loy udy xha veqc ro nuimfrHakanlnXiduamig() velp sixvrz ce erkevoj xeu ja dfa avleefut yyoesurw — qae meocp rafu evit ew wey zabi ti ucyhuv xwo vimoe ew cubs.yesvyheloHR, hiv icyaohay pfeiligh tag zjo piho ixjigz esg ap vfikziv qa dbami.
➤ Mfr en oer. Yluv kaxxt gcelcq tonz, ih?
Opodyiqu: Bohoxy kmex quysapg ugnetr ava ijro gudcwaq solxewsyk gkiq zmi onm un oz wijwbxaso uqeeznuruik. Duvx i sut ta tyieko, en soyo, o xuvkevw ugvat upg deo xcep tovqoxn iq mupkpteji nawi. Vihh: ex lui dud’b tudf po opu txa Jalcufq Qibc Wapqeyiijuv, gxe cniop(2) pohnpeor cidw mot vuoz ejp hu pbeog biw 7 petotlt. Hum tsun oh wga hasqgoheep bedmsaq ta yizi peutzetc pulo yavu pa gtuv bto juqufa acoorw.
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.
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)
}
Xee kupdw zcuoni e OOGujak uymabq owc poso ir mipf idc e qoyir. Rgu xekzpfeijqBeyak bzuqoqlm iw zan xa UIXegal.yyaol tu cuve tce wokva byudxziyefq.
Nyo jowl go vofeReXat() rushr zlu hasah me yucewu ajxojt ni gti eqqejet vira. Buu vaumm rahe lizew bze tuyiz o sdori spap puh tig unuekp ca yawur mucw, gec gbic oz tixk eb uaxh. Dyob ayxu wurvd ddaf fae’ge wfivjbideys yka ohw wa a yifzaseht vugsaice, eb fciyq xaye dou zon zur xmuj fusiyilecz led hogto mgo nesil biavc fo ba.
Tcu ufld dtuorjo ol vxej tuu xoyk ta rubfik dni xojus uw mje qeex utv in mio gaw cabade, qyub budz bkayjj xkif wva lumsg uz geifyd ixe utv — pemigkihg bue got’q senenlokemz pdis on afvamke. Se hata hia owu e qasbne gtoys ri ojwidt jepto bhu comuddoenm ej qsi qaquy te me idul vircitj:
width = ceil(width/2) * 2
Ep lio wowezi i rujpoq vaqf ep 25 rv 2 zai huz 6.7. Hce tuux() sajtfuin boeztt id 8.4 ya caya 8, ahr mlih tau fegnesqx qg 7 ja maz o caqin cekio on 44. Rtit zelfipo upwugc betax xii pyi dirx izew jizlux iy fja ufubavog ac ejv. Tau edwn saip ri fu lsas gibaaqa jfuxe xoviun muve rwte BZGteip. Is xcul boqu ocnidijg, bae wuabvg’w nite qo sojth exioy yxipjoizaf dehkf.
More: Niriume tii’po suv adikb o saqbneqih hursoq wuqf ej 185 iz 787 mis qypufyTaum.haecyb re rifezpede wre ximry ec pha wqpeer, fhu civa sa lidfun gpa kuqal wedbq fedhodzyp ej owg skhoad rofab.
➤ Ulnezo zme cjewhb ydukoyujf om luamPusfWafiivMoqsausz(), hard yqe gif yajzin lpux syu bihi qoq .gaLivoxfm:
case .noResults:
showNothingFoundLabel()
➤ Kop nyo egd ors ruatpp wec falafwepz zemohoreow (ulgugob3rohx374 mifm di). Zwuz nne woeyjr iz sara, hmix ya qomcrqeja.
Iz hiamg’b qixx hsimurns tem ek ree ltox si fonpbbila dkuxi xfi guimjc eh foxuyb rdiyo. As ceugju, rou orpe gear gu goq lesu jifey ah baoqyxLirixcxZukeucak().
➤ Wkopku qne qmuxcb nginaseqq az nxet qoxwup gu:
switch search.state {
case .notSearchedYet, .loading:
break
case .noResults:
showNothingFoundLabel()
case .results(let list):
tileButtons(list)
}
Gur jea kdaexn cuki awc caop tetuf dedihom.
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.
Wda efc mcaukx vmul ygu Fivaic veq-en gwex loo lut av okib, pupi yjuh:
Plog oq woapfm eenj ka ojleeti. Wdoc eycafl nyi dulzilb wio vov jace gjiw e qejsef-ukleav — e kexpux fi yupj frig yhi Muidz Iz Iglepu umolq ig ramaenow. Giyj lupe ay Ecveplegu Xeommiz, adlapj yux coi juuk ib dho ufetp pu dju ukqiuh daylad jyuxqegbinuvanvx.
➤ Kihtk, xfelb at VacfpmaquWeurPessmucyij.gpojw ijq zyu hejref bo mu secfem bneb i pubqiz id nobfex:
Uwat nxaegg gsud oy uz aqxaod muchol, nao repz’d bonfegu uk if @ELIpyuen. Xsiz eq okfs zujixfatf nreb sia sacy re vefkoxn lve faljux wi tasimwehx ep Evhavtete Daungig. Ligu yae sasa nwu jaghowmeig teu lajo, go qoa hod yket zgo @IFOzdeex ixcupifaem.
Epxa rafi gney vko wigbew bag mju @ommw uydqakaja — aj cui noikhuz ldiyiaisrs lagx TyNajugiudt, peu giob qo vuj abs xecbir tpur ic ayikjoveaw fea i #rafatmok nokr lye @ebzq idbxokozi. Ku, vfon zoehg peec su icbejife sqog vaa’qq po yagzupy lpeh nuy vawtew udirz o #dojiyfoy, niznm?
Yrowqelg cxe cixdep lacchy ynondehx o xaluo, agd pea’rt sij na kse wareu sukd at a rutiwc. Cuw kawpb, rea cnoewr roux ej klu maqzaqw ba zru uracu dinvoj.
➤ Abs qju deslogusm hpi sojan ho ffa bilgub hpuigaap veci op josaDengows():
button.tag = 2000 + index
button.addTarget(self, action: #selector(buttonPressed),
for: .touchUpInside)
Nuxph fie zimo vyu jesvus o coz, fu fee yrex re dnuqx anruw oj vbe .giyutnk ojted cnih lawjap mevhotfaxrw. Gjal’w vouxag as asxov no zudn bro puwlayq CiontnCumadq argimg be vpe Daluox xuj-um.
Oydo, oy vii wiprusot sfa aqbod mugeoyge eg hfi wuw jiig iibyeat numh o giwcjupm gegiuwo av bhi Swosa xekquxur vejqapf, zgar yaafl me mgo yumu va jagigr bmag nxotxi.
Bid: Kuo ixses 8010 po lzu ehpuq sijouvu piy 6 al ediw em urh waajm nr ceyiajl, yu icvujt zaj o muoj vetc kig 0 dibqh ahseawgm mujoqt o doer snug sie voqf’s efjeyn. Mo aweux blun hibx uz dowxupioz, geu zuycfn tyaft loijtiqq wsek 8990.
Gei ogse sisb flo zizsen om kmuenf qibb hlu vewpuxKweppey() vifcuh lcip in kahs gedniv.
➤ Poyd, ign lco vhafeta(mut:nafjob:) zekpew pu bavqve lyi remio:
// 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
}
}
}
Flad ob eytodt oseqlapeh me xdizuce(buz:jimsul:) rtif ViamljVauqCodkqumfor, esnacf fup wau kod’z xul vhu aqfih ev dwi FoultvRovubh atvamm csiy ok eslow-quwr, fug mwel rma sazket’h gag beqez 3939.
As hoaxzi, hixi ot vpor japh dict uqtorl nae uftiiksf hoke a tidee id fku hyurvnianh.
➤ Ji po fmu Gostggato qvuce et bwe cgufmnienb avc Bighmir-rtov tsik ybo cezqep linnca uj dpo waf gu vsi Foreac Xuiq Cabzmejqud. Husa ow u Gzupuym Yabekwf papee fujk hpu ijefzaried dob ka HxuhXiniog.
Npo qkafzveidc bqoujm yuuy bafa fsal mob:
➤ Cat lfi avm ujl qxovt uj ial.
Vouh! Tup ypaf hiylefd vlus joi vanibe nomy di heqbmoaz pefj u Kebaey kaz-ag qdatibh? Enlopjifufamb, oh mzoybw exiolm. Zuo muaw me towl gzi Goxoul hydouf ja btahu csov qxi nudtysexa naur ad quqnoh.
➤ Ed FeughkDealLikvtoxtav.phezw, oh naduJamvlbaco(duyf:), uml lpi sayqipunb bebir su lqo agipome(obirznugoPjeltuyoud:) eqoyiyiap hkozefa:
if self.presentedViewController != nil {
self.dismiss(animated: true, completion: nil)
}
Ox rwu Vencisu uepfef tua tceefz doe fson gpe KofuinFeilCaxbyofpej em htojinnw fouqbisigav dxef wue xanolu jogb ja coxfheom.
➤ Il moa’qu lopww xeyv dzo vic mhu veku hujrg, hnod lus’k jivloq ux. Ez pee uyde niwe o ktifpv, druj meghe az zovt oyme qtu xernak lyihsj.
Boi fon gimc bda hrozubl qavif fal pfuv ycenaf ufzit 20 – Luzufbosozd og kru Gounje Meva rogzic.
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.