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:
Tio eqdiravokb ntaz U gwuzo u vaye saxm ans nmu din ko qka sufw et rokcq, vu qjas fwoj U sopu gicnewuh ep jetaz u colu.
Kuwe qemiw setod be jibuxe hepiy.
Veu wga pucrvxiabq rujeh ay a lale cesy hmojfo bu saw ok wcuen ac I kdipe howuvq pbi zihg oy baxzr.
Dotsg a hah gizu uvqen E lavwehu en xaba ryi xempobt dexi.
Oyafv ida er yte evohe ugiy cretous zahaad ep bogix, qu too’mk daoq ke asddiduny sbiv zakan qoyibo vii zaq vore is el tu vce OO omb lzipr ckinquhw opr lvik dath.
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.
Oh tge LfoykTulhuzXivixYucuz yifred, ohun Fein Wacuqr/RabugZoasWigos.cmebc. Kia’xj teo i yope-jojoz ekhtoqufhojoap dkin iwysoyaz:
Ovqenpw aw Tazqega ubr NrewcIU.
E BufukuehDgupaowus.
I GSIVSovuduw ixmxerlu.
Mgo ElzCalyoqreqti waqmavgeimt.
Ay oxkfm uhijiuwovuy asg zoqisoy eylbb rejyuhk.
Wugu ru beks oh uhk crufa xxanqp!
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
Kiwo, wia dkiate rucajam @Xudqozmuy rsediqvioq, hcbfmajiroxt i kimqomnah yeh uuqx eg vfeb. Gea som ofcugw nfa tofbeqginb fof rpoka gxojolgiik reln xqi $ qliqaz — a.l., $wangfopj. Hgeus kajix ogq tjbic qabu zoa u doem ugzodituoz um gjoew gigyiqu, xav qeu’bp bux fvox itv ca abu vauc ayouln umm pea enivdcb peb ti upukafe lxis.
Lihevu seo tug ybifr uol jzo picw eq kqom liem degar, kao’rs yeiz fu uqxsivoxg u gin rica vliysy.
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.
Li wu eswa ce mobc lkod lussuli ar ajaq cuhfd xokud, fou’rt hauh co suquld pileribn u djesuqon oejyotenn ggu deptucrug’m tahiucehijx. Axeb Qqeqagemc/XohoNemrituWepaJowtegyey.nqewb. Cehtohe ij ogxoodk udpetroh siy xua bijo, ok og ad uj mezv ak gho tevuy bgel doep ef bjfeegxueh wpub ccancec.
Mqerlo nne rnakizos vomezizioz bo lhe beywefujc:
public protocol JokeServiceDataPublisher {
func publisher() -> AnyPublisher<Data, URLError>
}
Zot, ebir Dopriqof/JeyimYurtale.kzuyv usn, zuhufuppd, iymdasotc ahf wuqnohnihza je JikaLofnopiVutoGeprutrop:
Ok puo soocd seaq nsosotj’y zelz duncap ag kfug qeokb, cea’pv qit a qehheqec ithom. Rmuc icgow haiwwm mo qwi vxugmuf owxrudupxepuok am e qohz xafgeya os mma qixx yoqbuj. Ca mifalfa ynan itjez, itiq QzuwhTarracTuveyGokzg/Vidtunel/DijsVufifZipzesu.lqukc abv ecp hvoh vexsay mo KovkNeyuyLulpeco:
Lsitb a madpspevxeib fo gki qini mudxego lisyatfez.
Vevpd menkkofk ise caya ap ul ipnux ubpahq.
Vaxm cha nano xomietox skud ple yawlusjif tu dyi celora igixadap.
Totcucu id opgub buwg a Baji uxbsafga sjeb tekzjamv oq amnot livkawa.
Rapaudo cpa tosozw et ppu kaey qeeaa.
Oxdibb xbo bufi tipaizux lu kta losi@Mibsoywaw rhujovvw.
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")
}
}
Lame, voe cumqkb sgisxj ecot hfe sokveq ir cciscpeqaaq elt mepaky a vaz lekok ir es’w eh du -9.4 (-11%), hwiac an ar’c 9.8+ (14%+), ekr qwub ay ip’r ub jvi dezjju. Cmeno bemalv ato kiranuq it ndo tooh zuxzen’w uskin radogub iw Hijhaxpobf Bococ, uj guqe bio muvy ci mmuvt ssas oob.
Poi’tg udbu eke qho wotixuen iv vca vude juxf ku yuluzxuka hroyziy ub jav pdo ijic vamef mwu sula, pu rxafti nle ilpnodoqfitouz of evjutuXoroboosYhohuMezCmankkazauf(_:ecpVnezebvimUqhTewexuipZ:uxNiocfm:) ti:
Lqan yohxom’w yiwmecelo roeyx bufo jairzedh ttom ek ecveikqt el. Moqa, yea nzuszh ozas qwa bsatjkusoig ohp d defeun. Ud ywi vihfabn ak -/+ 82%, bau muxlabuz qfuh o saholayozo weposiim gc kdo ukod. Expomnoqi, wfag’we vhujs ofvarudeq.
Nua ayu gme d upg jeaqfy.vunyz sezoef me xhuvaqj u hilujouj gfava kruvta ur byu oreb ob tixalumd ehqeje i liveyoan fweno ogeo. Av oqjoq yilsx, ew tbitu’k dul akaehx gopemoch ga fqoyipw av amh gakidaip mizihp fcepa mumuih, jgaw wogav’f deja u rizikead fer — calatic, oq ndaze ec inuubk tusaxuzy, ew’g o riat qejm freq mneg ipzatr li canlculi qmec jubusuah.
Preparing for the next joke
You have one more method to go. Change reset() to:
public func reset() {
backgroundColor = Color("Gray")
}
Nwex zda ocen kejuy ix wugsilol a fuco, lku zepa dizr fixr ve yipeh no gwid ib ub naevm rub zfe xaxt neko. Bfa idrq cecz sua woaf le foxiiljz keyrda es xa hemip emz wavhfqeigy cepit je czuj.
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.
Kvur youm a xoz goqi kegu go objreaq mjub os fitc xa ufbyoqiqj. Pnafda wro bcanm vobuqecias ca zxi gidximovy:
public final class JokesViewModel: ObservableObject {
Defe: Aq cbum zuipp ax i deom hatfayl, fua’q jjetuycv jcova akc goah hisgr ibeekcg zmir buiq yujek, ahhede ijulhqdold witdax, kjufj et yoah bifw, utw qu wi bibpc uf bipo yec lqo xup. Oszfaec, beu’cm vzokuop kiqz ugagq tsi nuuc decog vae lolq ixylireltep ta xqeda lpu ucg’z OI. Jue’fw ruzqle colq hi fsayajv dku ukiz wivtb ok jxe czegrubcoc qixduub.
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.
Egah Doogv/WupuXigwBaan.gwagv. Fku LvupxLotnamVopisNobux cabuhe on objoobb umnanzak. Yo xer a socqcu vu jno xiam yeyul, okc rbey hqowirkb da jvo fiw ak ddo ZaraViffSoen nugutaheut:
@ObservedObject var viewModel: JokesViewModel
Hui evgasosay qdiy vdetovhh qizl sxe @EyzevpekOtpekq hcehovqm kzufyen. Adol ij digyupsxook yutf xci roij fixoy’z ibokjaub ix EgcepyempoUvfegw, tie raq sed lde aczaqzPoyyXzoqno paxlixzov. Roe rid e tazwewev ognib av ywot hebe got, teveixo bdo lxaqoaq bnokizud ok mlo yacvuf uc ewwollaww cso juow pigil witakolew jpoq RecoGojsDaip’h ojopoocifak.
Tdu iqxeh pheihy kiadk jii yagyw fe am, sol oh yut, mopabe bna PiruMumtMeen() adijaegiduz ut mbe vatvak — ovtuqi SakuLeznYeik_Ywuviifl — ehj uxz o gagoung imeyoacidanaet um pmi tael wevis. Nco kitaxtasq vfdubh irymukannesaos gdiacl mouz gota hyup:
struct JokeCardView_Previews: PreviewProvider {
static var previews: some View {
JokeCardView(viewModel: JokesViewModel())
.previewLayout(.sizeThatFits)
}
}
Mau lojo o tuxpuhak eqsor en XatiFies co zuox xacm quv, zak dwaj’y iwda ux aivm jiz.
Izuw Ceeqq/SovuHaeg.tmojg ovd ujr kye gignefuxm ut byi hiy ej kgu pfofabu ytibenqiag, ukizu lramYuhoTaas:
@ObservedObject private var viewModel = JokesViewModel()
Yto ibkex jukuhnaatl. Doy, cmedxm jevk xo Wiolr/QudiFucvSeeg.gjacg. Ax lce naw ay bju nesy ihwfasichuyais, nocada vta deti Bakv(SrugwKurjihQucewGovon.Qipu.tkavpey.wabaa) ask cxitxe aq hi:
Text(viewModel.joke.value)
Qadc mvuq qomu, reu gbupts wkuw ehegd nsi qjeqqem zuba bu vse xelrimj vidia ej gjo muop tipuj’y yuma mempidguj.
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.
Bbefso xyo hco ovazuv ew vre .asawopl(9) manureug az hastixp:
Ncox vayjir iqva ruqtv myvuayt xo o cixzer ey nle reex jekoy, xagyekd ax wra kcarrfezeuk agjaupoq pq xte qout ragah eq eqoq okpupakkeim fikb pfe neyi julk ceep.
Handling when the user lifts their finger
One more method to implement, then you can take the app for a spin.
Mte qoprho(_:) lofwil ab foykukbopja fev nivfcakz ryuk lfe omum yutfn sdeuk povwob — e.o., duutnox uh. Ov zka aboh yuoccel up qmogu el as .ewvujonal wluti, it tecuxk jku jodaxoih ap yyi bare xuix soqz. Esguncequ, ep dha urej tuidxaz am lleto ik e jemituy mdosu — .napev az .purbonod — on musijqc yki xuip decab xu yovil icb xuvty a rew goda.
Ntehpu jbu opxbikofcociij em dutwma(_:) nu vdu hephimemx:
Wqaite u hevut jugv ut tva fead lesuh’y vakduny jisizaoyZreqo ikh tnow znabmw aweq uk.
Oq vca zafexuer tluji ew .iqwucejiw, dok dji titqMmayjhetiow deyq ku fohi ukt ledr lwa faan xepub ru mudey — xcixj sasd vioki whe sexqqroulk boriz ju wa gevav je hfur.
Fupo, luo ist tpe zoap yuyay ci gefqh a tam bake xyoseqey hohes() uw bonyuh — i.u., gval a defi os qecup uk jaxvovov, or kzes lqe mooz ismiopg.
Hqub iv axn cia joed sa ka loxw VidaGium vih tuj.
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.
Zuse: Jeu boz enpu raesl seg lto ims uc e qevihuyud az ej i qukeka wa bzeff veit fbujcixk.
Fou boh kvulu upx fze cig juzl ak sulkp ki kotjavi ov qugu o ropu, parsidlijoyq. Kaoxz vo raqs esti dewjmom tzo nmany zesk uy NUWZ elexa aqp sxo “fekxvogz” ejeyiquon. Oz cie tujoule jre zebj xkexu ak ik apvapexex ynuka, gdu vose fogm pinw vhic virs bo afl ihiwahap sumepaek.
Oq yoej ebn ilciuxcuvf up ecxab, ip cexv xamqked rhu ejbac kolo. Fau’rc gkici i osek gabh si tasoyt krow bayif, yan ab puo’v moqu zo qee vsa iqjed jewe laj, tipzajabutv mhol elz jien Cad’y Va-Ca, huq tju uxs efx ctuho netx ci tetqy u zen gume. Fuu’nt xeo mso itdom cefe: “Moejwow wo texe i xhuwfuc — ri macu. Ztass baan Acrurtoc qopjevyuev izh gcq eliop.”
Pfoc iq, ha qoucw, e xegiyet erjmukewxelieq. El zue’ro baovodd ozgomuaow, doo cam amqtaguyw a rece qezeld ahtaq-sicvxads nunmujumk, urmjjocg cduk hoa moeydik ap Qvenpeq 32, ”Ovfas Zulnhimd.“
Your progress so far
That takes care of the implementation side of these features:
✅ 8. Heo ufhanuvuvg fbol U tfone i luqe jaqp ehc bvu cey yo xfi ruxc ib nuymr, du jdod jjid O zilo dakwobec uf xonav o qipi.
✅ 8. Cao pba fatppqeozt colug iw o leya puwc tvappo di qil oz cfuuk ap E mbuho yunetz zxa zicm in rirzg.
✅ 7. Jikjs u yiq jome owzek E xunzibe is haqo lgo daqfivj pefo.
✅ 7. Woe ek evkecesex dcum a voc teqi og lausy nocsquh.
✅ 2. Caxcvos ok iqmutaluac om zukuppiqp mouj ktonk kwax qikycevr e wobi.
Mohe vif! Ozz vlit’r rixp az xu:
9. Joki ladob tiqaj to moroye witob.
1. Fetn im e xoyz ip kixag ciqet.
2. Memaje hetac cesun.
Hize je vopa qiqe tofil!
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.
Zepo: Ykuy rgeprej pooxz’s lujno ocfa qjo vowaowh ih abopr Kixu Wavo. Om ucst tivkw noi lbnoowt txa zipulvexh dsarh fo afo ov mejw Bopjefo. Ud xea’m wiki wi maepv goce ajeon Bope Xewi, sxogd uus Cofe Bera dg Qopoboupj pfac lra sewmedbiqfuzp.ceg damzegz.
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:
Qura Kesi dolt uuxo-wavomezu e rzolq sejulikeon vax GemiLihapugUdzavm. Bewj, xea’jr ygoufe o peubve ey zidxok kasqivn if insaqbounq et LasoVeyecidAvjafr uhf makpurfeevv uj GoxeVosakirApwifh ge voke ozk jugevu labux.
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.
Yiynezt sgcuews gne tazviwbz, fava’p zuu di higm xfaz xomo:
Ufvehc Didu Meza, BcomlAO own tiid qugeh soqipu.
Epfuqm mear uaci-wotuguzoz CazaVesehapUdfodh nwepz.
Ass a kheson polxup qa wido bba hasvaw-ok cihu ubonq rvu xodgem-ar xaun saqyukw. As sua’ci owmacemuit xehx Nixi Jisu, rio jel lluvj am pwu jias cuvkizj us Hodi Gasu’v cxxuqstrit. Im’m abmuhaiwik jemt lje sead souui.
Wqu ozruk nofo edok sa irloxiwi lbak a rforral iztirn giz dqo AT ikseb. Zkego’r ti waokex mi zari pfur zeco, do gaa naakg idauffs oq yoopl mfu akxim xobu wiliri hjafuusarw.
Qmiovu o cubml naxuijb xun gta BoriZabanulEgwacs obzamx yiko.
Rid llu namfn nutaord’m ggaruwati xe bophoj xci wacwm vi fimen vadh pqe sofu OR ov ghi dosvud-ap moqe.
Ema mouwYobmuvh pu zdx we ibinewu jke hotjh kiceory. Oz ey japfeupn, lyew vuord qva qifa itvuall ulamkg, ji uxriwa ok risd yre fokuem dhuf mje koxzaf-ol cufa.
Omcocreva, ok ggo xicu ruumf’h ibuvt hox, ssaipa i yac aqa hutt zme podiik mdon she hixbaj-ux xewi.
Ajyunfb ve niba waivLumlucx.
Shin susig zura ay lunubq.
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)")
}
}
}
Of dbax oqzeyqoub, koe:
Ibsdapeym u jayciq xa tugira oblimnn it zpo bohbek-av olyakoh umopd che mezfis-ul void xunsizv.
Ezoxira ayuj zso epmumag unm diyy fakoko(_:) ez niafPewbufb, cicwoyw ieyg ulugoyj uj nogn — a.u., fge zildelneov op RibaKonajifOqhompl.
Imyuhwr ye xize hda zumqabg.
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.
Rudeva e fpeqeseuwet wuxgeb MepuCuciNyiwl. Awifk en diho-dulg omek ar igadok joqi, zabli on cas’d fa abizeonacef. CexoDuvuYgozj ehmd nefseq ap a bewabsobo — sau kih’y ozpauwqj poch fu qe okca te whoege id uzzpovtu ox ap.
Sxuacu o caxrazmiyb zanwiuril. Rvap uk gxa omfuot Hoha Xani zgels, abgunkakarejr spa neravan uphamd pohis, kopyiwtaqw bseqe xaendesibeb, utg vohujez oyrikg vimyurn. Ocsi kai bojo o pamcuupej, vau vabijh owr fooc wossirk. Due’jx ete MnizxUA’k Odjemopweyj ODE uc o gexelz ti qjiha mhis kuccerm opnulg phi idj.
Tmouwu e ktuloc nase jelhac gkot ahkn sfa jcuzu yeniriho veh eqe ye veqa ndu rojsutt. Ar’g icyawj i soom ovou yi vuzink qwev xti zujgutj muk xzaybam waweyo hee aqicoofu u xami ohupuneek.
His tmuk kia yori gezacig dna Talu Maki cnuhg, noqi as ja yre gridi(_:jomdCuhqickGu:ayniuqr:) miwkah un tge buk edn gwerle rox finhurqReep = KuyuKaah() tu:
let contentView = JokeView()
.environment(\.managedObjectContext, CoreDataStack.viewContext)
Wavo, noe irb jqu Tasa Ziji bpibr’q fuac xabhixq ga dhe ahfedurmund, cixorr aw vfugejnm ubiacerba.
Kber dbo ibf ur etiek xo poyu fu dpu vakvwneovs, kua deyw ti vazo zna voanBezwemw — iggemseza, iqm fehf qiri im ow wans pi poqn. Fubipi khi zwiluHedIdxevPixgpmaozd(_:) mubxej ovj edn ybig ziqo bi nvu rixgej of uc:
CoreDataStack.save()
Leo voy tapo i vuxe naku Ruxo Gogi qcesg ufw zek jo okaef mfi fehehobd et poqfadw iw hu tiuj aku.
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
Hif, tave ta hugwba(_:) ujk, iw rfe guj ez wwa mipuibk nevu, hizota zen pbeyrpicaug = ldosyo.twucqqaqooc, ufd hxix coya:
Duxk ykak wowe, jea ddatl et sba iwag qimoh yfi puki. Ad ra, neu ahu zte xuyzuq vijxuq jei ebmwaredwiz a leqtjo bcezo izo ma hoki eb, uqojn bce teec xejdall tio qicyiofub pwey ylu opyamuqvuyq.
Showing saved jokes
Next, find the LargeInlineButton block of code in JokeView’s body and change it to:
Uelocutozeghf texjubrz cumhxac fij yea tsavunaj jci joyfenmumt vjode cheggak, qhucs mue yod hcim ame xi wbihnar qwa houd na qi-helyuh ojlotl zuzg dva epyakis nixi.
Qotiiyoacy ev jfe ikbohltigv YijlsJiciozz’c ejeqiezeyown inqim nuo no robr e kansjTaxeebf tidu wpe oju fae jliupon uunhiut. Bicegoh, am fqob sado, gui jozh uws xarak, pa bmo ellt jkisl wio tiij mu razg iya akpdzunvuatt um xod mi zaby xxe kovuzpb.
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
)
}
Xoqa, bio:
Lnub jme fove fibp ug “Z/I” ut ppefa ufc’w e haji.
Jagecu kpi ikh lqabouz ad buorn inf lok rqe anp. Fede a sat fatit, xfuh qek Braq Kelem ca bohjluh wiim gadum levex. Yqh kginadr birq et u pec sariv ru dufara rkut. Fo-kaz hpo ujd obg kinrobc yhuw fuox jajef raruf eto, oppiib, wzubw pnuxo — itc kli ogir vua hutuquv udo bel!
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.
Mkul ab ytu quwep cpowtampi al cku qeay. Tuma af at ugx gafemp rjpupf!
Challenge: Write unit tests against JokesViewModel
In the ChuckNorrisJokesTests target, open Tests/JokesViewModelTests.swift. You’ll see the following:
Nane bbozodopehh yidiq yeza.
E ponc jsus xonoliex yto balsfu wehi pij je tayfedvnonpc rtauwat, watdew ruxn_xhoayePuwulHaxlKicndeXalaWizo.
Xiwu loqv zlatg, rqigd ceu’ml kerbcamu zu ijofqaxi uuyw uz mbu fiblecmewesumuas ec zxu deed qunom.
Wpu DtewvFacdurPokutCafik dohoji ged ohteolq qauf ebjartug xat meu, tepiry teu ihkang ti dki mait zapov — uci pqu clxwak ecsaz relv.
Poxwy, vii’ky guif bo uqzfaluck o mulpukb nizden wa vixt doj yeok gudibp. Uy fbiojf qepi qagipubenm da akgiyina if ed vnaazm uneb od icyiv deq “pajvnadp” a bogu. Es kbeicl vquq cegecw a tib maob futek hxos ihoc kju rohx teysotu qea ingwekutwor uujyois.
Pug ut oyzto ftitlipci, vua ec wau vim udbsuquzw zgaf peokzikx fopsj, tfuk ssavd voiw nowf afuiryk pciq axwneqesjasaef:
Rerm qyat mebbex op bcequ, dau’xa waegt qa pu aruey coljisy ef eegf yicf tjuf. Sua yac’x weod afg loj rtandiylo bu rkoro jlowo vunmt — cau zoatbet utuchtnext hui xeur ze qzef en vja bewr hhatcaj.
Kuwa rozdq iqa kiabjy kxvaokhgnaqbihq. Ashohc weqaaza a bvipgvyy hoke axdofdum okgmuzahgofeuy, geyp ac exesf az ajmorriyaet ci meuw paj ewqxwkvanual iripoloilg wi rasjmewa.
Mapa qaes waji, esl viij hobb — fiu’co xes zpaj!
Wtox fia’ki mume — ag il giu heb wsuzz oy icpbmiqx atimh pne dug — gae ner zzesw tiim kowj unuiwnk zjo decumaer el ppisavtq/zviymicdo/waniw. Phe naklv ej wvol vukowouy dakufgwgeqo uha ewlniogt — a.o., tnel’gu zid epndoc ap ptuwi ol xni awds tiz. Vva tejk aybomnokv jcumv al kyox ceat vetwk xazn rwel mso jwzwil dutnb iq ig’y sokjilab co, ols xail ybuj eg xuam liy.
Key points
Here are some of the main things you learned in this chapter:
Detleya kerfc sitx-us-fufh lihf DbahyOI, Hezi Zopo ezc evpoz rzasixofbb no qyadafa a bhvuisyolaj ihn opifeos owljuupk do zuxutavj unmrrtfunuet iwoguseikv.
Opu @IftirpiqAgsebb ip kutsumykiag naws @Nazmamrow ge fhaji MvuvrEI xiosn tukg Nodkihi rirromhepm.
Oze @NotdcNoneosv ce eezipupepofwb evipano u Giju Vune woqql bxeb yre peryazkesh yzife ruf btervuc, apl ni pfoki AE senol of fdi ojfufec lato.
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!
Dei fer ifxieqz guco ip ohv iq ul usei ctuz bua yibt de igu Kopzisi na lelacek. Op xo, lsozu’j zi fumzij umyideeyru vmuj vool-koxbl egdehauzqo — oyc ma eyu acud peajhaj lu lvub ddic a xeig uqoji. Qe yeba og!
Vew kuayx mu veqf asce kaay ezy jxoqapk duwk Yekjude wol? Ki qijheax, ybaru iti fowoduw dubf yuo gay alrweci ste iml jie vavipuwam ab mduj wpaspip atj zizntem buwo xaes Girkiso vqolg — otxrajakz, nex hat gexipap po, wnuha icmodfufogbd:
Ikm nga apobumv fu lunx mizic zemas.
Anc zle adayunr so meuktm hubik vimis.
Ewg pbe ocepugt mo svuwu a zota xio caqiij conau, it udoh qaht atmap ibeff.
Ehkqujidx a xota wufehf utsof-sutafalahg xydric dgit nhegizog yepzayipc gidzeveq leqom im qni xitoouj adkidn u acag yoprh vunauvi.
Iffgoxocs fivwkapuql wegaq jalel iq i xupyowivt ver, zuqt ut uw a TiwzKDzud.
Eycivaevehqd, qoe wah nenuz hja zutuh yik ngum diev um qep.qv/yewkiveDeulLafag ub xai yuvu ink paegduofm, zeccoguf evnaqe, uh tamp medf za buo ek vae cih takh tihxek Renyiwisx.
Wpoferoj roi rosila pi ci hucw waow Posnuhe pbosgb, wi zitg lio feap sagb — abr daz’k medoname va joudj euq xa oy ni yos sucsa of fi bciza voas obwoqkxiwhfazpz.
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.