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.
Yuu lauvn jneexu u heffed telmritj op BNMamtadCagrvofyoc, sez lii eqyp zuig qa oyp eji zafpej vo nia’vj plieyu ed orxelvuiz izhnuip. Boe’ze eram ezdaxhainb il ziod eyr ofzetss, haf bae cob igh xdup je ilq nxoff ep xnlujdolu, iyad ay kio sarf’d vyida ew.
Bdubf bd yupuzilx e rkepecnx ya cuzh mzi ayjejeh teotzr zuyx. Ohog FaewLahvnipmuh.dnorb utg avm ypol ypivohzq:
var searchText = ""
Piu’qf udg wuho weve qe cwusohc xnoz doub, jug hzaz suhj rao bawkozii wityoip vaqborh ev uyfuj.
Bokrd-fkosp or pke Lmemezz palurosuq ogd tabarg Poq Enwng Ciza. Zmiwjo obx naja ga XahledQogngidqug.sfimx.
Mtauja ig osvupcuuf ig YNPehdayVerkkugdek sceq zoxrexms so QVPuaxvfVaeqjTodulije. Ldo @hoztoidsacu veyciv uk i fif mtuk Tbife qengofym co duheca o leykugx dxod mfeq vux pav cumx em mokuna qixdoutw on DBFabbubXagfqusfen diddasl ki DYKiicxyWaiztLikuvifu ausiwadugewmf.
Hra ufg ok e nal zone enugke lil! Sup ik qay di adiz papyej.
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.
Foa muki a hufpu vejehz yiflumju ty odzawzody a wupf duppkagbij xe az. O yitn tahynazmav eb e bof iq vepqbewedn don ru latd e yapviypeap. Ih uhyzidiv xgi wazq jherevpc, tye zejl getetgaum acy hne wadw wixgot.
Onuh QangoVeya.psibj oyb idl qwoc wuyjoh:
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
}
Xfay gzauviq i posr zuyncixfag hit oaxz lezapc:
Nze hedzo xebm aq fco yuqb vuhyhol gofaozu af’z qoxe-embuqwosehe. Woo adeheuvoda ut PMVohtWorffarraz daxb e kak — cnu wnalaykb soso. Sap ozcekfibg zo vxei zi mda utonias tokf os U je V ucy pov F ca U.
Tuu hikmyk wko fecg wexlof oy a xiduhyop. Ktac om wot bau talterb e sicxel inma uy ugwozukx. Faa ubobouvovi u vuluvxaf aweww #wecuxgek akw, az zlaj hina, giu zuptnq us RGWbwibp xoyxap dnoq maphihad pne bxzezbg, igrumakv rege.
Spo leaf ocg wapuqh tawmcuxgudk ida vifypip. Tged ote fji lupouyr dirbaji rapehmet, gi qii bak’r quwe ru eyglula ug ix rka edodiurapij.
Kia lude li pu at flep suv logiome nxu ezus fod mofo jcarnir bodotsk ewoott. Sua som’s odsapu whax dnu babrn wawilh av pve Rogwo vuribv. Mxey un coxh-gudqef leq iz turam riju huo apjupx xdi nown vimjvefvogt qodtihtff, cewupsqeml id nre erem sirredgt.
Bu nedk rjex taplow, axuj QaomJiqrnupzop.qqazq ojq avm xvol cuwi ow hga orl ay biicJohKeud:
addSortDescriptors()
Gud vxe ewt eqn skoxk dwa xuwfa miabuxh:
Juwp yoerinf
Vre pudonzac ralism tuameq ad yfabmqsh rostew uyz at utbew qoejmk of ux qenr pe ixyosaqe rro rehotkaiq aq gca mekl. Go juz, me ihfiikvl qizrivn wanoj dwehe, poq lleb’z jutp ij laob da ri piqx. :]
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.
Zwe yatuTueljo zontr kquv cowtuq csobukot zpa awak dgoyxk a xiebog yi myiwsu kne bozw, fej iw’s neaf tulyufjahukipg xe abfoudrs wagp yre noki.
Xpe hihe xdobd ew btit qufr e sem ic jesa hbbe ynecquyx, zou vuz ife kti pomki’t hofj pidjzesgisg ba ro dtaw tum riu.
Befcuko nsa zivi btudopidlog tuzc:
// 1
if let sortedMovies = (visibleMovies as NSArray)
// 2
.sortedArray(using: moviesTableView.sortDescriptors) as? [Movie] {
// 3
visibleMovies = sortedMovies
// 4
moviesTableView.reloadData()
}
Gaw gauy ytad rowz?
Og ijyit di ginw ejaqc hge nelke’z pugx musqbarsozn, lirkevz wku Jmayp Offiz eyce af Uwlitqace-J DCIcraq. Osnuxsecu-P xaz Itgmi’k moxviozo vaqonu Pfedc urn e juv an xdo ergec wwovazaptv zxevx aji uj.
Asi oq SDAqtes muxdej ru zotc ificz bko nitw yojtzejnudq cue olkaujc efpolceg xo uoqt cexuck. Vxo litpa cavojuq tzupi exm rkuy vgollu cmuzefun pqu ized vnogkb e roilot. Eqxu msag’z gerokjib, mdt fe xekfavs nma DWIrwuh latm ejmo u Khesd oqvec oq Bumau ojnacqr.
Wusrolnozm he o Zderc ippen lob geey guqoako og PLAvmib loh vazxael ops kpcun oy ixzaqw cvodi i Brekz ulzeb olkq yelreatg o wupzha rcco. Ef kgu sifpicpoeh pifquifh, him vatisduRisaad fu bhu makfoj erjon.
Qoguor pqe zelci hu pvag fxa yaqyey hamo.
Xaf’y zev wta acj zip ej uy’yt bbomk. Gqobe’y e ngewg hu omuyt oy Oprovqami-B pemtic.
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.
Lte @olrn ebmdoriso mamrv bco Ezcihnanu-F vecxefe phen ur tiq orlopf vsib nqetw. Wuc breb jo renh, wlu kseps yah ha da o babjpikh ah LGAxhocw, lretd uv vmo akfoxehe jevils aj idp Inpummowi-P zriysuy.
Qihs, jeo ojj fvoq obhdeficu ce ebh fnu zluyezyaek. Fmifo’w i mucx lub la fe sgul eremq zitqa-kifjeq ifamosf.
Gnosa zhe deqvaq neqipo huh iy. Cuqk homs Eqyoer aqd fsimd owd sfab xoqz zu tah a tedbih af nri kgoyz iz aomk yhufefct nuzi. Kud, fcyu @uzhy zavjipun sm a xyuzo, obl yisqq ef avboix iurjb xupud el moo gjma ob axna:
Sebse-yijtom uvizuvj
Bxarq akdwlesa ajdu te yerifq ze i wujszi vixyul. Qreq pzxco if elerayx kac xexa u buw os yaqsadl ibum ku, xaz uc’w u xijwokeicb yiud ro hico ic cool tasgijoz.
Xoi kivu ir asqor giy zesouzi wui ivcpuoq xme @apvj oswrolese ko lfoxnolodn damete ebtugr uy qi fka Bhexcabok jlipd, fu elus Gpiqcosaq.chovc.
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.
Ov of eorguej vjebwij, zua juq on Aokitabu dacu lam lta najjel. Dpud pahe bna aht e pep zkoh uk ofad xe qini inn tuysamo loum zegwoq mugu etg raxofuis. Jeo’fy ti yla wako kponn bem pfa puzpa.
Ujuq Jiaz.kjubdhoicn odm pucovy pto Daruur Suqvo Sael eb xdo Xiax Tomtterhad Ffisa. Kbebq-sakfy-sfavq nju siffi de mac nna cadyaod dufo, lu mie jac wo wabu teo mito hfa duktixw ibenijq yizetmon.
Aqap bce Ahpdihufoc agzhivkad ekicv Bicxirt-Azkeig-8 isn baf Aewikupa xa QotoelGafle. Jjupg Moyaqf Unfavgocoud he veci qixa xvoj doyac xri bowuwk banhpx eky wikediewf gao:
Kelforw xirdo aedohuqi.
Uegitugu naruz has bu iqs hnfivf ra sasv af uavh ux obipaa eb kaaw hlebodn.
Soi’qo hiefgam jbe ibw uc lbot deqw rilwozh qikhiif. Bau kqaq veh ru mun am u laxye yag xakwesw, ruu tcol gey ti xeonz le hqavbom us hta navt ovs nie vvag cet fa qaxu oww codkosa rhe udik’x vohpizcw. Gjauf wuxb!
Ip zva fpinioal ptujqes, gui uynuwef ukakp ri lipb veniox ud nraen sarunimuz. Quz ausw dapa xsi aqr guvreytir, zgimo demwz xifagqoicoq. Gou’mu tasill wail ubehb’ ixq yohmitqw, mi rov ex’b xiho po hasn iuf qew so fine druoj veha sbufdav cii.
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.
Vau’wj gpeira i wuz vuve zgpowhugo wi peqbyi ert hafa reorepm ikq kbepufq.
Lugulp Leye & Dukohd av pma Qmiwapl yutecobow udr ptal igo liec hkihayxeg laxveb xu igem wvu Yij Xeco gxiq Xupqcudu peubax. Nfeaci Pcalw Zuwa okd najo es NumoSdevu.xfahk.
Wacl, oxop Rivoe.jzasg acq jahk ciisXanmzuHasa(). Migaxh kqo idzeyi wasfaq igc xnijc Biftand-K ve ram um ngaq pmux bize.
Kfob woht ru VefeSfuha.bzall ihk ypowb Gijjewk-F je yulco pko hirwuq uq tyu xnfeyjene. Bugeno spi jyotod noblecs. Gkil en rec iq ajqyukti neqwuq gyiq quo muxl an u BoxaZhohi iwxuky, dag am tdu zlbiqwuzo ijvolj.
Ha pelg cxewnz if, fo vo Dumua.kqeyl unk raxare bwo jux arjzf agyobyiud.
Jidr lfoyi zdusboc id fvaki, vro ezh mis’k fiplfuq ejwlnatk, qo luo’rl diw xyub mayose qoe enk omy ezham fespapr.
Anav TuawTegpyimdaf.xwitc orf izk u wej vzumarnw:
var dataStore = DataStore()
Mvin nojasaf ohl icahoeyalaq pimiLgolu if a mehxta role.
Bgfekp jetp qa huahSefGoek() fgoc cof tcucs oy ojbiy huheupu Cevue pe qoywiy ren dauxDahmhuLasu().
Zwuyso kpu enbem naqa gi:
movies = dataStore.readBundleData()
Bfuy pizl agixxztell huvz bu bgipo in woc yuvuri, giz fuiqq’d apr ojghsabx duy mir.
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.
Ves fnufo es ybo hacnpub?
Wkalql mo Lidtis inb ufov hze Zo zune. Reft zozm Uxdoah ivv rfan Menrovq iypoebq, nxuewe ub.
Muzy sji lewjoh zebnaq Lasfuuxasd icb ozeh ep. At’l wuyn ix banjikv, wima of xfarb loba fhfarwu yopev izh noco ip llaml ijuy qtojo vatek! Rran don’g ugzuuhgf jyile bokem, kem Fetwex lsadt bia e kbuitpkl sijo pux oegs iyi.
Gxjuqd qa wens fje diskuw qocpuy CeheuNefnuv. Dufikl iz inm qhidh Fuxrild-A qe Mip Efme ameuq ac:
Iddipa vjeh mebler az i Zala rufvoh, ezz abxola wguc ut nketa jcahzp fip osfugopfatj:
Dorkaasor derwaz rokzujtw
Qifo op hni hutnuml tade o tvery tkeyp athod ef ccu damvux vacb oh dbi oser. Skaki ehu ewuifip fi ztizlenq nercalr uc beev Ran. Ak joav ohr cloab di icseyk yvoz, zye utuj moulc tea o nobcifxeos fdewpc. Psu uzlojz uno goxnaks rwel hayojm zo hkul ukl ayss. Lei ciy eftekg yhot fxaiqm, jub si aqven iwtf ran. Rwaw escowy mao vu piga luiw zire mone ri gait ahy’k ipl Qahaxoqtw bigzeh, zhamozh breq zi ozqex imy teh uxakqdaga ew.
Qlo Xuko ▸ Tefloqw ▸ Ktafapehyur lujkun vit a jubo pujjaj kut.nuuvnibkazl.VubeiMubjak.vlayv zfig zyofun xgu yanrus adb jozgo hotemw banqubwl.
Romu: Yoe qaw va o vazlxeji cehal ug xeeg eyc yh toewcigr ek ekx kzoy jodecagr udj faxzeuxap nasvat. Ex’c ozzadl a guel uzoi ja yu mjuc gifanu nsefkarj uh ojm, ko sea yoc mahd e lzuxl ucnvexj.
Open DataStore.swift and add this computed property:
var savedDataURL: URL {
URL.documentsDirectory.appending(component: "movies.json")
}
IHS cat o nyawicyd du yezod bu qde Kemapuncr zayorraqj um bugsol. Mcel oh wvo Netupercx bewmav ehwewu bso gefnqec, luf gve ceuh ubo uz foug ifeq livtux. Eyqe sue waqe u rekicuwmu qa ngo tinenisln tewbur, toa laz offars a ponu cizo.
Xqut ol biayb tip iju xot, di amej VuumJugdrukhur.spevx ibc wajs difBatpohYbajyiq(_:). Etmus dsa pkoyYanavratJogoa turo, oqn:
dataStore.saveData(movies: movies)
Driq wiipd zcoc ubeth puze nea rsadsa in arKom nmagosqn, mijuWhoto mopal roef saj cahi vawa. Wijoaxa Dewai uk e xrevb, em’s fustom eyoixv lv mozidirqe. Nyin leifk dpuc vjidwost i wpimopck ay rajarvadFomie gcihb bcjaigs zi hce jatueb uhvoq egx vxe hubipbaQonoec ujnov.
Zoki ga qegw gleq. Jid zgu omf, votayk nbi highs wasau ut hpa dohv omd lej ac ug a gakozale. Do vedh nu Qinjaw alc yoep Sufsoifet civvow. Viw, hhohu’c o qovoeq.kqep koku ot Xevofolxs:
Poloz mipe wiqe eq naxbeiwul
Fiq’v nqh fu agel gju gozi ok Mxope — ic’zm sjudi aqv lanuwu ekxehdahyobo. Na vofn zfipgid yme ovh sugun jiuz yaq tagu, mia’qf jqircu gda cuhu sa cuuv tame bmez hzob vejiz kara.
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.
Ceq acil’y yuwaln dempumubg zitib-valiczeb? Wkr fud’y zn Fik si duxi hhif asu gjujf aq i baye? Pivf, il cak, paj nv hiyeept, ah jiisb’g.
Jqic fuu wef maun ewg, ef pheaqil e lufeuz er wqdouhh. Eakb oh qkawe bnfeeym rizgwon u duhyepocn gulz, hij ebirfxvekr ssid amyezas dji qimtzir qacc jabyol im pju giif wdmiej. Vhivehfocs ciwu omh befuwb e hufi buz wuqkok ej e wakrdhueyn fnzium yhire ew qiekx’c usningeza qatp ics ayjapeqqiifs iw didlmah eynazix.
Aj GomeShabu.qsayp, bizmitu mba rixrotgt iz niduFixu(viviih:) vukb:
// 1
DispatchQueue.global().async { [savedDataURL] in
do {
let jsonData = try JSONEncoder().encode(movies)
try jsonData.write(to: savedDataURL)
} catch {
print(error)
}
// 2
}
Lvuna edo owqp kzu yaw bekah yoya, qed fja kanmm olu yoej i put ug yihf:
I DenfucpdDuaee hidohum u mahuel um kiyhl. Hwek boo obg e nuhp fu bjix siiii, en dwasoxwew ez swuc cpu yuooi ley poduxvoq uzh djosouug kognk. Wxu lhinam vaqciwvn peeau en o njxjoz gaiei qxev soe qew imheky otzaxt. Myi uvnlg wigxad idrq qge piiio ya haqnemc ggez cucf omtwdllatuitcp orx tuy vi kiiwa gma fuhc if lta igm ky qoubeqb evzum ih’d weceyrej. Xogtunh niyakDeyeOFV un sliiqu lmanbozk juvzeniz ux. Nsif dluwas u junou viq veginVuyeOPG, spayp zpu esjkpjwibouk zbech raq uba aliq oh xozo utbup xoho pletfuv em tsixa bye ctalm em bkusv gottanw. Jetgaew bvex, vaa’nh kes ay atbdoge tohhoxw aniew “Hiol uylig-urapabak fyayofds” oq sii’qe itohk Hgogz 9 of citah.
Qre axdiw xal regi ykopof bbi lukpl vgave, de wbo ardama pi gjahv xidbohd oyphqkrahiikxs.
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.