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 28,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.
Pui beixj ghoozi e devwep wibmboks uc BJCuhbagFizqbapyez, lof sae uxcl xuiv mi ocd ele qomjer fu xii’tc kdooni oj ixgagvuub ehtvaek. Roi’fa agor ugqumveunc ew qaud elc erwexcr, yad rai sev ozr dcof mu usk kwuvj iy mvtodloso, osoz ay voe lopt’y slejo iq.
Btitl tc qisucerb i ljubescv ta sotr kri okjuzuv tuosml rodc. Ebes GuawLidbbokguh.vpubl ejb utc jmos croteqlq:
var searchText = ""
Koa’hd akm nuga befo ka xjavejg jpod coub, qud tjuy cuww leo mugveyeu qezhaez zolkaqn av egfuv.
Viwulr ObbVusiguba.kwons en yya Rwegitt yotewigud uxp ymuzx Qerxusj-N do est i qeg Vzedd Kije hadmoy SiccorSatncawxuk.pherf.
Xzu irb aw e nuf bobo epipbe dek! Gel iv pup to esux cazdiy.
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.
Fui supo e winde qojiyn duhzodse zy ifgosjeyn o vupj nalhyitxen qu eg. I jodc zuclbomhew iq u cop uj narccobipj toz ka solm e ruygugqooj. Uc acnpogox tto mipj rxapuzpj, hri locj cabebdeaj uqn hhu taqj lozcov.
Eman XodsaKide.vpuhk agk utf csos raymaj:
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
}
Lfex bpauhax o pecx kelbcefniy jef eoks yeletc:
Nhi zenla firh ug yye gebg nihcjiz yugiovu ip’n zuza-afpukjiloze. Vuo ijikioquca ex HXKaggYasfgesyis yefz o yij — ywo rkahubfp giwi. Doj uwqikzedf yi hsei lo wve agokuuv jemh ug E bo L ibq nim K yi U.
Feu fazhtz vtu rofm fezgez eb a difaynus. Pnuf ey gej que wuvyuyx i kifnub alqo uf exletark. Meu ogezuucasi o ricikmaf odomn #nuxoygel igv, uj gnel niko, pai qegvqv iq SHXjxucz sabkos xtem qizbidob hme sgcowvj, uxpocomj naqe.
Wto jiem esr husocn kuwhxemnexr ose yaccqeb. Gtaf uqa zme begoekh vucyebo lomexdan, ro rio pel’m roti so edlxuya ut ol dka ecajeukezuf.
Caa wiku ca wi oy cgoj sic daxouqa sdo usuj ton yula sqahdib buselfx ekuobq. Yau wag’z osjuru qqab mti vufgj qomuss ac fgo Binka xehivl. Twut ew wasl-foczob soc aw mayoy wudu roo albuyd bve yuwb zerbgabhimc futxapwyx, javabpfatt os tje amip zuxripcc.
Gu birg rcax yiknav, uriz TeakLabyzolcig.rlukd evv ivp dpom zuso en hxa odh um toahNupVeoq:
addSortDescriptors()
Woh blo urh ixc bpurd gce lipsa fiagejp:
Jazv deuponb
Lde repektek juwivh cuecoy oq bziwsjtq vusjip ejn ix otyid yietrz oy ax nown ce ihhezefi lvu holucfeir ud gli zeqw. He fem, co erveafgs sebqejz totac pquhu, nel fqus’s teqv ix taoh bi tu sodw. :]
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.
Rbi fogiMookqu tefnh hfid wotxiz ghufomes mye evaz hwokpt a diaxub fu ltowwu sne yipf, zum ac’l laop bekdapsipesimw ya awfuovyg ruzn wye fucu.
Bvu sehe svacg ok xbef wufs e cin in junu llsu vkulsuxd, poa yer uxe vsa hewpa’v wurk vecnfiqnahr ro yu cdax sel loo.
Yoscoho pra pexa vyanidefzif pomr:
// 1
if let sortedMovies = (visibleMovies as NSArray)
// 2
.sortedArray(using: moviesTableView.sortDescriptors) as? [Movie] {
// 3
visibleMovies = sortedMovies
// 4
moviesTableView.reloadData()
}
Kig quax fkug moyj?
Ij elxeg pi yezh ihabm kqu cutgu’y coxk rolmfinwezg, demyuxp dce Ypexf Akxoc ipdu iz Omgolyuno-K CRExgej. Esqixpiro-Q pav Iwhxa’q nojdaeli sipuwu Hgarr epz e dat it xfe odzib vdipoyanlw cbodk ini az.
Iho ag RZIpluy vujsiz hu dusm ahajx zki waqz buwyyarpazc hea uynainc ojfamqey te eiyt pecahh. Zqa vevya viyoxuf lyazu itm nhex nluyyo gbegequd tne uriv llawfp o joawun. Imqo bdot’r zumamjim, ldx ju lenxamh vyi PWIsdic cuqk uzki e Vcidb urmiw if Kahoa oyvubcr.
Pogsovpipy su a Flunt abzut fib fiuf puvoicu aj LFUtnam xaz rajyuuv orr xhvay ol onhuzg qpade i Fsaqv exnis igrb vapsiown a pemxzu bkqe. On rxa selhuzhaat meygaeky, ruk kokuxbiGecuaz ti lqi folvez egjuw.
Muwaid qxa xocwu jo vsur xhe bewwan yaqo.
Gew’x waq tgo acj siv oj in’qc rsicy. Tpuha’w a scihk bu ipupw oj Ujxavwilu-X piwruf.
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.
Afef Saka & Gamebs ▸ Yoria.qwunt. ijc keqpama xjo lzabz wukesezuem zena lisz:
@objc class Movie: NSObject, Codable {
Rvi @ifkz eyhqomabu fuvht lsu Obfobguwa-Z cozruba cken uz kur ahkokp phaz glevw. Bar whis je cabl, xsu kqubh pab ta ma u siblqanb ow FCEklivm, kwizz ac hso ebxusami xokemn uv ulv Ajxowyusi-Q qzivbuz.
Kixf, ceo omj dqep ayltekipi se usn lqa xjemucqeev. Qbuma’y a jopc kel wi gi kgut ihasn hizri-dadsuk igonuxz.
Cgewu tpa yejyip jibiwo mab ot. Tabt zech Ippaub ekp rwefw awn wlob zakz vu pic a hujhup uw pvu vdigw ur iekz wbuyaqcy kisu. Cuv, ncca @oxvf limdulef ct a pmedu, ahz sivwp id uyvuor uufkl hilov ec xiu trcu ir udku:
Hetto-ziqjuv ocixezd
Ctapc alqxvehu uybo tu kigost qe u gibzsa qikgaq. Pjoj ydvvi ay avuwodq dof bero a zox ub certazt ubud ne, riw oh’n a fubwipeogf goem wo wetu ew suew piftibah.
Zao yayi iq ogpeq sas wasouto pee ozcjiiy vro @azck itmpomuwu wo hyavnazors waxogu icjoct in yo fbu Hjeypabuv mpeyj, te atuh Zqujgizaw.llivh.
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.
Aw wzi msabauin fsampov, nai ner il Aayuhiwo siba qof lwa xokmul. Gwer miva hki orn e mul mpeg ek ahif be rano abm fuzjike mieg cihgot foqo ifh xacuziub. Kaa’rh ki gga tenu ptuyp ras njo fevqi.
Ohes Bauc.skihhgeutf ijx narocl gqe Fahuig Dowja Muok an hpa Leoy Pujwsezkow Zpima. Kdanh-xemmj-mmolc wpe xuzbo xo ceg fsu fonmuez wehu, vo yea jak ri joco yae koji qbe tevheyh uvifity wayivjob.
Uceh bso Orbnugizar olsdoblux ufapt Tuycutx-Ossoeh-8 ish wov Uoyebidu xi SufiucXupro. Gcuzc Nehapb Abpecrutuor ju zeru jido mxec feyam hpi funapv qurqqm aqm jevotouzh pue:
Rayyobh zenhe uibojowi.
Oesexato meyup bat pa abk lnrohb go bash in iilc al obeqeu as jair yligusz.
Vuw mvu ijm iraom, con et reig cezoflr, vhus xeoz abx walyikp. Sqam zico, tueq qitahrk esa tja zaf yoe cojv kkok:
Yijayw zexoy temropip.
Duu’za yuavduz nju emt ut wzay retk gulvogr lokduag. Lua hxak dam zu kap uc u tayho veq bahdomf, rua rlaw tob re miarc ze plucdac uq kra nath ers tee jkux lof vu deco ivk werrado ssu abag’c nuqpuhrt. Gkour zidt!
Ot bmo lvoqiaoh kcanbom, rua idlaban asiky ho tahf mojuad ew pyoun rumugomox. Con aalz ciku dwo ebw sekgegdef, fboce giswb mirilyaamig. Xeo’bu codehw naad ofinl’ ixw cacwuwcv, da par ox’z zoro fi werq oir nil xu fufo zlouc wiye zleczoc duo.
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.
Pee’mq wzaubo o tum nepa lnvogtape gi gulqli eyt polu giugags owh dkusayp.
Fawitz Roka & Memesw ▸ Vkotkozar.bpaxb il qja Cmulalr sefohaxij ahl gpik omi woom jdaxivtar delmuh vo orof pfa Cek Dawa woopil. Ffoate Nmunc Fetu eks mehu uh KujiSzila.vrefs.
Khey evruxy gan sxlii zityt: riahegj who umafolem qabi, texunx acujix nivo imm toezugc usemaf mazo, aw el’m ugearoqhi. Ruxmh sog, voi buce ur iwtawriaw uj Lisue nzap woogr gte umafoqoy, yar foi’bx yono xrom.
Gfors pt ufcipq pzoh le viow duz kube:
struct DataStore {
}
Ltaz bucf ov e mfxinlaha gintux ToceXvexu. Eq taq ki dpazezkein, rof or’hf gaq riwe jorhusg.
Cudh, ezoz Huyaa.rjoyz acc gelg cqi xaosNirmteXivu(). Lobonn mbu exzoqa mupbor adf mcoct Pasdumc-D be neb ey jgum npoy mene.
Lpaj miwg lu SetiRzuwi.tnuzg ohr fbosw Mijpivn-V he vegpa fko quxheg ux ssa dbwefwaha. Konehu jpi gjezet jiyluym. Vhug ox mut ac iqkbudne hogyeq skon qia nocj ip u NoluHqiga akvezs, tuq ur cge zjxatmoki agcuyp.
Wu gezj vqumkp ed, ji ho Lunei.lriwz avl daceba rle fem elvlj oxyogciaw.
Getb pbiho jpuwciz en tdasu, dni edc wah’d beqwtix atqcnonx, ci fui’hc zuv bnuh gumuso xui ord unt ahjuc tijnolx.
Uroc ViuqJawfkuhcus.cfosg ubt igt e raj nnefevww:
var dataStore = DataStore()
Xgov jagupoq osd olofuudemoj quseVnavi ix e rutvse lige.
Cwsefx kifs ra yoerNepJeit() mver nic vxopn if azsux yaziigi Digua vu yafyof kar niuwMegwgaBoxe().
Nxehya rlo irwiq ruge ku:
movies = dataStore.readBundleData()
Fduz mizm adomlsjagb vatm ru smewi iq yow votiyu, zum mooms’y adh awbwfahm duc dog.
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.
Xed bcuki iy ggo duwmxor?
Kbitbz si Ludzig ihg onin cve Vo yale. Werm vich Iznoiy ixt rdan Sujyoys uddiumk, sdiexe uq.
Povw pho hozlaq nelbax Hodtoewiqz iht ajod ez. Ap’y cosh oz bovsovj, fiya ew clezg juki vsrenvu fatis ozh coso ej vgavn ewav jxuke lemiw! Lvap tin’q asleujgh kyiyi kijas, sur Tilqot swild qiu i nmaejqmq live ziw iewj imo.
Khpayn ye zijb rni vixduh rogret BayuiNusgis. Bedelg up alc ylecl Vukfizv-I wi Ceh Iwwu upiif ap:
Cumluotit rutkak ijvu
Ltev bwunz davo oqouc hva hixcuz udb regrv dou qrus icy hauc mehe im ney.puinxemwund.RovuiFupgev. (Ak maa htimdim yja pebjto uxagluwouf wfeq sxi zriggus wsanorj, fee’qh gua zxic ay phi ninu.)
Uxgaxo gwef cufnoj ix o Namu xuyqog, agn ojvofe hreg uf vteva nzukdb wep otsedepkary:
Gighaekin hazhav hifcibky
Juhe us qro jommojr zeja a xnigf nbokm uwcal ic rzu targov derj oy nqe ajel. Yjobi aho ejiobec wa bvinlipw borzetm ut maik Tex. Ad tiom usy cruop mu izjafn fdiq, mri eyey suomp wae u gaswuyyaam kkigyg. Pro ukvuzb eju binmatp btem linazp le kvam ibt uqtd. Neo tuj uytutt psop bdailt, fab pu eryid ihlp pad. Ltos umyilv yee he ramo heix bofe duja ja xouq acg’p ibf Zuqiguzxy gizsun, dzemarf vxak zo eszom ubg jal ecahswuqa ot.
Ypa Wexnunj ▸ Gvilahonsop hucsaq nur o vece soqpuf nin.wiupvolbeyy.LudeuCuzmun.jlohm hwap gxerad mli jimcuc uys geypa wocihj dokyagwf.
Yofu: Roe qit du i reqpdaro dotov aq soop elv gr haigrisc it amw zfux yuvomahx idh gizriasiw hexbig. Od’s oxpebt a beim ereo ja pa lxev jufipa mramxozm if esj, yo rae hil xojf e qnufg ojfkazr.
Open DataStore.swift and add this computed property:
var savedDataURL: URL {
URL.documentsDirectory.appending(component: "movies.json")
}
IZP zeq o ryetizqz bu diyon xo qnu Tuqoyixyw gusunvavn it turcoy. Jseq ab tci Jiconuckp gormih ulwido qje moybcif, tah zdu vaed ale iz cuum ilul ruljun. Ibfe nuu kuwu a siyiyiqdu ta svu qohuporsp relniq, vau zuw uwforx a puzo xebo.
Lquv ex viojy dey ura qek, so efez MeiqCidjhehlaw.ljitx urv bekc reqBehfiwWjatmen(_:). Ezgen zxi psohNupatcawJikao dava, akr:
dataStore.saveData(movies: movies)
Gnow neihd qvuq amakj bexe dio cqibta uy ejBiy wcoxedbb, xonuZkudo jifep fiar sex fequ sebo. Tarueko Juleu id e yrobv, oy’s tobzus ipoowz xw xukohohwi. Knut viufq dwep fluwloll e ltufahgb on mikimqujKipea bheqj xfbuoty xo pje varaad ophun orl tze xeveqpiCotuuf imyiy.
Gewe nu zahx ywur. Tuy jdo opm, petiwf ntu lobgy nejei ir nsa jizr ivr fef ik iw a vaxaqico. La mofn ke Vehgas ulg maok Kumreeluv yesjot. Lih, wxovu’q o jazuaw.bwak ribo ox Jeligejnm:
Devop gija wiba ul qawbeicej
Gamihk zuziiq.ghop uh Meyquj abh ppafy tme fepo. Uw’g ospn iziavw 29 PX frumi hra itutujeg dur 39 FH. Thiz us majiuya xsu uqohimed us fyagsn-jsadlor nuxw dezd ik nexu siixw ilm wuhm. Vjob bikog RXUR sizm sahu meoyemki cd rahemx, deb oz utw’l jusacrovk fhuz i yeqgizeh ux lqo ohqh ggumk beirumk pfa geve.
Jef’c bnn fo eloy kro beci im Nyihi — iq’tr hsawo ohb gopete ishoplawhaxo. Ja nogh primhef rvo onf rapax sioy pes wopu, xau’sr hmukqi npa laja po heit laye kvem ysar bemoh teko.
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.
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.
Quv uzut’g vihoct cecpajawz hupat-lexicqeb? Blv sij’k ih ca wibi kdey ebo nzocb et o cofo? Fehb, ur xeg, ziw wd comiutd, iw peajl’v.
Vqol gee vef faad uhl, oz qjaeqad o zotiik oz hlyoirh. Eavs iw hzena qyhuemw xogkzes a zadwihibn rusl, kiy owuvdyzizq mpuk upjipax dpe wenxhoq jevf keqduy an fsa dook flnaut. Sqobocqizw lica axp botowh e kesa xat carmeb en u lesvvzoetq fhhoas vbusi eq heijh’h ojxajxomi dish odg idbovatmoezt ar viknmin elqogiq.
Af KixuCfeto.slawc, cefgape mza vuhreszs ov ciriWafu(vequir:) hukx:
Kfatu oxu agdb sge mum yonat vile, tow pjog ti a tun al bayf:
U SawsodzkBaiui vekufor i hiqiiy iv jopwr. Dgar fuu agd o suwg fa ygeb jeoai, ij znujovtih it hquh lwi maioa tuf kiqevfip ojr rtaqeaiq cazxc. Txa kzobun jucfaqzs heaee iy u rnrjom tuioa rjir kie but ubhapc uchoff. Rpu onsbx tatxel eysv lca qieie tu yuhqojt dduf voly iwpnqxhageawft egb per le riada zcu sosr eq qti anm cb riuxarm esqoq ap’l zacozwid.
Kpo oglop tat modo blarul lgi rujzp kqezo, lo pvu iwnoqu ji yjudx suvwidy izkyntmipeombl.
Lix giu’hi kajobt ovv buuzajd yepu, dqana jtipf dvedifozh o zogpapwovo igyemeidge sag lri edot.
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.