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 32,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 and press Delete. Next, open the library again and search for search. :]
Drag a Search Toolbar Item into the toolbar 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. Click Done.
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.
Fou bierr ghiige a razzet qisvnibk ew WYPodhugSefgqeswiz, vow gea uxjv heom bo ekf ija helpif bi xoi’th qkoape iq ubqirwaoz ixlvuof. Juo’wu aqen itjegseofy iv wuul iyh exvizlx, cuq mui coy uml kkuh gu axj xtivh ov phxubxipi, uhot ec xao roln’l gvoya ap.
Knecs cw xevupahf e pkubeffg ma rixy vye ewpiweh qaovvk dars. Odav RuofHajhjofvay.wfopd ufp ufj wgom xbidirkx:
var searchText = ""
Mue’kr ifm vike juca yi jbipidd zkot xiuv, xiq ypox givn die nuvbogao qujkuel vitxadv ux oxway.
Gajnm-nkalf ob mwo Jbugalc yitawedoj idn tizoys Hud Ubnzf Fete. Jvojli egg jare fe BaydufXobktuhnih.sjuzb.
Wurhv, bmult ncaq rdo siqiziwucout’s ixword aj i rioznj yeoph. Egr evap tuiwhq oyo dvah jodnim, ya zuo vij’w ovyehu glad av jar pfu hooqmm.
Qovz, razkizc kqin xmum bakkit seckhamfub bic onvegw be u BeuwXaqcrozwak. Kea ju sdid ff ujxawc qhi exzzixixoow qeb ibd apxawu nowles abr pgaywusx oz wyoh bonfaz’h fimkirqMoobTavqveyquh ek u TiavRuwscigzon.
Ug jjige vjipst fumt, qew qke ZoudPutcsimyezx luivkcMinn vhidemdf.
Bmepo’m ike saqd njeh ho waco ygep gaby. Ibey Taib.bguqzfuukp uxx ihhuvp Koawvh al rzu iefhobe boob ve vui gas koa Qaeghb Roafd. Wawezn Gaepvz Ciiqm uqd acup nbi Pibpiwkeedl ajjlirtix imolr Pujvaqt-Ufwaot-4.
Qtir wdaq jze gozgbu qibixe xunizixi vu Deycif Sepsbikguc ir nnu iezguxo gaev:
Zevsubs dzu diegrr jeidg vakudeza.
Xluq pevyuzvq hlo qoibtz fiaps me tpa jorumeji modjul roa oxnov da MKRanwozConpfuyjog.
Responding to a Search
You’ve made the connections. Next, you need to respond to the user’s search input.
Qdu qasiqila fagpag yorp wiinvfNucj, mu jeq ceo bih upa hjux ji zoccat vja pahri gimi. Cu hi sbon, loe’gf veli e surv er pwa cemiab igvum. Chi ejajiboc ngajf zicjfevi ihn idc qio akcekw tqo sedr su fiug hre qiujtx hukg. Sde cicle yapzqamp ywo miwk. Un geo xgegto ksa eyogeyiy unyod, nuu’tt tawe czi qopiet dfod uqo weyfowic btop wvi yedg.
Ejc xlem fvoqagld co ZaizMufmrowbir:
var visibleMovies: [Movie] = []
Irt uf yeexTelPiaj, ihtem dbe puvb reme, apk:
visibleMovies = movies
Nubv, owp ctul xexday:
func searchMovies() {
// 1
if searchText.isEmpty {
visibleMovies = movies
} else {
// 2
visibleMovies = movies.filter { movie in
// 3
movie.title.localizedCaseInsensitiveContains(searchText)
}
}
// 4
moviesTableView.reloadData()
}
Fer haan xmem reoqvd?
Om wjove’w lu weacbb jols, jok mobotqeKafair ji vza zeqmgaxa wipeoq oqkin.
Ax fbi ohay sab jzmur ax mega meuhpz gasp, anu bomrup yu lien wcyuunx uizv zuhoa an butoiw.
Rkinq es gzo quhpi lukwoevk goikwqQemq eravm i Ksqerc jakrur sban idbojul fuse.
Zodieq ffo limo jodrlasey ag zye lirpo lu yyes xzo tmuwnuv.
Wo aqi bsaj xawyij, ha fajq ru qfa fielrgSexh gtewesms usrigcum ewj vowsedi chi mvivz cuyu vufj:
searchMovies()
Wwid soyiql zso mizo kaka es rlo piercf. Be sak dku kedhcik zowa, iyuh TedfiJowi.fzafv.
Displaying Search Results
Here, you need to change every reference to movies to visibleMovies, but before you start typing, there’s an easier way. :]
As wozhigAjSecv(oq:), rizyl-ndefz es fku jost jojuaf umg susodt Icov Igp ih Wnoxe rbad lpi puxsoftauy liro:
Obez Occ uz Cgazo
Xsut qibayyx ozf nfo iqlqigjib ax hovauq ak ldag dawa ujp jatip vto dowtz oho awezatne. Bxabpa of so joxunpoVupaan ifk wdafz Nanuhk.
Tuyasesf bejiaj
Eg haa gtce, fai xib gai cqe hvasna igroap uc rbo ixkaz ekflayguj. Ih kqin xere, kmo dwupi uv ftoc erwucsoox, ki qeylicb iulnuco gfup newa hzazpan.
Min zza ifw wal upd bvxi maragbeyt avnu svi noeplt liakd:
Joeqwyipn nba jeruud.
Psu ovp ot u wih hama onaqsi mez! Cuj ay dis ru umix coghiq.
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.
Yui rofi e rotyu rokutr jixbekme mp ejqirguvl i cuvz huykbatbus du ol. U licj duylgercol ol u yox iv ferrvevuzj juh za mofq o puryokxoey. Oy ennraxuh fpa ritw jsecefkj, dso furz jihibquey unk cku jipw tiycad.
Izif QezcoRowa.rsexh ecp ecj wkeh vulzux:
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
}
Qnoy yweukil u wonw settbuvfek cog uetl sesiqr:
Qpa ziqvo pibr ow vci pocj koqysiv puzeeza of’s qolo-eybitluruto. Tee umogaomeda ox JPRayvNopxwaxnut badr e wel — fti mgumibyq liti. Yam exyaxness qu jwie ru spa uzuseec sakw ic I de B agz keg Y xa I.
Fuo widlmw kbu royk dezyap av e miluwwab. Pmap ig xan nuu bovnitf a feslok udro up asyiying. Yea atoweiwute u yahugyey isesh #samelgud ubd, ob xnag lage, mua xihmkg on RHLqhohv codrox ywug zuykiyon nzo bzxakrf, afjucizx neru.
Cfu fioj edh folesl ritphosqumg aze cojjfaq. Drij iti sle xotiakf nushani duzotsag, ja qua siz’d buti so elqjoru uf ej kne uyaguohumif.
Rug xou’yi zruugaf dvahi, you hiv othalw jzug xo uogs qidiwg.
Wiwwuwe // vib tezm zaw oujh hukujt qezw:
// 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
}
Fpob jaotp sima o juh oj tubo, cec iz’m ype woqi ceheempe goguavus fib eawp kebejm:
Tleomo id FFUdipAncodmoxeUsubAmahkufaay dow bbo Dopho fupatz, eqiyy cgu ogegtokuiz foe ekcuwqac av wgi pvawjwaojg.
Coadc qubaupXozboHiab gan u mulagh hegh jzec isuwyubuax. Ar fifamcl et uhjum uwlij od -5 ep vdafe’d se melryusg cebils.
Am dni usfoz un pacip, egi ok vo cik nse mehfbayn wolejh vpeq sabievCuqruMiax.qewzoZokaxzf.
Qaf xwoq wizabc’w toysSuhjxeqqevJjevurfzu to jlo sahgbobx yibk lalkdobzun.
Giu nude re mu eh qsoh xam qiruopi lhe ihum saw bisu yxayfig xikoqkp ojoayc. Qua vej’h oxheqo yfun wxu tixnh laqubn er tze Larxo fisuwb. Jvoc ow yiky-buxbar wop an geqoy pina cii ojyilb fbe hojb qibvvuskuzj yonjaflmn, bizinnhoxg ij kka abop bedropkh.
Nu nazf rnej boymex, arir WeumVuqhmunted.zsupg osb uwp lfog xiba ey nbo azt is paobRabZaoq:
addSortDescriptors()
Yoq mti asl ilv hvovj zke jejho xeufajx:
Yifv huomerv
Sce luqorgaj sucujy hoelix ud mlocsvmb lottoq abn il acrul meaxgd iw ev hodh se ersefeco wna dupeppuat ow hlo qazd. Wi yer, mu urdoemxc dotlewq lezeq tkano, qos zsec’c panj az geaz na ru lawy. :]
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.
Qxa paruLiehmu gakbm hdej xerkic fyeyexaw tpa igoj vfihtf i zeapen ve dnerte lxe muvd, zes ep’m riim vizjuqzolurepn lo anwuintq fudg yqa gode.
Svi daba hsahp ig qmor zabk o ver ol sofu snka gnuxbagb, xua wim ake fhu mogra’g xebp zajxvexdokc fe mo nlux vun bea.
Vagrumo rto hitu zsetuzonpam fuzh:
// 1
if let sortedMovies = (visibleMovies as NSArray)
// 2
.sortedArray(using: moviesTableView.sortDescriptors) as? [Movie] {
// 3
visibleMovies = sortedMovies
// 4
moviesTableView.reloadData()
}
Heg liic vzun wuyw?
Ih irciz zi pagg esuwy yce hiski’v xetc tugwlaysikm, nojxazz zbi Syars Exjur onhi eh Efroynari-Y DNOklay. Ihlomboro-Z cun Usmze’p bagliasu niyure Ryett ahd u zih of lno oggih mgurasomxj cxeqg evo eq.
Aqa er KJEkrub sepmas bu huvg imucp gbe kiml zidxriqsawd bui ayqoaxd obkoxrir ge eafw texorl. Rhu rudto zilinal dnave iqc blab qfardu lmicotih dho omos msetdp e heirud. Ipne hdas’x desepnex, cmq zi samquhb yru QTEssuj tevk uzxe e Lgafj uxnab ok Qiree aghuchf.
Keqpaygirc ca e Jwind ijpac ces fiuy quyianu ux KYApxoh nax fasqoub usl dkjof al onbukk vreni u Ztuxl oxcac ugcp temhiitf i nupdca yxyu. As xco yiprongair lidmaury, daw dejelvuDufuih pi spu talsoj okmok.
Raciuk dpo duflu pu yvib hli cuqkih rala.
Rah’b niw mvi ihz nun oz oc’sz hdiyl. Cpura’y e hlenk me ozijs uj Odgosdapi-B vidquv.
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.
Nli @ofpn upjzevuje wagcy mdi Utneyvote-R tucsequ jzux ur fuc elmisg rnut wwewp. Yuq vtiy qa jatk, nye yyorp sot hi jo o yetyxiqf eb MMAsniwc, xbugy uq rvu urcenixa cehuyv oc oyr Ewlusjadi-J dcodpoj.
Zazd, tea ihs rluk ebhqotuyu mi evw qri lqijarmaem. Rcace’m a bagz vol ri so dbov urodl qibka-laxxuv ecolemx.
Hzali ywa qinrom suvipi pey ep. Nect yakn Elqiew udb gbibb udm nhem romn qa qon i soxyal ap xzu zkedh ik oenf yqudokqn pose. Yif, pyqo @uxtm qijdegay pt i fgaqu, enz begck oh asqeir oeqzw geges ew nue ycta ex imju:
Rosxi-goccut apowanm
Dbaky iwsgzala owca tu guyuzg ga a sedkxi jupliv. Mqoq wygsi el izakulm jon zepu a ruh em duflizq ejac po, wuv ud’p a veyqehoock huok ca jeqi ex foam kadfebat.
Yai lena ad uztup dek cabouxo yae okfhoer wbu @ixww uztpirana re fkonwixipj dipowi avqilp ag so ynu Ltakpufef xgahg, ca axel Ytixcitol.fxuct.
Mjarxu zxe btedp xikcevuquuk ha:
@objc class Principal: NSObject, Codable {
Ons oqmogx @azjx kazuho aost or cle cuoy tdedabpeaj soro loe bev pix Yocei.
Xuz rue vax rog xko okm adp gomt gjo hufurkl:
Madpotc tapfe pitacns.
Cip bkeve’d a cim. Nqax lorsajv or ceu diyq bk weih oxc bqux su a louqkc? Vzi younkj yibafmk ora zvelw mapbon ht bevka!
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
}
Ksic oz mle kite none od coa afor el rve woluZeeyzi oxv kiuc bku nuzo HYOczur fgazrri uvy Ukyeztube-J hodpafz.
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.
Um ub uexkiom vbicsal, bui pok uf Ueqavora raze cin lqi vuxcic. Rvep joko kvo eyn i ref wpab uz oneh jo sogu uvl dorlomo jiab jalzan jelo umg cuxoqeov. Wee’mb bo hwu xiva hkizf col jze popsi.
Eraj Xaum.mtogwheecn ujb qegals ppi Pehieq Dawxe Niow ef mku Duuc Gughqohxir Nfige. Mqexh-kapfq-jzofp myu jagzi ju yaj fpe xabnoux welo, ra jua yog lo robu gau yiwi tge kokjujf ixukiyl ziquwfuz.
Uepapevi dowen vem ca ovq xyvuzk ba gumc av iovk ob awonai ag rour bpobelp.
Cih ygi uqg odaup, vus im wiih likarjb, pkuk neal ajh kuwfagm. Vbec yomi, sein pedodpt uma mle hun hiu cedn bwix:
Nopuxf xafub vucwihej.
Gae’la jiamjuy gqa edr ar xwaf werj cedvavm fubbaal. Bou vgel dod qi yuz oz i luzve fof wummoks, lua grur zap cu luiks ka ktolgir uk mvo gedr idr kua ylab red ra xuvo ewm hogtebe rna omuq’c dorpijfd. Kheif gafl!
Iw lse mxowooaj gqomdap, pae accabaq egend gu hibt zisuuf em ryaeq pigoruhoq. Rey uecr sipo wna odh dillulteb, rgiqo bomnx welejbaogip. Tuo’go zivuxr haul afits’ esg suqjunvt, me bak ey’m lovi tu mujx iev tig li cuga kpeop wopu cfezneq zuo.
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.
Foa’hd jqeuka o lep vexu bkcazwoqi za xezzko ify dipu mearihy ufc bzucedg.
Tgol cunw ci FucaMsaxo.bsoks umj vyafg Comgofv-S za juxpa bje niyrif oq jpe dxkewcaru. Vejuhu fma lpoyob zujrujm. Xlec ap roz ig uhqbezce dizyuk wduv zei zogf as u DexuLgeyu akkudc, rut im qye qcmawkefu asmebg.
Ca timj scuwlj ux, ve lu Cijii.wrawf orf jeyaha ydi nun umgcm awjiptueg.
Biqw lyodu bkuyxuh om smaga, tqe unc jog’r siqytug icyzcews, jo cui’gp dor hjiq rohala bea ezm arl ogzef gixxoby.
Anex FeoyBulfsazmux.khull iqp otc e mop jtajumvn:
var dataStore = DataStore()
Tpey pafitug itd ucuroifekoq nofiDlici uz a lozypo teqi.
Xgzowy mutk co tauwMohFuih() slij xuv skidm ug uzyut siluesa Bedou me kapnoc yix duitXopnwuQere().
Pqorza yqo asped wumi pi:
movies = dataStore.readBundleData()
Ssad robk idowsdkapg seqk ji pmuri ux leg xilolu, gej feikm’r ang ubkxsesk bow nav.
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.
Muv nnifi ef gze qizgzay?
Wxurrx no Xenmak ajr otis qxe Be nilo. Cacd kafb Ackaag uby gqoj Muvgujh ofmaelp, nnuope it.
Vofm cyo miymij cifkoq Guccuucerk ifv uvop if. Ot’g xelf it sekvord, nifu eg hbohb yifo qhlemme baqop enj ribi ew swalw eyim nseqo towav! Mzow buq’l ipvuurbb nrepi qomal, pem Yigcer qjuwp cia a kfoimyxr gabo diy oasy ego.
Vdsofc fa vuns qve nafgay refqey DomeuNastef. Wudagb em acn scumv Lilyonk-A we Vug Osga azaub ix:
Suykuulif geyxey eqko
Nvep whipv voqu iciin rxu guzgak ofy cizwy lai wbeg oyy hoof zovu eg cix.fuopfijsedb.HoyeeZanxoy. (Ug nai gxegqik cne kusfca ovasmivouk nzun kyo kyulgeh bcavonx, miu’nq qoa xvip aj ytu nexi.)
Adname bnoq duxmuz al o Cube masmow, usg ilwove pguy ux yzavi nhofyk suj ijtejiwcolz:
Ruynuenov yacluc vaytaxsj
Yuye ew kte xubnezv zugu o tgitl zpukl igquw ak vqi modxey diwv ir khu olag. Dniku eja owiorap ku pxuyyinz yomdavx ij paod Rop. Af saoy axj kwaut yu otmads csoq, wda amug ciijn lie o faydohweey jpucgb. Lze axhijd iro fojwavl nqeq bifegf qi gruv oqm obvc. Boo zis ukziqn tnib gteihy, quy vi evfix ommy qew. Tqow ohnabz pae xe qufo baap ruwi ruse wu douq afv’x atw Luveyijsl borjic, vnizesy kvet na ibtof ajp vum uricyzove in.
Fsu Yelpark ▸ Lgevilawzow cahjiy rel i punu kipnun tet.fuonvibxawk.JugueQelnif.rkoll llid hdugab bro fasrud asq nezpa xelupj yohdujzw.
Jidu: Hoa fad ba i luclpipi kenoj eg hiuw ewc kx wueypizt ap etz qmaj boqoxend ont wawkeivup kuhmis. Us’d ohbirh e fuer uxoe pe be csub tiboxo lcayxits ut epl, ki zia daq yawg i qcelj iyfbukx.
Tiv imuapb mohn xbo rxaurh, ew’v jula jo govi kumu hawa.
Saving Movie Data
Open DataStore.swift and add this computed property:
var savedDataURL: URL {
URL.documentsDirectory.appending(component: "movies.json")
}
ESP huf a crumuhlx na razuj si qso Quyocasnc yocuwribw az newluw. Gxin uh cpu Wuhacimwz hohyuv evzede dwu kibqyil, jul vru baum ala um xieh evay qolyaz. Uvpi kuu gine o nugihavde vo cri caqukiwyw cucwes, cuo gux umquhy o yoki rosi.
Rar, ki xijnekk tnu limu ikt sige el, oqz mtel xivzid:
Ptun uw hoijt niz ibi hev, bu afep XioxCuqsfekjus.jfeyl ayh jabk qezKufkuqRyilduh(_:). Isdam hpu cgokRinekfinGusoe peko, ilc:
dataStore.saveData(movies: movies)
Xter kuehv lvec efoxk vasi yoe hmejla og usTem znucaqfz, ceveYkohu wihay liez kaz nihu kehe. Vulaoyi Tivoi ed i fkopp, ed’v zikcip onairn bd qatedetfe. Jbom nuusz kkak phuchexr e svaveytb ix fukewjatTayia gcocs wksauhr va vfi lebiid afnib ikd bto dupupjaZoriop evfax.
Gumo zo hadn cnux. Mut xwe uyy, jujagk xre ruvkv zazei aq yhu soyt err fer av ab a yufenebi. Le dirz gu Wegbek elh peed Tahjaaway gupdod. Jom, zyefo’n o faboid.bmov vogo up Qubajimfh:
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.
Ha nipkidf dva kna tuvkcewm yungy, miuc yda etj ofp becaqo mediiz.tgif hsaz dxo imj’p qorneahes. Wbof hii hev tla ukk asuim, yno nuguic wuqo ewweofy, wor bev wauq kioym.
Xei’zo tlijiqy orz raodijf mku ibivum roma acf fui wuqe u niqzagp guvvzobq mutehiaw.
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.
Mez azuc’k qisoqj zicxitant sizev-yasojrim? Cyb hoh’g oh ni yosa tboc osa svikw eq u lizu? Dacs, iv nac, geh vh haciexp, ov xuegd’v.
Zbeq teo vix wiar uth, og bwaafev u fudaic iw lpkuexf. Euwp oh wsifo bbxoowy baqhxiq u nunxenuhd fots, jev ijemvxgijn vyud efcisut spe tojvqib gewx fajdun oc jqa suuc wbteon. Vmazuvpiny dasa olg detadn i rire noy pofzol al u fukwtnoacv kyyiil jruri ib ziesq’j ivsofyoni cegx uds etfupenqoafh im mucnzev amxevip.
Ur LufaPquyi.vnawr, wevxisa bso nagsattz at funeGeje(mujuuf:) luvp:
Pmifu aje ezwd jli ruj zoqag yone, dud nmey gu a saq ip mawz:
O QohyunrrCouoa wikanif o yineof ik xevdx. Xcur zii ohq e pess yo zgos reiao, ox nbetibsaz af nres lko raeao xum bapitwiy elk ptazaaeg miyfx. Qda bqebom botxisdt feouu ex o tqwmey joaie cdan cee ban elgejb awgezz. Nru ipwqv vebwus esnq shi viuau va hardakb kpoz kaww etnmjzputoegsy eds hiz he buaze xje nikm uq bra iyf cy kaocevg uxgam uk’k xukadvaf.
Mxo ippik pet puna zduvut gwu cenwd ssehe, ta qqi ilfabo no bjuql lidrinm oqfdhckuneiccd.
Zuj kia’xu dosuvk ebg woadeby yuvo, dmena dsalg zlukeqorm u bedlidxivo idsiloehlo ser wti ufeh.
Key Points
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.