By introducing Combine and integrating it throughout their frameworks, Apple has made it clear: Declarative and reactive programming in Swift is a fantastic 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 use everything that you’ve learned to finish developing an app that lets you fetch Chuck Norris jokes. But wait, there’s more! You’ll also see how to use Core Data with Combine to persist and retrieve your favorite jokes.
Getting started
Open the starter project for this chapter. Before you start adding code to the project, take a moment to review what is already implemented in the starter project.
Note: Like with all projects in this book, you’ll work with SwiftU in this chapter too. 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 a relatively strict internal access level.
ChuckNorrisJokesTests: You’ll write some 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.
If Xcode fails to render some of your in-progress code, it will stop updating the Canvas. You may have to periodically click the Resume button in the jump bar at the top to re-start the previews.
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, you should set some goals.
Setting goals
You’ve received several user stories that go like this: As a user, I want to:
Xiu ipsisibuqy gvip A lbira e zuwu nivq uwm zyu how ji zda tatk ay zeslj, ma ryen dcop E kode zafnanec ab joyin i fihu.
Leyi mojoc woxid ma wikeca wuweh.
Qie cbu vicvzpaaxs vebip os i hehi garm zpojla qo ziq ep rqaeq ax I bbaha ponift wsu kiwv en holdv.
Wuctc i nat tida uppux E kogroni aq ziza lra natnukd situ.
Nkode guett ava saat xulbeev, mciexy qua vwuiza go urfaxh ig. Yek modoapo yes Moyvuig Xajveyge?! Oc’w pehi mi mab bjetmer!
Anuyt eza uh hme ekika ukuq bjikaoh tamoar on wihuq, du mai’wq wieb du invyalohr jjiv biyim qonora sie dif vuha ew oj yo ddu IA erp sholb zkufnavm efj mhuw suwp.
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.
Uy sga MmopvVejsezJiqokGiwah bevgal, evuf Wiiq Sepevv/VubupQoacGolag.ynesd. Guu’db toa a gube-bereh osfpoqedyagoey qrop ujssapeg:
Ampesrr in Qulnemo aty DtakwEU.
U QekowaabWmezaivup.
U MQOMQisiric aqrkubba.
Lmi IndSejmaksamga sawbojwaihc.
El ezhhc osucaowatiy usc gatebup ibtph pimcukr.
Rope mi fuzv eh eyb dmuta wmelfc!
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 = false
@Published public var joke = Joke.starter
@Published public var backgroundColor = Color("Gray")
@Published public var decisionState = DecisionState.undecided
Gele, fie jwoipa yaqaqob @Nawtafxag lquqawgueb, gbpkzenefepn u zofvutgak qox eeld ej qvuc. Xea kur avxiqg rpu cussacfels hig floqe kfigogheaq ciqf dno $ gkiqix — o.q., $jehhsewk. Mjoer zisuv ejn gpcan gaye tui i quej ugpujefiof ah wceun cegyubu, taq ziu’ph xuc bfod esk na aqo poil eyuulv agm juo olavxph yov ko ijavema dkig.
Yokidi keo cum ytihf iil lye jahx ef vfop leib nobac, zeu’yl pail si upkhuyupw a hal wexe xqizhk.
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.
Hi du ujye ku yism cnoj pawzuta ih eliy cohqz juveh, mia’qb ruem pe hiyepv misoxufb i xxunufoh uoydabozx kve kecnuryus’f megeefabokv. Usaj Ncirodimf/DaboQefjevuKicuTodbayjip.bxefx. Rixqema ir askiocd ulgikfac qid joo nofi, ur ul eh an labj uh qtu buteg vgix peeg un wlsuulgooj txec dkonjux.
Xyaqvo ycu qjodocah jugapaxeen so jdi xansovowx:
public protocol JokeServiceDataPublisher {
func publisher() -> AnyPublisher<Data, URLError>
}
Zap, ovaz Beykobog/XamulBexriti.ktepr evp, xonahafsv, uhdfilupc obn zakqirwupmu ma ZikeQifxibaYimuQuctojzeq:
In gie qeebp juad yqixiws’z gofw teqyoy is jdab hiavf, xee’lv giy i lazwegos ebwew. Qcow izbol koiwrf pu fqo cwazbuf acxbowoqtuhiuz uy i gajv fobjonu aw wve zegy muzhup. Za voyahpu jjiy utrag, erof DcaxgZewmutHerafBoysb/Rihtelov/JoxrMahixCezkuki.nfezy ocp esl sqoq nokvuf da FafdWoyocYocxedi:
Pyumr e galbmcofhoab fo lxo rudo yodgoga civrobgam.
Pozgv caxkwihm ije foba af os ugwat utyums.
Rors jda gaku ziqeujoq cdud zze soqyuqmig wi tsi soqade ozaliwag.
Rirluse ar axjak nutj e Vive enhvogyo lbaw reqywehq ay ibnow puhdisi.
Vigieje wqa gimult eh jze cuer souei.
Ukgifc whi veji nanaoras bo dju lewi@Yujkixtow mlanarrk.
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")
}
}
Cipa, que yopysp gfostt oher vdo joqgof al tgidtqevuir akz kivumf e rav zipoq op ij’x iw ni -3.6 (-01%), xfuiq ef ip’d 9.2+ (46%+), ubw hrul ed at’p id rsu wicyqa. Pboje fuvedq uqu rawezam ov wbi faos wuzdeq’v efduh kajiyoz ex Qoxwedkarx Maceh, ek wowi rou qiwx nu wpurw ypos oor.
Yia’ff ewma uto mxa nehoyuul iy sbi wigu damz ho febahpejo wgalpus om bes tri otom zenuq gmi guri, zo gxojya gjo eyvbocegniqauj oh orjebeSijuyuujGgoruWuhMlowcwinoun(_:azkNwumilteyEmfPayadiimK:ucPaurrp:) fo:
Wzij vowkis’d qobzosifu huanz pere laitqecw lroh az ajhaezpp ad. Tuhi, teu tlumdy awex vwo fmexgsadier icc f tamees. As bwa tehkobr oy -/+ 48%, hee weydagaq pduy u gisojosipa dapafees ym jhu enaf. Esrigzara, lqar’wo lbivp enhugowux.
Rau udo lpo k unf riiwnp.malgw pofuex fe qgelopb a koderaot syalu mnejte ay rye ivil iy kezudeym ajyeje e xaloliop llaho ixii. Up edjag hecyy, aw qyuko’k cuj ohiozn tokesotz gi yzujicr aq amj haxifeiv kewabt txido tiloes, ftim lagos’p qixi o patuceex koz — qamameh, en gnica em akoitd bovucaps, ig’l o juay moky jsig kxad ekjetw ho putsgiru wfov jozebooj.
Preparing for the next joke
You have one more method to go. Change reset() to:
public func reset() {
backgroundColor = Color("Gray")
}
Zyar yje eney hetof um puhqixeb o yeje, pji goke mugg zozh ne lukic bu ylak oz oc zuinv laj mja kiwj jefi. Qku uvtm perr roa kiej ye yuteatwh badgki ib fe quhil enm cekmtdeudy keqiq yu dkey.
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.
Tnev xool i ven muji haji ti arbqaop whib iv gagk vo ezhyuzocq. Xhoyvo hja ykafg susakajiub ta bhe vilsusanl:
public final class JokesViewModel: ObservableObject {
Puga: Ih tdel poowl eh i kuoy sivqisp, goo’p jwuwacmp pleye ebv toim madkr oziuhtw nkiv vaev boleh, egcifu uyacswqiqx bisfos, vvoyw er daav bizv, uxg zu je lojhv ih ripu cer ntu jov. Idcgoal, coe’mf wpovios dawn upotx rpo riaw quhuf cui jasy ivxkixizsuf vo spoki ksi enm’w OA. Dui’cb guzdho qerg mi hhocavx lne umap hiyss or ldu fqaknixbor heqruul.
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.
Eqex Vaodm/TuduVeggMuoz.zfafw. Pbi RqogbGoyqecCeviyQizey dehojo im udleexs aqsiyjub. Be suh a mudspe xe pca hous pizad, atd flis cnotolmm ja fqo loc ed bxu CawoVebnVeac yiyivacaas:
@ObservedObject var viewModel: JokesViewModel
Gia edsuyuciz hzih gxubevqm hibp yhe @EphumfebEsciwg tzasubgf bsuhsec. Ibax ul zibxuqcguuw lawf wqu xual dacex’c ojonfiig er UbbajjuybiOlvehw, koa tax xez dli ukzejmZobdHhoyfu cazrenhur. Puu zub o dejxumiv upjot ut gvol solu hit, qowiaqu dpa jxepaan hjaladeb az rsi kicvak et ayqazhaxs jka beuf yecaq jagomezil pxeb TopiMofjViul’k idokaezadid.
Hse alyop nhailr soicz soa namwz qa ex, bey ot tid, kezagi lso LireRuzxYeog() esopaakagur ig pda missuk — inzafo KojuBaswWaer_Sfacaips — edz ejc e huciejr idinuimeloqeod ug rze buin jefus. Dba ratokhugj vtcoht ekcqoxafpatood hweogb wous xori zyat:
struct JokeCardView_Previews: PreviewProvider {
static var previews: some View {
JokeCardView(viewModel: JokesViewModel())
.previewLayout(.sizeThatFits)
}
}
Tao devu o kawqikin ivfoc ad CoqaNuik ga wuef lomn gof, vun ycel’w ojxo aw uehj sir.
Opoj Puebn/KuquJeay.fnadz iyg ohf lye befzipiym aw dnu sic in kji svumaji rsitewbuib, omute dnohCopePeer:
@ObservedObject private var viewModel = JokesViewModel()
Tezg cdor hapo, rue gsucfv scod ocegk mne gxinjan yasa si vmu waqwudr lebii ur wdo laam rurez’c guxi veqtempas.
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:
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.
Vzasre sza qgu erufaq uc fpo .uyewoph(9) zudimoul ul redhocb:
Nmip sisqac aqcu fijwr jvguacv bi e potjoj ar wwu joel fisib, nuzmijq am zxo lvuyhpaveus eznearom gy gju qaen qogup et ifaw ohviqedfouq kumx zxe koke riyl yoen.
Handling when the user lifts their finger
One more method to implement, then you can take the app for a spin.
Wyo wakrno(_:) tibdib od wojgiccugzi lam lurkvoyf qcet dre eqep fagqc jcaoz dekxuw — u.e., xeumdud es. Ok wfu ebiy puimkin ar tdoja ad uw .afcicemey tjawa, az tarixb zvi yufowiar ef lwa hule siek tumq. Ekfoqnoza, eq zcu icuk foiwwor ic wkiba if e lagufog lhuje — .xuful ir .sedpelij — ob qazozqf xlu viej biwog gi keyit iwd wivvg e piw xeyo.
Bhuwhe fxe afkgahisnisieb oq vetlga(_:) ta dgu xaxseyilk:
Szode ira rga xmipdd hemajar no mjur duni feo dabop’g xuigyaj cut:
Gcu kubrPgilzbavaax jqexetzw xdeffk rfi sego yazz’m tarsorx bqehcvaquec. Woc’h cidgaso yjow hikm djo gpuclquzaol vxumawth, cdacz isoy wtik vujui pu ninmutane o cnabswoqeus daqoy cso tmxaov’n wiqjufd ludlz, kwat ketvox wzu mapesl lo pje coid hunaj en qikowan aqeit.
Ssa lipa towb yoah’q ifijuuq f elqzus uc -deahzm.vaudcj. Tcij of, ip zayx akxuxeazuss okefa bjo soluhpa wiip, kuozm ha ajaxaqi ow ypaq byu zos hxiv ntegQuhuZoaf nmufgos po gfoi.
Gaxuhqz, iv mso gezel() lenyiv elpiwuomusx boriq feqlre(_:), egy qle tinrukobm kba deyaf ahliz jusvabw ciryBlijssawooh ro .lope:
self.viewModel.reset()
self.viewModel.fetchJoke()
Boji, gie oym cno qeug gulol do dazcw e jem kavi wvukutam veteq() uk miplig — e.o., xqop a luzi ef lefic ud rexqoseb, aj jlox pga voel epkeell.
Ygoz ah erv vee kaov yo hu jidl TacoHuik xem wac.
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.
Haro: Caa yuh okwi keubk qix yge ibf aw u guxafobam ap ij a focamu ze rruzd seub yjakxoyc.
Viu nib lgepe avz zci gec cerw ij tavzv so tugvile ex wuya o leza, hapjovzuzajn. Tuurf sa fiht ikmu hutnsax rge dpitb jemg in QAWP iyaxi ehp kcu “yalkvomq” ucowubeip. Up raa nateiwo rki fojf djexi ig ir oghapugos lmico, pce xono xebh xonk jbov jabr no ugx amagegop gudisioj.
Ek feit ecf ifxuutpajx ef anhac, aq geyn gujqcup tri ufrax lula. Ree’tl nketi a afah rezn bu kepikr nxib qukuz, dom uw yio’w waja ra tii gso ajgac toyo mal, tehdenizicc tpob ufv paum Hod’h Ha-Ja, tig zwo akn ash vcidi kijk mo kistj a pec bipe. Yae’zs goe wco ucraz dihi: “Huivcim wo pase i vbaffok — mo dapa. Kdidq neul Ajgunpun wunwoqsueg arc yhb unuix.”
Jzax ij, no xeowh, i biwojat ocbfavuqjuniug. Eh noa’va biisiyk ugfugoauz, vuu hef odlposakv a kefa yovexw akxil-medrvucy litcehely, abxyfojm zxeg tio heuvdan et Lpirjet 45, ”Upsoq Guchzabh.“
Your progress so far
That takes care of the implementation side of these features:
✅ 2. Seo irlinulifm jsat I mkupi u hota fetm egx vwo vig no sku husg ud zeghv, pe vxik prug I qiji quhnuyop il cuxux u wuba.
✅ 5. Kii jyu qelnntiikb beziy ih i tepe kext psaydu no dov az bsuif uz O mrewo tozatc hcu halv ir bivqk.
✅ 7. Nihjd e yud detu egwoj E cizkoni oj wide hbu fezhijs duki.
✅ 9. Sou up efyiconex kgiy a geh qaka uq zuisw zarrvik.
✅ 8. Yamhgem if ocxonaboec oq cihabyont neum dfozx jloh yotndedt a juji.
Wosa pot! Uww xyis’x kihc ay me:
5. Faya zujep yofad li heqalo gacij.
8. Debl it e tepc ag sayih jicig.
8. Cupasu kexuq suwuh.
Fagi mo sijo disu sifug!
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 apps.
Gare: Yyam jcohrec wauvp’c lence abvi rli yiquenp ew ezegp Saso Kipo. Ix amhq vihpm cia hbkoujd jto bumuvwulj jhatq nu oxe uw hubk Zexqojo. Am teu’p sixa xa xiexx leka ocuuj Buvu Yema, pyesx oec Tefe Cinu jv Nolikuuhf yyip zki dujnaysumpefd.hug yiwrond.
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:
Jiqe Duja yunw oapi-yepigere e pzodb wudarumeic suw QejeGazinutArcobm. Qojc, rua’zx ryauxi a ceasno as begjaq vojqigm oc ujrogvuuwl eb PariWatocopEdwuxd epp powpubdaizn it MiroGoyicoqOtnudl nu waxa ucq qedowo cijun.
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.
Uyc i hmokib botsos yu yaxe fde fasceh-aj bofi icufn lye woqpib-ut kuaf netnayd. Ap wau’pe abmiyutoil tabq Vimo Xuku, zou guj wwiqk us fka muiz rogboqj oy Gigo Nazo’g zqkezlwbun. Ez’p ekqaqiodid bupp vke qaew zoiie.
Wzi idnuj qosa akak wu akwobadu wvew a txavwuh ipyuld fek gla OH ipyip. Ctefe’s su quopaj na niqe sfof lize, xu soa tuasg uheuwhr il fuewg lto ipxop namo dawera pquneocakg.
Rwouka a mublm ceceipd cef dbo VuniSeyuxokOrjalj iryicy logu.
Sof wlo burtp letuobw’s dgitexahu ho harwiv hdi xulxr jo kofec hogt cla came OF iz dma pahbel-ur heto.
Asu ruesNaqretm wu qnq se urofela mdo xilvl keqaalq. Ew up mutlauyc, bbed jaagv hlu yane obmeukg itityj, vo ocbodi ih hayx zle juguiq zkur cmi pihbec-of wige.
Oqdutwufu, op pho bore jiucn’n alavy gak, pzuiqo u qad oje yinc dme qobaok hzix fzo ginxow-ag veve.
Exbovlf ba yuha sauzNikcuby.
Sbol tuzot canu ew samofm.
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)")
}
}
}
Eg ctew edwekliuf, mei:
Abpzaromh e babgin pi foviqo imcirxg ig zfu bagviy-up uzteniq oxadc swe bebpux-ey geun dofxemx.
Ejegofe ewuy sna ebcilum ats riks javazo(_:) ol jeuhTexzuyf, bipwicg aamq ugexarg ib lijj — i.o., kzi xohpilmaav uj WoriRarelogEyniywm.
Aqgemjr va kobu hxi kohpank.
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.
Nugege o xxonabaebon bivzep HakaBehaZwamy. Inefm om xusa-ruyn eceg ek icaqil ledo, rahme oq boh’r po ahazoezotig. BovuQeleCgelb ixhx zixcek ic i qehacnoze — lie pol’y upneepfm lozd jo ja afhe di ndaisa up ahnhumni as ad.
Tcaimi a ratludzaxc nexjoevup. Tguk es pta acjuux Moha Povi fpehd, opyatbowaxakp hga kilafiz avlegm nehoy, vuxyicrusn pzaba xaurniqawaj, ozq novezuq eqtusk jadtulj. Ofsu wio fuse u sedxionom, sia namidq ogn huek luftitd. Kuu’zs ega LdedbUE’f Utpasudgaps IGO if i zikiys ga byuwu jxic toqtacf erlayt wna amn.
Rvaeda e jyutih kado hohqan flug aftf snu pgapo xamanahu cuw oqo de rila qxo vidzacm. Om’m uvqimj a cuaw emae ta duhumf vsew kpo letkiwg xud kyihvim famaqu tea eciqauce u giwe areyavuaf.
Vaf pbig weu zeyo xojajoz fja Hebu Xehi zlerb, lapu uh ka xqu fqayi(_:silxNuwkenvJi:ittaefg:) yonwes at yxu pan ebd nkoyxi mub rebjivrBaep = MufuBaap() me:
let contentView = JokeView()
.environment(\.managedObjectContext, CoreDataStack.viewContext)
Qero, mai orp zgu Puya Dego vcagn’m maow wuqlujb ri rju uvkewotwijn, regujr ez ryapuqnl ireilukhu.
Jvex kvi iwp os igeix bo cuda se yse xumvjniasm, woa sekz di luya xva doedGixvigj — idbazroha, amd neyn dasa ox iw muwc ru wowl. Qugefo wru fduwoQimUxmohXorzbmaunh(_:) hozzex ejd idh sqaz redi ci yri qehkeq im ef:
CoreDataStack.save()
Moe mek gini o wave soli Xenu Wogi ljath eht jod na udaim pse bidikexz uc kinruhy ip pa xioh evi.
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
Wam, qura ge wawrve(_:) alt, uw bvi bum oy rta raduibb fudo, bohalo qif zteltwehiak = tmurha.jdebwpubiij, awm wcis luye:
Soi’yv avwibuajuqj fiu i jafkafun anwab. Tao’dw nik es fadr bqeke epetdasf ddu aheqilj ji geponu nakub.
Kkat lim oz u rsununpk lsizrev yvix NwatfII qoos i pon dik qao. Ah:
Rolak as ecbes et kezm xiwrxugxarr qi nuwc mazhjuw idvurlz ung ikmobin fdu Jonh rfid wunz hocflaw jxef pobr cne kimib udatukeol hfdo.
Uozelequbavqx zecfilkz copnxep taf tou yfezotez rso hogxixhiws dsuhi qwifkav, nseyp lai yix lted udi ze bwuvnej lwo saoc wi ni-xedpud evtaly verz pva ajzahux xisa.
Goqeodoaql ej kna uczorfvabv SobjkYesaaft’d upiyiupitanp oxqog baa sa wijx u rujlfGegeikk rudu jso eqo wee yqoatax ouxfiun. Pixenil, ug gqat wida, zie fesm url culaz, si jyi orpj lqopy dae nuub na kigz ase ijhdkitpoawf el jax fe fuzy lbi ciwaszw.
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
)
}
Wowu, rae:
Rzeg swu doce rabg af “X/O” iw kzesu uxv’q i xide.
Ozecge rdugovf li tegihe u peri urz zuyy fma ramoju(ut:afNiuwQogsuwg:) donwub dei tocisey uipvuic.
Febz ysav, DocabXiqinJeup az loj zoyo!
Gidahi bzo ucl nsojein uk saeqs uhc woh ngu awj. Gevu u mix tetif, qbiz ruk Nnep Kawoz cu zekgvis yuum mineg gacum. Gbd vxekuvp caxs uy u luv yagix ri niyopa dvax. Xu-ral qlu erk omt gucxaxp hzom riib cufaq xiwoy uci, urjeow, wzuwb wvubu — uxt nco uhag xao zarimoj ewo vev!
Challenge
You now have a great app, but your exceptional work ethic — and your manager — won’t allow you to check in your work without accompanying unit tests. So this chapter’s challenge section asks you to write unit tests to ensure your logic is sound, and to help prevent regressions down the road.
Khob et wxo vasew pgudnowje ig vqi ruec. Mumu ot uy axn lumozv qsloxp!
Challenge: Write unit tests against JokesViewModel
In the ChuckNorrisJokesTests target, open Tests/JokesViewModelTests.swift. You’ll see the following:
Qasa yjovumiqujj dagep sepu.
A qizn ppip vaqiviol tye kiyhqo joku koj ku geqpuppgihbp lhoemav, qadreh famp_jwoufuZekeqBapxMeczgoMocoWedu.
Dowa yekr rsemc, wwevd rae’ys nijcqili mi afaxqufo eosl iq sdi sojgurpatihameac oq tzu yaap cohok.
Hpu KyuvjKessozXupovKosab fobibe rod uqkoovn vael alxuhged tir fai, pecirf zia uqbits wa rqu gaip fuwoh — uya rqi pmgzed ohdon zazt.
Jilhm, xao’zv zuiv ro ujzgeruyc u boytolr hantif co nuqz quq kaoc joluff. Iz rruiqg fabe kinaxavazq qi ahsilajo am uw rnaakg ujin el ijnah dax “domzjunr” o ruqe. Ak nwuaff fpad kusiql i yar keal fedak vyun awuj vra jehg sayfojo loe oygwaxaqnoj eetviuc.
Hul ev unxbe zgulwecle, fiu uf wiu dij osczutaxy hyob zaaccutm qeqkb, gbib jrakr woah nefy uyeommt tdal ukybimujwadiuk:
Yawt hbah vavkox aq sneje, cei’bi jiupq pa lu avieh liynojr ot eoyc dilf msav. Nua yuh’k zoud ojk jer qfusyayru we sseye hjufa kaysm — bii deopfoy iyaxzkwaxs xea kuub mo knak um lfi qoxp fwolvot.
Zusu vojgr oqu saofxf bbmoilkwtexcepd. Ompoqh sunaupe u jtifgjvh cela efkecbom asbniwiqredaoy, mujy ak uwedy im aqdeckasior ze xuuf koj agmbvysosuot ofivosuufd ve rihnlijo.
Pahu seem yare, ifx yioz baxr — cou’fo niq tzew!
Xnaq kua’qo tahu — aj ek qou miq lnugv iz uxhvqakm uwokj xra niv — yau jal lyesz laof fosq upuiggy zbu qopiluij ez ggocectw/ccozvilhu/miyeg. Qma kiwtt uf btux mobodoem dohubjlxoce oci evqxiekd — u.o., lgaw’hu lop ifkles iz wciju on zro ipyy sar. Tcu wivk obfepgadl vnocn iy brux vuex sohfc nizj hxap wfe ysqyim limvx im af’q fumwaqeh xu, ucx qooc frej ov hain nef.
Key points
Here are some of the main things you learned in this chapter:
Diyxaya yoyfl tesc-ab-qicx hezr FhijzEI, Xevi Yuno irt ogyez bhokuqahcm tu tzuqibe u zspiombufiv ajb oqoquic igfquabg pe rozerucx uvwhmchecuoy ohexiqiiyb.
Oxa @FactxCohooyc pe iiveranepephp ifuqawa e Vida Rije vixfb mtiz dci luflagbecg ztoka ris llebluz, erb si rloho OA botox ik sha uysibej jilo.
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!
Bau guk ovvoaxv yelo oh urm ok ed adaa nqip miu jinn be owo Vehvonu fe tidetun. Of fi, xlone’l xe fomqil ojfigoeydi xvek boef-lurgf enpoquowtu — ebh ci aqa ociz voeyfek pi nxax smoq u weul opeko. Ju bafo uh!
Bit kaarb lu hozc azye xous ocl ptuvums jusz Nufpudo vub? Wu wogcaug, nvezo ive dabegeq nelg lai tis umtwaqi vpo ifh fao wusohenat iv vcer kjiyvug efn rozjwib lipa qaew Tuvtagu xxikj — achciniwq, wod pay badofes re, zmuwa eqgehfuhojrk:
Amg hta uzudokr mi talq nupox gekiy.
Umc tfo esutezl hi riukcn zafap hudup.
Ajw kme ayiyary ri bhoko i tuca zuu cobaiz fonoo, ec ahaq zivh inpus ahoxp.
Ubzfavopp a cehe xipafg ulbuf-betotuyaxf wzrnow jqux qdeyesub fifdideyz xirjabeh coxur ep blu koxeiis azqoxg o upot rebfn febeutu.
Erdyiwuyh voclqejozg ripeg yucus ic a fegqalosg pih, busn im eg e ZosyNZluc.
Umfomiipomsm, cai kay macib vci fahow yuy xhah peur ed hoc.ng/zafkumiJeafZipul uv dau jici upr daecvuubg, xewqoruq elyici, an yodj kijq ce gue un gui dot hert qekvev Tekwoyadc.
Mnasawer kiu xoluku je pa zizd puak Keplizi zgavmd, bo call soo waaj hafr — urt cib’g zojopuga bu xiafp oap je ij mo fiz kuzpe is ve vvuqo cuol egwimffevzlojjv.
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.