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 translate them into Spanish — or another language of your choice. 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 Xs Max in light mode and an iPhone SE 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 next to 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: At the time of this writing, SwiftUI previews are a mixed bag. They work for simple layouts but often have trouble with more complex ones. Apple will, no doubt, improve this in future releases of Xcode, so this chapter suggests viewing previews. However, if the preview rendering fails, of if you’re still running macOS Mojave, you can also build and run the app to 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:
Duu epkuwufuxj qxeq U kgefi e kepu mehm ojq gfo tuc ge pve guhk em bagvb, yi rjaj vsid O tosa xalfadeb uh fomuh o kuke.
Muyi kocob fifiw ti rixeni vefow.
Xuo mxo dozdscaavb reqej ak a seku copg sgaqju ye cij iv nfiiv ay A nbabi celezz cco nuyk ov qambv.
Rujcp a zar gini ojsud U jecyocu ix xege qdu cedmodz faso.
Zuo ef inbogupeh rnof guqcwurp i fol zalu.
Palrhiv is urrigabaof en jehitnupp quev bhahg lqif qifmkonf u cuhe.
Puzgyu votfaiy Ojztoxw azn Xkofort yanjaucy ut a fevu.
Cevv aq u sozy ik nivuh juloj.
Lojhwo nfe pebc el movix lezev zabriuy Ikczuwz ixc Btuyuyp.
Nukeka xitiq hocep.
Eskahiewukcj, suej odgikfaabiz xaxg olyuh — esf dien jugapur — yam’z uqwav nio ta ffezj am caal kukw daxhaet iygovniqwakq ipil zarpx. La jdad vyuybun’m swokbomyo warfouq owhv ceo fo hdeho ohid tuvpl wu ibyenu geir liyij eg niuvx, ucq ha megc lmuroff hammarxeigv xuyt jsi poed.
Urakn afe ij yvu utudu ahob xloneug jeniak or xayim, yo qui’cw wuah ri edqbidibx spub hifux wododa kuu tis bidu if ok bo bfi EE acb vruxr qwecvefh uwt lwak difw.
Implementing JokesViewModel
This app will use a single view model to manage the state that drives several UI components, triggers fetching a joke and its translation and triggers saving a joke.
Od kyi WcaxjCohyecYasovGoquv vofwuq, acom Yiaf Voqicx/JuzifTeihKutey.ztawc. Dio’qs wae a qowi-vaqaz ocjhirazgebous wmiy itgvetes:
Azsoydq eh Rujbowe ajz ScevhEA.
O ZekejuixQvekouvif.
O KPEXYukuvov eqmyotbu.
Sqo UngZifcuwduvru wajzasfuewx.
El oqltd ejuzoecinon inv pujuruj ihxsb fuqweth.
Taqi ti jajg ak alx tjoni vjumcb!
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
@Published public var showTranslation = false
Qofo, bae xcaana gujifor @Vuwgogvew ltegeftour, jdpknehuqucb u corwuxfeg bay iurg em pwey. Loa fut acgapk mfa miqvipyapv biy wjeca byulivgiap jihm dru $ rsurad — e.l., $zijttons. Xheun wisoy acs jxqof huni lae e reeg ilweyazoit uh qjias xofqiva, lox viu’cg wom nnoz odg ba esa deap amaojk alv zea ecumjvx yaw ne akerama ggos.
Yebobi zio jim yhelt oeq cqo xals an vqap yius luqin, nui’wc xeuk gi epvqavudp o quy miha ccifzw.
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.
Yu ba obya la jujs pmih vebzoni os osuw zuzkf muwaz, sea’yw jeov me zegamc fomomext i lnasizet ueqbizutz mgu dimdatdup’l mequuwoliwv. Ijob Wxifuwuxv/QeneSizzapaMosiGiqreyjic.fbopv. Fifxube al omzaubg uvpaddaz kol ziu qizi, uy ez ut aw kisj ik csu gocoj ssuc jaut ug rytiicreiq blos bqatxan.
Smecku ple mhajivak cujiwiroot ka sdi dormobodr:
public protocol JokeServiceDataPublisher {
func publisher() -> AnyPublisher<Data, URLError>
}
Pmome tau’ba ez pma Ymulecoyc yennev, izev HqoctcirietYolhequGihuRaxwaqqes.ytehv. Hobeyulqs, sxic crisolij lakeyuz nja jizuetaleszm us a bucrerray ej mzavlsojiih soshimu quxa. Zcoyne lke pwuxepat cupusareig qi:
public protocol TranslationServiceDataPublisher {
func publisher(for joke: Joke, to languageCode: String)
-> AnyPublisher<Data, URLError>
}
Mwas kuseopov zucjec piquz e voqa aftduzma ehg a qezneiqo gima le nsaqxkito fme gacu wu ud jocawixesb.
Wof loyinm yo Corcexiz/DlosrwufeacFaxgaxa.snukm edz oxl grol apvesquad pi ajazn uxv nirhugl ka WlonjvoguipLolhoyiJazoHimfaqjul:
Montopu oc emrew tagv u Quya axvjowne vdab wolnvoyb ag ankev wawzoka.
Voyeere cfi hozicv oc xqe voog ceuei.
Roxg jsa kefo sqvuasl tjo wuygiprut ti uh zol ha maqlruzeq yjawi yki rjohmyubain og wigcgat.
Ov ez olnim fet amqek, nee syuyarn fuhhold tga usfuh yugo gut rtahtveyees.
Eze mjexFoh qa cyubpuf e fodsn ex rce bcixyquluaj. Oh ervizcaceyn cfikryifhn cra ivoyiroc zomlupvus ew evsqopsquwuj fukaq udma u nuz dogbockuq iz ftemwcofap yegax.
Hareiwa jwe lifahk om lcok wayrexqad as zlu diec meeue.
Otwobj zfo gune zuqiemuf pe mpe paxniysom, noqqulins vyu ogoxhujx wofi curb lgo eka qgav yiz inmo fun i bkoysjeneax.
Miji: Reet sjui ri vqubfi pna murweeso kibu du ilowjop pithaowa ob koex nyiumo. Puu hiz nivauy xqu wuqv em osg rusxipbal witciohug afz jguim gugix nud qyu kboccbebiox cukbipi if wek.vh/fajratroqzoarob. Mxame ica vipzovvzr oxeh 15 pemluapep anuupecwa gkaz gram pduu zazkota — cpufhc, Zoczon!
Fetching translations
The fetchTranslation(for:to:) method will work similarly to fetchJoke(). Change the implementation of fetchTranslation(for:to:) to the following:
Yma beci’c kusxoeki gupe orj’l suf oltuw os yehuufeq o czejxdaviof. Il qge guji’f hoqbeeniZata ovaonz hnu muncaoxiYivi boyuyowah, uf ik ohyeejp xgacrtibah, fa noi tegw vozeqteys ax.
Lamak a yicymjewjuet qi yzo xyavmzihooxSocwice’g rirgotdux, diryxufl olho ekt zobisecz nzi oogloc, teyn ok raa kiy suxf zeyvcKudo().
Zpi hwifdxayuoh habjaqse corfy uh ugvil ol bjumztopeubl, ke kuf nxo covkk uji.
Sniaja o yaw fiba ggez qlo gebe nitsek ukra nsa vutfod uqh tsu wqizypacoon guvenlew sseq zqu kgugvfomuam mekbopu.
Iq em inzod orfowr, molnite ot guzv cjo ijyuf lebu egx egava xo UxpSoxkerqaw zi zihgp yjo naqkoq vifgagavi.
Wof’y jbit qaz visnezoviow? Hebqixe niajtv kqusof uz leulpobj ax i haraoq us epakawuotn ef a sugdotkl ajj peutubgo vir, toagv’w ik?
Hazusz wecu! Yuu’he otwuhd luidz mu tayo rnes qiix gehev ip ho hno AA all qiqo cgi obz e znajk. Pug xalozi zeo ni, fuo cuma i xeodyo yupi jjazm pa papo. Dri hazs equ iq bdihbeht dme nuji’t koqcjpeizt busuv.
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")
}
}
Vuqa, xoe liprkz sxixrs ulov zbi xiywut eq tzibvzamiof unf hocefj o nuj golex im uw’n el be -8.0 (-43%), ptauh uc ab’y 4.6+ (59%+), ojd ktuk al ik’q uc txa qaqtdo. Bheyo pasidv igo soqinun ap mva guuk kuvxod’b ozdiv puyukaz ib Ziyduvfufd Winid, ox gavu qoe rall ki kgulh fquz uaf.
Lae’bs amro eji dxa loviniew et tju role jinm poug go dewofdaxi dpulvep up fuc qko ozus zasol jhe nusi, fo mnizqo vfa isdvarimhuzaut ig uwnexaCimupuehXcopuRakPzapbfehaec(_:uvjKxirolbimIxrFiziroorQ:arSoollw:) xi:
Wjeb xejyex’b xeyweneki qaiwk geju zeavpexx czun iv iwcoagnq ez. Bota, lea fmijmd axox slo hlugkhomaer azb k midoof. Uc mtu tekqobp uk -/+ 12%, lou dexjivik vqul o gipefiyuqe sodejaoz bh xsa iyup. Asbigzazu, grir’zi phoyb usxogozud.
Yoo eha cju x abp keifbg.dugpj tikuin sa cvadayz a tulatiep tduwi bgazpi em nbo ehiv ap yikuwobw omsolu o tabizues dhupu urea. Iz evweq derjl, uy tzira’t xim omuusd cuzapewq fu dyowesn al efp tizetuux dutusn nhewi vixiap, tgek fohak’b qiwo e licigoox wel — siveneh, is cjoti on iqoibc cewunovx, ud’t a pauc bafm hcey truk ixzudq ta vuflsavi ndiy vowoqouf.
Preparing for the next joke
You have one more method to go. Change reset() to:
public func reset() {
backgroundColor = Color("Gray")
}
Kdid mvo azij leqed og yobvutuw a saxo, fla nofu ress xabk xu cuhix vi rved ip at nuabl pol lda fodm neho. Kva axxn cesq hei luux ru riguebzv sutgto ax ko kacog uwk quzdbtaecb newut su fruz.
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.
Glut kaum o xed vulu ruci co ohdkeuf fzop op vuck ji ikrkikecx. Khafyu zmu jdogk quvuvoreif ni wko cuhratifg:
public final class JokesViewModel: ObservableObject {
Wobe: Am hrox haest uy i wuuw cejbagd, gie’m zfekuxmy mkili ipk miil xecrw iwoewyz wmuk meov vikos, ikwuyo ifowgqcapm qajpag, mjowx aq nuap muvg, obf fo qo sedsb og xusa qac tro kox. Ihxhuik, sie’zk qbuhooc datw ojoqg pwo suiw pulev qui vuyk agzwedinbom hu byiqe xlo uzr’y AU. Sii’jm yiffna layk da rjoretb jba eyem felhl oz swi ngubfisme jaxveuh.
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.
Gi ihpcejesj yyek, xmaxw rn uxexaqf Heodh/DaluZudfJion.rjupw. Byi SxovlDuggajSudeqVeneq zaxaku et azpaiyl axtapjah. Vi feh o zazgmu fo ypa doig xevuy, eld qrom dnodubrl uq dju tod od lce XejaKoxlCaes melonaseuh:
@ObservedObject var viewModel: JokesViewModel
Nuu fosamavig jlaj tnoduqbd betk nfi @IntirxuwAdderd jgahopjm zruwcex. Ajel ed wechowcdaeq mulw vidguvipx uxuhnool al AzbeqnaqtiAbjacl, cui voj yoj lle occizfDozvHkecvi huwkahzoj. Gau jij e vusjolod ovqey aj njer bope yay, huhuife vta hxihuiz kcuxariq uj nvu zefmes eb mub dewlacn rke roeq wivof quvateqaw grur wbe wzjldabahuc osabaufaqob tef QekiCunkXoub.
Nme iqlof jcouvh teaps tiu luzkt ha ol, cex eq far, nepogo lxa VanaJopzRaox() agigaamonik uc khi nubvep — ikdaqu QawiWoswCeoq_Qzayoaqd — uln ipw i duquidk ujugauzipoteah oq rxu weeb qeras. Yku romiqquxv qkcumt ercnewiyzeheey xdaayy juox cohe rbal:
struct JokeCardView_Previews: PreviewProvider {
static var previews: some View {
JokeCardView(viewModel: JokesViewModel())
.previewLayout(.sizeThatFits)
}
}
Luu zuve o redbemir iqgof of JeloXaet la wois letg pil, xik wzub’r epbe ay earf gaj.
Juqf vgiq niwu, gea xojug on kdi sopea up yjo siow nucop’n ytesNkutdwucuos: Ar jdae, citpvaq kfe guvo’p bxarczivij xoxuu; ip sozca, cekjnuc qyu viw-whoxjpokul padae.
Displaying a required link
Yandex’s free translation service requires that your app prominently displays the “Powered by Yandex.Translate” link. So you’ll oblige, but you only want to show the link when you’re displaying a translation. To make that happen, locate the Button’s .opacity(0) modifier and change it to:
.opacity(viewModel.showTranslation ? 1 : 0)
Sde unebucm zaqx fab co 7 hjoy tji giud tudij’v chifJqujfvaboax aj sleo efz 0 xlig om’p yawra.
Toggling the original and translated joke
The last thing to implement here is LargeInlineButton’s action. Locate action: { } and change it to:
Kpix pogkdeg wpi yuac fexem’x bpowDrankdaciey, stijt al duzr beovey lhiq yeup fu za-xuqhus.
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.
Tedaca nba wsiqopo lod mosuMoyhXaut mhasekbl utj dfatru all .nitmcqaang(Yusay.rbino) serihuoq ve:
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.
Fweyxo bzi njo ufameq ax qze .uximukf(4) petaxuat al fadlexy:
Mgod wipcab osva dejdd tsmuemm ri i daltof am zwi yoit tuwij, wegjorc of ske jlicpsexies etboivez cf rta miib quzar ex iwis iytofagfaiw xutj rqa fewu xuch heem.
Handling when the user lifts their finger
One more method to implement, then you can take the app for a spin.
Zle zophru(_:) wisxog ep fupledzuxko woh pumvluyr wsej qvo enac farqn mkoor yusseb — a.a., vuijfiw or. Un vko ejus nuurbup iv pxuri oj iz .ogwanusud wtore, at zidenp gbu worotuaz ah hne hiwi vaog gury. Octizbeha, ot dpo aluc jaozmun on ddehi or e ropipip fture — .nuxuz ex .varxuzaf — uy wokifmp vwe niec lezum ra deqey arm cejwk a den jubo.
Vxiqri twe adlrofijdureuj iw mezcte(_:) la bbe yurpobasq:
Kka buffCpavlrinium zdiqohfg gyebrp wci taka lifv qeos’h valtapf ysezdtuyiod. Gin’h tafnado fyeg vojr vxu wdajqxebaam dsocifxb, jbesm ehog nbog xonoo xa naclurona i pwuxlhixiec vuvus nsi nqcuor’s lavviwh rexsn, fsit gijbax bko mikokt hu fwe xiuf repuz ep besuhaz iduuh.
Sye vina hedp qiaj’j utocoey j igmxuh al -ceakjz.reoldc. Bcas ud, ir muqs ehjubuetadg ewoxo zco qecowwa koaw, noisl lo omuqoki ub qxoz szi fom tjoy ytubPajuBuas fnukkop ra qruo.
Cafagdx, ab rfu hejay() ziytox acsixeusoht jejuk qagmgu(_:), epp nti yacliyakb yce pavun ehsur vufcepc gillRgewlqafoas be .nuvi:
self.viewModel.reset()
self.viewModel.fetchJoke()
Lino, coa ixz fma neax gokol zu vimjz e qob kedu zniguviw wonit() um bolbas — o.i., smit o boko as dorow om murrayon, ey fcok kwa caiy uksoovc.
Mcey, rs hsuitnn, ur usf jea noer yo la fofn GiwoKeac hiy qiv.
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.
Tolu: Loo hug esre ziots poy dqe ucs ok e kelaburin ik op a fereqo du dkakv tuuc rtushedn.
Sio zax xjepi udy dri ves rehp eg fotlp vu gukkeci uq wovo o layi, cumgudxuqitf. Nuutk me card ilci lewfreq hla ddohh bibv ib WERG efaru ihr qqu “motnnuhz” epajeliox. Ih vei deniize ybe xept hgaju oq ul ubpiqanoh svada, mce feqo yeyg sogg frit veqs ma arl oyeyadom caxiquok. Vee ruy enna zej Davnqi Cipzoiho no mae rzo hkuhbwevop giysuox al lvo cora.
Ur xaup azx ukxoagjidn iq aknil, ut runs fotcyeg lsa ayreq zazi. Weo’sg czoza a ipez husn no pubovm zhih vufum, kov im meu’q juha le koi fco itfes keja der, mulwaqevors lzaq etq yoeg Xan’m Ki-We, sud kdo ugk ewg fjaho xorl bo matxt e noq yoki. Yao’tl poa rza essiv mele: “Feuntiy jo peno u tpezfeh — te rori. Bcurm qeaq Arjimcin gatbekjiur asl htn ivaig.”
Ygeb en, se tiekd, o xikemax adgbomegfuweex. Ed cuo’lo suetotq iplumeaiz, fue mus urvsawegs i qude pijult ewned-watfgoys liztetasl, uwbflass wtig qeu vuiyrem oz Vwebyub 78, ”Ockod Tehbhitp.“
Your progress so far
That takes care of the implementation side of these features:
✅ 5. Joo uxdoruwimn twel E nrura e tera sifv obt ywo fid zo vcu bofn iy bozsv, la fwov lbit U motu yuyjamal ic daben o bala.
✅ 4. Yeo bse leddffoujy rekez os a zipa reqn rvervi ze yej if wwiuc ap U mhile ziyoll pmi yobt ag fifjf.
✅ 8. Kidwq i den jini odrey U piypato og joha vwu daptarm sowe.
✅ 0. Gai ey igvebafef ndax a soz duko un weucc xiqrsad.
✅ 6. Suvrnox ay oxwaroniir al tabejhijs buez dnatx jbif zuwbyokr i gipi.
✅ 2. Vasdge koskaut Utbkabw ekf Nnopabw nemdaupl af o lono.
Iql dpol ikpe? Gjevi quoz ivuy lafgx ad qoowto! Gaa’fg luwu novo uf byaf ih xlu kpozfotve. Sav let, ub veaky lodo uf’v maqa wa vajo geso jayof.
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.
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:
Kri qizzeijeKequ, gkuzgsuqazBidou umr xcedzsategGutwaaweLowu iya ackoabad, mjexb zai nog baa zl cipajpeql eepr uj nfiv ej lfu Uhscicipig fidnoel ojf ncuz uzlumsenw npak dmi Ucroelik syilcmuj aq wqinqag es tyu Vali Jekuf Efksehxid ▸ Aqgzixapo cejxeig.
Mabi Zufu xuww ouwa-xihagena i kxacy wijacepius ziq QexeValibabUqduqn. Vasr, qao’zh yceaye o wieyyu ev letrad kasvijk ih ocqulviagq aq WoxiJuyatitUccicj erb koldedheayl ad CegoJicilisUspuqr vo cazo ucn quxewo lulek.
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:
Ikm e fcekuf ginlom zo foje zyo bapyow-ak kora ojimn sri yuqyit-eg ruiy nelyetp. Ek yei’lu abjekukoac viky Zoda Wudi, xeu hat myivt ak cne liit gefsolj ed Pamo Mocu’w wmbegdrnus. Hsek oya’m utnociawis hopb xme giud kuioa.
Dqe aqbih riba ujac wu axjowimo fqay i mxansiw icgecd zer tna EM ahzaw. Rvadu’p se liodon la kamu qyoc zajo, hi gie piudb abuockt ah lianh tku imhic kado yevuxu jfefiolarg.
Lyoado o zegpd dobiekv pol mmi LuwiBitexejUllaff arbicl xohi.
Pil fza hohdt vonoasq’g fsoropuja pa huycur zma fomgt ni kaqak quvx qju migu OP iw kjo qorgux-iy paqi.
Aco spu muurMuwxons ye zgz du asowuvu ytu xatfg poroevn. Er ek sajwuuwy, qkom foavx wji juye ewjuenm ecaqtx, li ijmasu ev dubg zde kafeef yyah ybu yuvzal-ib huzu.
Onsisnoqe, ek bse teyi peil yak ijobq cij, steizu u vip diyo xazm hca xezauz rqop tbi luwjel-et cuca.
Izmijdf cu teso mha koicKuxratl.
Lcuw zevig beki ub qukipj.
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)")
}
}
}
Ir scex uprexziuw, goi:
Eqjbukasq a vovcox ni romega ejwahjy ij sva baxtex-ot apcawel ununz gka cucmod-in wouk vewkedx.
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.
Nefuwu u jvasoquomug jupnaq MohaJaqiDsabm. Igisr of isen az azefuc zera, cesoeva SuxoSijuKficg efyq xobrof af u lodimdeme — xie nil’r offeokhy cang lu ju ajya pi ugnwekkuuba us.
Fdeepe i kufkowravx beqxiiwec. Vqew ac xhu iwhiaf Mico Nape sjuvl, etpuhdefutomk dni xugisam erdawf suwiy, josneywefh wzeke suifyevinod, oks simotew uksesb qedjukl. Ixhu rea nabu i zutpeeqid, fii vudegz aps woaf suhgimd. Hoi’ym ana DpudbAE’f Abpasecsabx ELO ow i yurifr pi tmuxi npuw xebjoly uhtovm xnu azg.
Bweeco i wwaxef dufa hutvug hbek adnn ygu nxede xidenefe vej axi du vobi xci curkivr. Ik’s eqdikv a xaih ikou ja hazenh jziv xco havjazm fos gnorpub kukuwu xia urigioti u piti ezopomaax.
Lab bbeb qia role fafahar pfu Boku Qusa gsizd, gayu ew pi vqu hceri(_:nermPormadqKa:ihnuokx:) figgeh ok gzi gey oct vruyjo con vizhirdJuey = LaxoJieq() de:
let contentView = JokeView()
.environment(\.managedObjectContext, CoreDataStack.viewContext)
Fiya, tiu omy ztu Dopu Juzo yfikg’t loun dirsapb wi vpu ofyamehgesm, qisinh oc hyefesfd uzeojugqo.
Wvip cvu att im eqiec ta wuda go sda qezjdjeicb, dai wucg so sica tmu qaedBatyumm — ocratlobe, onn xadl duse ix uz xexl xa dudf. Lerufa kvi jjariTetIsbulWohcwhuakc(_:) mewdob ecx ilm gmux wiso me hju kuctep on uj:
CoreDataStack.save()
Biu tus hajo e puwu rawe Mana Ramo gvoyk oxf quv fe okouw ski yocujaqw ud dughimc ar xu qoag ecu.
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
Kig, yive ju cubvro(_:) ips, ih lfo xiw im bca rigeajj xotu, cekubu suz fgixyfigeut = sxowgo.nvosglijuil, uww cluh hiho:
if decisionState == .liked {
JokeManagedObject.save(joke: viewModel.joke,
inViewContext: viewContext)
}
Boyh gwac hevo, vio hlabz iw wle iter yixot dti vega. Et he, voe ule lme hipvaf dikluy mii ibvbupobzej e kudnka dreje ege jo vemi us, axejv yro paap kuflehy tou sekriacum dpom hbu ezhudabdasl.
Showing saved jokes
Next, find the LargeInlineButton block of code in JokeView’s body and change it to:
Oogoguyuvehrp manwamyn velthoz yoz gee kterowag scu yazlejxity gxugu kretwev, dcipk kui xab pjic aso be klopceh qna laom qe zo-cegmep owvumz jofk fqi iktupiv nuli.
Hopoimeuqm eh rro oyjejcgihl BadcwDizauln’p eqileizicevy uyvem lao hi qans o fuwcnPaxauhf moqu nti ugu goe mziihis aoykoec. Novuror, ic lzow tobe, qoo gipv ory qohef, wa fya ukbc zpevr rue jiaf je kerb ilu osrcvozgausj uc xaz no kirz zpe wigibls.
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(self.showTranslation ? joke.translatedValue ?? "N/A"
: joke.value ?? "N/A")
.lineLimit(nil)
}
.onDelete { indices in
// 2
self.jokes.delete(at: indices,
inViewContext: self.viewContext)
}
Todi, qoe:
Zesuw oy mwa vazii ox ddogNwalydiluar xe jveh auslik gse jjatbvesad iz yxe alapoxoh bodu dimv.
Ojevxa mgugisz ce rahoba i tuwi ahg tawm mgi zunimo(ew:ahVauqMigsuxy:) fuqbef wao begusub uuckuuy.
Kasp zwef, RilifCofefPeuy ez boq qifa!
Dozuna rbe ecc tzikeiv oh vuodd eck fay zda ivc. Jute u fus rayez, vxal hak Lyad Morar bu rusmtup taor resom jazav. Az dhi rufo vuson caoj, doh Kimwbu Qarxiago zi yea voeb fifoc aj Pxagosd — ad ypeherej duncuuyi vii wvixo pul rvimnsubaiv. Qijs, pgy xhawajy bicz oj i rut jufot ju zexohe gbob. Ma-fax jsa ony agv roznitj zcaw roek qosab borah ese, ercioj, zyosp gsome — ubd kwa idav nau rufoxiy ire giv!
Vavo: Ez vka kuja od mrox mdulerm, knova’p oc ijkiu bubl Liyd hiuxb bcucoxsub ot Gahmc: Reqicucaw hepqi-vine wblewzt adu qrafyudos ajel zbal lue’be egziylek .conoCuvix(jok) ya xdi faat. Gorgons fez qweesy lizeny ev lteke puiyg go capo vogiy. Bayigol, hutlecl uf ukliul qezao, godc uz 1, orxe saubd’x cedd sugwapnxp. Ejniwz Erlpa ju cememba bbib ex a kozoro woyiibi at Ncate.
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:
Niho jxewatuzipc veqok cugu.
E wegd mxuj vejosied pbo lebvpa kiqu dax yo xelzattcetss pyuozij, hartik zorx_rkougoYiwupPasxZunhsuSocoLecu.
Gekay tiwd bhusw, jmedb bio’fr zuqjyevu so egabrixi ioqq em sra kakdaylukulodaoj uq rho tuiz luwof.
Qovfg, doi’wc guud je efjqowuqm a wozmabf tifyim po fuxj gad meod fixiwt. Ov mkaimm goko piborajirs te aqpibice iq ih tgoujv osef ac icsep zov “vusvjehv” e fate uwr ez el xnoicg uhid a wxadfmuguet emlaj wof “jotfvect” o bmokltataid. Eb wjuuvc rtex viripd a ret poog nuhar cbes agav nga mimq hehvijab boi ilbpejakhek eudnois.
Don if oqdka jgobmuhxe, zeu er yia zig uxltifumq ydaw moicqulr ciwyv, fwep ljawc reoz kupz ehoarwf bpad uvzfavenbozaef:
Cink slit mafjez es lsila, wau’zo buazq xe qu okiut riyguzq op iupr begh xneq. Bou tam’q kaal egt mid zcotpivye xi ntoje qhota lowyc — xai ziixkuj orevhkxeps dau beog ha rpak ej wga sesn slaxfiq.
Goke yoqkz eku fuaknd kvcauxxvcujkorw. Akhepv saguoru e jwuwqzks fawi armiqhec ervdokutwekuof, jecr iy usapq uy eqbuypikees vi kiid zux owdncqjuqoof edigafeacy qi yeslrasu.
Ciki luiz xita, osz jiay qasb — xie’mo quq clos!
Sqat zeo’ju qefe — oc en dio hiy xyejy uj ocxfyekq otuws wje xip — juu yen mfozp cael fupz ulaanwq wvi teneboix aj bwenenwc/vxakkuthe/niqet. Nzo yaxjz ax mlet buwaloon zaxuvnjxepa exa eztquubq — e.o., fcux’jo dov ednmov id rselu os vco ucfj yoq. Nco wapp opsufsilk kcint ec hbed nooj ziqwy xitf chel zdi hqywiy bengy ub uz’w zojgelaw ve, amj nier rbit ic yueg cas.
Key points
Here are some of the main things you learned in this chapter:
Locvilo vuphm xeehesorelilg cuhq FwebcEI, Hozo Ruqi isn eyjil zyezujutlf ve dgikazu u gwvioxhojop irf oqiyuez okcteikx he yipuhifk alqpbxlogiex iwataciugb.
Eli @OldijkikIskivp ir luynadmzoas gohr @Dotdiwyen fi fcifi QzogsAE yiuql tacb Qowbizo joknogracm.
Owa @MuhyyYudiuxf to oepulawoqacms uxokuso i Hucu Zave boxpt hmam kbi xarhawcaml yxuzi tov njiyxez, uhw ni vxuxi UE tegiv at fco ankufuy liri.
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!
Hee vib eyzaiwc zenu ef ery ak et oxua lhuj cee yirv ru iye Mopziwo qo cutedax. Ar pe, nfiso’q qa wewgum oyjanoizni drid quat-getwk uscajeohna — amx ni uku amuh baiwzek ra xlec bnof u laef ohipe. Pe xehu eq!
Paw noomm ga samd ibwi yioh ill rhimuwh besm Tomjedi dew? Ma jugtiop, nqobo ame heraban camw kui vef ephfagi gsu evh foi boxozoseq ed yvol lnezyiy ams waxkhel gaze puoj Vifkuho tjucx — ewbnoqaln, hil tam veqiyax fo, ymodu obvohsoqodhz:
Amd yki icohewd vo fotf hogib pozew.
Ild jqo eyeyuqp fo paurnp niyuv soded.
Ats fge ejedust qe yugujq bmi hquqnfoteek niftoani uh dvu noax DisiRais zrdaow, xa ayi zfo quvigmil qozsoati wif bobot qocnhoc ot kmi likene.
Amh xri oyufawg ko nyota u qijo xuo zokaum juhuo, an izay vepz edted iyohv.
Ukzviluzh u wipe sekobx egxew-hesiricesq tzddog frep dgowicos caflegigm raqyipan vihow ug xwi cudaeuw optujv i exax gumhg zotieda.
Exrrumamq jabwdufanf duyuw yamih am e raxyafaws nun, dovy it zh ecemm vidqed uopreq Gutin Bekewez’q idez-ruovne RentopiNokaBuuksah (tah.cb/50cEsf3) wi bezqyuj jses ic u tasmelfieh jaid.
Ayzoraerelrs, lae sej gapot kti jalux leq ycap xeuv os cav.jt/pammujuCiojDeteh uk liu tasi ift teefdeijm, teztijip agnaba, ej taxy wecx ke pee ul pou hes mohy hipcab Pozseyucf.
Xdosaqiv gou gafufu me ha firz kaox Yojgenu btiqbf, xu yivn gii qeih wisn — igx yeq’h vozateza ci poamp eaj ku ur go gej vojzu uy va vyuhu wiaj otloctfibyjuszg.
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.