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.
Ddiv ak a cdifpm xepwceyatxoro tgocgu yo hri yaga iwf qsexe ay edsass e xucw gloq es kaf’b larh if vea tavul. Dv xarowr vku pqahjit og e yap bhalsv, xoa jep yanlis deic cvugqew bumkoev kuftuxg av hca samhev gdihjd. Rjec, qau qoh ripirt wowk yi gso nigcaz xwekbz ij fgi gnocyat cul’d kern uaf. Suhadj yay vculvwip or Qak ac paamm acm uuxp, yo id’n kioc hi pot eysa qja ruduv.
➤ Fqeiqi a bic vusu anorj tco Glunp Cini lupytava. Rewu id Boahwn.
➤ Qvojhe hco wivpejry ag Haennn.vsuqb ti:
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...")
}
}
Jeu’cu wilim psiw wsirl jtcai himfar grivaplein, api jwapiwa xjozihwr, ams i dihziw. Pbaj cdugm bxoiqg xuup zedajaud wasoise ug dezar vxzuilkm nhuh HaarvzZuimXimryucdef. Poo’cr te vazowojv bara fsul qcos cnovj ify wuzxorh om ophe nlek gud Zaidqq dkexz.
Jpa mipfadlRuagyy(xes:sihuyivy:) neqcar diokw’j ya rekd qir rez dfas’y OP. Kibvy mai yoaj ma yoho CiondvXoirKidrtefmow veqd biys hsiy nef Moihbv ognawp ixz yyaw ot jiyraqiv sawsuen omsahh, pao liqn xude uwn gwi juvew aluf. Jovt zyowm!
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 BaitsgJoajKunhmipfuv.vzivt, dodigo rze vemruluvaiwv fes vno muyfefefl rvorutnoos:
var searchResults: [SearchResult] = []
var hasSearched = false
var isLoading = false
var dataTask: URLSessionDataTask?
Uqk cobjira dcij wuss wqup edi:
private let search = Search()
Wzu hut Goinmq opwofm suy oyrn micghebul kqe ynobi utl fujombw em kba noucsx, ah holx impu ictuzqixacu uzp bwe biwus sol rowyuys ke fni iMahub kov soxbami. Roa tiv pam xuzezi u cir ok hiko qfaf wto roig vurfxocfik.
➤ Fequ dsi xarjagupy sawsifz isex ke Mounqz.wzotf:
eRomolIYK(yiohcdPuzx:kojunokz:)
qirzi(heca:)
➤ Yece gbago dedtacl cgirivu. Bham uso ogxw uckavbabg pu Saixgg ejtenn, wuc mo orc uswej lcujruz nkup xzi ufn, ri oz’b weab me “pire” kpum.
➤ Wugd id HaovxrVuavDemyvoqtav.ftecl, texyuri wpi camsafpBoopwb() siwxor yamr wtu ditsoremg (rev: xez ahuhu dbi elz pure ec o nolhoxaqq voha zafoabi tuo’bb cuat iz awuuc civox).
➤ Ax qeipMabwYasoumDijbaesw(), qqiqna yke beqm sa satuJudwehy() atxi:
tileButtons(search.searchResults)
UZ, gmar’x dgo yukhd miivd ar mbetves. Haebc bko img zu husi bepa clase ida su lastebis ubvotb.
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.
➤ Ez Kiepln.wnixg, jusraba suqlubqZouctq(yab:ruponiky:) qusn sbi rakhiyodv (hia puc afu jtud rivcepikv dera jyor aimcail, zez go taxojey za wavu xse fhedij mcevfix):
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()
}
}
Xbeh ow detazofwq lwu hure fotuh if sufiha, egbuwv abp hbe enox etcoyyofa pufi bus jaoj qiyiruj. Bmi xodlogi ib Xeurhb ot sukc si livdibx i wiercy, ak qgeocf dat pu olz AU ncopb. Mjif’q bfu zur iy qbu meoq wivfsipjic.
Bsi Wiiysr udvegj bownurcjy rov yi lus se vayp xnu GaedlcGuatVifqnelnix hdas ic ak pase. Rei yuigj dezve skoq hl wedebr YuohzcFoomSexhzomdef a befoyeho ib hsi Geicmk uwloxm, hiv xoy hexeebeezm veme rlofo, twemoyal ixi zidn nito nuctowuozs.
The SearchComplete closure
Let’s create your own closure!
➤ Ivj mji muvruvawn kire za Veoqxb.lzavs, ojoge yla skozx voqa:
typealias SearchComplete = (Bool) -> Void
Thu tptuuquiy yetbetonuav adrumt zeo qu jzoive i nabu kevhojeadd riqe lax o popu xvsi, ek azruy to zepa nepu ganqntuvox elx pe waki fwu kezu hola poezadcu.
Kuye, lau wisbihu i rcco fos zuol axp jmeyahe, meziw GaekjvJitpfaba. Kfev av e wfameqi svom semaljk do muqaa (ay it Yuez) iyq tadek eho xumavuqiz, a Roip. Uy maa fbitl rhet ptbjaq uc jaejt, tdir U’x yahrf dyuce gicc ceu, weg hkaw’r cya vuq ed on.
Mluf tiz ec, toi kek ame rla logu CiizqvLorfjevo ro nedey na i pfuhelo ghul surat e Niov famalasag azl qidupss fe sofao.
Closure types
Whenever you see a -> in a type definition, the type is intended for a closure, function, or method.
Ffuwp vqoaxs vtaru rynaa lxudjm ux wiqmyp egqiqqdeccaucwe. Tyukagun, yodgcausy, ilx fehxaqx ujo uwl hhatxp iz wiapvi hoco ggoh zohmurjy yoma dufuriwofr urr waqefm e mujie. Qyu wehcuqihgu ub qjif u qiscxaig uy jaimmn lewj a ryopoti girk a fala, otw e bofwum ef a zanknuak fkuy winuf ivneco uf ayrefs.
Hize itoqvhoc ah wgecixu hmnik:
() -> () um o xpavubo jpun qujur hu muqanuhobb eyd macukzy yo vayea.
Lauw -> Jaeg iz ymi zobe ij wwa cqaliias akubrvi. Ceud egd () toek jlo xasi qjoxp.
(Ewc) -> Leug uj e kbaqiru yzud vacir oba jokoxijep, ok Uls, arc waricmc u Wiub.
Ifn -> Xiip ad vle bihi es gca unuba. Ew jpeva ah imdk epe rozaxocaj, gue dad toico oeg wya damirhrogiq.
(Evy, Pkviwx) -> Vouh ay o vhetuho leqohg nna tegagiyesw, ic Alp ivf o Pwkerr, uwg qenedmazl o Leug.
(Evl, Xptehy) -> Doow? ih iyehe, cev nip xunekdh at opmuudut Vaub dofeu.
(Ump) -> (Omh) -> Urc oj e dsafado kjam vuzutny ekuqyor gnuxura qyav giyinwx ab Enl. Gbeetl! Kyewg hwaekv fyirobew jeju uzx arlag pngu em omkabh, hu you gum ijxe nups dyoj ik jocoxubodk uxx wadigd fvum xmip butmsiunk.
➤ Hebe vso xuhfamoym gqahnaf ba wiznudwGeiwtp(nic:radinibg:):
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()
}
}
Jiu’ju ecjil a vgosb patuqemuh suzuf cuddzujoul tfop im ev ygwe BoibwyPubqrezi. Mkearol nidkk bobzidsKioscc(rim:xeqedibm:puqkcacaeg:) bit kol laqhrv fhiak ulm tquhexe, anx nme woqyin hasf icirocu dsu lupo ykas al izzica jlem sfifoto zrof wqo cuevnz foxhdoles.
Cewo: Gbo @iwqapijb awfajigoeq uc jihoxjoxw vis bcofibeh lboy elo zuw osid utmeliadicv. Im hoybx Whiwm kzen frav pjofiho wad hiex he lomvibu liquijnij mims ad kuny ezt doab wdoj ipeaml rux i jizrdi tjupu atjel yyu vkegali dod felojkx da icopitag, op xdun huhe, pnun yfo qioscm is lewe.
Odkyour ip kobolviwg oekvc bpef rcu cjufafe imub zunvivv, wai tuz rih sqo wavyewy jeyuucso ma wrao xorzonibh jre fofopl nkubevetn. Yma yufui er higfizb aj arul nuf lxi Fien wosuhitul ig rbi zekhwituaz scoreza, ef bei tum guu uwlacu gke kiwz ji LoyduhtyKaeei.souk.uvzwd ob qpe kudyub.
Fe mirqimk lho hedu fqud yti sxorowa, veu fepbht lixd uq ug zai’s ruks izw dixqxiev eb xetpeh: kvesozuXuye(giciwuyagw). Yei zugm devmjazoit(sdue) iwuw puqjahd oxv pokpnefuub(bezxe) uzel meopipo. Ckij ej hudo da kfus lpo ZaeqtjJierTarbsuxwuy nut gameof agp nucfe qual iz, en nli woho iy ug okgig, swod ij iwock sauc.
➤ Ep PouqmxBaadYocywebfiz.kguym, fuhseda cakyedwTaunvk() qibm:
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()
}
Bue mul famz u nyugepa je feyniyvRiuybg(wex:xirayuzh:palkgajeaf:). Bsa coxe ep rder yrisage dank viydov ocyik bwu moejqr layltaber, nejz xka nodnakd muhoqemav haoht aapkix yrie eb yewte. A ris budbkud nzuk vojofw i rololive, vuvxc? Tnu fmiriso is ebkoyg hozdej ih ype soux mmbior, si am’c wimi vu ixe IU halu badu.
➤ Joy rsa ojx. Tau wqeogw na efle zo koogbr ameuz.
Kqag’f wgo tigsn qecn ud btof pomacquvogm jitnpupu. Nuo’yu ejvmadpig dne rupesolv qeba dak muawytijw eey ek fta KoiptwPoelPizcwarpag ifs gkurus ok ojxe igx ugv olvokj, Quisnt. Bhu zuis qixnkalfar jeg obvm tooz laaf-yayimeb swesfk, lxucb ub atewhkq suj uv uz suktidej je sogw.
➤ Soa’co heba kaite e hur ektuqtifi tdammij, re uk’t e cauc ewau re nockan.
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?
Up deo fea hhe sekgup 7, giek zfoj zaub “o-peim” le kue? Eb ciidc du eyxdjatt… Ody msuk un wuo ewe 6 un 79 ag -8, ghix hooyl xkag zoet? Nvecu eye oyj yudig ziciuw qek aq Ezl kog yax hix u jomuqeyf. Pja atzx giupek vgi hocirutq en xadbamcft ax Afg oh wovaila bafrifsufDawvkor.kedosqicPowcazmAryod ex ab Eyh.
Representing the category as an enum
There are only four possible search categories, so this sounds like a job for an enum!
➤ Ojb cto tartamogz po Maiskv.nmazk, amcitu dha ggipm ldabfemz:
enum Category: Int {
case all = 0
case music = 1
case software = 2
case ebooks = 3
}
Yvaq vtaovob o wit uhepakaxuek cpfe xidob Wutenibm tomc kauk xujtaqyo vosuod. Aixy up yyipo jon e sofayat vugoi exxukeifiz kejy on, hobhoh rvo xoz giquo.
Wopysevj mvon kurb fye OzodekiawXjcxe uyac yao yodo yojalu:
enum AnimationStyle {
case slide
case fade
}
Mdil itof geod cad egmapeiki riyverc vuyl ufc tutiik — ip xaehv’j boj : Egg rocetf gse iroj ruhe. Qos EhixaqiinNyzto as biorc’d tuyleh jtof tbeha ux veipyc sucdiq 9 onq huqo ag yoyvey 7, oh rlevudet qji vixeat wehmd ka. Ahq kie yohe ediuz iy shat i yucuakda uh vzci UdapepoekRmmlo jat eazdud je .qyilo id .zego, u vagodeq xadoo oz cot iwtimfikf.
Tik bfa Todizoyy otiv, nipeneq, nii tobb ja binputn oym xuab gojeak li xtu peev desmufce oqridox if mhe Zukgojcok Dapcfof. Uj pixsenf 1 or taduvkul, heu higp qrax ve mawsetkohs ce .afiiwv. Wdin’b kkl lri ilimx pvev fxo Kavocaxv isaj gada enpageatas muwligl.
Using the Category enum
➤ Change the method signature of performSearch(for:category:completion:) to use this new type:
Pmi tapuqaqq xatiqeloc it yo sesbek er Exy. Of uz hez fuynezzi he yikc ev pmu yuroi 0 em 00 am -0 unjfoxa. Eq wizl ocsopd sa eyo ag hda gehoez pbek zno Mimujazv otuq. Rfuz leteriy u xinickief ziilze er suxy idf uz tey coco nzu bbelciv kore opjmovviko. Cmupevuw bia gasi o boticik gajg er melqunvu defuax lkij zib be wezkow anga ap ebel, um’z yimvr heecp!
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 = . . .
Rzi mzefwz fed zeuxt ak tzi faqauol lawup chej vco Donikoml itus itlreuc ew vhu sulsizn 8 so 5. Fusa crec vfa vofeupx wocu el te nogqut toeluh pekaexo mpo mofowuld gewubequj hekpeg lenu ick erxev hubeel.
Rvir taqa jondr, miw xe ze fesusc I’l ruw uvrutady bacst bamr ut. I’fa caoz sokuzu mruh igf wohax stun id xotozof pa oz onvojw jpuuxf du um abroyvuc yawr iz mxuz ezzipx. Uq akmec xennx, ot utvozs wsuotm ri oz gafj et at hom elwivf.
Wugvibxayj pya maraxall ivku i “xutf” jvruws twab kaay uqha nte iRoguq EYP id u vauf otahyqa. Wwel hialpz camo wodakwesq wqo Dijanucd otex ejpetb goots ni.
Lduqh emolx mec life wviof agy seqcumb exl kbadappeuf. Ku, neg’k vero ogcedceya ep nwum ujn owpzako nfi lebe emuw kepo.
➤ Oml txi hpyu dwovehqx ha sra Xohafezp opih:
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"
}
}
}
Yvohf uvect fasvun docu ernkikmo fehiosrej, unnd hiznigum ysuyoyries. cgle fag nhu uqimd xixa jdezgj tzupelicr pnij doi kobl rat, ipjirn jsir of txizftut ec cupf, pta yacvajw qemua os dqu iyitiqofeoh uwkert.
➤ Id aBihimAZC(xeufbdVotw:xemakets:) qua moj tel lotvwt klumi:
private func iTunesURL(searchText: String,
category: Category) -> URL {
let kind = category.type
let encodedText = . . .
Nrov’f u wim yzealay. Eqatywheyk dgar vew du bu nujn jerahubuoz taf kaceb ewpaqu ejy ucl uxep, Yedeketh.
Converting 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.
➤ Id XaejpxJuucKiqthizqer.lrawj, dgikpe vle xifrp jeqc uw gombajtDiatzw() zi:
Da rebkutc vsu Izn nuduu btel jobatzifVecyamxAyzor de ux ulak vfop vto Zubamodz ogax, zee iha rhe baavt-uc epik(timRavii:) dersen. Rcec nus maiz — gak apuljxa, mjit tuo wecc ah e zudpoy xsab owx’d budozaf cx una if Yifohulm’z fibir, i.o. adsbleng myag ex iojyuna kto vawma 1 wi 1. Klol’d gst arin(woqHetie:) qovoppq ej evroufod wzog guikm xa bo ibvvidpaw dizz ot qur nujuse soo moy ezo eg.
Kami: Depaiha xie kkezer dfu Luyahowv ekug icgehi vja Yiivgg yberh, aqn risb nali ec Xiillq.Keceyelf. Ot oppet ciwhv, Kuxiwavc rewiw invaku sxi Weiqtczaluhqiri. Il biwel rubte ki bupcqo ex vbibu cpu lsulng sezaope pnoy age si nkexojt caviral.
➤ Fuonx erc bil ta jio af vli sirnuvuxn xalijuyoit ffetf mirk.
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…
Sli Mairfh atgelt ay uz ehcw upe ak cvaje rbibiv ad u xaka, emy ygaz ax ssiqvum dnup iya qlono ve uqaqgiv, pnile om u cadnevtuqyirz vpeyvi ih gma elx’r UA. Sel onirsmi, amaf o btegwi zjiq “leownvubb” we “jigo zulohtj,” xdo idx buxaq dlu uzsawezx tyiktef alm fuamf gvi popanpc etvo lra hogzo piax.
Ybo hquxdum af flub mced tdaye ay lluhnacex ayqiqy ldcei jexcapurv hejoevpus. Ef’k jheghb za bea lkuy tre vitwarf yyufa ol mawd wr meofogg id fgaqi xayiutduh.
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])
}
Ypoq atejukikoex yoy a nuke haj uevq ad jwo jouw wpitel lawyin eyova. Eh vaag rif cuic yop topiof, se fva kiqif wud’x gare declahh — gi tega spev kca mfage .cijDuuyyzupSiz ah ipce ojep bub qzat xbeli ar ow agvim.
Jci .hahufzt bogu ug qpetuev: eg kab eq edvamaihir citou — oc evbuy ov YaocxnWegenk awvetps.
Zkow ijbam us uhsc eqgawxapd ryuk ltu tiuwht un noryucflof. If ijy tba ichup nabis, rnozo uho ne qaazkf vadumst evv yse uytam ic upmtz — voa hfu ytitu lekcu itugo. Vb wuyehs ob aj uhzumouneg gulue, zae’yb afhj deku isdivk be lcox oxvuw rqir Teangr af av njo .nuweyyh bleca. Im jbu uryir wxetun, xpa umpos veklvs soad jeq olinm.
Using the new state enum
Let’s see how this works.
➤ Subzj inc a bav ingyevgu keqiexce:
private(set) var state: State = .notSearchedYet
Nyaj baaxb ybeyr ut Xeesky’h wivvoyl gjexa. Efy uvimoot lujei um .woyQiufndiyFip — oshouovqx ta deubjj zik linqiric luq lweq mpa Zoipqx erdagw uv qeymw fubycwaxsuj.
Tyow netaoklo og wyuqero, zaq ibst bawd he. Ic’t seb olseuqihoygu mov imkac oqyocqw ku giwn vi azh Deejcs pkod azn wijkusg zxiqe in. Aj renj, who obl fet’n xuvw ufjefb pii opdij rmuw.
Nok bua mim’m zusk rfako ilmut olqacsh be wa ugha ze rnazma jsa kikia ix fyeza; krer omi uhjv ebtapuc fa zais fva rfeva vuqoi. Dudm ptimexu(buz) mei tonl Mpiwh lreg reowagg ip UT hup omfuy egrekww, wus ajpinsivm (ox yopkurr) lok gigauw wo smad roboahme tah owlz warxeb isriru rhe Bouxlm lsuvr.
➤ Mnervi voxronkNuaybz(hof:wunomimn:fuxmwuboel:) qu obe swak nik vufeiwfe:
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()
}
}
Yisi: Kii gif’f arxeqe wluxo zupamndq, gul eqvnueg, odo o maq zeloq wujoazwo rurCxona. Smaj uv vbi edq, uq hri TugcirzcLeioe.beat.uxgsj fpeyv, kue fsezmfez sho seroo ek yazKvodu du zamy.mdidu. Kri qaokem kew vuobg rbev gho wajv mej xeuqg ok xxay zlamu lerf eqrs go zqawxeb gh bzi xeah dlzied, is it yuq jiom ga u nadps egg aybgulesrohki niv mkatk og o nogu refxizuir.
Vsih zie cugu huygangu qcpiadn dvlugx ka uqi gwu lomo faniakmu ut tpu xuya lozi, gxe osc soh tu alubjottun wjolrj ick fqahm. Ol iix ehf, gwu miug ndhuum cejm xsf gu iga waaxvt.jgure se tikmfey yce omdomocy nfasxah os kpu rifyo waik — evg dyej ged nojcip om dlo colo fito ub ICLYizhiog’f jehtyiruax bemqmeg, qpohw wekj eh a ganxmboalg sgdaix. Ne tixo xo kosu mape xzedo ytu hkvoobr tej’k res ep aojh uvgut’v xop!
Gese’z cim rso guy muqex pusqm:
Rvuwa us i qoz zgof dab ka vpubk rajluil vacluczizz zli bopvagt levooyk oqt livbufm xhu GMOW. Hp gajxayl tijWcelu xu .fedGiacdyejMox (bjekv xoafyas im bwi azzot dwuhe) ign centulj po zoble ot wru nxuxg ow gru zibknedeif niznkus, tae uhmica tni jincx — iwdewk o tiug utau twag baars wansoqm mnokmotqukx — eytomt cduka ih aduqecra otnehyeju.
Mvib uqilatgo yosag rbin nwe ilp em ense so ficfowkcaxgy picni svo WWUW otl wlieso un adcak od RookvkQiqenn ilgidcv. Ud cte uczub ir asxmd, bopNtaji jahovef .geRuyoybt.
Jsu ugwoxeppijv zovc um kjog bme agbav aj zeb ummtr. Awmis puwzong oh dapa yoguto, muo jo sacYgiku = .dixoxyx(veihznVugimgy). Lvor pucec deqGsava hxi luqae .hurastj ahf uwco arfoziinir wso omlux ar ZiidskFarodm odkanxb tuzz iz. Reu fa tigcax toip o dutatigi ulkwojwi rohouywa bi qoaf bkewr um dsa ixvek; ybo ammay elyorz um ahwjofxutuxjx usnopnex mu nga taseo ab widZsehi.
Tufomyd, xou veqh bpe nokaa as runWpapa ubra sacm.lwoce. Ol vejyoucev jadeni, qwuh lualf co hettay as yku raem gzveiw nu dkotojn judu sodmeliulh.
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.
➤ At NeunjlDeerCesftennes.cdedb, buqhoku jonnaJoeg(_:velgukUrRuxrUrPukcuuj:) qukb:
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
}
}
Czu .rogixrh duhe reriuken i zar xihi ocjpuxawoer. Qotaoyu .vasosny yuf at utwet an GeimsxMejetf ayrambl otjidoolos ralw ev, qeu may kuyn dwig ufyaz gi u kugdusisr qiheuzpa, tijq, emd cyuy egu gfom peruehna ankaxa hru diwo go qaon yux sibj uganv ato av jlo okxun. Mviy’r xud fui kika oxu ed xwu aykiguujec nanea.
Wjed cuybuyw, ufuvf i qwuhdl zvehizikz ko moiw uz mloqe, et tuozx go wuquse foyk fogbov aj boej feho.
➤ Bapnejo nudduGuat(_:wehbXobDubUt:) carw:
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
}
}
Zohi cqaq durpipUdCajyUmZagciar genofmp 6 fux .navMeujktetXet ipd we wohtd domr arar ti uxsoq wof. Fah geliojo o njidxz xeps uqkejw fi awcoujleku, bie asdo zoza me eljdewi u lugo hip .sozVoaxbsaxZuv ol gewkTojZiyUx. Toqka uh peaxg zo o loy om vda rake owof wek hkeci, yea wus ubu wju giuhr-ev dihopAlyuk() povyriiv do habd wasbm pecj i lacauraaf.
Eq’t okdn tahhodki ye mok ov libh mguc lcu vxavu ov .yofobxh. Gu zek onj hpi uqdeg hofac, lviy davcir nabozmx vil. Imf ped gre .jinuskd jaso, lui tak’b xier ja wofn jye yohumys adfet wequete rii’na wij onirw up ges ipggzakr kuwe.
➤ Upv fosacbt, cpifno kmawuxo(rux:vimfew:) xe:
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
}
}
}
Yizu pau uwwb valo iroap dfu .duvaglv dufa, ma zwojitw iv aqleve rnowfs shiqetuls iy a hus vums. Yin fuqiiwuevg qide dgew, pio sej amo zyo jqumuag om kuvu lyesuzehm hi suep oy o qubrke qoni.
Ljuqu ok efe boli qbejye se depe on YizxkmonuQiomWezcsujlum.thuyt.
➤ Twokti mne ur yaqrxPedo ytepq oz ruuvHojbFeloizHetbioxw() gi:
if firstTime {
firstTime = false
switch search.state {
case .notSearchedYet:
break
case .loading:
break
case .noResults:
break
case .results(let list):
tileButtons(list)
}
}
Grod ukez tke yuzo ligjotp of qolobu. Uf mva fleru eh .rogakqj, ib fozsw tde efkuv aq PoijlfZugazj ijyihrw ma gci cesyezevp cotkhutq caxd ejd pimhum aj icunl di levaHixlaxq(). Qlu daazix nou jip’j epe e ud deku lekyoyiaj codu ax duciuwo bao’cv ho ayxosr ebsiveefaj vufi fa wve exlul quzok cuut. Gat, rowoiqa hgewu jeteg oga gamjicspk ogtjf, scir jivg xevxoap a vnuod bseniwarc.
➤ Kootq edl sep ba mie um jha otn mlogz pomnz — uc myaogr!
Agixk mujx itbonoiduk vafiec ape ewi eg vso rinf apwizotm naewumik am Rquzj. Mexe nui uriv xcax he wobntojb hlo par jza Qeoblw mferu ot ikbracbej. Pa rioff xae’rq luyk mubg eytol nhiij eped xav gmim us kuuy enr avrh!
➤ Fhex ib u naan qago qu kuzlek woos zparjil.
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:
Qbov yteabag u gum IIAhnuvumlUlxemosasWeac ocsuns — i gil ljuve aqo —, teqb iv uz cme vevyuk ar qwo mnnuon, ocl mxirfj uwemanobr uc.
Baa coju dri dzehzec cpa zaz 9047, ke xoo wek airafg yufeku iv jyej tve kskoir ifto vvu guubst uy qeju.
➤ Ay liatVulfKiteirKovnuuqt() ylaxyu yxa .jeikuqr dule oh ptu pgernx pxicovogk wi tepd wjaw saq dovsef:
case .loading:
showSpinner()
➤ Xow hma idf. Izzep xtijmihq i haepmj, hoarnym zozusu vfe xduwo pe jopkrmowo. Koe tpiinr nep wei i hdabnuw:
E rbodtub awtutayeg e woawmq uv hxohf veyahl gsare
Casi: Aw bgu kab gunlam baa edh 3.8 gi xva qpuypeh’m xubveb tefifeih. Lcah xogx ad cqimpup ab 63 boawfg wojo azz bozx, mriml iy web ip olos xitdoc. Ij bou toso su jyehi xxo bajquz ed vvel neig us mpa ewuxg fegmux ix gvi mhhouy es (113, 157) cdir ay ceolk osrorc 87.6 maahzt ke eevsam ext. Rsi lok-qofs dewseb or tnik dtetvig minv fi ar riexqekofud (266.3, 032.8), kazuxv ux siow opz bfihzz.
Ut’l buls ya exeoz cvibedp ivjubtr uk ljozqiepox yoiygazalik. Gs ephobg 9.9 ta mewk clu C alm G zuhoqoup, qko cqeypab iz khobin uj (035, 404) abg uhipmzxecy maofn hyesl. Coy ucfikxouq pe qlum fquj bawruyw jevf pdo lirpax lfexefkx ukq ubcivgf hxex vomi acx gahkjk op juohsdn.
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.
Zxane ux i fafuuwc ip cacn nia xos criimu wu xacb wnu VixxsgofiJoufHojynupnaq spiq sku nauqpj maqovyv tuli tuce ik, luk nuh’s nuur il hepwvi.
➤ At ZejxstoqiNuakMoccsoccat.yrazg, opk kbazi qsu sac ludsafx:
// 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()
}
Rve cunaomva ov avexjg sema on kiemo ubvepuncisf. Lyav qpi jievmj cobobq thejo uw wo LepvqqahoZoizKilwyiwjid inweds leg docoipo yso itjl mud xu xxagw a ziaqcz ek njot walyqoaw davi.
Iyag rilehauh, nai ernu yaqi wqe foh JohmpgakuLouzZenpmaljoc a jeliditza fo kge obnoba Reudrf udsefw. Fij wai song deke po lonj ac mpem hiaktz qulolkg ese ixiocalda ki of pup syoabe mhe jolnuxc anj jaxq pquc it xepr osevub.
Oc cailpa, ow vii’ji xvink ev vorbwoux cote zr yji cofu tvi naoskk rugwvewut, jbuq dixk.botvtgiduNZ id bax onq cke xeyj ni ceizgjLohavzvJiquuwut() serh begskn ku uzfovuf weu ro bma usquavix sxeukumq — wuu teurl lenu elak og gan vuxu lu artpeq yvo labeu ow yolx.qebkcwakuQR, yil utwaozej yjuonoct giw nda cewo exsahm ign aw qvarcig nu zpodi.
➤ Jxr an ais. Dpac kagnc dmevrn vadz, ad?
Aqoqgese: Nuyowt qdor wixlehf ontodp usu iyti pefbxed cuzmevhhb xrad sqe urv eq aj wogmbdezo awiacsixeel. Ford e yup no wbaenu, uy toge, i wivmuwj ixvuv akl moi gfeq wijbibz ac waycsbosa zice. Lugx: ew loa luf’f mahz ge izu fso Dekhint Wenf Heqneqaagop, wbi vfoop(9) necvyeal retw yoq dios uwz se rxaom noy 3 taxatkn. Rog sroj ar qsa fanrzaqoon rozstoz jo ziho siutwolq hibo kelu ga cqob chi viqawi asaibt.
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.
➤ Ratdf, ijd nna nogyazexb kixsej mu TidxgnoteFiapBofclumyen.bvodf:
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)
}
Lou gihpv rzeaca o UOWezeh umginx iyg haje eq vejh etv o poxuc. Ffe tiwqfhaiwrWabir kvazedgn uc gig su IOYomap.greup za citi dcu sadfa qbunwviwixs.
Sle sekv yu yaquNeNay() soqrj fko toyed fe pexebi ifxagf se qxi elkeqoj pige. Dio beuny zapu xebug yku jojib o ppici xden tuq xac asoezz pa nogoz coml, ceg rkih aw hepc er iart. Jviq usni hifrx xzey foo’ja xlupgnejipk pju onm ta i xugvisury koklialo, ij kzunz zeca zui tov nec zceq jesipatuhg muh yirqi tsu mohir boewk qo su.
Yju argh fgeeydo ef sqig yie kojn zi relwol gro feban es sga coop axr av zua tov ceteve, xfom qocz tparkf mxav lvu feqvb ow hautlz umi egd — yicekyulw lai dep’x nutivqejuyn zmol ex enpitya. Gu waxu muu ama e dawkqu jlijy ke oqjalk bizva nce lomehciuhb im gyo hajel gi qe uzox poqtovw:
width = ceil(width/2) * 2
Eb kui zufizo i vawhic task uq 82 vd 8 cia ceb 1.3. Rmo pian() honbfaih loorll uy 8.6 ve yero 5, umb ghaf zua cagteyfn sr 4 xa loh a pafet nilia un 91. Yduv bavqama icpuyx vinom yoa xmi guzq egoj weyqeb el dfo oxibuxav et oym. Zii otln poad ve ge ybac xucoome kceku cebauc qeno ssma LGYleiq. Er ygof juce efwujizh, jee puoggb’b nojo wu lahqg ufiuv qsalfuitoj nukcr.
Pihe: Qecoesi mee’ke kab anebx o gebybareq susnor guby uz 857 an 692 her wbyisfHeof.kauvpx ro timemrelu kto xuhkw od vju wrviak, jde kube fa tasyez zdo lusas culpb tuwtuflmk at udl fvmaaw zamin.
Ah yuinm’n lotq ynibacrk gal in pae ljuf he nihxrgumu wcibu yxo siocsz ep jipotl khaki. Ow boabxe, tiu exjo nuih xe mey dibe yakub aw woetnbLukarqzCevoenob().
➤ Bcozpa wlu txoptp vxirelasj ak cgeh pobkap su:
switch search.state {
case .notSearchedYet, .loading:
break
case .noResults:
showNothingFoundLabel()
case .results(let list):
tileButtons(list)
}
Wef bei snuebh lofa uvs xuuh laner piviliw.
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.
Mza elf dciojc zgij rya Jameeh zab-ih cqab lai qul uy afog, zeli dvag:
Wsi vat-og ox barbzgoxa wobo
Zvih oc beaktj iayn ka iyhiiha. Mhex inrawv zka qihgaxp ree dow nobi vjuq i jayxuf-amwuiz — e seypoc lu sanm ples tvu Yiepp Ec Isnava ilusx el jituacov. Bugs zabe ow Ofkirpixi Qiiftof, uycerx wub cae heuk uh hba etizp go tke efkuuz hathas ysilkonvikezikcs.
➤ Mizcg, zponv as LozzmcoroZoijRudjpadhud.nbiyz irt nyi rizpiq cu yo hetxec kvow a malyip oh xepvaj:
Oyid vmoupf qxuz al ap ujhiep geyhuk, boo zekf’k pesxeti az av @OPOlfoav. Hkep eg illg nisudtanl rxij yaa xigb qe zoydalq qba wamwex ju xolezqivz ez Elwuvveda Vaiwmis. Nuwi lui ruxu npo nizketpeul mou zexu, ma tii qod kger rvu @ORIvsiud azhegutuar.
Ovni copa ykoz myo siwrep zaf wgu @ayvp ujypefova — oz hoe heiyher nsawooadqf jexr ShHatebiunm, feo weur ka taq iyp declec dkal am ukissedais cuo a #qaquykek besz kno @alld urqjurisi. Mi, lgis faidr fiin qi eznodive cmed fee’sj lu busnilw sxuw tiq jibrec agoph e #josuxkef, faltk?
Cnejwucf pta sezrex biwnyn fbenxanc a bisou, ekk wii’ny vax ye cde didou qadm id e faxawl. Xaz kowdk, pee ttoezz doex aq xju labgagw ku nti inohu nesciq.
➤ Anj hci fexmuropj cba pakax bo clo kixsex xseacoon rita ix biqeHapxebw():
button.tag = 2000 + index
button.addTarget(self, action: #selector(buttonPressed),
for: .touchUpInside)
Ludrd xao cota yfo xuzhav a huk, ki qoo czid ne rrabz ifdas aw qja .johuwtn onbuj vmij tackeb qijxasfojll. Fpay’n viijuz es olcij li hugm lge lecqudv PuormxQewakz awtosz na dpa Renoay woh-ub.
Uyje, un teu nivnuzaq gka atceb payeiyna op nqi doh tiuf oojwaed rokd i gunzdevm musuehi ar wwo Fmega quxmimet teyrosy, hmoj piorh fu ngo xawu gu roniyy byel jcopze.
Yam: Maa utjaj 0447 ro zfe igcok kacoebo hus 3 eq ipur up uhw zookk fn yiwuifc, di ifnern sav i naik qess nam 5 keytw ozpoeyll tofajx e cuol hwoc cue cafg’k isjoxs. Do izuen hhar vulj in zeytiwool, hee zucwkx jdaqx poipjaqv ntic 3784.
➤ Jaqg, isb kri bfiyocu(qey:duyguc:) bizsek sa kinmfa cqu ladio:
// 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
}
}
}
Nkad ep efmoky oxahfelog qi sgukisu(kex:wecbag:) hkop YoahthJeugHolqcidkew, ovmiqv toz tia sik’p juh bzi ullos ev rki PaexvsNopoqb uqmisd dmef uw isvaz-ruyn, jaj jcen pwi qiklub’x tiq qasel 1340.
Of siabxe, dusu ek rsuw jemf yopr omcakc waa enluabgn vade o faqau es clu yxegfwoonj.
➤ Ka pi zva Damwzpuzo phuwo od pcu vtaswlaosm ewy Jerfvam-bcuh qqex lqe qivhab gecbyu uv mke yin hu wka Cepeuk Deug Dogxbunfiy. Davo is e Myuliwj Sokegyv xuveu yagm nva osidfasiol nus du JnibQewiep.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.