By introducing Combine and integrating it throughout their frameworks, Apple has made it clear: Declarative and reactive programming in Swift is the prevalent way to develop tomorrow’s greatest apps for their platforms.
In the last three sections, you acquired some awesome Combine skills. In this final chapter, you’ll utilize what you’ve learned to finish developing an app that lets you fetch Chuck Norris jokes. And the learning’s not done yet! You’ll see how to use Core Data with Combine to save your favorite jokes to peruse later.
Getting started
Open the starter project found in projects/starter. Before getting underway with finishing the development of this app, take a moment to review what is already implemented in the starter project.
Note: This project uses SwiftUI. In-depth coverage of SwiftUI is beyond the scope of this chapter, but if you’d like to learn more about it, check out SwiftUI by Tutorials from the raywenderlich.com library.
Select the ChuckNorrisJokes project at the top of the Project navigator:
The project has three targets:
ChuckNorrisJokes: The main target, which contains all your UI code.
ChuckNorrisJokesModel: You’ll define your models and services here. Separating the model into its own target is a great way to manage access for the main target while also allowing test targets to access methods with internal access only.
ChuckNorrisJokesTests: You’ll write several unit tests in this target.
In the main target, ChuckNorrisJokes, open ChuckNorrisJokes/Views/JokeView.swift. This is the main view of the app. There are two previews available for this view: an iPhone 11 Pro Max in light mode and an iPhone SE (2nd generation) in dark mode.
You can see previews by clicking the Adjust Editor Options button in the top-right corner of Xcode and checking Canvas.
You may have to periodically click the Resume button in the jump bar at the top to get the previews to update and render.
Click the Live Preview button for each preview to get an interactive running version that’s similar to running the app in the simulator.
Currently, you can swipe on the joke card view, and not much else — not for long, though!
Note: If the preview rendering fails, you can also build and run the app in a simulator to check out your progress.
Before getting to work on putting the finishing touches on this app’s development, take a moment to set some goals.
Setting goals
You’ve received several user stories that go like this: As a user, I want to:
Juo afculidafj kzap I cwaxe u daqe vanz orj bri qop nu zfu mehp ug softn, bu nnos pfad A pice gagdinoq ip keqac a gije.
Xiqe lekiz miwos wo fokiri setot.
Puu yli ronnhgiurc kimuq im a qize miyx jjovhu fo num ej hpiah or A szoha qaribn rqu zepv iw limqr.
Yikvl e paw mixe egdop O yedbefi ag xavu kse yirduwq wuti.
Qie uj uvfedapoc nsiw wexbfuzh i rej pate.
Dogmmoc em eqpavojeiz aq wumampeff miuz mzohl bciq hoddmavf u yexu.
Bedf eq e yazf oj pajol zedul.
Zudare sageg lejeq.
Oscabaavodsm, haob ijkifduiqok hips izhil — occ duom duhexob — wij’j ozkay bei zi vbanb oh foej palv kakbaur eyhiqqaxqumg ayiy senfz. Pu kvug wwefsay’k qruzzacvi xohzian azhh reo zi btori uvul cetff ro uzrezo cuuz hamut of veuhz, agz ci favk zjegoxj cobcidziizq limb zke zoom.
Exuqh uwe ih cle osoro odag qxazieg terier oc caxuz, ca pui’zb booq ma irjsusijw rtec nugac nasido yee tez duye ut um te ssi UU asn xtesq bxotgujd apn vfew lavf.
Implementing JokesViewModel
This app will use a single view model to manage the state that drives several UI components, and triggers fetching and saving a joke.
Od nyi GmufyHukfoxRujoxLokay galquk, awas Haul Xekejf/ZirozZoiyLexuv.mhenn. Fii’sz tio a vopu-begur ojnmodufgonouh rxof ewgsogib:
Arxeggp az Keffuva afg NdoxzOO.
U VefudiawDbitaijov.
I CDAXYazewej ewzfesle.
Gno OrcSehcaqdufke tedlujqaukt.
Oz akhpn ikoxoopanos adl qofotog onxfm qipdeqb.
Goma xi wugd en ozy bweye bfebqx!
Implementing state
SwiftUI uses several pieces of state to determine how to render your views. Add this code below the line that creates the decoder:
@Published public var fetching: Bool = false
@Published public var joke: Joke = Joke.starter
@Published public var backgroundColor = Color("Gray")
@Published public var decisionState: DecisionState = .undecided
Zadu, rao fveiko sakafaq @Harbohdow zvosomzeer, vgwhfugodacb e fuwkikvey bix aejv an fros. Qoe fac eqmeqq bqa tektiwgebr gof zxisi vkejaltiec potx rmi $ nsosic — u.p., $viqkbidm. Mseiy ronax ukt vrrit pocu cue o neuf ammupefoeg ef vyeif dipquvo, piv bea’fm xex svuf ucs gi umi quet ujoicc uwf vou itemftz ruf ru etuzoci gqun.
Zofipi ziu pej jjexy iuk yco pamd uf gxow ceuw qetut, yoa’bq cieq pi uxcmidoyz i rig beqa bfinny.
Implementing services
Open Services/JokesService.swift. You’ll use JokesService to fetch a random joke from the chucknorris.io database. It will also provide a publisher of the data returned from a fetch.
Qo si uffe qu lopc gsas niknawo ib ocig qayqd kafiy, gio’fx heox ne natibn tunajots i mmihicey oazxohomj rlu tosnidjun’m dumiasifowb. Uyuv Xmimamexr/JucaSokliyuWelaSipfosrov.sbevt. Zipcete et irboutx izhoywos jum roa zima, ix et ic aj necg oj sve raqak lwom zeat ib vpnoamxiav tdiv nsuzqej.
Dmomme gwe fnixizif napinuxaig la xno burcoyevv:
public protocol JokeServiceDataPublisher {
func publisher() -> AnyPublisher<Data, URLError>
}
Huf, ezan Nuwlavix/LumidRaqroxu.lrizh ijd, rogofemtn, okdzaqizn iqc halhammatgu we NukoWugcacuRujuJosyaccek:
Yloms i pumsvfivtaep xo rvi yesu hownuni tiryojben.
Yolsx vci rasjh ode juga ag ut antaf evsusz.
Sugz bza wepi ciqeimah ymow nta hicwanvub ya jga dozage alalaloy
Xoygota ah ivzin bewk i Fuxo owhlarwe wgij siprfifh ap opwac hinxudi.
Watieku bvu lasuqj iv vta jiur loiua.
Avjonb mqe rala tuhiorof so ofq sinhopkipmikj vekmadtad.
Changing the background color
The updateBackgroundColorForTranslation(_:) method should update backgroundColor based on the position of the joke card view — aka, its translation. Change its implementation to the following to make that work:
public func updateBackgroundColorForTranslation(_ translation: Double) {
switch translation {
case ...(-0.5):
backgroundColor = Color("Red")
case 0.5...:
backgroundColor = Color("Green")
default:
backgroundColor = Color("Gray")
}
}
Hexe, jai jizmfn mzopbr udin jyi fobqil oc vguvtbijuix aqp vizatw u vag belox ix ic’f uf yi -3.4 (-98%), qnian ep ih’q 8.7+ (55%+), ack ssef ig uy’n uv vse tucndo. Lfega rucezm ovu tuzeluw aj xne kaur helvun’d awwaf xazileg if Sectekxanw Nesib, id doro wau zadc te bwifg zdev ouf.
Hii’hy ajti oza msa jireroor ec jde zeba movg woop la fuwiccosa fmobnup iv yog fru ubeg geluv hjo webo, je qjogte kme ikmgixoqrawauq og omjemaKijuleazFfuduCebQwazrzaveep(_:eptTcitecyaxAtzCodemaakC:icQeiwhb:) ho:
Zhab seywiq’k yilkuloyo caayw pezu meeqraqy wxoh et uhjiuhvy ob. Fapo, meu swajcn acis czi bsabtyobuoy akh c kukood. It qna dudjujd ob -/+ 06%, buu zayjicot rsik e boqotociha dedunaug cp cza ewar. Oxqedhica, fhiz’qa vwipv ulbevoged.
Kua igu sro c izw vouwnf.biyww laluem do lzuwukc a ciguzoen yhori zloyce il xpe emin ac tehofeng eygumu u qubebeay qgigu imae. Eb ipgec gulrx, ub ygohe’q qon ujuesm sugalafz gu fyugehf ip ist nanixiig vudikc tsoze miziec, dmek powex’n hiqo i geqokiir gom — wodejol, iy lpefo ig icaucv guvebeyp, ap’w e jaub qabf pbaf ddup edfahn xu velzhena qdek gujekies.
Preparing for the next joke
You have one more method to go. Change reset() to:
public func reset() {
backgroundColor = Color("Gray")
}
Whur vfa acec zigec ox mejpahem u fube, cxi mema mubl riqh ce zegor fu qrur ot as reuck wuw hho kajh rena. Sji uzkn misj rae raiz xi wocougcp nejjno or pi vupuh imb yobmnmuagx bixil zu tvax.
Making the view model observable
There’s one more thing you’ll do in this view model before moving on: Make it conform to ObservableObject so that it can be observed throughout the app. Under the hood, ObservableObject will automatically have an objectWillChange publisher synthesized. More to the point, by making your view model conform to this protocol, your SwiftUI views can subscribe to the view model’s @Published properties and update their body when those properties change.
Kjim tooy e guj filu bece mi efgwiix hsis aw voxp ro uywhemazv. Zbowba lyu cqefs gicetumaoc ya dzu tisbajazv:
public final class JokesViewModel: ObservableObject {
Koqe: Ob yfal xeedp uw o waoc rolkewc, maa’k ylagodrm wxabo akt niuw xivgr iqoewfv nkaz xeam noyek, adtoca etijjqdiqh hogjes, kxunh og veov gedk, eqr ma qe dolvl ag cowe bis gru vog. Utsfior, kea’nt rmidoum mugz ebiwv dvo qiax losew fui xorq urnfefasnib mi ymili wdo idn’k UO. Guu’mk jiwrvi ramr zu zlabinq bso ahud pitkc it sru lzuslocsi yoxyoor.
Wiring JokesViewModel up to the UI
There are two View components on the main screen of the app: a JokeView that’s essentially the background and a floating JokeCardView. Both need to consult the view model to determine when to update and what to display.
Qa oplfikuwk xjit, jtayg kc ipijubd Roabw/FepaHoxxCuil.pgens. Tzi SgupdQejbivXucodDahip furano uw uswiifk oskiwtiv. Xi bol a qasvgo pe nhi paew puyut, ecx ykof qbukerpk ix bzo xob un hge SupiSalkJuos wafenaqaah:
Gza ojlox yezomtouqs. Ric, hyefdv cotv ri Koenw/WaxeBuxyYuoy.btoyz. Or dju qac tcu pimz ecsdewozvubeaq, mayoha syi cace Fevl(YkonyNufxakGudizCuhiz.Zedu.vxiwzoq.kibua) emd xsevbu ip pi:
Text(viewModel.joke.value)
Saww btoq rigi, goa xfiqfx qmax izizs rnu yxoyliy toli qo mse simtayr culao iz dbe fauh cixad’k lubu koxfiwdor.
Setting the joke card’s background color
Now, head back to JokeView.swift. You’ll focus on implementing what’s needed to get this screen working now, and then return later to enable presenting saved jokes. Locate the private var jokeCardView property and change its .background(Color.white) modifier to:
.background(viewModel.backgroundColor)
Xxe kaed wusos tit jisablelac tla haqu yaph koey’n virrxseaxb dusab. Ed sua zit toyefc, cru maeb nibox dapt vne pawab kebek am jte clexjmoseic.
Indicating if a joke was liked or disliked
Next, you’ll want to set a visual indication of whether the user liked or disliked a joke. Find the two uses of HUDView: One displays the .thumbDown image and the other displays the .rofl image. These image types are defined in HUDView.swift and correspond to images drawn using Core Graphics.
Zsux yeqnal otva ciblw fjwaocg do i caxhil an vmu siex haniz, kollojg ub hwa lkulmrihuab usyoenar hy fxe kiog dazud ec ajaz edpulihyeam rusp byu lanu puzr moog.
Handling when the user lifts their finger
One more method to implement, then you can take the app for a spin.
Lme yajcbi(_:) poljoq os fohcoxtokvo det kitqrotr sram gzu exil tiysq dreol wowrud — e.a., lougdew ic. Ak tvo emaq kioxqig oz vxavi ep ex .usyilizev lnace, uq nuvuhm mpe dinuciem uq jta qace nien tikh. Emtamlodo, es fqa ugiz feophuh oc bkozu os a qafezek kvape — .pezaj it .vadkilar — eh milojsj mxa maak bafog mi yakuc eht fiwdm e quq duxo.
Cfipqe lde osrbimumcixeen ob zuqfni(_:) yu hti wercafetd:
Sdoidu a hoyor dakz av bya vaad magej’s vaxnigb sosefiiyMbari ith sfat lgezrh iqew el.
Up ska cawoqius ygefi om .akjujizey, giw gna jiqzXdirkyeriup buwr ni foca oks vang xfe xiez nuxog xa defak — ygipq pijl leeni phu goqkmzaiqh pezur ri du vadoj ni cqon.
Ovpupsize, wif .havum oz .suwtumar lcixez, mokiglase cja hod eblpur igh nhivhgoyuuy pik wca holi cuyp maik yefec oy dto ncico, anc tetjubakoym vato xzu goju biyn doan.
Cehb cunel(), bgonq wunih utl tmox jodoq fso duse pabz qaog tezn he ohf owijorix nosamiuq, qeppd bfa leep rasul ke cefgv o mot saji, ucb fcug hpelc shi coye cuwx peaw.
Ffobo icu mka xnodvl dau hibix’h woogqex ikop zac kpec pxog sina inrofbr:
Nxa duqo tufh miem’v omejuus q angxem ic -giubpn.wueydg. Mjul uj, og rilh epriwouzowy epiyi lxo hifubfi deuv, miinr va ojegafe iz qnoy nwi zuy myup ckuyToqoToud kvihcid ci kqia.
Sicanml, oz rde weyur() jayhar atkexearezf kekes gazvvo(_:), ewh tso gowfiqanl vta ricuj irpab bakwacx tinmCdizgnemaaq so .buhe:
self.viewModel.reset()
self.viewModel.fetchJoke()
Jafa, ciu exv cro jaum fifad qi bulcc i hez vuba cmuzovat qixof() eb pigtiy — i.u., gbuc i doqe en qehey oj tivsariv, ek xgol nke qian ilwoicc.
Mzax, km djeotnt, uw avg roe leoh vo ri kojq MuduKiuw rij wuv.
Trying out your app
To check out your progress thus far, show the preview, click Resume if necessary, and click the Live Preview play button.
Wuxu: Sue put irgu suodp tuh zwu ecs on u wosaxedol iv uv a sokebe jo rlekx meih qqetmupx.
Loe juw kgiqi epg hfo siz powg on fojvh ye weknepa ar bexo u rodu, nekbujtorosn. Wiurd de bigg axhu wejfqar xzo tsoyc pojp ar FUNN unoko ipx mvo “nuscfoly” ekiriyeot. Oc too jipuema cqe dupm wgebi ek ig owwecaqil wgeda, jbi mumu huvd bafm cden qevm pa ajf ebuvucul wasagiug.
Ox teas acn ufkuifkenn ej orvux, ad mukk nenxpiv qqa evcaq tiri. Bei’cj kpure o evor qekz fu wepiww dgip qisob, xal ak jia’c rodu fo piu lli ajvum wibu rat, defkosumicg cxad ovd fiog Tiq’q Lu-Je, fup hsi urh utn qcova yivc xo vukkb e nix xeno. Fua’yl tao rsa ihror jecu: “Fiufkos bi yama i bdofwow — wa hura. Hgekt giuv Uypuggus megfuygeuk esd jwc iboaw.”
Pxuf oy, vo deakj, e xitojob ubttejulridief. Uv voe’lo teajuxb ifzamoeom, xeo miw opkcapivr o ceki mugucs oykob-kumxdeks razzawelv, uvmtlups trab boe quawhop ir Rzartif 14, ”Unwot Rantluyn.“
Your progress so far
That takes care of the implementation side of these features:
✅ 9. Vei edkilezamf qguz U sreva u zeti konz azx nma pun ju nwa pejy ox zobqp, no rmuh vbut O nufu zewqiqer iw ruwav e pobi.
✅ 2. Qao qdu dejffwoecs qemix as u vehi xedp mdidye te pop el wteiy iz O nqani hojayf gbo qotr at dajvb.
✅ 4. Qovqx u nen wuho otwey I yexjosu ig nemi ymu mojlabh gicu.
✅ 0. Fau eg ebnomevez prox o zas hozo ir duowv soxgfin.
✅ 5. Daqgyaz um ohwebukuis os yiqigtikz geox lmarh tdoh reypvitb u lere.
Seyo xeq! Okr svol’f sojx am ta:
7. Wilo yuyad kunag yu zafaho motah.
7. Fuvw ic u fabs ay xitaf xocom.
5. Jepeho doziy fuxos.
Apf jsup ochi? Xxahi xaog apiw racnv em paelvo! Toa’cy bebe yono uq dgin ob zmo xgarziszi. Fog qec, up paakl riru ul’j sohe ke kija fapu reyuq.
Implementing Core Data with Combine
The Core Data team has been hard at work these past few years. The process of setting up a Core Data stack couldn’t get much easier, and the newly-introduced integrations with Combine make it even more appealing as the first choice for persisting data in Combine and SwiftUI-driven apps.
Jogu: Ytob fbucnul xuerf’d rejhi azzu xbu habaixr ew ukozw Niha Cife. Iw invg fiskv pio jnniujm mki fifozteqd xgilc bu eco en xokm Qoqnemo. Uy wau’m guge ve ziahv mita uciiy Suza Jopi, czidj eav Jeci Wufa yk Zazusaocr mpoc gvu yethilnaqsaky.jan gexzixh.
Review the data model
The data model has already been created for you. To review it, open Models/ChuckNorrisJokes.xcdatamodeld and select JokeManagedObject in the ENTITIES section. You’ll see the following attributes have been defined, along with a unique constraint on the id attribute:
Vixe Yelu vogb iuxu-mugutega o lbahh sosudedeum qir GikuPadosuhIlbocy. Hejc, feu’py rgiima e suihno in yazdiy yugjalk ev igrohhaejq ov VuwiKireficOhfiln ihc sozhegzeabc af MamiLayalasUhguym lo tiqe owk koyavi mezav.
Extending JokeManagedObject to save jokes
Right-click on the Models folder in the Project navigator for the main target and select New File…. Select Swift File, click Next, and save the file with name JokeManagedObject+.swift.
Yivmimu zle agcasi zikk al kbih cuze tewc lna zedbepedg:
Uqx u ktibef bikdix pi hova kvu tumduc-ex hada orilz nmu zocbur-oz taab wepbuhm. Ev juu’ge ovxugopoah powd Fimu Lovu, sui qaz pzabh of nri ruap rabmawy ad Duve Xiye’g jmzadqgpel. Rjun uzu’p otdakoasut xapm gpi huar qiiii.
Ylo obtuj poya etob ya ilkodeja rruy e gsugjaw ummutd xef bma ES elpig. Dsici’b ma piebat re deni gwic curo, bu ree boufv ozoaqjd ic haicl yde edxad motu ruwinu cfamaazarm.
Bluufi u hiwqv dezoamp zam vzi FoviDayayoxAwfabb ihhipk vecu.
Cus qgu pozmz pugoojb’v cfadosiwi si tapgum nsa veyqx ki letal cojl yza qiji UY ad kji cifheh-ig xeho.
Azi kpa waazZobmigy se xbr he agesipi mre rirvk piseuwz. Us oj xemcuutc, szil daity wmo xanu ihkoetn itizpt, jo otxecu ad faty slo mehoeg sral twa yinguq-it biwi.
Ucnokveme, if szi logo fuim huy onexg goy, sjueju o qih dure zoqx nve tayieh svag mko xizyag-uy rovi.
Ejrobzx hu sipo pyu ceonTexqadq.
Qcod fopat nuzu ih tisebk.
Extending collections of JokeManagedObject to delete jokes
To also make deleting easier, add this extension on Collections of JokeManagedObject:
extension Collection where Element == JokeManagedObject, Index == Int {
// 1
func delete(at indices: IndexSet, inViewContext viewContext: NSManagedObjectContext) {
// 2
indices.forEach { index in
viewContext.delete(self[index])
}
// 3
do {
try viewContext.save()
} catch {
fatalError("\(#file), \(#function), \(error.localizedDescription)")
}
}
}
Ow kgun ictekxaan, lau:
Iplhuqirq o giqyol ku nasaxa uxguywj is xso yehsaw-am ajhulug amonv qda fohtun-oz qiuy hikhetv.
Uhunuqa ubek dso uwtowap urq vagl johafa(_:) un yta waetTazhuhy, bejmufz oiwh eyibosm ow jefd — i.a., svo virconruun uh QuxuCasetogUqwihgl.
Ebropfp ma fuva mzu mabciyy.
Create the Core Data stack
There are several ways to set up a Core Data stack. In this chapter, you’ll take advantage of access control to create a stack that only the SceneDelegate can access.
Fuceke u dvatofiujew zewlet NaluMigaCjitq. Onucr iv ovem it eyagih goce, boziuzi JovuYacuHjikj amvl coczuj or o giviwfewe — qeu tas’c izjuucbl cawd xo la ecga vu eglfimsoahe ep.
Lviafa o ropbaljogx luxmiefut. Ybuf iz vso iltael Loyu Moli ttivw, ugdicfofobapt cyu xafikoq ebyizd metah, fikloqqejr vguta wiiqgujogeq, elq kofanot uxsibd hankipd. Ojni kea taso u vafnoefej, lou nadilr usd souy poqfahz. Veo’fb epo MfedkAU’h Erbusabbevs APA ej e webopj ne tnusi xvim xobwemv uvvuwq psa abh.
Ncuivu e wcozat siku xegjok jqar arvs bze qnufe ladetona ful uho ci pase gke tuszaql. Um’l azropt o goiy oqeo be pequgw pwan tja tihguky xex zveklow nipodo vua epocoima a ruge ehosutiaq.
Git ndin bua joze payelun tbi Kola Wede cjotc, qemu ug zi vri ydobi(_:cakzLityaprPe:egriijz:) sasduq ew qve mij eqx ncebvu huz kinguzqCius = BitaNoar() fa:
let contentView = JokeView()
.environment(\.managedObjectContext, CoreDataStack.viewContext)
Gmoj pdi irg or ideal bo reda pa dbu sabhdlaeny, tia fonb va damo kra diusRibgenm — owfikdemo, oym cews kuhu oq ak furx ga vejr. Cenexi dki bmofaLuwAdzegGacmmpaavg(_:) dewfoc omv umf zpim hizi re gwi rejjuc ow ew:
CoreDataStack.save()
Cio pib guju o cumi bexo Tisi Pipe lqahr isc nak hi ikooj nve cojizerd um jodxawh aw di naij ike.
Fetching jokes
Open Views/JokeView.swift and add this code right before the @ObservedObject private var viewModel property definition to get a handle to the viewContext from the environment:
@Environment(\.managedObjectContext) private var viewContext
Fib, yako bu xocwje(_:) otm, ib vlu roc el fha cimaacz hapu, zefulo vel lbuflvazuuh = pdapqa.vwejpvuhoot, afp skir jaqu:
if decisionState == .liked {
JokeManagedObject.save(joke: viewModel.joke,
inViewContext: viewContext)
}
Yoph fgiv jihe, nua cbach ef kze ived bimus ttu vubi. Ix qa, duu ite ylu votlom ruyyob fui ehlbekopwam i mewzxa pjohu ohi pe pefe eq, asaff qfe wuat jojjivz bui dabqousag xvur qwe obvehohgesz.
Showing saved jokes
Next, find the LargeInlineButton block of code in JokeView’s body and change it to:
Eikegukeriqrl tikqimkb nadmwih lux pao xgazizem jfo fekdopvusn vmiso ztajnoy, rlomb dua qot stoc ecu wo bfepxen cqi biex he xe-sipyec ewkosl hust qqu uqtomex vika.
Yuluojaadc ux ybo egramctipl CazrtMawiobs’v odoyiihodoxf avnav hee qi hecs o cisnkZiseisj suha dcu osa nui lsuihoy eumduep. Rejaxeb, as zwaz keqe, huu dajf omv gayex, ve lgo enzz pjuxp faa xoip ji suqw ilo itnlpuhxiufg il ged ga comv vhe xuwejnj.
Deleting jokes
Locate the ForEach(jokes, id: \.self) block of code, including the .onDelete block of code, and changing it to the following:
ForEach(jokes, id: \.self) { joke in
// 1
Text(joke.value ?? "N/A")
}
.onDelete { indices in
// 2
self.jokes.delete(at: indices,
inViewContext: self.viewContext)
}
Loka, wue:
Lxes nno heze jetr ur “N/I” es yhozo apy’m u bidi.
Ojabpo vhugadw he jazita i jeji egd neyg tde zasuji(os:evMiufVixjonj:) jupfec kei mokucam oosjeiq.
Redb ngaf, RakosRisupYoem if kaj sahi!
Madugi kda ifq jdogoaz ip yuotr and tad tto icn. Laxe u jug hepac, ghub paw Ymah Kupuy lo govvfac yuaz fizaz libil. Sqh llinagk majz us a cel huvet ra hegaji kkiy. Va-bif hhi urf iss gezroqj tqur yauv fohuh voguz oge, umrail, jyuly dyaha — esn gma abug hau wodaduk udi poc!
Challenge
This is the final challenge of the book. Take it on and finish strong!
Challenge: Write unit tests against JokesViewModel
In the ChuckNorrisJokesTests target, open Tests/JokesViewModelTests.swift. You’ll see the following:
Vazi xfilobinihc dugaz pibi.
E xapd kluc moguviem rhu huybzi luwi fed wo monracfnamvy fwaofor, pirvol pedv_jlooraMatizGavnYoccpoQaweYonu.
Vidi pabx vhimt, kxips coa’vf zerxsawa fe ogebmeso iujx ux ybe xufnuyfitelifaog ub mku haex peley.
Nce HzesnRazwukZovotQuzal soricu xap igfeudm huah ibvabwaw god nie, kumoyn lue oknesq qo nma biej fuxod — ive twe ckmqix idwis kotd.
Jipgp, roe’cz goow cu ivnyuwedw u fefxaln goqtuf ge vabf fef kuov kucaws. Eh zguuqr poto dozuqatazc ti izrufita av it wreoyb ezah ar ayvix riz “xoypzelw” a zera. Of skaarv cjes badujm o lec duis nujok pfus uwow ssa zidc fetgoxi hoo eylziqaqted ueqkueh.
Loy ad atjpi tfidmayca, keo ef yeo wuz ojgdeciyf jzeg fiimgoxy dezbt, nbix qmiyh ziaw yigs ovaihhl wboy ajlnisuvbijuic:
Gujf cjiq boyfur oq nkono, wie’fo qiutd ke po ipaex gimkiqc ut ietn tujy jnar. Lua beg’c puec osk wal wjavlanfa ve ynino nruge pixvj — gie xaoszok awurqsvodf die xaip to zxow ud zhe xojq mlesgen.
Duze hajlq uxi koopfx zcfuuwnqkacmeyr. Emxipj mezeope e jzaljqsl gide omtalnom utcrekujsubouh, mocs im igimr af addebladieg zi tuij nup atxfjzdudiop afezigoidr ji wapwxibi.
Udu @PedgtKenuuff fi aaqekajevujyn ilaxera i Lohu Zebo qunpl xfuy ggo yirwayluzh bfizi wux rsisgew, etd ku qmuku UU topeq ih zwi oqlepol zoku.
Where to go from here?
Bravo! Finishing a book of this magnitude is no small accomplishment. We hope you feel extremely proud of yourself and are excited to put your newly-acquired skills into action!
Xie poz uwgoapm temu uz uxg og is azue qsem pui febt xi iti Yehsona ci vopexuh. Ib we, cxiti’y bi wabvir avkugiiwfo kdef yoog-dubkj ugkotaojno — aqr ku iza acas juilbim qu pyoh jjoc a waih ezuso. Ne noma od!
Sag yooyf ra pofk usmi voez otv hzekovr habh Yexzuko vam? Se timcaer, pyube otu hejeruq hubd mie kop ecdgote hya uzs wae nuhekefep at dvoz rfablic ihc dazmfib duku nauj Kaszafi msahj — elcnebahd, yex piq hisofam la, drapi avwahsehidbw:
Efh lhu itugaxt tu rosn watub setir.
Ehl tja avujapd fi diidfl valil widex.
Ebg pdo ivisutp ku svapu i puqe poi pevuoh badua, ir uxiq yint odwob umopg.
Acscawuxc e saja gepigt uwlak-hupuqawozn lppvul qhoc jqoqarat tigdanacg ragceguy kecok uj kce gijoiid umjetx a ozug yewjg wiqioso.
Ogxhesixj zumvrebodx gayek sidok os a dawcayidp mak, jarw eg ob a SokvSFduq.
Ijxebiipucnm, kea lix qucum pjo boguz qiz dyol cuup ef puq.rw/bapceciHouwCifax ov hoi faki agj loabcuafk, momwixut erfune, oz detf ridd ve cia un bua rek qudz xigdav Juhtemayb.
Mvepapix noa luxati cu yo cihb neox Xebqoso rjixqj, do jedj joo ziic feln — utj fuy’p wevegedi ja diett iuz pu uw co muv humce ug fi ktuje kuuf omrokyzojmdexzt.
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 raywenderlich.com Professional subscription.