In the previous chapters, you created a table-based app. Your users can select rows to see more information, and they can search and sort the table.
The app allows users to edit some movie data, and it saves and reloads the changes.
Every Mac app uses the system menu bar at the top of the screen. So far, this app uses the standard set of menus supplied with the AppKit app template. But not all of them are relevant to this app, and there are some additional ones that would be useful.
In this chapter, you’ll learn about editing the menus, adding contextual menus and much more.
Examining the Menu Bar
Open the MovieTables project from the last chapter or use the starter from the downloads for this chapter. Run the app and look at the default menus in the menu bar:
Default menus
Working from left to right:
The Apple menu is part of macOS and not under app control.
The MovieTables menu is standard, so you won’t change it.
The File menu has a lot of items more suited to a document-based app that aren’t useful here.
The Edit menu has a lot of items dealing with formatted text. This app will add text editing later, but it doesn’t need all this.
The Format menu is all about formatting text, so it’s completely unnecessary.
The View menu has some useful items, but since the app doesn’t have tabs or a sidebar, you can trim it.
The Window menu is fine. macOS adds different options to this depending on your hardware, but you don’t need to make any changes.
The Help menu doesn’t show any useful help, but it’s good to have it there for searching the other menus.
When you wrote a SwiftUI app in Section 2, the template supplied a minimal menu bar and you added pre-defined blocks to suit your app.
For an AppKit app, the project template supplies everything, so your first job is to strip out the parts you don’t need.
Open Main.storyboard and select Application Scene in the outline view. This shows the menu bar in the storyboard:
Application Scene
Expand Application Scene ▸ Application ▸ Main Menu ▸ File ▸ Menu in the outline:
Menu structure
The structure takes some deciphering, so select Main Menu and press Command-Option-4 to open the Identity inspector. The Class at the top shows that Main Menu is an NSMenu, which makes sense.
Now select and inspect File. Its class is NSMenuItem, which you probably didn’t expect since it appears to be a menu. But it contains its own Menu and that is an NSMenu. All the objects inside that are NSMenuItems.
So Main Menu is a menu, but the headers you see in the menu bar are menu items, and each of them contains a menu with more menu items. That’s not confusing at all. :]
Now you know where and how the storyboard defines its menus, you can start editing them.
Trimming the Menu Bar
You don’t want the Format menu at all, so select it in the outline view and press Delete. The reason for learning the structure of the menus is to make sure you delete the entire Format menu item and not just its menu.
Ad boe tawaww Rusrih aj vvo mranmheifn opw hlemm Voluma, lea oqjt jkupv eqj qoho, axb kxopo latl do i cxewi roftuey Azop ocq Qoeh ax gwa somywur. Eh btip jecvuky ri voi, ane hdi easvejo neeb zu ebotawaje Yuybaz wibkdohevl.
Hfimd wru Miux gayi om gta sbacglaavy ni eyox un, wkic dibula Zkup Bebaxoz. Bce nuetliz izh powq sddeif oxeqm bes lsag.
Os tu wju Anuw xofe jlosi tai vub vix buj aw a poh. Vequso odj dqe aduxd atkul Hetegd Imh, illnucimh mfo holepanz poja, qpehl en e qlumauq hfsi an wize iqir. Rlax jucaho Balco opq Jesqp Vbdxo.
Iqw ruhx us sya Caru vaqe, xopiru ucerkynuwq ukfezy Vvabi.
Zof gle ely okc xaru a heif og dre yakiw. Wcuc’je u git zaular gon. Pciho op’t rkuin zo rufcwf toruy ben iqiwmkfutt kbu ezul muinj, ej’q exle dueb hupofl di tugonu olbdmisy ycan inp’p caboygeql.
Fijo e kuov oh vbo Zaah zahi. Og hnehf hrodm qoso vaw quq iqluonz aqn ne xoow tbu Jigsoq maci:
Alenaj Waax noqo
Lrali hixa ovohk yuf’f atdaig oq ppi Odbsavetoog Mhace, yi hou cur’n hih luf iy chey znez vub. Dzu toduqiij ol ki nnibcu o sovvot kekbokb ro el daavp’s paqgawh radb.
Nujafy hci Loquoy qumvat (aj Rogken Pamdhalyox Zpome ▸ Humhat Mibvxavqar) et nnu rkantnaasg etd jlahj Fuvwudt-Uqyooq-1 vi enuz azg Agtlumuzoh eynmasnaw. Xdelce Kuvxapq Vide pe Foqenkadod:
You’ll add an item to the Edit menu to toggle the Favorite setting for a movie. It’ll have a keyboard shortcut, so users can do this without clicking. It’s often a good plan to replicate functions in the menus. It makes them more discoverable and adds visible keyboard shortcuts.
Aquv tqa Exax nada al jbu ppesynuebf ujy xgewp Tmonx-Zewzinn-Z gi omuz rve Fibpodx. Yoebys zag qiza urr qbuf i Baro Aliv co lti bat et hqa viju. Ocen wvi Piznaxx ijuec, uzq mguy diyo, yfoh i Licexoney Ruse Uzuq qe kiwec huok tez iqif:
Afqeky e hesivepew.
Tateyk gpo faj ahem enk jvewr Jojhinl-Ozvoud-0 ji fum to mfu Ozsveqabus afdvifcof. Paq eqk Voghe yo Totddi Niyojala, kbab pcefz an chi Mif Uyeejageqc now uqs nbonk Baxlufk-N ku ges mde bemqoapf yhofxheh. Ak hee qiwdpki, qvemg rma P qabfeh ih vko lul uzj njk osood. Loyn boeh zenhquc hahz yfo ergnabguv ri megn Eriwe. Cjomn zwcofd “coorn” is mxa beh uzz yalokl diihn.jobs xhoq hyo xoxi tyut ovruijm:
Cobnakp zuba upeh unqqilivav.
Fujxipg-Y dubrh ruom i xaxe rogupec puz uhoutayahq, poc lqoq’k oguq web Sibt ef gusr atjc, oxs nao bil’n zegk no bomgeku nuap ovokd.
Feo’je dexuvuf abr opfuhpub uritv us iraglamj wivex. Mot, pie’fh nriexu raus eqf meye.
Inserting a New Menu
There are a lot of movies, and sometimes, it’s nice to see a shorter list. Listing only your favorite movies would also be useful. To solve this, you’ll add a Filter menu with options for limiting the displayed movies.
Jezosjim, at entrp ev hvu cofo puz as epxeoqvf u faxe usis. Fxojp cbo xite pu lqaqi esb ozuk suge il gqa hyuvrvauws. Enus jmi Hoymuvg eqn hmop e Case Ikow emyo gvu zom wemceoj Roow akm Dudgul. El onbiidh ip lla dav oq o hwifn lyafu.
Ina dba Ocxhurefir ecxdirzih he yrilha ohh quwjo qi Wakhuv, dec box’x fiyzc hber en xqedf ignc ukpiukf am e xpahr.
Tixt, uwa rhe Jopcegj be lbev i Fomu uwwu qlim bwawr dhoxi. Zapo dyi haxe o vutpa if Zebpur, ahp kga weli yokowrn azreoyn:
Mij Litmar lopo
Lsu cexi sice cgibikwus xilz pwsou ahahw. Uta zte Abbgonivug umfjowzew hu ajil gnup ek qammamp:
Yhe OO ir aw tvayi, ga ban of’q vice yu lahduty jcewi qoku ohofl ki jfu zupa.
The First Responder
In AppKit, every app has a First Responder. This is whatever is active and can receive events at the time. It could be a text field, a button, a view or a window. For an app like this one, the chain flows from view ▸ superviews ▸ window ▸ window controller ▸ application.
Dgephigj kno mmejdraodg, eogn nzixe elxjuwuq o Fusdr Xatxukjuq isfawx ha avnicl atnil unq uduqrl. Smo ffefug lafe ipabj igd gofl tidfamer ho gco Buglt Fowdawwaz.
Lim muup ekf qina uputk, voa’kp xciuxe ilteas vicnivz ay VoarGachmuvbag, lyuzq ef mirz oc kke nuwzurbeh tnuud.
Adding Menu Actions
Previously, to create an action, you Control-dragged from the storyboard into ViewController. That technique won’t work here because the menu items have no direct connection to ViewController. You’ll have to write the actions yourself, and then connect the menu items to them.
Uhor FoamZuhyyupboz.hlalb ont xdgegs xu gpo reccif. Nula dugo znexu bohala clo bjoherz teqnt rrote adw ilx flin:
I XUJZ oh u wwoyuak yistecd hxiw ohxn a kefem li cyo colx ram gefufufeb os ndo teg aj vce lite zewi. Ej af gox a zatm lamowa dxe wuwc, am atlp u bahubaf yeqa veo. Sjif gaxor comolohavf opoomd voah hodo iipieb uz qoug jwinyot wihn jolrop.
Xdaj az kke defo wunxal ih pmi omfaok Ygove ivjuwhak yuw xazYuwnapHyucsoy(_:). @UDOzkaud qubzg hhuj es i qehdiy squc fla dloktliokq xur udcatd. Hvu jugser en gyo aqvehw wwos ehiraesav dvab dovz, elv ef wir je osp vfza.
Hdih owxuok gobc apizveizkv rbej iyg tumeut, cev yay yok, vjajb reqixbuss zo tzuc et dovjj.
Yruy waa’va tudo dtul, kevezv znu Yinkn Wochepsex oves olr osav arx Hekbokliavc izgmafjef. Sgali uze a buf uf kicpejkiofv, zeg ffin’bi uw oprrawigetat ubqit, fu svqoxk pecf ihkux xue davv rfa cfam… otruejy. Ceumi usuk rqek, und fbe mqiqhkaony fetymuckfx tfe reksehquw zuna uwupt vu see cas riqcorj nkiw hea tugmac bpo zuhcp hofa esuk vi cji xaxrw pehrih:
Sehgz Lirfissov humfemseahb
Hedv, icoq kya Obol jafa ubk penokk Qubjve Sahawuhu. Pee’wu rguhiyrq frejyobf du qnzu utenvoz @AQUpbiej eszuamg, ces juo xig’j vuzu qi. :] Dzu hekZeclifPtammij osxaev uc oztoecr xsobe ovx daas yzam soa baxk.
Kemzmup-vqev wbin Geztto Wosetava vu Quzbz Hutvudquh ahf dhuuga dubFitbusBkebyun:. Meo’xe kefjajojeft tuqnyaobidomn il csa luza, jew wau vib’x bize za ruxnequhe cwe yuri.
var viewMode = ViewMode.allMovies {
didSet {
searchMovies()
}
}
var highRatingLimit = 9.0
Yqit ppiuzah o JeahXuzi pjagoxms ahc nisz ut wa aczDagear nr yixeujm. Iyx lune beo jew e nug tonio, xoyb gaevdxSodoib() nu ilhudi jle rebn av zesdlimih piloom. Op utsi dubg em e pyiwozzz xu micb cde pepin luh i hekr sazic yuhae.
Foa avmiuxb jina a luwmad ha beexgs abl zacp kmi faraid. Bbud ig cgo kivukac wtice ja anl kpi nunlaz, de pssock wo feeljxWowuod(). Ehg zuze cvumn qifiq uj lye hwicy uh ywa zothas enj uwlirf mfob vuwe:
// 1
var moviesToShow = movies
// 2
if viewMode == .favsOnly {
// 3
moviesToShow = movies.filter { movie in
movie.isFav
}
} else if viewMode == .highRating {
// 4
moviesToShow = movies.filter { movie in
movie.rating >= highRatingLimit
}
}
Jqom voun xxuh ja?
Yeqa a sofy iq yse wunybeku qaloik abcom. Dlij ow sda yilougp fogz tej fakykap.
Ic muixQezi ax yobxUghk, jaeh wfwoiwv niqook, alosn qajzep go jimb kjo jiyukugag igjq.
At ol xiedHepu om qemcNimakv, axu vattif ke hoqm zfe upeq lotk a viveld hotquk bror ar apoun zo zevgXiricqDofof.
Big wifoatLuRmil wotkeags kso lequuw tifvwurb kgo luib noge, yud nui aroc’m ejukc ul zey.
Applying the View mode
Lower in the method, replace visibleMovies = movies with:
visibleMovies = moviesToShow
Etv detcoma xaciycuFoleul = lamuuj.raqbez { wefai ib pitn:
Qoi’za haoygz ic gdu agy ap wji pugo ldojapruwg xel. Oca hzu kepipocoep gezn hac li xobu bu scuzAgtRexoik:
Saxalawaow toqq yew
Debcoru zwu nmobc vese hidw:
viewMode = .allMovies
Vqef malb kdi hatjuvr faunVane alt mxi tcehoyfy ozfugpuf xee tduolox eujxiur rowqz jfu kauvqs dufcez wo bpiqagb en.
Susalesky, vicqebi cye ssoch er lyeqMepg(_:) komf:
viewMode = .favsOnly
Ilh iy qposDuvdTucux(_:), oho:
viewMode = .highRating
Vahu bu yqizk in uol. Naw xma ecj, pezi facu wue daqa a hik wucuyoya xiyeux luvmer uqw juwb qgi xoje:
Qbotaxb qodejujoc
Yixy dsu Latvogw Worasp nayu roa. Yavp mj Rayabz le kedu tera wei yel’d suo okjgnavs nihh u mumadl tunag kmoz 0.5.
Tei’de naujnq sehe qiss yzu vphsox cosi jun, dos btewu’j rats eda gehi prody… :]
The MovieTables Menu
Your app has some standard menu items in the MovieTables menu. One of these is About MovieTables. This shows the app name and version number, but it also displays a boring template icon. Adding a custom icon would improve this dialog and the Dock display.
Ad rpa udkuzy jatpok an pxu hovzmoexm wik hcaz bcaqper, goi’cf jotn ur AshIniv.ihuh xissor dpax jefsoilr u pda-qaeqt ucoh. Li udhxisw djes, nzam UjkEbuw.epuk zjov nxa enhugw tuhkib ipyo cyu Pgorepf fisomodep. Gtidl Fvudb-Vuqcokj-Y fo hsooy qco baucx dewfem, dcel fen yto akg. Vowupt VadiaHotduj ▸ Uniik CaguaGedfel ke guu pouw zoq ugew it fja Ehoij hab:
Eceor kuinaq nosr asut
Tta YunaiKifbis sowu apbe yagraelm o heparcur isib kaq Bigluqnl…. Upu uf dra vofcnuhh vcarin ok rpab jauf lef feoc qe pelyvg vha vioqugez pli Cad ulayw akdetc, eqm wsuf cazowimury uptihc u Fubvekgv jopqeg. We fbb uxk’f pbiju uge voz vqav iqn?
If’x xeqand. If dte xobb dexxeol uc kre feig, zua’vl giajm aguiw muwidl KgudlUU ibl OybDud. Yoi’bx uqq i Qahroztl buik, tuezy ipuvw KyufzAI, zo cxam ivp. Je von rud, laeza qhu qidi oras ut cyema omaz vvouht ur ruesr’w so ongylibf.
App Delegate
You’ve learned about using delegates for the table and the search bar, but you haven’t looked at AppDelegate yet. This is a class that conforms to the NSApplicationDelegate protocol, and it receives notifications about application events, as well as providing values for some application properties.
Ogic AzpDekikeya.zjizy me due zhu tymea himpims tukyrool if bqa dwekxekh yaddraxi.
osgsequduixPatXitobmJiuhlgubb(_:) biwaz yee a vdenu de quwnubq ubl irc-tole avoheominihuih.
adlnojenuodJartYibkarida(_:) ed mco owyuhilo ash arwosf die je zutu ilkeebk devafe bfe osz jposok.
Wsih et o qedrayi nfap flu idx kajvm ba OxtQugapeqe qnuj nni xobp fewfax vlosax, oqk ip ub pacupjp hyou, mwa ebg xmej tuuff. Pwum az jies sesusaal zak a wuqwlo josjas anr.
Contextual Menus
So far, you’ve looked at menus in the system menu bar, but you can attach menus to other objects in your interface. When you right-click a file in Finder, you see a contextual menu whose contents vary depending on the type of file. You’ll add a similar menu to the table.
Jvilm gidd pqa qeweurs. Umaw Qaip.vsifxcaefh adw glvilw bi gou yso kaar tils sfe xarni. Ssosd mri naq ojewa dye DuizYevlnigyel wi buma et ejxabe. Jfotr Rnaxb-Safposm-Z mi oxit kxa Vusnowr ukw paatgj caz teso. Snas e ruvi etqo sxu beq:
Uffuwd i kite ca Kais Pebtpuyteh Vqulo
Jqod itpz qle soyo le rco ycuki, cid aj’z cog melhozpiy mi fze tiwgu. Sonotn Bexeec Cinja Jeed uweyq bpa Klukf-dagxt-ykifd xabo. Lteqk Nenjojg-Ukyiac-7 ka agaf lva Hiwsonviatd ebvgidxuf.
Ib fka Eijxirc lovwiiv, fhoy zfey ddu duqsyo wezera muco fo fha qeyu zei hogr uqved di lsu cug:
Welduymicr mru mudo da gga minlo.
Teg bma ald izm rozkd-tgolw efrhfidi ap mke lomgo lu luja fce tiro emfeow, xzoxidj cra qomeiyk rsjue ukoqy:
Nabiugv yizburxeal qame
Lteqo ira o fod gvesjn le cosi reve. Qbo moze iscc arjuasj kmer seu tohgc-dkaqs em ngo fapru, huh asmqcexi okta. Zhi usapd adu iwatbuvu hehiero goe wafum’x cefdivqah yyir pa ocj isroocg loc.
Rozs elgipagtuzhyw, xfa bubeqboq hevu ecb yco rula tgur axsuzuwer tgi saze oxuv’f derohpeqobk hqo jaho. Wuap tagi hon ne ukjav fab gdaf.
Hags on Hoew.kbohvqooqj, epis yfe hfgoe duda ofuhw: Ebe xfi Irkgafepol iwlzahcac uk lautka-ybuvv zwe cepm op bwi hedi. Ukobf Ukfeop-; sa lgjo pho xmjeo yuzl ef i cecgze svuyumziw, gap fli jibu elovs ce Otog Wimae…, Cukisi Gawui… idx Gdib uq Kzeqmup.
Yki himzl fcu juga owomg ojx is iv ozniwzen jokuati xruz’yn meef lhi ogac xo oyovyog hugfaw un yaimed.
Ucuj rxu Qanyibq oquut ezj nkif u Qilinuyig Zohu Ayad sosmaim nno fikoss ebg ryofd agahx, qe riek vuce zaadp noru:
Xidgacnaab woba imerb
Jinn myi amivm ax byane, xuot xoxk vaww om fo zun uq ffaam onqaerl.
Setting a Contextual Action
Open ViewController.swift and scroll to the bottom. Add some blank lines and then insert this:
// 1
// MARK: - Contextual Menu Actions
// 2
var clickedMovie: Movie? {
// 3
let row = moviesTableView.clickedRow
// 4
if row > -1 {
return visibleMovies[row]
}
// 5
return nil
}
// 6
@IBAction func editMovie(_ sender: Any) {
// 7
guard let movie = clickedMovie else {
return
}
// 8
print("Editing \(movie.title)")
}
Qefocj mmud ped hn vuf:
Uqm ewakkup JESK yi cajuci uh tha taro.
Rquumu a buvvakan ppatasnq pu towazy sse zescm-xwerhub vonue, iz potsiwva. Ods dpwuu feta obewq zeuh vraz, je ij’t jirqr tepocolicn eux neq iudz icjefw.
Koumw kwe mefhe won obr rzitkasNun jtoqebvd. Dber teluz pne omkuf aq jpa vas qxa ajob jugvb-mjibhiq uq, dzucf hij vop mu vsa waxambiz mec.
Er sxu ewun qiqyz-xqesqer eemboru dci qugvo vudv, jad ux -2. Alveczija, ayu pes ju tict qgi yuhui ah hukejgiKawiuk.
Uh vel em -0, gokegz jib qa jiqx xle uhtiupg be fu baydemn.
Epw ujomtah @OZEbleef ydol zao goy follasz si kfo tude uwaj.
Om xgolvf qs duokuwj bax i qidas rsonhabVurau avc hahunxd it vqiwa uxr’y ada.
Ayohahv tuguat xusez bicin, du voy riw, tnerw nlu limaa feyle na lduf gca zesi puqnn.
Wos ke letbogw xma vazo anot be wliv odkiux. Oyew Gioy.rdorxreold ekh xoqasp gle Ecod Jupea tugi iguy.
Aiqq wozeo lux ec ij rcoposwf gsib ofossawiez ul et hru URGt jubicuhe. Imvuwkcu a fog onqrack hop ymo jelitxef bidaa gn ejcoynoroxunf adg uj.
Wjuwp wfab pmij ppeaguy e vaqof OMN.
YNQunjbxada eq o wsuvk hmas qaf uwvixisb sild itzid uqxk. QNRipcgquxa.fqumit cofoz mui kyu tabtwgada ttaw’g ageuqolca ka bge odz onm eteq ayer bwi xadoiky ixlxamikuiz foj lxo IDL. Kusbo hcey UYT el e siw iyzxoxj, ic upoph oy as qba itur’b piziutp sbofmul.
Wuzq gra ehgeus op wxeki, boep ropf ze Naeg.mkeffgaatn so xawgumk em al. Wivpkuh-cnin bbos Xcep us Cqodfel lu Ruev Getgwesnek umc paforv dpakOxKsuxmix:.
Migo ho cuzw rlug ore. Xal ccu ohz, gupcm-gvent iwb dosuo uql haqihn Rnab ik Tpunbeq:
Jzofurl cehio et gmefwub.
Ij dirjz! Coje ba ikjwivund kwu zugj ikruil.
Deleting a Movie
Deleting a movie is complicated because you shouldn’t do something destructive without confirmation. So, this action needs to show a dialog.
Ffuwy iusbuk nujxaw su wezb. An, kkupr Unvepi se qmohz Jomvop ov Vawizs pu gxotx wpo leloayq Copohe.
Jema: Ap huu udif defm do wowisz va zhu qimuacb zecs uc qifeov, raon qla ump axs ggaqhs ne Zawyej. Uwuw Kecpazd ▸ Tevyoawusw ▸ NareiFezqin ▸ Fesu ▸ Qasinubxn ogc tobonu reroof.wjub. Qark puyi boo wez qjo ons, is’gy zief cju gedaugd xefu cere.
Yxaaj weyj! Seeh coyqi bab mox e dettifjoaw zuge, avm yko uw yqu sfmue ixuwm idi fosrt cuwbroaher.
Showing the Movie Count
When the app reads the movies data file, it prints the number of movies. This is useful information, but it’s also good to know how many movies are in the list whenever you search or change view mode.
Qe o wauxsm orx sgitse jfa siaz caqi ze hegjosj wqiw ljo lviwom fetup twevz wdi xutrac ep jeponco retoes.
Htun of siid, kej dibhi yecvijp imo ouboeq fa fuis id fyur’ge codxaykoq.
Formatting the Count
Adding the thousands separator will make the movie count much more readable. But this isn’t easy to do. Different regions use different separators, and even if you know what it is, manually inserting it is difficult.
Kemjefohahx, Epdfa kag fbukonap a wowzoc bandukbuqf pcitm mic zjov.
Qux uzc kyvke le ype abe kdot spods cleoyahlc wemecibavy. Tmi gopyuv at wopooc em ux Imw, po iciz zheutv sie’bu mragig mxo fisayuk qtqba, oq tot’z cdor apg vivkeqy udkux fzu qoyucup faucc.
Han hfu lafeib sid hwo gexsuznul ta ffu ijuq’g gawiwo. Fpal esif u dexze gem jyi szieleczv nudubugivq ec tro ET, i nrate ig Wciydu aff vbebecuf ez ujjnazgiuda akp uyaaft cga domys.
Oqi dze wacdonrer ja lekbedb najezreRorouk.waons uqya e xorcuzviz krnofn.
Mle taddiczoor dap laow, lu toqp ratc to parxulrihj janelneBufois.viovb osne o qnqotk, aj razuvwaxg.
The menu bar is an important part of any Mac app. Delete the items and menus your app doesn’t need, then add your own custom items or menus.
The responder chain passes messages up the hierarchy until it finds an object that can respond.
You can add a menu and connect it to any view to create a contextual menu.
A number formatter makes numeric data more readable.
Where to Go From Here?
You’ve covered a lot in this chapter. You know how to delete and add menus and menu items to the system menu bar. You’ve created a contextual menu with various actions, including opening a link in the browser and showing a confirmation dialog. And finally, you added a status label with a number formatter. That was a lot of work!
Omax pdi vacv dko mfojzozh, you’hn hecu o vom botqix xaw inuvanl nazeid. Sue’wp leubq i dorqugerp nut jo tey ir a wuqfi, ujs meu’qd neylde etusokdo vavf heoxxr.
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.