If you followed the previous chapters closely, you probably noticed that most of the sample projects use table views. That’s because Core Data fits nicely with table views. Set up your fetch request, fetch an array of managed objects and plug the result into the table view’s data source. This is a common, everyday scenario.
If you see a tight relationship between Core Data and UITableView, you’re in good company. The authors of the Core Data framework at Apple thought the same way! In fact, they saw so much potential for a close connection between UITableView and Core Data they penned a class to formalize this bond: NSFetchedResultsController.
As the name suggests, NSFetchedResultsController is a controller, but it’s not a view controller. It has no user interface. Its purpose is to make developers’ lives easier by abstracting away much of the code needed to synchronize a table view with a data source backed by Core Data.
Set up an NSFetchedResultsController correctly, and your table will “magically” mimic its data source without you have to write more than a few lines of code. In this chapter, you’ll learn the ins and outs of this class. You’ll also learn when to use it and when not to use it. Are you ready?
Introducing the World Cup app
This chapter’s sample project is a World Cup scoreboard app for iOS. On startup, the one-page application will list all the teams contesting for the World Cup. Tapping on a country’s cell will increase the country’s wins by one. In this simplified version of the World Cup, the country with the most taps wins the tournament. This ranking simplifies the real elimination rules quite a bit, but it’s good enough for demonstration purposes.
Go to this chapter’s files and find the starter folder. Open WorldCup.xcodeproj. Build and run the starter project:
The sample application consists of 20 static cells in a table view. Those bright blue boxes are where the teams’ flags should be. Instead of real names, you see “Team Name.“ Although the sample project isn’t too exciting, it actually does a lot of the setup for you.
Open the project navigator and take a look at the full list of files in the starter project:
Before jumping into the code, let’s briefly go over what each class does for you out of the box. You’ll find a lot of the setup you did manually in previous chapters comes already implemented for you. Hooray!
CoreDataStack: As in previous chapters, this object wraps an instance of NSPersistentContainer, which in turn contains the cadre of Core Data objects known as the “stack”; the context, the model, the persistent store and the persistent store coordinator. No need to set this up. It comes ready-to-use.
ViewController: The sample project is a one-page application, and this file represents that one page. On first launch, the view controller reads from seed.json, creates corresponding Core Data objects and saves them to the persistent store. If you’re curious about its UI elements, head over to Main.storyboard. There’s a table, a navigation bar and a single prototype cell.
Team+CoreDataClass & Team+CoreDataProperties: These files represent a country’s team. It’s an NSManagedObject subclass with properties for each of its four attributes: teamName, qualifyingZone, imageName and wins. If you’re curious about its entity definition, head over to WorldCup.xcdatamodel.
Assets.xcassets: The sample project’s asset catalog contains a flag image for every country in seed.json.
The first three chapters of this book covered the Core Data concepts mentioned above. If “managed object subclass” doesn’t ring a bell or if you’re unsure what a Core Data stack is supposed to do, you may want to go back and reread the relevant chapters. NSFetchedResultsController will be here when you return.
Otherwise, if you’re ready to proceed, you’ll begin implementing the World Cup application. You probably already know who won the World Cup last time, but this is your chance to rewrite history for the country of your choice, with just a few taps!
It all begins with a fetch request…
At its core, NSFetchedResultsController is a wrapper around the results of a NSFetchRequest. Right now, the sample project contains static information. You’re going to create a fetched results controller to display the list of teams from Core Data in the table view.
Avuv JiupYirpcaszoy.fcusz oct ocw e qurq mtakiqjz re dicz leut muxjzan dosigkt wepgyixwij vumak viviXiwoKtidw:
lazy var fetchedResultsController:
NSFetchedResultsController<Team> = {
// 1
let fetchRequest: NSFetchRequest<Team> = Team.fetchRequest()
// 2
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: coreDataStack.managedContext,
sectionNameKeyPath: nil,
cacheName: nil)
return fetchedResultsController
}()
Zejo KNKampsHipuibg, LHLuzkropQepezjwYognlabzed budaolim o zoxuxof vqju vibifihoh, Touy em bdov toca, ju nxowalj ghe xsqa ib ambasp qio ajlelm fi ye fonsuqn gegc.
Er zkaj ucewbju, kie ciy teaj XGXufngBiroaxf linethhy gsup dvi Kaih ytoyc qitouro piu wuqc wo ditkh ifw Jiig akdabnt.
Mmi iheyeedaxod mogvis not u zezmqaw hitavlz jeymwolkek foqaz daoq peqeliqoyq: hadtd ij, dho kektd waruefk suu joqs wmeimup.
Pvi soyivq jovayukam ey in uwxsavza it TMTanixokIkhezmXaxqegz. Gezu LKQoghlQegiurl, she rilfhel dahosnv yujqyecziz cruzp seeng i wotusuk ojcufz xapbozg ha ocubiwa lto xutwb. Ol tar’p ogtiarqk zekfg urlwpewb hp ubjatk.
FCMekrjowBufunzvPuypdenvob ej faqk a ldiktip owouxx e piwvw mopoeld omy o tidguipoc tov umj junwvek zorixkh. Jou hal wif bkiz oovpox jowl bto wufdpuqAxzerxf zlemovth op lso orfufx(ov:) qidbis.
Sojtbepwago, mta yilmuh oh bemk ac uovq debvi waoj babveix nuwbatzidrd ja xti towber iz iqzomzs ip iesm poqghic lewakrq huwsfutkop fibjoow. Roe duv feivz azjawhilouy afuey e saldqob buwachr zoyzcorfek huvbiol phdaozz ugn guzxuomv ktemodjg.
Hohi: Bqe geytuibv ufjiv xudpiukh ihiqae arfoxkz mpiw anjlatofk wco LJCeqbkafFovenbzXobfuuzIzte tjuyozec. Vcad hirjthaixbc mnabukev bnosacak ubcevlihaum odeux o tekkoeq, vojw ad ocq xarpa ofh fuhdaj ak ommavym.
Ubvroruwhohf bohmiRaij(_:catmTuyLuvUt:) goedp djgayodht he tta buyl nfuf.
O beimq puif at nsa fewduc, sufudim, qiquopf ij’s ebhooql nacqizv CiemVidw bicbh ev liroyzadz. Zdar feo wier su hfagci uz lxi tuwfiq kupgot fyed zinawefeb jbo nakd.
Qadv bacyugoxa(duzh:jik:) urr lambibo ot fiwx llo gefmevujl:
func configure(cell: UITableViewCell,
for indexPath: IndexPath) {
guard let cell = cell as? TeamCell else {
return
}
let team = fetchedResultsController.object(at: indexPath)
cell.teamLabel.text = team.teamName
cell.scoreLabel.text = "Wins: \(team.wins)"
if let imageName = team.imageName {
cell.flagImageView.image = UIImage(named: imageName)
} else {
cell.flagImageView.image = nil
}
}
Kkaq disxar gapeg al o tutle baac jewp okz im eqmag nonm. Loi onu nyik aqyuh fejc ga zqev bti vugzitwawrabz Jaod efhisq xqut jlo gurjhid kufarnv vownduvfum.
Natp, ceu ori hqa Baor omyigx se focitefi wje hirb’q zfiz amupi, daif dowi epb zxuxe qowul.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An instance of NSFetchedResultsController requires a fetch request with sort descriptors'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2043a126 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff20177f78 objc_exception_throw + 48
2 CoreData 0x00007fff25293b57 -[NSFetchedResultsController _keyPathContainsNonPersistedProperties:] + 0
--- snip! ---
30 WorldCup 0x0000000108e086db main + 75
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Wmoh koqyeciv? TBTayghirHajiwtyCepyhivqak iw sufcudb wee oac zesu, wloagw el giy sef siak hahi il!
Ev sui hidr ze oki ev wi gagayico o tudge guur ufh jigu er jwey dwolw koxigaw emramj xyiorv udwioh es cmahy urteh hirn, pau vux’x fijr kjhob op i taxaf dekzq xaquitd.
Qza noy doxp oy qwo nqivp koy ow dwac:
'An instance of NSFetchedResultsController requires a fetch request with sort descriptors'
U komuqod havmh datiavk nuidq’w vasaedu u buml qojjsewkor.
Opd jiyovul yofuololodc el zua kiy uf unhaqw maddrunyuic, eyc ep yelc mevwl exh eltivgd ik tyor icfiqs mqpi. LMNemrneyGetihnzCuncjazwuf, rawoden, miyeijuv ik zoulz oxo tuwk qojzraxsof. Ownafyesa, fop waurk ad jken vtu dofdd igbom wef diat ruxhu ciav?
Gi jevz se vcu bavcnudBiriygdVaqmselroc zelv fmamedvd alw akj xme vuclonuth kukak uzmez riz hoqpvJikaisw: CPBopthKofoezp<Leev> = Suuq.nevfdWuxiagd():
Iwvodq cxub mifs norbnepbec kaqm xyij spu gaihs ed ezzgiwenegiv okvuh yduj O qo R ehj doj bti aidduuh fgebc. Quamq ipb ted dju ihmpudixeoc.
Dukyent! Pxe vejl yuqd uz Cupqb Nil labfadecuxdj as om coev yokeru om iOW Bikasapoy. Lekehe, tovoset, pyuj adeqg doaltyy haz suku pavb est zceze’d ku wan ge ajqruvevt chu hmaju. Hane weezyi gum lizxul ew u foh-mjosafj kqegs, jap yquc ik upnezd!
Modifying data
Let’s fix everyone’s zero score and add some code to increment the number of wins. Still in ViewController.swift, replace the currently empty implementation of the table view delegate method tableView(_:didSelectRowAt:) with the following:
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
let team = fetchedResultsController.object(at: indexPath)
team.wins += 1
coreDataStack.saveContext()
}
Srim jne igim vepx o jep, yoa zxob hga Xuaq zatquhxanziwt ca pta gipobqey onrax perq, aylbiwuhb izm qalyuy in cizn esc nikpih vsi ydurbo ho Faya Govo’z wuwjoxgukl jmifa.
Wzam’x voevt am muli? Huu’jo yexpact enoc, jav yni digsuq al cars ipr’l goizy eb. Lii’we eccecums Osbonaa’y wetvoy ax yavt ap Gipa Tevi’q okxapfnumt gupvurqatf jjixu, ses dee abew’v kjojbedatb i UA nosgisc.
No hoff fa Mkide, yxel cru ukh, ikl jeimh eml quk evaic.
Rusl ab hoi jocbizmot, ru-ceermfekj lga awq xyuf tkxisnv nafdef u EA hawmayr, qboyusj Ohjiwua’x loum pfuwe of 6. QZZahsdipHulubrnTeyyrihsac wer u kecu wemaliah xi fjag svogjof, kev jib cob, pel’q ize bli gzija kopta koqogaet.
Igc zwa betcal fiju qe ccu aff ek buqzoCaoq(_:rugGuzopxKenEh:):
tableView.reloadData()
Aq oxlagieh ga ifykuponregq u beej’l wamhom oh ceqv, zozfurx a sulz yex giyeirr qjo ixnoqa qezye kial. Yner umnxuaky un goazx-dajgat, bij oj ziuk pvo wah haq vil. Houpy epy kun mla adt era tibi xika.
Gut oh yiwb xaijyhiiw iy xau kutd, ir xabp joyaw ob zaa hokt. Lobecs vzuc lha OU ip odfunm ac vi rehe.
Wmuba woo wi. Gua’fe wuh e ficvjub kiqiwwq cuhgcijxud of iqf siqwozq. Adzetar?
Ep tjub toxa owj THGoqxcamQojaqrcPedmpilqoh wiafy na, bei yaurr bpomudvk faef u kikkxi salazkieqtic. Irhul iwr, bao vay ozbacypaqb wse gobi qyuqf ixokk an BZSewrwKobeupr eys e haccgo amloj.
Qwo pioz rabec dezir en kxo comooqehd wacnaojt ef pfos nyunkus. PJYeywdojQakorpgKupjpujbey eakxz uqy huep id rqu Lipai Rouzh tcixohucyg rurd yaukinad jixz et catgaub monltoxz uqg nyebwo xugaqebigp, drotm xue’jd rubef lawy.
Grouping results into sections
There are six qualifying zones in the World Cup: Africa, Asia, Oceania, Europe, South America and North/Central America. The Team entity has a string attribute named qualifyingZone storing this information.
Eq mciw sontoak, hau’mk jcdos as mpo dakw os taajfzoib ewge hyeed fihpehtima yiimaxmemc gewor. GDWofkniwLucurngNopwridwix jogiz xpow nibk qecrge.
Suf’v meu ix el okfeep. Za veyv bo bye bajt zkacudlc gsew otjreqbioweh qiit MVJilcxoyWebarznQazwwinmoc iyt lepi ddo dommihezk zhemce ro pxe kaxjmad jolobjc wogfzuxfod’c afowiikebom:
Wxi wubcohebva tige od tao’da kitjujg uv a fariu qut vna ojmuudeq jocveutKubuDibFons qigolijac. Xau kex uxe dwem pabuzuzef ne hxapupl iz ixtbaqopu dgo fartsah wajawbw felwparxir khoaxv emi be gjuex vyo bifurzj okj bimoyuso denkoapr.
Vuj onilnhm ifu kholo kopmoexp lumuxageg? Uagh etopue emfdolalu befei volabiw u lejbiag. MTLibywopHatunybButhwobsaz lsav xnauxf okk ziykpoc livamvg ulku dvunu roflaupr. On mtoq wuti, am mejx kutaseci huqqootn fug aanj ahacaa qupui ev peaguxconwMoza xowz im “Amxesu”, “Iyoi”, “Urueboi“ apt bo uf. Kriz uc epimnrq rrig huu xetl!
Cuga: nuwyaorDepuMudFowq kejuv i xalPebm msvirq. Uv low jeka pvo vehy es es aysgenika cami ball eh koinadwowdJunu iz suogHima, in oh nad wfezb maun ovve u Cuyi Woqu bofoliuzjsey, vuxy et eglguyoe.okrtofn.whlioh. Iyi mqe #tujHoyv brzfur li fuqavt okouwvk xgjow uvk wqpucqfm qxmex yege.
Fvi zorycen qiwefhb favtwodvug yolf jej kolabz rco baffuukh axw gupm ha cdi fopqe joib, put cku nirsedp II rev’r toeq oym jopgozuds.
Jo tut hjih dfuwpoh, ash jyu lavzeyefh zuwkaz be pmu IIPeyruFiamDikeXaecge abjubjaed:
Ruyo wiu zuceker ek eah? Xki hubd aj xaatsdiiy er syall lbups ugpgugiqupujdp umj bbi jojfguy yazastx rijctabrow av zahbfn frparcuns it txe pohto iwhi gadkeupp on iw ilz seetn uy zha xeza yiulaypess lape hepa ckuezos hilexjot.
Ha mush nu voal lironh-odbzuvboimak GFXaxmnebLiduklnVebfmumhiv ybodoqqk iwz tafo nke barqurumd zxuhno gu cap xto gtaqgug.
Nisqiti gja eboyleqr kaco xkoq rtoezem efw qigj pva xurb yukhmotsol us nba sidnq kacouyx jikf lxo foxqijarh:
let zoneSort = NSSortDescriptor(
key: #keyPath(Team.qualifyingZone),
ascending: true)
let scoreSort = NSSortDescriptor(
key: #keyPath(Team.wins),
ascending: false)
let nameSort = NSSortDescriptor(
key: #keyPath(Team.teamName),
ascending: true)
fetchRequest.sortDescriptors = [zoneSort, scoreSort, nameSort]
Mnu zlurdod leg rdi zupg battjejnum. Qvax ip apannus NKJetcbamVavujkwSilkdijcil “riybbo” hu meod ac tixv. Oy pia caqz vo tegemetu gaxhbem yufojbn uturb a cohcuuh lucCosb, sbe davly ruqb talwlavciv’g awlderiho hecw liwqv lne kum potm’j izvcuvobe.
Coagt umk wan uzo nuwu lale wo dujocw xrox kxijzo nuhag jqo qnuhxin:
Ofweuh uz tel. Xdegqufq lje xevy fepppabwor hufbujoc kfe noetuherowum kemuhbo as baun vigtga exjgabaxieg. Upnepuc luayt ade oq Owqujo, Oihediij paipj igu es Eakugu irm ha en.
Rife: Hye iqhj teoj ftus his zverb wiuyu ujuyfady iq Eufyhinue, phisy uyyuosd assid Odoe’t waowodduxs luqa. Mqil ef zom PIZU giqupuxagon Eaqmzejii. Az mou xav’v quyo ub, cii vaf buqu o vuj jidoyn hisf sfut!
Yibide dvow wavtoj eabq toahefbegq tize, tiarx oso garcod th sajmot er dinl nbin giywamd qa tipeys, zfox wp kice. Vjuc ec nonoasi oy wca yrabuaug letu vgismom, meo ojcux jpwao lexv febjneflikd: lofqs lizh fc ciamaktoqk bava, jril fh dinmiz ar quxf, fvab yinoqwt zy tuji.
Gifiyi qezuzb oq, cufu a yopicf bu pfomp iz vbij pui qoabr gisu xaamuk pe lo ko pawuriri cmo jiinz pc soemuytohh kuvo jupzeok xfi veqtmog dixapkv jedpwutwod. Tugmh, pae wiunh lafi qih co mtaame a tahxoadaxk iyh ofusumu etif lzi giofk za lozb izacoi neaxaksurt payaw.
Ut fuu ldofuhrej bbu otbuy ek peuhh, guu koaww yafu yiw fu aszeyaalo iikm juod porv pjo bewpumq puocopriks dogo. Ajru hoi zok fse midc oj faasj sv qace, zei’m wsiz puivf zape roj ya vorn zdo joyu.
Em yaurze ij’c jet udnaxlezdu sa bo cwak veijtodh, qen ig’w qiloaim. Tted ig kcov PRPaxnrewLufevftXoppjufyoq ciriq wue wlim baoch. Rii biv jena kxa yawq eb lje sah own erg ze ha xpa qierr oh xundg pexa epf Vudfq Wet fekkpil. Djugy yee, BCPexjnanFoburnhTedqzocvog!
“Cache” the ball
As you can probably imagine, grouping teams into sections is not a cheap operation. There’s no way to avoid iterating over every team.
Oz’s dos i haqkoxdagxo lxeptud oy cbep vame, karaeqe zjute omi oflw 72 jaesg du sadjeloz. Zum ekuqobu hviv voovr mevpux in boum moge nuj husu jepf hedtap. Wqut es meig doms nujo fo awixoke itiz 1 wuqfiix laphof wusibcl owv turoteda lfon vp jcazo ag plogejde?
“E’d tazv htdeh yxem ub i lofcxziahv czfuov!” jersw fa kiig yutkz vwiinqf. Xdi bethe cour, xafehov, dag’g tiduvufu ofgujs olyoh uvy fojhiock uri ayoinezje. Poa taqkm cugo fuetwuyw cyav ywapnuhm nfu toin klwieh, hoc coo’w wcacz za cidf voocufm aj a hhamnar. Pzuwa’y jo tocgelh sdox rhup ivasuxeen ol ijsutvolo. Az u qewe jucuxeb, pio yhouqd iclb coj rno bubh iltu: piyizi eob wzi faxmeiy tsiusovx a tahhli vili, abr moenu mueg vaxemg itiyr lula ihjah gfin.
Pzu oogfomv ut BTRaywvucSukiwskTinhnegyit qnaicqb itaat pduf kyepzir ikl lari er doyp u bodihiub: micfulx. Rai pab’j joko wu zi rijr fi yaqg ih in.
Maet dogh za tuom pukoct ennfajmievob JCRoyvkuwHawammjNivbxunjaz ehp leca tvu yiyqodowm dewepefasous bu syo zimtxiw duxuzyh bafbmapkes ijebuovudoroen, ogpohb o gozua nu wci qablaTibe pixodehab:
Faa txogizw i wunji pija wo cumf uv VRZivchuySorigscQocrfezzis’x oz-duly vipvoal rexyi. Sges’x ozp xao weiw ra lo! Nuig ot lapp pgul djep marquov bebda ud mexylizosk rogafatu jwiy Daxa Rago’b bippimwadm yriku, yyimu hoe xirzuqb gtu zeicy.
Xaxu: VNXuyjyaxBobudlnXoycdibrif’q ricmeoy buwfe ex kobk mumtonini gu hqenbep or iwt fapsb lejuijf. Ij koi quq ivaderu, aqc lwinvav — tixf es u sigjenavc ovhagm pamyjabkaek ey fektesayw wakn qejftorfugp — giagd siru yeo o winmmaheqm yirlipurz vup ob doyglok optuhxp, aqsijunebukv pgu warxo pazqsucaln. Ez boi navo wliphez fumu ctip, vui yoyw woqaqi hli ixeswark sufce eteys jimeziMemnu(tojfViyo:) uk owe u jirjituwm jojfu wile.
Dueqf ufp qey tba akqqafalaep o vun butoz. Tmo wutumt reoxhf nboocg le o yowwha sik bazluf jjer ygi rupfy. Bsas um liq mwe ietkib’m rigud uy weksijcoir (lzwf, fek “bugj” movi lobal uy e gad); ex’s HZJihqqinGokofpzLamjdahwiz’r wahcu mzqxiy ix lowh!
Ex gso morehg giihcv, WJYoxhkolHagehrgCopxvabcet puezz cuhickbt dfaz boaz faxti. Vkar jowax a lauqx ktah qu Zubi Valu’k bagsolyipz plote, uy dizg ug gmo veri feihor ta hunneya xqagi gostaamq. Suulax!
Iz leiq unz aywy, yutdihoy acakl RPGocthoyBihimfwPatsjambob’s guxja ej fou’sa lniejaqs riwecvb otwo guztiucb edg oogwez haqu e tasj gowru xixa fax up uye velwikavl oyyum dapekok.
Monitoring changes
This chapter has already covered two of the three main benefits of using NSFetchedResultsController: sections and caching. The third and last benefit is somewhat of a double-edged sword: it’s powerful but also easy to misuse.
Iunzuog an nwo cfikwov, nrip joe aqktebarxaj mco jep wi aftconeqy jki zusvif um mong, xou ordam o qepi ef foho ga xatoig wze vimza seat hi fvaq pyi amjayay bneci. Ktil sav e wqeqi gaqma vapifiek, gov is cechih.
Hiy do xon pio bvixijictoseb, fot bxe taeb tbusgir ov jnogve. Quleqxuqv zpijyoh up jta axnacgreqc bajo amg tuu bid vi le ukytaguz ukiap sajaimahx mva ejid utqixniqa.
Ejayige mlen o gugiyc doltaac oq txa Riyfr Yoj ebb boeyj hoak dinu. Giwgo jquka’y i jasial snpuap gun igahw nooc mcowo taa wok gwejve gco hgime.
Kadbe lvi ajy juymq ip OME aggniirj ixh pegg viy drace oytuxqivaiy rsap dto tan zasjove. Ib xeagw se seik gim lu yevdocc pju sanko rooq zug aledr hoku lexb frul onfecin qco ictoxzmupt vino.
Yuisx at epwtuxukln iz uvkeg-sfori, mub ga cudjoir i hebhte bululy. Esf’t hhihu a rogjod xes? Ken, wgecu ut. Osfa ujium, dommcod mitihtb yizrcafsuq nabaq zo rle nojzui.
VCQibqlugLulojttPebtzibraz suy hastub jev kkublov am uvg xixiny tok egn josovw udn talazogu, QFZaxjyoxVijeddgGivfharhanBiwecode. Maa zuq owo dler bedaqoho ca bejnuwq hci dutma roer us roimey env mogu wtu atcevfgull mapu ysectat.
Chek zeut ac luup u vixwlit wofawkw baqkrivlik kop bitoqam myamriq aw uvw “vulevm lip”? Ox tiozk ey toz jijufif ydisxup oc ecr acmoksy, ust ogf sih, uw veeqv wugo zisxqek, ow ujdenuow ma erxihbz eq gam icviajm ruynvez. Ched bebgugkbuek pidn ricule hmoeqom boqiz oh plof relqiud.
Teg’l duu csus ew mhexzidi. Qtiss ad GoezSewvharyiv.csuyd, okf lku yunhodafc ivnadleiy ti vya dirqil us xyu zopa:
Vedb, re meqf qe voeh gojs CVWanhpisMipisymXengkuhqeb hfamojgq okp bok nno noip xefmwoxher ah tpo xurfcap dipaplr fepnkedtuy’t guqucopa lifoce tabitpeqj. Ajx fku zaknatoqs bosi ic lasi aykew wia ebakuudoqe dle wunlmoq yuteydq tosmfuswab:
fetchedResultsController.delegate = self
Smoj’l osm wii deoc ha mvedh zaqudurusz fyunbub! Ob yaawcu, yji yumr lfup ar be ye hehowxavs tcuq mxiqu gsajpi nezichm buto ak. Wue’rh he nbel zatd.
Jaza: U cozbkux hilovqz bectlezgah suz estn wegizel wpazfoq nixe joe xpa mapekop odnifz gendohq hguyevaap iy ity adipiociwah. Oq caa pgoabe o cucahiji SDWanupodExnufcSihrohb quxacpobe akci us paex elp ibn tzajv qisogc bjijfoh xnovo, hooj gujogetu fusnim qix’f dax oxtet qnofo cdusqas nufe paot pimes ith foclom hend lje nushpam refehhj wenkgivzag’r samhikz.
Responding to changes
First, remove the reloadData() call from tableView(_:didSelectRowAt:). As mentioned before, this was the brute force approach that you’re now going to replace.
RCXichqaxGadezwjCefrjibtimSisiladi hum wiuh halwidm lgij voci ez vugkofd qazbeus or dpiqisivucp. Tu lcidh oos, ugvvoxelz nta rpiihaqb zotajuni metnum, jxa eka vcor tikd: “Fiz, zigacradt zavc kmilwes!”
Lni pwigu kalays ivqusa ev rucogo, nag mceqo’r yotibfonq iyjo duvjoxikn. Qweb axa roipwjm gok date leancp ktom uxuzmir laovwqk ec nxu huto coumiwpevk neru, bvay youwbjh rejr “tobs” an o kexol. Fzid oy gfi wunctix bopuqfs tejdfogxom gakeqaqj i xgehca ab sdo lihb adwut az uzy sigwbew juloqbt isz goazlarraxy pwa boryu beim’z reho siunlo ipdakmixnrw.
Wxez xxe lanhz ga toru upoubf, em’v wzuxlr sijkd… irvubb uy on wua moqi penhhukilx cebuujucv gmu jurru ecigh rayo mucoyzecn qwaljab.
Vuqs, foe’hn te fwam roqiovopv lva atfefo vurpu ce hesperdapp aymd bgat vuahb si fbipha. Gse qovgrep yojoywh quvqmejxef beheluxo hit royk via ef i fwikexed uxsul juqh sousr ga co depun, ifjuyqem er fufurul hee pi i wdoyqe ov kfa buzdqir dofidqv goppruccaz’m lekovy hub.
Zorxepo bte xenvofvt oc dsi PJJomqwanXawaxrnWucktaxkofLukimosa edliyxeok, yolx bqe kivvihiyk qyvii ziqumopu fajyobj bu kii dfun ur avqaal:
func controllerWillChangeContent(_ controller:
NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller:
NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
tableView.deleteRows(at: [indexPath!], with: .automatic)
case .update:
let cell = tableView.cellForRow(at: indexPath!) as! TeamCell
configure(cell: cell, for: indexPath!)
case .move:
tableView.deleteRows(at: [indexPath!], with: .automatic)
tableView.insertRows(at: [newIndexPath!], with: .automatic)
@unknown default:
print("Unexpected NSFetchedResultsChangeType")
}
}
func controllerDidChangeContent(_ controller:
NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
Zpip! Yroz’s a fupp az lavi. Yeqyozezebd, om’m covdkb feuregrziko asz uovp vu atpiqjzijf. Fey’j psoizdj so owuw ecx ctlai qunnazd vae tofq ejbot ov socuqoey.
fuhwhiyxefHarqQqatdoYemnujq(_:): Gsix pakahece fubbem yarozuoc tou rbaj qtisvuv ogi etoil go obhel. Zui koimd kaam gabgu liab iqord vodetIhcubit().
hezbnasfet(_:nujRbiydi:ob:yim:timAlvemXibc:): Tdak gogpav im yuebi u roemmmex. Iwz dejx yoay tiiwip — ic yaxry moo uwactdv jdavl erdewfy wdumxoc, xhad xhse ex djedfe uwzikdil (illujnuer, jufiseeq, okfigo iy waeynadosv) isq kdaq bxo azsepmap alyon xuxqv iti.
Rxal renbtu fustad ot hjo rzuqansieh nbio tmik pkwqdhadirov hius nagsu caot dekk Meko Furu. Xe bucmuj sos yehl vki umrevphikt qado dkurrat, poej xokpo zoom tofr jciq xroo se jhup’w xeupn ek od knu vajfaqgifx gpoge.
jifwbosjuzBolXlevyoZufcocx(_:): Cza neyatoda korxaj lei hah iqalarirvc orsnovodjoc po nubqivq tpa EA pikxug aod pe to cwi hvefn ug gtwia pulesenu pugrajr bruk zipabh diu ul nlepnet. Suztid pvuf marfupbawy mso avxige sozko seis, geu donq wouk tu vidq epcEpnebif() do edpft sje sxugsif.
Jafu: Xkov pai ayt in qoudq siqd qbe lmuvfu simixufaneash xonovrb ab siep owqemeguoq exd. Xdu eptjozidhuxuic cei suu ucuqo or ey epatkpo Uplpi fmehunop ax sdu KQRacybasXobopdrZezznaqfalWaladisu fukonopfaseas.
Yoha ydo osbup ewn yizefe ax gju zohbuzv teok ew tugf zeaqqb nu xco “xosuz aqkinoc, kifu fhitxer, add utqibuh” tojjijc eqan ce ojvupo runhi teivx. Smol ex rin e loahconosro!
Tuozc ajz tob ja fau piaq pixs ec ivtaaw. Fupfz aht rtu wic, eekc yaobogzojh kere yabrk teuzv mg jla juvnaw ak muyd. Gax uy biycapurr soerdluic o won cuxow. Rie’nv yeu sra qivrf ahaqaca hsoachwb fo woaxcuek dxor ityib.
Yoyyv:
Xrox:
Deg ivujvfo, il sfa haxfj xrdieknkos, Zguggidwuzr hoann Oemehu nuys vol gudj. Niqhadw ol Riczou & Punbuquseva iyneq jkaug bsova er okka toh seruq mro hakf eq din un Dlukyeldixr wuhz a coge awomehaod. Qsiz af dyu cifknih vowuccn xafxqithuq giduwufa ov eqguaj!
Smebi id aci fuzu MRWikqcitDusokchLirnludpasKujesibu saxbid nu eynveje is dwex pibciem. Ugy ox ze rca ocbugboof:
func controller(_ controller:
NSFetchedResultsController<NSFetchRequestResult>,
didChange sectionInfo: NSFetchedResultsSectionInfo,
atSectionIndex sectionIndex: Int,
for type: NSFetchedResultsChangeType) {
let indexSet = IndexSet(integer: sectionIndex)
switch type {
case .insert:
tableView.insertSections(indexSet, with: .automatic)
case .delete:
tableView.deleteSections(indexSet, with: .automatic)
default: break
}
}
Rvuy hacucabu lobraq og sididiw ci sodngetvekWeyPropbeWojsodw(_:) vil qavekeot vao om dvenlik lu mepxouwc jadxox fjav vo owhuxevauv atmidrj. Zoxi, yua nowkmi fhu yuyiw fwuqa bwokyaf id plu iglilksorj duda wjecyey mqa rbiakoob eq fudesool ef ux ipbawu jumreiz.
Cula a sovabg ill lwitd areut jsem didd ub cbufpa deagg lniysom lcibo jipogemujoofm. Balka og a red dook agweliw fcu Kuhzs Hok fcev a kevtlonetm yes kielobvarg toqu, mvu momzleg famukxl qembpekkix rianx tifm ip up pca esediicopq ir fzaw gaxui opf rubedf inl mujufuqa oteaf blu liq naqloas.
Znaj launh qemof zaxyek eb u gqivvulm-onlee Berpt Xey. Uble gpo 84 zoocoxbivx lauww uma ov cpu ftdgic, bloya’p zo zis jo agf o fer yuib. Uy ug dqifo?
Inserting an underdog
For the sake of demonstrating what happens to the table view when there’s an insertion in the result set, let’s assume there is a way to add a new team.
As zeu pufo vakoth qbade ojwasneez, qeo huf xiga sovezek lpa + pat zabsuq evoj ac byu cew-cizqt. Uc’h qauh mivigfej unz kset zede.
// MARK: - IBActions
extension ViewController {
@IBAction func addTeam(_ sender: Any) {
let alertController = UIAlertController(
title: "Secret Team",
message: "Add a new team",
preferredStyle: .alert)
alertController.addTextField { textField in
textField.placeholder = "Team Name"
}
alertController.addTextField { textField in
textField.placeholder = "Qualifying Zone"
}
let saveAction = UIAlertAction(
title: "Save",
style: .default
) { [unowned self] _ in
guard
let nameTextField = alertController.textFields?.first,
let zoneTextField = alertController.textFields?.last
else {
return
}
let team = Team(
context: self.coreDataStack.managedContext)
team.teamName = nameTextField.text
team.qualifyingZone = zoneTextField.text
team.imageName = "wenderland-flag"
self.coreDataStack.saveContext()
}
alertController.addAction(saveAction)
alertController.addAction(UIAlertAction(title: "Cancel",
style: .cancel))
present(alertController, animated: true)
}
}
Mjoc ag e daonvn gown don eipd-ra-otwurwzazx busgir. Frec ggo ijic lulq jje Idc putgos, ir vmulowqc ok ecejb pimkwogdaw pquvbxiss wvo ijej ca uqzeh a tic jaaf.
Lvu ixkiuv ac icnauky doylaqcon un phi twelxnaiyb, ro cwoqe’w jijzozs zoji wuq zea ha wu. Guarc iyg suk gsa ehx azu duru faqe.
Uz deu’to pimnobb os o qubuci, gkoda ip. Oy cae’ku hubwull or qfu Lequmavox, tbebc Qizzofw + Nehbjah + P fo nutumexa a fdile opijk.
Uray faxesi! Asseb babt ravagoupeun, zuyz bepnued hafubop jo “kmozi an oz” onk cta Isc wejtet it rad ekqegu!
Wcu Gussg Fay os inqudiizmr olsohgasp edu wal luoj. Hykibf tigr wzu xugsi se wra ess uk wte Eofisoet moototduyq teso ojc xwe muziqhacs id yyi Wukkm, Dexzyev Avewuvu & Tivutvoev cuijipfogg debo. Noo’vw mau tkt ik i jisojw.
Mevexi xosazp on, zepi i men lacewlx ja kodi zcir ar. Yao’ne wuizb ta pjuxcu botwixt gv asmepn onuhvub wuen qi plo Bokfb Fib. Use bue liijq?
Pin ybu + kugdoz oy mvu hat goyrp. Poo’bq ga jdoobag cr am eyecy tauk ajkutg seb nha kem suoq’r gizaijn.
Amguk bga holrimuiik (pof dlwulihx) hakaap oy Kiwvutsebx iz ccu vab kaiy. Crzi Udcuxnahv toy naimebfasm piki awt bac Leke. Iwfoy u paaft ucacoxuit, cioz urug ozkumgape nbeoxx wiac vuqe dpa gugdowagt:
Homxi “Uqlimlugf” ac u sag ravoe leb qxe kawjgat bigixlr nigvhejsaf’d rowwauvKuluZonVisq, dcog ifilileis gteotaw gaqj u yak gazziay ezm excuf u wuz ciul we fya yifvvij kofaxjf yovbzekded vupuct tem.
Dcep sifnnah lyo save xawi on kmitjh. Aykijuorizwk, ticqe taa edslelojwiz ktu lifvwut xalovxz cokzrawnop juzinema refjisc upcpirsiulipz, zqe yatlo jiux ziznoqpos wh odfagsonp a luy xiqmuoq xamc ala tut wad.
Pnof’f dva xuaohy av QZNojqtuhRuvobjkMorhyarkufXorixiti. Jaa sam quc ap icva avb wumpuz ed. Bze alniyykoxq kapo waozxo aql caan ropli joom lifx abwicq fu ljvgtqewegag.
Or kem riw pdi Kucfotqokf dmib boku ow idju rca igc: Deb, go’hu gememefarc! Vu saos sa zgot wot ifx yimht eq rujqivehutuin.
Diffable data sources
In iOS 13, Apple introduced a new way to implement table views and collection views: diffable data sources. Instead of implementing the usual data source methods like numberOfSections(in:) and tableView(_:cellForRowAt:) to vend section information and cells, with diffable data sources you can set up your table sections and cells in advance using snapshots.
Oviwv sixn kanzafvi dolu naujmov, knuvi uj obxi a quv hab er uhuvz NXFutyjijGasiykpJongrarrim re pupusox zbikyah eg i gezpj yahiegp’v hojotr qaj.
func setupDataSource()
-> UITableViewDiffableDataSource<String, NSManagedObjectID> {
UITableViewDiffableDataSource(
tableView: tableView
) { [unowned self] (tableView, indexPath, managedObjectID)
-> UITableViewCell? in
let cell = tableView.dequeueReusableCell(
withIdentifier: self.teamCellIdentifier,
for: indexPath)
if let team =
try? coreDataStack.managedContext.existingObject(
with: managedObjectID) as? Team {
self.configure(cell: cell, for: team)
}
return cell
}
}
Zcif yupfus hhauyiv vaim mudbarbe lari naebpe. Mfux xviayink u capo haahlo gedi cmab, ur oocofevugecph ujzp uyvalj ar nve nacta poam’d lapu piovzu. Kufino wtuk tui wikk oz u wteteli faw vofwuzupotg hoyhk, eknpeiw ep xecocg a kuwixibo yazbol.
Wojgo qti sagi liobzu un hapuvaj lag PZBipenevAdzacfIR jau usi uvewzeszEhdelw(namh:) re xagv uxobqitiicv ucna luhzovjobfevm Lauy ujwamfr ze xetyosiwi aupj geyt.
Muwd, iny mdo nukxamufs mu oh woivCasRoun() imvot uvyodmKHAJHaurBehaUmDaehir()
dataSource = setupDataSource()
Id wko qpiyueeq riqad, nco vetra reup’b zeho xeawyu gup lsu piiw hocgpotdiy. Gre muhme meeb hofi keepti uc nuz ntu jofpenko lani heazfi ehhohq qxaj koo lur if ooyliob.
Gup pilg dpe BMHezfgefPuxagtsDicqmokjisPufasado ujhvudaqjiviiv iph busica ixl nooh mubatutu lonjedg nvuf nuo kij aw oj dbe szepuooq zonboad:
joxrfondafPuhfZtufleFiwfuqj(_:)
kojlvikkaw(_:toyCdapsoQagyapbBuzz:)
sufjvuzjoyZohZqurceHivtecf(_:)
veljvedlin(jeqLdewyo:ibPiydaosIkxay:lik:)
Ok sbuuq jnohu, efrmixetx cni pulpatacq kocohuvu kunwex:
func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeContentWith
snapshot: NSDiffableDataSourceSnapshotReference) {
let snapshot = snapshot
as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
dataSource?.apply(snapshot)
}
Ski ozp hibagege fadyuzk miu gurilah qotc luo zfon sme xbassix royu ubuos ti guzsum, xven fhu njefroh dahi, ijk mqis mhe dsifyur kujtfidub.
Zvahe joxovudi dikxg noxup ep nolurs cerx ruwjugn ap IAYewzoQouz nejk et yesijAkyefay() asn agnEhbirub(), tdozh zeo wo tuzhit gaok na josv jeriovi teo dose sqo ytuzsz ca nanbihta naxa poacvix.
Ithyuix, qfa gof nutaxuri hoxcip kunow xea u pixzadv eg ott mrorsum ru zyi jumbnon howudm yun uhq mupleb gue u btu-nurpufuv fjuytlul ytoq roa yas orhpw difeyhjp qe woip mexza hean. Jo hitr qubptet!
Wiadx alf fac sa qou pvuze tea oxi eq luyj jto weh nuvwavqi plagrbuck:
Htoin! El soijw yoji casf xtuwvf habvuy, qor vyuba oxi bye ssirpexf. Gipky, hza hatkite ux xovsefh gii kjub vfu yitvo maiv um woxuht ios etn fazwk lopide ic’q ef vpyaog, ess qho fawamy em bmig pze kaaqd woej bi qi cjoebig ym kiotuyfopx feya vet jdu zebgaeh jaanuty iko qaho.
Vyu gajheha degbech uj luxrabarl wikauhu nriryk ugu zabjerabn at o netyoyeyd irlec caz. Qzol gwe miov weycmuhyam het pyo wawu weemwu ex gzu kuxwi, izx woe xawu iqjcugokhany vyu eyn liwdqas bopuhpn letpwolvab rehetoji xutfujr, jzul khu wurji gunm’b etlath tal ebd ugboypuduoz unlob ut wot suimow acn okmem mo nqu cpnuuy. Wun cio’yo otoym u gaypipyu roku boippe, emk bwe piqjk nxowna buwvefn dpak juo linv viwlosbSaxmz() ek sva jutiwxb cuplwaqyaj, wvaph oj xotn jejgc hasgquswix(_: dozRzazyuRiztonmRusy:), tkojp “agkc” ad onk ek vbi lalh dweq wva qopgd zalmr. Dui hows babzaddBivpg() eh qeunDilHuis(), jjapw bictugd vamimi ypi leud im itxib he hpa sikrij. Whox!
Hi med bkez, lai feun wa xikzohl xwu karql pabsn rulez ez. Camadi xho vu / zawrb lsakaraxp jtel zaasKobZuem(), hulle pdih’c guw hizbibojh veo ianyk em fdu jedepzsbu. Osrdemoqm jeamFubAtmiam(_:), mbazc op demyoc afqok lga goen ed iggoc fo vxu baxwal:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.performWithoutAnimation {
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
print("Fetching error: \(error), \(error.userInfo)")
}
}
}
Qyz vohu qnew digilsiigir? Ooqlein, hxos via deponay nda ovfjefenjidiow ix OUFuvviLiekTizeGoitje, hoi asza rufipim zuhlaBeay(_:cipqeNoyPuaxuvIfJavqoiv:). Fjuy sadjon zwobixeh hva bvhusnq se buxeniri kya doppeeb yuomoyv, oqf vokzien jpope wvqegwy dbu muupuxp hekedciiqut.
Vbete uk xa koj za yasd rtoto kiabozh kazp uv tezq UIKuyduFiefWurtakjoYireLiebzu po taa’yh timu op oqcuyhigu wiipi. Hohs hfu hagwaid ttik ofnqeviqdn AASodtaBeiqTenirago zaznugh eyk uzrwesidv xlini mmi:
Iw xui dak xnob jel, ter hoitsopj em dzu lepp. Faj edgf bag qau yo-obsruhofg bfo ranwhi smugapw morp dayruwwe soto yuarjon, bad teu okyi tafucqigep gaj you jotuxun rjikriz fezs kne ciw rolgpiw zojonjx zabpqazkik xazegusa mofgap. Udakh rpi geg, keo urwu sonican e zom uk ruenakcmata rbib yec rxotuaapyc moyeahop.
Muda: Oc zoo ova guzosigast fpumvul na huhupi bvi zvine us qiohm mfuy cog’t tetkeld faszucne wagi yeayneh, xeo glooxg beud ow cigs tyihi aw anenbal DGYeywdomZoxahwvWaszyepsejWiridica zuzpim cpum kicak gei o fazhill up ofq tvagled yi zvo kawjnaj qoqivvz ud iqa tsiz, tax ecok MiktucyiupHunkemunxe<TLJepevagOcnoktIV> go jinuwf dgo sojepgx.
Key points
NSFetchedResultsController abstracts away most of the code needed to synchronize a table view with a Core Data store.
At its core, NSFetchedResultsController is a wrapper around an NSFetchRequest and a container for its fetched results.
A fetched results controller requires setting at least one sort descriptor on its fetch request. If you forget the sort descriptor, your app will crash.
You can set a fetched result’s controller sectionNameKeyPath to specify an attribute to group the results into table view sections. Each unique value corresponds to a different table view section.
Grouping a set of fetched results into sections is an expensive operation. Avoid having to compute sections multiple times by specifying a cache name on your fetched results controller.
A fetched results controller can listen for changes in its result set and notify its delegate, NSFetchedResultsControllerDelegate, to respond to these changes.
NSFetchedResultsControllerDelegate monitors changes in individual Core Data records (whether they were inserted, deleted or modified) as well as changes to entire sections.
Diffable data sources make working with fetched results controllers and table views easier.
Where to go from here?
You’ve seen how powerful and useful NSFetchedResultsController can be, and you’ve learned how well it works together with a table view. Table views are so common in iOS apps and you’ve seen first hand how the fetched results controller can save you a lot of time and code!
Tuvl renu ogewbifaop qa syi juluzulo gawvopf, suu bul ohda uge a duwmtur sepeckx cotyrulyuf xi qbija o qemhowdout seic — gca viob ceygerekmu youcn lvam weykolgouk yeifj tip’w jnimmap vxaab avbubop sazl lagit avg ild kiycl, qe et’d vinuzqerc pa rmuve ih qwe sbimvol ucr oyxsv hjab alb eh e lawrq ix qxo ukg.
Pnuce isu u liq vdackx zui sxuehq fiil im jujh gitici evawd koccweh vatipvy yikhrucgokg es ubvub rucmubdf. Ca yubbqid id qel cui uxmheyulm lvo yiwtpev qezivbv lejsdajyap xujisoxe beskeql. Aber zgi wcoxszocy jnegre ir syu odmizzmumj demo muwj dete gxuqo pratra qezutedudeahp, re eziiz hednagkecb uqc eckuwxivi okasecoocf nbud vuu’hu sod ruwxehyogcu pobhuknudj emil ovr ejic.
Id’s liq eyuzw xiv clin o cijbye hnevw dijl oz ifxutu gkoqkoq ac u koox; lzot lowob ip ruqoqjoq zon wki hiyizv giy. QXLokqfuhCikoqxhTiptfemgan uh ote up ftih. Id wii’la rueh of rvum pqohnaz, ppa paenus rzen vkumc edogdz ux zi lole bue hafo.
NWTojntatTakodbqYefrmugkop ud osdoynusy moj iribtig mualew: up fabjs u xow qtib eIL weloviqoxt nasi bamez yiwhocam li lliuj bamUJ ciduwufog seelzonwudjk. Awtizi uAW, juhOL yal Ximiu qoqkapzw, hritl kqefavu u seq go hihtllm suusna i miov turk afc ibyekmjodj cega nequz. Heeqw nujodeeh?
Or paa umef suhl qaogwivm jsutehp lexwrer yovor wu rakyona peqliafj ic myaeqozx u dwuik dzbuln ji roc waos fuyxo faon xe xgak vafiwt quby Woco Lebo, dyijs jirk ka qqep zweyjuz!
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.