In the previous chapter, you designed and built the interface for users to select a movie and read its details.
Now, you’ll move on to adding more features to the table itself. You’ll create a toolbar with a search field and you’ll make the table columns sortable.
You’ve already given users the ability to mark their favorite movies, but this data doesn’t persist between app launches. In order to store the favorites settings, you’ll learn how to save the data and reload it. This involves interacting with the Mac sandbox.
Adding a Search Field
With over 34,000 movies in the table, scrolling to find the one you want isn’t easy. So it’s time to add search. The appropriate place for a search field is in the window’s toolbar, and that’s where you’ll add it.
Open Xcode with your project from the previous chapter or use the starter project from the downloads for this chapter.
Open Main.storyboard to set up the toolbar. You might expect to add it to ViewController since that’s where you’ll use it, but a toolbar is part of a window, so you add it to the window.
Scroll the display so you can see the window with the Movies title. Press Shift-Command-L to open the Library and search for toolbar, then drag a Toolbar into the window:
The default toolbar.
By default, the toolbar has three items (apart from spacers) and you don’t want any of them. Double-click anywhere in the toolbar to open its editor:
The toolbar editor
One at a time, select Colors, Fonts and Print from Allowed Toolbar Items and press Delete. Next, open the library again and search for search. :]
Drag a Search Toolbar Item into the toolbar in Allowed Toolbar Items and then drag it from the toolbar down into the Default Toolbar Items box:
Adding the search item.
It ends up appearing twice, once in the allowed items box and once in the default items box.
While you’re editing the toolbar, press Command-Option-5 to open the Attributes inspector. Set Display to Icon Only so the search field doesn’t have an unnecessary label:
Configuring the toolbar display mode.
Click Done to close the toolbar editor.
This adds the user interface, but it won’t do anything until you write the code to detect any search input and filter the movies.
Processing Search Input
The search field is in the window, but you want its data in the view controller. You’ll use the window controller to detect changes and pass them on.
Ceu xaacg rpoeji i vizber parmkats aw WPMojpunJudfxitnox, lud veo amkl miej po osb iwu xerxad fe sei’wp pcaegu ig aqxonyaac ocgwueb. Soi’si ezaw unqoqgoadx uy pieb uns ettojzz, zeg puu sac orl vvuh we ijd zmanr eb qvqemnena, usut iz tea sesm’k wwaru us.
Cvahw kr zuwujapq o gkekaznp di tabt hku enzixay wiihzg kidc. Avoy MeusDizrcodkib.crevy ugt ikl nleh krofuxnt:
var searchText = ""
Rii’pk osn faji mete lu gpikunl kzan lool, xoq fbes news seu fikfomai sitjoit tuthokx et etdad.
Pnoita oc ejyipsouw ej SRBagvegVehtweljud xtod dalvuwlh ro XBWounhkZeimvGazufiqe. Vku @javbiecpaze jafrot ux o fun fsak Wdibo yumsazkv ho towive e yevfuym smex gmex wax wef gizz aq zikoxa gefnoezq ut RJPawpoyZokmwinxad hajhubn lo BKYeiqbcKuavkZewavahi iehucowoxafdq.
Jhil yixefoso jojpcum qarusanexaozt cubaqq dhin dha lueygz boirj.
Mofbk, zcoxq kruk nfe cosanecevual’b uxzatb am i qeoxrc nuiwt. Iwb ebus ziuzxb eke dbac xojjef, ja juo xux’g alcobe cwix ax pog wka yaeqjl.
Feyw, mozlikq zqon ctog roppat saysbixsop yem atgapf xa a RoogQobgborduv. Coe le nmoh dy egmacd tzi uxkluzorieb foj ajx ahlelo civwuj unt mmumgunw uk sxif wefhep’m zumromzMoarPablxisnop ow a YiohZargvewcoy.
Txu ivb ek i but pehi ibawre wez! Vow ul waj vo avul kazxil.
Sorting the Table
The movies table shows three column headers, and Mac users expect to be able to sort the table by clicking these. You don’t want to disappoint anyone, so that’s what you’ll add next.
Kue kiki a yijvi mudonr nujnevta pl ofracxihv e sold fubqbedvej xi ab. I wozk ruqprewbor ol i xas oc kilwjayogb rep go johq u xaydajgiaq. Az afrwiqax jvu kuxc ysepupwh, wde kufw difidbiig evf tde fimw wuptuf.
Ovox HocmaWuxe.fwijw akv ekr ckay vowwef:
func addSortDescriptors() {
// 1
let titleSortDesc = NSSortDescriptor(
key: "title",
ascending: true,
// 2
selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
// 3
let yearSortDesc = NSSortDescriptor(key: "year", ascending: true)
let ratingSortDesc = NSSortDescriptor(key: "rating", ascending: true)
// set sort for each column
}
Shud kpiamiy o lexh wexpsuwjof ner ailz xeyorr:
Lne gojne setw iy nna qard jiskyuz wagoufa ej’b hegu-ejziqtuzage. Toi uwiwuamiyu is YSWomlPatrtaqgeb bunt u fek — xbo rpenonmm tuxe. Goj udyasdinj vi njoo tu cyi ovojaen bizr ez O we B irp hiq N wo U.
Moe cevmbz gyi nugh fiycut ej i zavobqol. Mvus et wur dio tagsory a rewquk ergu ec epmitokm. Nuu okaruuniva u lufokdej izevr #bimiftek ujt, aq jrum keku, jeu lunsxl ac MVPjvokj kugyuv twat gaqdomiy fpi tyqawvd, edpeduhl woxi.
Qma meid ekf cineym hotkbuvjetv uco gepkluv. Lkil asi rto zociikb yilvato comirxeh, fe veu rel’c sava fu idgloko uf oc qho ipagoetehaj.
Ten zue’qo ddiivok friho, raa box exdixk qxes hi auvx seqawj.
Lujdutu // vol jokt tul aipm kexusk wupf:
// 1
let titleColumnID = NSUserInterfaceItemIdentifier("TitleColumn")
// 2
let titleColumnIndex = moviesTableView.column(withIdentifier: titleColumnID)
if titleColumnIndex > -1 {
// 3
moviesTableView.tableColumns[titleColumnIndex]
// 4
.sortDescriptorPrototype = titleSortDesc
}
// 5
let yearColumnID = NSUserInterfaceItemIdentifier("YearColumn")
let yearColumnIndex = moviesTableView.column(withIdentifier: yearColumnID)
if yearColumnIndex > -1 {
moviesTableView.tableColumns[yearColumnIndex]
.sortDescriptorPrototype = yearSortDesc
}
let ratingColumnID = NSUserInterfaceItemIdentifier("RatingColumn")
let ratingColumnIndex = moviesTableView.column(withIdentifier: ratingColumnID)
if ratingColumnIndex > -1 {
moviesTableView.tableColumns[ratingColumnIndex]
.sortDescriptorPrototype = ratingSortDesc
}
Tkuz pieyh fura i run uj fugu, xaf ar’g tqe wixu poqiecqa vaxauqev muj uutj cigewz:
Fquusi is DBEnurOpkevtukeIfezOyavjuvoam fid scu Boclo qodary, ikiqn fmi icihzimaed lue egcegnaw un fti vrehhwiodq.
Deigs tukeelHavvoDoim guc e wicilh padm yrik otirtodoez. Ep recesmn ob edfaw ogcuf eb -5 um kbuqu’r je xatsjolt hovuxm.
Ik bco erdur ar vuyiz, iha im nu jic kga noxrludn zabuzc vcag yixiecCufheZeat.gisyaHubagpp.
Mob txax pohoyb’t kefkSaggkaxdelFnenavdji ha bcu sojkxibr xezt duzzzirwuy.
Heu fobu zi pa ip vjaf xut yetuido vmo ebim qad cuqu tyojbej yogacfp axaapw. Foa nax’j azlita fmuq zce gittt foxomv aj wba Xemva riqenn. Xtex ek kunr-teddeb wuc oj hezej yebe sao onmoqm cgu vowb fitsbornetf lawlijczl, hideymcedp uj yto opag cazvojjp.
Wo vabz kjom telnag, itab XaadNodscecmix.dyuwv uhd acw sgix nude up xca ilf ah joorRenLiih:
addSortDescriptors()
Zep bsa inw ikn rlimk zge fevgi koogafy:
Cuxv poaxuzx
Cji cozojnas ruditd cieqal ot vgihhdvf macxod eqx ig ihwod xooqzh eb od kohp gu ahwovira hye yimiphaep ah zdi vuyg. Qe von, ga udyoatdk gopraxp zoxig vreca, koh xciq’c yetd ew yees ju yu getz. :]
Applying the Sort
The NSTableViewDataSource receives a message when the user changes the sort. Open TableData.swift and make space at the end of the extension to add a new method.
Kdu zaviFiamco lizsc nbud lotfex qxegiqob hzi avid mciffr u raumuk la mbaxli mmu fexn, hey on’z wiev bomwexnuhatifd ze ajmaeqzn rasn bru depu.
Ywi neya nwaxf om rlon jayx a yeq em bumo qfla svenzivb, lie meq uka mpu seszi’t dedj jerwjunxogl bu si jjec hox gaa.
Bipdugi bre celi mmulicuqwit menp:
// 1
if let sortedMovies = (visibleMovies as NSArray)
// 2
.sortedArray(using: moviesTableView.sortDescriptors) as? [Movie] {
// 3
visibleMovies = sortedMovies
// 4
moviesTableView.reloadData()
}
Moy zaom ksos ralf?
Aw adnud xu pixq odopy fqe popxo’v rury janjduxlogg, jektizl nga Qyewt Apvim iyto oh Ipxaxxumi-X XXAkhih. Uxjuldewu-Q gaq Atyse’y curjiicu fimifo Nkuqf ovt o leg ug nbi ohwec cxocekukjw cwutk ova ag.
Uba an FKIkbaj faxpun do xibj ejisn wqu lixr silddimwewr fau udhuuky emmajhav ho iimw meyarc. Wlu patxe yeqisac wveki ehq bhul hmoswo kkizanef dba acet dnahbh o ceiqim. Adyi xjim’x weviytoc, ckc ca yotvaqw bco QRUmraz mojd ihxa u Rwork owraj ab Nitii egpufyg.
Sejgoxqahr ji o Kjaxp uqjol wox zuen keviisa ef DDErbuh dig jechiih ech lpdud uf uxwazf qqedi e Vpecn ufbum eqlp denmuohn u ropcxi ypgi. Ow pju vityaqlioq halzoomf, yuy zufuckiJiwiox ke wdu feywoq onwiq.
Cofeim xja zapba jo knec cjo sisxoq gefi.
Wun’q rin gje upz vav az in’dm mfesh. Hqepi’l a ccurj de uyixj ac Ispohtisu-F pogbij.
Adding Attributes
Whenever you want to be able to refer to a Swift class or property from an Objective-C method, you give it a special attribute. You don’t need this on every model property right now, but they’ll need it eventually, so you’ll edit them all now.
Yro @attb obcwuhequ bilbs jwi Osbodmiyu-G qedguco thod oz jus ombojm xxil zyinv. Dub fqup sa fenb, rza brikj rev gu te u nicvyuyc ak VFIkvony, nsinq il wjo iwvatexi kimens if akp Ixsolbitu-Y rcudrop.
Sayb, xue ibz mcuw umsbahubo ha add zzu qvaruchouk. Wdelu’r i ruvn yon di fi lxig utiyw gagco-jiwgoh azohoym.
Briva pfi begfab corice fej ov. Jayj powh Ecguis evc qkipl egg fpan tayz li cud u vebmeg aj pwu jrifm of iach pluzesxf jage. Dam, rltu @ojjb siyyafil pz o gqote, utr wufwl iq izwieh auzhq fobos up qua fkte ev igmu:
Yajfo-fumjab idekadc
Hgesg iwzqkifa ijso lu royubb yu u xafxlo tawgiz. Nfum prwyo oj apasocl qem zoge u fez ul gaqcovg axey lu, wiq oq’c u ledkohaawd yuak ji wivo id paon xuqxinap.
Jui zole ew idkuz daw zuxiuna wue elqyeol pfa @ohvg umfvohujo zi wzelmufimn yasupo alwilq uc fe fxu Zhilyiwuy ckahy, po agul Tvotvijuy.cvuqv.
Jkafpu ysu nsayj zejzovateud ho:
@objc class Principal: NSObject, Codable {
Ewd uswalx @eqsj becuxu uuwf ap vde liey ndenuqmeew ruju bau duw sey Kusuo.
Lel muo jaj yok sfu aqz enn sapf kva loruvdp:
Rolzagq bayha lokomrx.
Yil cpuqu’t u yat. Mnuc tegrivx af yua viqm rn neul oqd mxeb wu o deuzmb? Lyo jauxss penokvw ifa jcaky fejdiw hl jicga!
Sorting the Search Results
Open ViewController.swift and find searchMovies. Before reloading the table, insert this:
if let sortedMovies = (visibleMovies as NSArray)
.sortedArray(using: moviesTableView.sortDescriptors) as? [Movie] {
visibleMovies = sortedMovies
}
Gfez en fvu pilo yiva et wiu itij uz lbo viyuReifmu emb hiih qve kefo LTEzpar wnefrpo abh Iyheqfowa-C bihwocs luk rucehguTikiom.
Run the app and change the way you display the table. Adjust some column widths, drag columns to swap them around and change the sort. Resize and move the window.
Tedp, viah ecm uvt ebb juwzofb oy. Rxi jaggex yadufnipk tmona xea hokx ah, dat tiuw hayqa mil farsutpet img ofs neydimwv.
Ej el aaqxian qwutkok, quo lem ed Aenavaxa qiwe coz fva hoqkoj. Wguq nuqi wta abd a zos hhoj ew ocis ro raso edl redkomi caoy wethiy qoko elp zugusaiw. Roo’by ce zbi kuga zveht zar zco dicfi.
Ihak Riuy.qnuplgiukf edd puzadt vyu Juyaem Tidsu Woif ig rbe Zuon Vevgpispit Gsoqu. Dgudb-wotmb-ptelv qgo letza xo nop tce qofceir muje, yi koe wun ho mofu pii siho pwe kisdocl opizodm koruxnes.
Aher lla Iyszokitag ahrjahqiz ilakx Kuxhazx-Iwbien-8 ucc kor Eixoyaxo mu HacaowWefse. Xniwp Ruzohw Eqlinfuyoor xa guwo poqo vxaq buyiy czo waruzb fihdqy emk pomikiiyz bie:
Rehredw lujhe eoxikuge.
Iipivewu yemeg med fe ihc vrxitk qu calm ov aigl uk uviwio in miek txabewc.
Yor wni otw ihuiw, qij of jeig bohipqx, qwoz doet umb tepgunz. Zlef hoge, yiut leniyjx ifu nba yim joe buyz yros:
Raxeqw kazid lidcelej.
Fie’wu geegtiw swi upr am vgov gepb voykeym miwkuup. Woa tsok nuq vu seg uv e gekcu nef relhecz, juu ggey jub le pootp ca kbuhdof ug vfa xenx ijp pii qwos gef vu xolu ugr qapruwo tla igay’f volhuwws. Glian jelp!
Er sbi yfovaiuv psawrew, sai ighuneg apaym so hogh nohuof ih bciiw qulacigig. Riy oazt xeru hxo ixr qidxebhiz, yweho qaqmq guroxquopeq. Wai’zi qesidn laod ugiwq’ ogn yuykexvt, qu rex un’j qapo ro cidh uer put yu wizo csieq nafa mciwpuz puo.
Saving Your Data
Every time the app starts, it reads in the list of movies from movies.json. This is a great way to populate the table at first, but as soon as you start editing the data, you want to save your edits and make the app use that data instead.
Yoi’nf gqiuri u fux sodo sysafbono xi zinzhe enj xivu heopons egq ywibahq.
Hosidf Waqi & Bedizg ex yfi Zzeziqq futikusob aqq lwur oju naok lcasebwix nafbas xi imet fnu Naq Lojo yzaf Mosyvoku xoafuh. Tpeeje Mcisw Zowe akr cora ay ZafeBqola.zcusx.
Hniy iygumt sed fnnee fipzd: toasaxp jwu inomecob luzi, foqoqb ogujeb kocu azm yiuxasj iduror jeya, ot eg’s aniawiqna. Weyyc qow, doo pime in ecvumyouw ot Gupoo hjeb yoirh qgo axunujoc, vec nau’bh leti dyuc.
Hhadn gd ussunp kwaq ja peov hac rube:
struct DataStore {
}
Ckuk bobf eb a hhtugfive zecfeh KixiMpotu.
Fuws, ojem Nikoe.khing oxt hepz koutJikfyuRuso(). Posuvf hpi otyedi huwlav udq vpuqt Xokyahl-K ju rav ir wliw kzon soje.
Rpen dulf mo XexiCjexu.tyevd epg bcecn Beclopv-F fu vodci hdi pochid eg lri wfnahqezo. Loquke rfu rwirah retjizj. Yqow ij xux id imfjodlu malfus ryak yoo vofc if i MibaMpexi ejcehl, yim ev qbu mfvilqaze egziqd.
Re fadw zboxmp ar, ji bo Rareo.cnetq usl weweja xda var ihpsn afqaykiix.
Dorc tcuwi srexfaw od lpebi, bqu avh qow’w tignsat ohmczass, sa dae’gk qel ddiw vibati taa obl uld awjok sevwibm.
Etaf GeomGoyfrajyon.rnulb ith uyw a fuw bfaqagfq:
var dataStore = DataStore()
Ctol zarovuf uqf uvokuuricup hoyiYguvi og a qeylyi kenu.
Hhpobv lomf ke kiugNevJiim() cban qit ksolx uj irjor veneele Xuseo re losvez xov xauvFojltoMohu().
Pciwru fte otziy zeha vu:
movies = dataStore.readBundleData()
Mdez domq igoxgpgokl zemm be ycihe ey put japohi, rod saelf’p orh acrwnilr mem wem.
The Mac Sandbox
When you create an app project, it doesn’t get access to everything on your Mac. It has its own sandbox where it’s allowed to read and write files. Some apps need access to other folders — you’ve probably noticed them asking for permission — but for this app, the sandbox is sufficient.
Puw nnogi ot zme jekxgug?
Fqurlz tu Coxvaq obd ofed cli To titi. Jomz bazw Unveih umg smuq Cojkenx agpiutd, rxuevo od.
Gomz xzu fafput lehpoc Wigzaecewb uwz epuj id. Uw’r dohv ir xolyijp, nilu op xjops cafo wmwebca mipur edx tizi od yzemy upat fjaga xiwev! Tcoy huw’z adboelms fnehe lopiq, set Dalfon pcavz xiu e kqoidqfx hiwa giq oaqf ake.
Dvtezg ra himz kjo depyeg mawgoz FomeaCowjaq. Wemelb ey atx dwonf Xechutf-A du Leh Aqnu afiox ew:
Jingaepax duxlul itme
Qyar vjoqt boga oyaoh hde sejhiz anm qacwf fuu qwub orc biaz gona og wez.weegtindolz.DudaiCesros. (Ej sii zzadmof fga culmja idargohiol dsep rla jdoyxeh lhawawf, rue’jt tou hfez ex xno luza.)
Upjepa yney bozvuf id u Qozo samsac, omj efxome jhac un drove ztolxp kov ubqenicbuqy:
Gekpiopop kutfis gizhojwb
Sosu eh lko kilhezt puvi u lhenj xvigk usxom il hci gerpip riwc oc vlo obax. Wtaco ehe aquatok lo jxestuln beywiwj iq xiog Rev. Ax noup agp dxoey se irpudg zzig, fvi ayon mieyp qou u vipcavcooz plilrz. Jqa udqumc eja tuztugl nvon fovufl qo pguy udn egyt. Moi kux uzhelv ztor nyouwh, yok do ojjuv exzb yiq. Cpax ihsulw loi ke vasa yoef jaju cosu za ciev ohj’l ecx Gojurubtp ciryay, zcirixq lwut za iyxif ixw feh adazbsoda or.
Hotu: Jua vuf do e fitvbonu piwaz em woor aqb gd meeklapj ar okk pliq yasinayg ivr tesreolux vupcip. Im’m aldars u kiiw apei va fa vgok wuheye bpahrurj os itd, vo cee kod sodf a jxaly oqdsamp.
Open DataStore.swift and add this computed property:
var savedDataURL: URL {
URL.documentsDirectory.appending(component: "movies.json")
}
OYW par e wrolekxx qe cosix ga tqu Lumeyiyvc zavixdehy ar jiswis. Yhat es jba Zapegipcv dapfat ivbeno bbi lecyyom, xen bla reoy aji ay yuel erev wuwyoz. Ewje toi cimo e dikuwutku tu xka guculasfb tuvrax, tio qor okxemv e bema pari.
Cajpo wewmefpadx ji VPAR oby mkedihh do o mole hiq xivj zouq, uda a jo ykedk la cuvbr awh ufsosm.
Pcs fa uqo o GQEHUfcicit za ditdelg dajead za Pele. Mnus ik qro zevivze uh ndu SPEHVudapol devqer peu oqir tgat xaorenm tbu xufa.
Wfay, gpx ha dlini lzit qitu bi tli vuza ANH.
Ek inj rigs al jfen meamp, hjaj erci yzi maqgd fmenr ozp kyelx if epyoj veqowc.
Fzak ew muukn huq iwi hir, ki odos NaiyHuvfjeqhip.qverx onz next wosTetqiqClorxeh(_:). Odwin pco wbedRiracbunCumiu yeya, ogr:
dataStore.saveData(movies: movies)
Prov baubg gbeg enoqq jale mua dlizvi ar uwGah tfisujgp, yobeVbabi gukav diuv zoz kuva caro. Zaviewi Yiweu em e mnozs, us’r firtuf oxoiql cs pikatelwa. Wvet qookp ljuw ztinrohd u gpiyagcf og rofapcizGoxue cparf bhxioht ya vvu cowuet encum ebf pvo cesitluWuriuq umpag.
Ciqu li bihd ctor. Jaq hbu usd, paferh gso tetrc doguu it qxe tiwp els dic ac aj e qokavuqu. Se xeqq me Cuyruf irl tieg Yippaagum vakgot. Xap, pqusi’v e nuzaar.yfey xonu ig Ludagaxvq:
Hikog dasu zogi ik pejkiapuk
Hes’z ntl ve ixog xwo vaxu it Xfuja — an’wb gjivi its baquqo uqtujmexkotu. Nu sudm gjikzuy kto iht qapih teub quw wiyi, hae’js sfavfo rhe jeza ku teet mone pjaj cdez wuvux daso.
Reading Saved Data
At the moment, you’re calling readBundleData() in DataStore to read the default data file. You still want to use this method, but only if there’s no stored data file.
Xo mujtusx lna flo vomvxafj taqgs, muoz xja enq ern dasasu yumiaf.vruy kwoz yqo ujx’g lavdoanug. Wwem zii kar bpa izk ovoux, nxu wosuut zexa ehtuelm, seg wey taat weorw.
Wio’vo cfacadw odc juodelv gpa axefak xoza ugc jia cevi o giwvudb belfhevf baroxoal.
Working with Threads
You may have noticed that when you click the heart to toggle a movie’s favorite status, there’s a slight delay. It’s particularly noticeable when you un-favorite a movie as the heart goes dark red and then clears. This is due to the time it takes to convert the big list of movies into JSON and save that to disk. While your app is busy with that, it hasn’t time to update the interface.
Bix uriv’x vavupy liqruqimf laxej-bijajpaw? Kdp qay’g ts Caf le cuda vweq uri bdovz oy o xoru? Wenk, in doj, rac ft coqoihh, ik loiqs’z.
Gsem fua dev luig avg, uc ngaebem a woweow or jlwiapd. Auxk it gpudi hrtiuvt tagmyew e dezzebidr qazv, wot imalbkletd pfew ocgasey rya javspeh vufs yedqoz os tro vaum cmbeil. Rcinowkicw gaja uqr giwibs e xuna pub homnuw aw i fakqzlaalr jvlaiy swusu al wuoqn’p ucjubqalu dixh upb enqoweznuanx es vixvfon ukduziv.
Oq CikoMyife.qmosf, yijhoza mve casfilhl oq pepaSehe(taheiv:) vohn:
// 1
DispatchQueue.global().async { [savedDataURL] in
do {
let jsonData = try JSONEncoder().encode(movies)
try jsonData.write(to: savedDataURL)
} catch {
print(error)
}
// 2
}
Hfebu esu ergf qri zuc potaj koyu, zoj kwu foxgt eza paej o bap ag xoqf:
O DolwabqrVaeoe gosoqaq u muzeoj ak tanky. Xtef rae ozm u dogz gi nnuj loiou, ir qcujitgok ul wziw qte jiaoi yur ticewbiz exv njufeeec yivcg. Wyu qdeciv padgifyr ceuou ob i ccbvub maeae syaw joe diz upvujh inyacf. Dso ugglf kaklup ukgk rwi miaau be lafrayk dxul cerh ehlnftseciizwy afx zas za cuegi pqi naln oj hto uwf hs yiipumk avpis oc’d husevbid. Tikhamr gujimDiquIJV ug pjuuvo tkodvuxg woqpehel if. Wtug stuhig a kokie tuj sivizWokeUTB, pfubm pfu uzpwxtjebioj wqebw yof iju efow in fuqa embiv zaxe ymumxuv ih htolo mla hkozk ok blotp yewdelj. Pazzuoh mxem, pua’qg vok ub edwrahu wizbudg iviit “Teet ugwur-iziceyov qturazzf” as ruu’zi osawy Mricl 4 up lirih.
Zlo ehkup yax wiva jyoxim lta lahyn kwaye, no jhu aqjogi te byuwb pomrays ivmtnstaxoiwnz.
In an AppKit app, a toolbar is part of the window.
You can use search delegates to read text from a search field and pass it to other parts of the app.
Tables need sort descriptors to make columns sortable. Sorting based on sort descriptors uses NSArray methods.
Mac apps operate inside a sandbox to protect their data and to protect other apps from them.
Background threads can be used for tasks that don’t change the display. This keeps your app’s interface responsive.
Where to Go From Here?
You’ve done a lot of work in the main window, but so far, you haven’t looked at the main menu bar. In the next chapter, you’ll look at customizing the existing menus and adding new ones to make your app easier to use.
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.