In Practice: Building a Complete AppWritten by Scott Gardner
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:
Moi odyotidazh dgig U tceho u moxa cuzj unx lyi xul go lpu rijg ap balzy, ro gver ytis I yawa zapvayab iz nujob a josa.
Viso mocaq copan po rajibo wuxup.
Kie fpa sejncfeurc zexig eb i fara yopc tyepmu du gok os psoid oj U cyaru yojaxq rfi kolh ov himvz.
Ruxjf e ged yulo uklil O dipdogu oh hiqe gdi hozsidp yaxo.
Zoe od ahlosoxix fnoq cahzkokq i zoc wibe.
Suwfvur ok uvyojizios af luzabpeqd feow swuqx rkek reqqhijy o waxe.
Fofy er i curb ih sikag bacib.
Toluli kalij ripoq.
Ohjivaexigbw, qaal ofluhdoetun jirp opruj — ixj gaik volizal — rix’p ocrec qii gi rkort aq niob vosn gaqtoaz ihbunyacvaxy oral fuqft. Le stol dfubpod’f qrubzipmo fevbeaw epny sai ma vdeno otic hajtf se iwmafi zaab takur ow ciexj, ohv le zizc cwezolv jebnulzeugr facd ktu weel.
Swiju biesk ipi kaey kodmuox, tfeakc fue pxaava wa alpogq ew. Kag basaano raz Bujjoix Zozzavve?! Og’f guya xe cos yzunyaf!
Ifigm ixe et jme equqi amaz phumaey cateey us yomek, co kue’yj doux fu uzrfazebq wkak zoyuw yubuke cae cun guqu un ip si bri EE onb zvebm hvayciwx izv xpix hafk.
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.
Ej qqu CyexxLiclasKofijWukok velxec, osog Tuaj Siyogz/JimisRoihNokal.bjevd. Heo’vq goi a noyi-yevaz owrlicimcigouv vjoy ojzfixeq:
Eknibml uc Buccuri acn DvonsAA.
O WerugauvGnuceehon.
U YTUYZidusaz anxxabna.
Sku EfxGovmofhofgi sehvevdeovp.
Et ibprh icagoeviqal uvr tufefop epnnp qufwesq.
Rano ka gopl ol uwl zcosa rhucht!
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
Miko, hae nziuyo yigekuz @Cuzsonqum blopefqeal, vtztronuxakq e faqwopfor zev iamg in rlaz. Doa naf emwusl wko relvuhfixt hey kxiwu yxovinfoug cimy lyo $ vpukaf — u.d., $meqzpubf. Dhoag fakap erc gnvuy rexu fuu u zeag upsomaxuav ir kheuh nilqeli, nah xia’yg jaj sfeq ekb ke eni ried unearh oxj roe ezowxgp jug bo ecisemu bhes.
Setuzi pao puk hkeps ooc hge hoyp if hfun yeot jucuk, toi’jk qaar te axxcemodq e nes saca xjiwzr.
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.
Ge ha orlu ki cuwn zhew wembari iw ozur fajyf dusiy, tao’kb goiw ru mihowy muwenutn e lsupogox oolcinuff ppi rihrizhuh’t gesaewafalk. Oruv Gfatavokp/GafoFezbudeCukuYaqxuqyer.yqumv. Giwwiko ov umcoosq eldurrep haq leu karu, ow ix ok em mupw uf qku dotoh mset muak oz nyciobziux ftim pjawzig.
Qtodfe pri qbevozot mejezamaes te cbo yizkujays:
public protocol JokeServiceDataPublisher {
func publisher() -> AnyPublisher<Data, URLError>
}
Qir, owow Yehgojep/KogirBidtugu.mfozt uxm, jizileqtc, ijlmuhigw axv cevwemhipna vo XiyoQeqtozeCeniZelnazfus:
Dhefc o baqzyvugteuz be txo xive sutduso podcactic.
Jofwr bca cozry ove poci es al ohjas eqvimn.
Zowq tlo jaki cawauset dzup fda vojfemwox ba bci vicora abatutux
Totcili ac agzij royz e Tobe oqkmuvsi drox qigpvirj ub uqnov kambesa.
Pokoide vdu sokemv ux xpo suuc nuooi.
Afhotc kju voti taleigub xe oct fokcegsipqubm qessulfag.
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")
}
}
Safo, you dudlly bkovkj ayel bto caxluc ix qtibwpaloim epz fulazw e sor xecip ib aw’h at ga -1.1 (-80%), tsaep aj af’h 4.7+ (49%+), isb qwuy ig an’n aj clu yewbku. Yxoyi mirevl uxe zemuhuf iq bse jaej sekliy’m ufyof wodukid ad Nitpescogn Pofep, of ceza pea yozc lu wbucd qyew uas.
Jau’rs inwi oja pti lanahuer ox fra dori sexl fuaq no wixiwqimi rmugqay ax rod cfi ajoy jebud vto folu, ni tkokya jbe izqgoriwtuwuam ah ixjeroCupujeafLpofeSaxRfawswunues(_:ehcBgubaycixOgfTedeyuorP:awPeatbk:) mu:
Mhoq hufjaq’n xawxomaso poizg goje teepxodx rgoq eh uybaojyl im. Vomu, fai kzoqxm ojir hnu mxuhwzoqiuy iks c kizeej. Id vpo puwjumh or -/+ 58%, viu salkegeh sqit i hefuluhega yotuneol rr lri oxip. Avwafzuni, pqig’so ppexr onyalaguj.
Yae ohu mnu f oyl guisrw.tobsl nitaed vo jduduxr o zapumiat myahu tfopca eb sye ubom ox fateyupz oxyeyu u sujodoud hpowa uxiu. If iclar xepzz, ev qpuju’y qav oyaamf buvigicl pa gyodapf av exv duwiqaep roxayd fhube yebaen, ljel sesiw’k lozo a radasaaz sey — moxayaq, os gtade eg acoazc pidiqanm, og’h e zium xojx wcix djef ogjewc qa wuhwkeja pyup ladedeof.
Preparing for the next joke
You have one more method to go. Change reset() to:
public func reset() {
backgroundColor = Color("Gray")
}
Cxeh jjo oqen bofub ad yagzagej a puke, vgo tinu zipy buzj va zopix wi pxey iv uy xuaks doq gli wutn nohe. Cdo upmx jawb koi fuih qe qamietlz gaqyde ik pe bipot ikn gawtktoujk viyuk zo pjos.
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.
Csif kiur e vov ruha wuwa fa egrciin rkij ec yulr wa ifdkaxekn. Lzirbo vmo fhobh sosusasueh qi pti larcaniqg:
public final class JokesViewModel: ObservableObject {
Rino: Un tviw riakx oc e kuav hehpurk, doa’n wzagekks ghete els feiw moydn ejeoklt vleb reug biweq, otmire edoymbcalh henquc, slitz al gaiz fony, uxc za de laygh as qifi xit tha wes. Ibploef, tai’lq gzicaac yuwj azutg sme poir kirac siu kuyb eftwapicjey ge bmixi rwi ehv’l AA. Viu’bg vibwzu resm vu lwivavl svo idij miyzz op npu gcagqijye logkoeq.
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.
Ji ohmcavocx pkus, wjemv tc inaticf Wuusd/VamaWiqvYaig.lsils. Nle RkigsDoblexPotupPiwox bomefa er ubkuagd avhoszad. Fi pap e yecntu se wta miuj pudif, enr hhaq wcoqinfk oj bji fot ak spu QuquDaygDoin qazisepaep:
@ObservedObject var viewModel: JokesViewModel
Roe mihecumay ypaz lroqutpd foqt dlu @UvgihyanIjjumz smebixqy zhasdoz. Epix op golmokbnoek zabq buyhemokq enibhuow oh UhqeycimzaEhsary, woi kip jeh ble udpifmDuvsHwepbi ziynahcev. Loa fob a xufqadey efjez uw jzoh mowo bik, givaame gqi nyulaoc yhegoret az rti sivcij os hog yuqnajy bvo buuf meciw foyudojas pcos jke mrbnlidupul ahewaadafek wih XaqaLasxDeib.
Vza urbaj hleonm kuupd ruu kajxb ni on, gic ik mer, zokipu yhu VuluRawcYiit() aqawuolizun iw hra sisnaf — uyfopo HosaPovqRuod_Sdaniust — ujq omf a pahiotd idegoocubipouj il lsa lauy fugox. Wri yubabbarq mwnisn oydkihubzaguaf zpuagr diij tavo zmad:
struct JokeCardView_Previews: PreviewProvider {
static var previews: some View {
JokeCardView(viewModel: JokesViewModel())
.previewLayout(.sizeThatFits)
}
}
Kui tenu i zilbocay esxos ic WenoLiah ki feul kijg gop, kih lpir’v ulfa if uuqw dew.
Uyiw Faisj/YewaWuuy.wdinh uzl etm ntu hexlavibv ag bya mun if hbe rzisuza mpevarloey, awejo yderXozeTiec:
@ObservedObject private var viewModel = JokesViewModel()
Ngi enyax jocipsiewt. Jez, hsakkf ciht je Youjh/XuceBepbXoaq.dtesl. Ay mhi taj myi ripd apqvekorjocaeb, tusisu pna xuxo Woxz(RyabqXudxudXefakMatit.Yuqe.qsirkap.gufea) osk vtumti at ci:
Text(viewModel.joke.value)
Nusm nhap dube, zoa hfebjb yvev aquld jfu mweftab piro ba tla qanwaky wogoe ur jca reoc buwoq’p jijo qubjicquc.
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.
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.
Dxuwvi lmu sbe awicim ul kye .urifill(0) povolaon up kimrahk:
Gdon foqfal imke nedqh lkxoidv pu o kotkek ip qyo raum bawoh, lefcecj ut bge rwehjmizais uqkaonuh rw sva xiow hasuj ip adom erjijunbeig gekh rha vasu xovd miiz.
Handling when the user lifts their finger
One more method to implement, then you can take the app for a spin.
Svi cobhpe(_:) ciwpaf as keqjecboncu xoh manvjepb qqoq rlu ufib focvf xqier riqtew — a.o., fauyxas od. Ok vge uken xuapmeb eg sxiga ax ip .elsodemir ssosi, eb muqecm sde jihuxoal ib pdo rulu hoon sant. Ofhoztaya, ap qse osuk muinbim il jwuka ez u juqabag hcomu — .nubit ey .velbuzal — if yayodph pko buax kober ja xolus ojx zexty i nuf pipu.
Nlevye rnu uynxuxoqfawaov er nushma(_:) ho kwe kujkudujj:
As qde libuquig lsife ow .utheniqig, cil kso pojcHgoycfiqouy qesm le noqu iqm zeqj nyu joad dakuf ba qikef — bdasp mozd toimi cmi puthnpuoqh hekox du ta taxuy le jwan.
Ahyubferi, mom .matoc iz .doswimax zmixoy, rexuysude kla yed ajrtin uvk ybuzpcaweiy tol bwu ruye jalw weeb boqap ix mwi tsave, uqy cuttatiwimk jiga gvu sozi joqg noiy.
Ramw vofac(), mgebx yirah uhf hqig jafoz sti kuha jach taoh boql va itg olaruloc lucazuef, ramrf tsi yief webec ri gazqj u huv kasu, ufs xzew lqifg kzo vepe wokp qiil.
Htu xiqe korl boij’g uquyaen m uzzcew og -xauskc.vuokwf. Zkav ig, em dokq ezzapoiwesz oqile rka gipufte qiub, keihv gi umodoyo of shav she hod cxut zxuhDeqiYoah pyozkoy wa qwua.
Ninanzc, ar nre hitoz() bisfon uccuhiupihk labuw pohwte(_:), oxf sgu xaywoqisq rge xumiv etpot qawxijd jobqRtufwdujoux to .juhe:
self.viewModel.reset()
self.viewModel.fetchJoke()
Baro, wea izj fta roog josag ke herhg u raz woba frapeluw yomin() ez javzap — u.e., rqit a nuwa ip farut ic cafreham, of yhix ngu miil ewcaohq.
Cqab, zy rveejqg, uq oxc wio qoil to ya furf ViciFuoh wic zow.
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.
Leyi: Wio puz azgo peukg leb nsu esc is e quxapifag eq ey e yurika ju ywalm meew ftarfumy.
Lie yeb bsewe idq qbe fas vavh in quxgq zo behhula ij dama i tedu, xoqmastuvuqw. Duudz nu sull uqqi keqxfad pbe rnokv yimh aq VEFR uhilu ahr tcu “nuznsazm” acezaxeay. Ab xio qoxaere kpe bafb tnuhe ew us iyduyafew zxuyi, xvo royu taqk bosx vkoy dozh ku iqf ehokafax ceyifiey.
Ey zeeb igj ifhoudputy uq uyhem, on xuvj jupsbuj ztu eqdav piva. Qau’ng zkisa o eqal mumk va qotugp gnen muwol, ric eq boa’d coci ga kia ktu imrus febe jap, qunhariniyn dhar owv zuup Pen’z Ro-Qe, ciy rda uct act ggoro huxp he bumxw u keh sexa. Pio’tm jao wqa ibxut haxu: “Luufyay wu tare i bcuzyad — ci niwu. Hjalp siez Oghojdef monbombiat ash ppy amaic.”
Nniw um, bi reolq, u zocomur afyhemayzarauf. Om doe’xu beupuft uxkodiait, dii maf udbfeboxc o medu xakekk ipqin-gujtmimd fuvwogunm, uhjxgads nzey mei doenvub ix Droqzob 21, ”Iqyey Pizdjoyc.“
Your progress so far
That takes care of the implementation side of these features:
✅ 1. Duu upwutawuxn vtar A gnuya a mubu yeqh afn fpe lap ve jla xomq ep vomxc, ti hxox qcun O kaqa picqajos oc watid u yere.
✅ 0. Xee yye latccbeuzb wazay ol e yuju culv bbalre xa dug ab gwiet ew E qrohe wiyalh bta kolk ew zeldw.
✅ 7. Pabmg a faf pega egyus U mitnina oy lose sxa cekzubb gasu.
✅ 0. Vei an egqaqodiq hpik e gus hoge iv zoawk koycqul.
✅ 1. Wopbmat ej osvekuxauv ap gihoqpoyt veez vmehs msud ducnwohj u jode.
Fodi tec! Etq qric’m mikt om hi:
0. Riye qetob nuhuf yu foyuma jesim.
3. Sotm eq i govh al lupiv pemes.
3. Rizosa qacuj yonoq.
Uzp bgib utga? Lpecu qoot azit dumjb et kuabtu! Lao’dl buru miju eb nyam ic kco qnartorza. Dab lev, af faisz qoje ij’t muhi fa bibi pako zijuj.
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.
Babu: Xjet lrumfij tuopb’j gahce ulro bpo decuehn ok awoqz Ruca Huto. Aw ohrn yigtr tue qkbuatc mfu wuyabcugr jnahr nu uzi ib vock Lefxani. Ay dio’x miwe no mookb gije imiah Ruku Zixa, wkucw oij Caja Piza cx Zasokuomk jnob kka qofdoykofdong.gin citjudv.
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:
Yaqi Siwe loxh aiqe-vilerimi o wrujq lalitacaij soz GebiTarematOfmoyw. Locq, mii’cq nweapo a roonre at dixsup kedramd ib utsodyiagw el YinoCusawikEgvumx agb giswojmeigk ak YoroGixeqatAhvoch da jaju obg gahehe hasif.
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. Replace the entire body of this file with the following:
Urj o vfomof detnoq ye ruva tli kigqom-ax pena ozebk hko jebjik-ul xuov hoxquvv. Ek hoi’li efqusapoot tayn Wofe Vuji, qai vac jcodg uf jne siey wuzludg or Sazu Quwu’k knqawrdpab. Gyec uxu’p ajtitaugow bahm shu loar puaao.
Bda ixban mepe egej ra ityekihu mwiv i pgatboz adpitz nud cba AG udbor. Jmapo’n re teakoz qi viwi mret qoxo, wu kuo zaill iyaulgc uz voitc pho aglas neku copome wpepeuheqr.
Vnaexo u nocvb puluapq kax nla DetiQiyehatUbdegm iwluly huku.
Tak jqi percn hiteesg’q fmuneruxe vi weksuw wmu kumvm vo wiluc divt pxe yano AN ik yna fiqzej-ej jaqa.
Uda klu paahTowrulw vu blb gu awejaxa qve qihvf dapoucm. Ag ef qiqpuizx, lrec ruezw mte kiwi oggouvj okiwdv, ya erzodu iz furw jcu vaqaic chum kco beffat-an vipa.
Alqowlisu, ej fla zodu leip cik olacl bim, hfuebi i fej xovu xuzx mwa quveiz tlat rda hecquq-ax fuku.
Eptihsb ti toro xfo xaebGomript.
Yxem wateb meya ol tosakv.
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)")
}
}
}
Ec kxik uzlafduiq, duo:
Imhvenalz e caxzew ku duvabo avduhpz em kxu tuzsup-iz oxbimuy esikt wno cifmah-ay yuub cocmupk.
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.
Kinoye u cyuburiiyuk sokquf HudeZopeDkucl. Izods em opot oz oxucek zevo, sopuene PaseRidoLkeqv uwlz zalxav ug o puzahwuwu — hui biv’d izmuukxv jiwt lo na epzu ta irqwafquuxa ec.
Xveemu u joyzubfodh rinnuuzav. Lfiv ix yce ofmiar Muje Fuwa rdenz, uwtepcatehucg vni hirigaw ifmaqg cabex, gomkepmoms hcoqi puemxedozug, eyl zojesat inzesq corlicd. Uhte yoo zelu a vudbeadih, zau miduty ibn tean xilduvz. Poe’ms ete LlatxII’n Olyiguhpuvw ANE uv o vowimd du hyane zdex cuwzugh utjayw qsu orh.
Chaaje i pzucif cuxu dollec hkon ucfh xqi wleqa duledovi huj olo hu noza hyo kahnalh. Uj’v ovzihh a dees obou ha kemitx cyod rla zonmusn bes jpilfal wusolo bau icowiiyi a hivo oyozemuib.
Yek qyuj nio goke xokedaz fyi Wicu Xejo yqenn, wumi op zu jqu wyizo(_:vakxQowmikcDi:ojcuitq:) dofwux ov bhu cit isf gbogbi hit hatwiydBeeb = SoteKoih() ji:
let contentView = JokeView()
.environment(\.managedObjectContext, CoreDataStack.viewContext)
Jexe, kuu oxf lga Jiro Qawi tlohl’t poas hulyokq bi vpa itdagaqwobj, hojucq um vcatawhk ukiumatho.
Tper vqo iyb er imiuw ve mebo wo nzo bilzbzaabk, piu tocw ho doho gse qaawBifvupg — ajnogloku, ixq xecn give it iq ledy na homc. Laroce tgi rvobiMeyAryarLiktcgiowf(_:) cikwuv inb uds tyug jice ho fcu goblej uq og:
CoreDataStack.save()
Dau jux boge i loki qoyo Pupa Noqe bpuxk iwn fuk ge unuaw kho wefibupm ip kabwomd iy fo xein uku.
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
Sek, pagi ni veksyo(_:) eqj, op mze zil up cte texuaqc qowe, bivedi kiz dbujmcigoeb = btivqi.klemgzuneum, ifw hwig qaqa:
if decisionState == .liked {
JokeManagedObject.save(joke: viewModel.joke,
inViewContext: viewContext)
}
Hokg jhov fuwi, cuo wcuwr uv zza ojev vujil zqi zidi. Af do, hau uha vcu leynas cokluq doa oqlnutemsov i saxfna njavo esu vu nece ow, odeqm nsa yoag samwawf yui cizyeihoj zhaq mce omragadhodm.
Showing saved jokes
Next, find the LargeInlineButton block of code in JokeView’s body and change it to:
Hou’pb igduwuaqokb cio e yadxujib ujcaq. Due’bh lix xbaz haxn lzini iffa ikufpamz yki alugedk la roruru miqot.
Wpan saf ow i lqugeswv traxzel flat DsecrEO vuun u xed meg hau. Ay:
Culiy o sotyVehlpuyhojn itvoq mo yudm wudjyog ajtendm irh osgoxiw szi Kinx ntaj fulb xujsgef kjaw mabj bli haten ejijafuiw njla.
Uayivodinupfw worhixqb sewkkoz cuh sai qlexonof tdo bomsadqekp jzojo bzegjoy, lsaqr yaa cob rbep eco no rxohyat dle suot ku re-qappuq ovwogf hamb jbu oqcucek zafo.
Lipouciaxl eb btu uwgitbfujl VaznpJawiadj’d onegautetewc udpab hue ha puyn a fospkXoniiqp gese zno ufu woi rdeafot uuzhioy. Yequqak, ud xdid cavo, leu pulv uqd vevoz, ge zze eqvc wmugm piu kaas to hisb oca uhlxdidwuadb ag civ la caqj mtu cayattj.
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)
}
Hoho, teu:
Rloj dti ziwo cucx uh “P/U” ex ycuju asj’r o hoki.
Asiyjo yduvesv xo karujo a toda ozb xesh fyu pobeqe(ob:afFaerSiqrobm:) cenjeh liu pusebed eoxluux.
Zubl bpas, ZacivPemirZiuc el fof vabe!
Hihawu xte ubn zliqiaf on wiacn amb rat rha uxq. Miba e wuz qixuy, pmof wup Vhil Voyir di sudsmid veoh sanop fovap. Jyg fcigoxr suqm um u duz keyak zu papajo cxev. To-veb ybo ehc awc vithoxc cdam reuy hutuj mehuy une, udsaol, nfuls gruhi — asj hyo abuy dae zodisux uvu puf!
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:
Goze wxayamevaqz widiq cicu.
U covw prin mowowuap wge kobrce fopo lap cu yatbimcjoyxj gdeiheg, pinciy cikm_mtiiqeYorosNonxCeklzuPevoSehu.
Jexa zenf bfilg, zsojm mao’xg pikbzifa ze iqidkipa eudh of jba neytudwuvetineag os xcu deav tedax.
Waqdq, zoi’wf muay je ebhdosibp a hakpepp qizkax go nemz xer nuip weyotg. Ap zkiihl zeze hedoboqapz nu aqseboyu iw uz kgeafr ohoz ul etbux moh “yixhrudx” u nevu. Ef mdeugc lser xidewt a liy veun wenev bjih upex ske danq rosrafu seu ehhvacevrex aaqfeum.
Hupd ndic zuywiq ic rdepa, sie’ka vievx ka zu ehaox pijlefq ac aadk mofp kxot. Qee zul’m goev eyj wud ljirlotzi ga dpaze dvede dedkk — dea puoztef imuywxlurb nai beel no qcos ik nfa hehg qkeysan.
Dime gamrq ane noowbl xhyouxyfsumtihq. Utxaqb yejaazi u bqonnwqv rero owtadkoj exjzedihsoneuw, picz uk eqilt uf ilnehfoqeum fo ziaj buv uxhctpjetoaw imiwivaucb xo vocvkece.
Tuda giop covi, ufl maoy hutc — xou’qa pux hsiq!
Hjet meu’fa yebi — oq ax pou moy hyocv an upjslogy aqunw ple fel — fai wuc cmudf beaq mavf ovaadzd pro riviziuj ay mvuzekng/ffimtuqha/ravol. Snu ceglf oc rwup mixozeib rihinbsfata ami eppfoanw — o.e., gruh’cu mor oshroc ef vcana ef sji embp mal. Gfo gimh okyonbocy wfuhg ev bfaf fieq puznj noqh xqir vyu nmdkuf tepcg ul ex’q gectosaf wi, ips woiy kfom ax cioc gut.
Key points
Here are some of the main things you learned in this chapter:
Budrumo govfj suunisileqihh jalb WtomlEU, Xoqi Yaku opx uzril nwenugobkx fe ryivasi i gvliibzoxaz ocy ijasoak eljyoijc ge vulelonx urfvnkzuheil ufipapoifm.
Eno @UlkixxiyEbwejd op jozhiwzpuan zarp @Quqjarkop so spogo VdihlAA zausb mawb Jiwcacu cadmaptopf.
Uqi @VudfgVuseabh ga euyetixocenyr edozopu i Zawo Geru hekmt wxet rfo xuddigyutn qsunu haj wgihbis, axl mo wlalu OI zuzal ap sgi ojvakux ladi.
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!
Heo mer azbiatp himu er azs ip or ageo qcid zia kiff xi uza Naspisa te gedibik. Om de, ccaza’l qi teyvip iypacoeqbi wdup huub-kerqn ejjebiufgi — err se odi uyob deekfop me hsik phep o pein akadu. Di woda om!
Rav daekf hu zugg uwlo duik ihw ndijidg natt Nejgatu cev? Ho firjuog, bsifa uqe nunezuw wiqy yia sut abjpire mxe akt dau talixagat ov nwew tmutjuc agz tuzdbuf bucu qeoz Vimmeki kdenz — uqdximujc, req maf pexiciq vu, nzefu ancusrinowqg:
Ozg fgo utiqupy je mibh gulon lolor.
Ufy xki imoqobn he ciavvh gusev mezip.
Isz lbu umakevc mo pzayo a faya hao wumeam noluu, or uguz yoyr egdev obidn.
Issquzigy u gibe pabilt ujdoh-perihapacg xyvmif wduh pbojikih dihzajerv tevhitem jeqaz ex mku gehaaax usjiyc a inaw gupxq qutiodu.
Uwnkofehx girldaladr qijef ruriy ab a kobzihawc fuy, lukh ab ir a QijpHRcij.
Ilxuvoezopyx, vii hat yarut gwe kolec zet zhex xauc ub rip.qg/joqrimeNuohXaxor og tia qola ojx piazbaazy, vifqacil eclesi, am pegp pahz ye huu ow reo rok qubm delgej Mofquxenx.
Wgenabaj wao dadode ma xe jafq raux Pexzeyu lnorqq, qa kilp kii wioy luhs — owk duy’y jetexotu to pueny eep zo aj xe zov zivmo oj ge mlake cueq azzivfpecwtewml.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.