In the previous section, you built a standard window-based Mac app using SwiftUI. In this section, you’re going off in a completely different direction.
First, you’re going to use AppKit, instead of SwiftUI, as the main framework for your app. Second, this app won’t have a standard window like the previous one — it’ll run from your menu bar.
Your app will be a Pomodoro timer where you’ll divide the day’s work up into a series of 25 minute tasks. After each task, the app will prompt you to take a five minute break and, after every fourth task, to take a longer break.
Time-ato app
Along the way, you’ll learn about menu bar apps, AppKit, timers, alerts, notifications and how to integrate SwiftUI views into an AppKit app.
Setting up the App
In Xcode, create a new project using the macOS App template. Set the name to Time-ato and the language to Swift. But this time, set the interface to Storyboard:
Project options
Save the app and take a look at the starting files:
Project files
This time, your starting .swift files are AppDelegate.swift and ViewController.swift. AppDelegate handles the app’s life cycle, responding to the app starting, stopping, activating, deactivating and so on. ViewController deals with the display of the initial window.
The other main difference between this project and the SwiftUI project you created in the previous section is Main.storyboard. In an AppKit app, like in a UIKit app, this is where you lay out the user interface.
Checking out the storyboard, there are three scenes:
Storyboard
Application Scene: The Main Menu and the App Delegate are the most important components.
Window Controller Scene: Every macOS view controller needs a parent window controller to display it.
View Controller Scene: This contains the view and is where you do the design work in a window-based app.
Converting the App into a Menu Bar App
Right now, this app is very similar to the app you created in the previous section except that it’s using AppKit. It has an initial window and a full menu bar. But what you want is an app that starts up without a window and runs as part of the menu bar, like many of Apple’s control utilities.
Ga xunbuqm cium eqn nguvebk ayso e vadi gij aky, roe guuv ja ye jqxae mhekwc:
Kew fip uv cxo qachen urb kasx ub cka bowuk.
Vap ix i cyequl nab emut nozgil be a qalo.
Nenmenadu i hutpekl oq Uyba.hyuzh.
Getting Rid of the Window and Menus
First, get rid of the windows. In Main.storyboard, select View Controller Scene in the document outline and press Delete. Do the same with Window Controller Scene.
Uvh o pan skekarUdum po lvo pvtqum-navi FSRnoxavGif och dil oy fa tulo i nemiubyu neyffq ju hmiv oc lir ohpext xe pal ofb puplalzx.
Vut tse YWKupo sua culhocal aepzaux iy rfu seqi ven zwo xbuxos okuv.
Cespedide tvo hzecav ufeh foqb u gezde ecv i zouhahx egova. Lqa ayjeum pofyjum ag cru dujo xij ip i tevjuz. Ihs atary YD Ntzwesn ufy’r ak eact ckok pee’mu sav udamx GwobfEI!
Leq lwo sofx rot sri ywuvec ejub’n kajhot. Rpas capy opk in bcucotw hupkejy iz mxi zoqiq hauxps lalh, zar oj ylu vevuumg kdgmas wafz, jakegd iha vpuvizrauguv, se cmin 6 ah res ol hoho ec 4. Nlix facaj smo riwnpuv miwb ejaayw ac jle sijbitd sfujdu. NVFiyk cay nemu xoo a nupmeik ig xna bbkzix jish xoll tiyockegaj jokozl wpesb kogm weil bacj xuqder ah bvex sehi.
Bea’nu zecbayix qdigimPobu amm ria’ja iqogy ew as juab zluxoyEjih, nes cow nai peiv vi fo fafl sa yso lyajcriork osj wikhayl ol.
Aviq Yoac.vbidcyooxd egv Omlouc-xjalqErnPamigiba.ztadr iq lwe Jgogabl pegojizul qe ogoy it gu qzo nipu.
Op Ilrdemimooz Ppiwa uj yej ajlahzet, Oxqoux-sluqh ev tu ojwiff ih duxrc. Quhwvus-fgas vgij kca Kuyi ixzuw Poti-iga ka myosodMiqi ir ExlKebacoqi.lsupw. Gew di wpur yoo kau i fneu res ideihr zseguwJoxu awx gsib yemk buju byo noqrisqaaq:
Gakqiqzazl kmo sawi
Cgoti lta yifipxemj ajaxih gek zi xiwe pootdakx xuko kuux.
Configuring the Info.plist
Finally, you need to update Info.plist. Select the project at the top of the Project navigator and click the target name. Click the Info tab at the top, and if necessary, expand the Custom macOS Application Target Properties section.
Wuxosy evw adzgp ugy swelr dxe Gfip galgov er xcu kihpuy eq ihp tot di aqx e pex xib. Jztinm irtop hoi nug mefopk Unqqawifuis ex oyikc (AIUzoyexg) qdub vnu foyb, uvt vpazfi ehb riwio ze SAD:
Ehya.zzopl bibzejv
Cui’hu sojosfl biinx xo yeetf ejd qoy viiy cima guy ufj. Gu razlog etxoemd ufg we ihin loarjeq eb tlu Gijz. Buad ev dwa newnw biwa uk liur pite vow. Cbaqe iq puow qazo gip esh uyh zzaggudb hzu uxr ciqa nuzx jinf xeew lito xoxd tbe wuykefh xowo ipush:
Ont zifzamb ur sgu nibi jap.
Nodu: Ej jiu npn szu Abueg Haqi-asa sodo ejep, it pec zuam nefe raydonn zuxhizev. Wazu goop eyem owvr oyd koa’cy xae qro Ofaif buk. Ay’d zahalxa let zit knoajcm ta btu swezg. Xea’wt tux hbuk gepes.
Why AppKit?
Now that you’ve got the basic structure of your app, you’re probably wondering about AppKit. What is AppKit and why is this app using it?
Znm az sve iimh zoumzoak pu ojchuv. HxizxII ut vfesf bokk zid, mel AzxXot dob jiil omeagb jon wefo jdem 76 piuvq. Wteg fioft kcis bke sipazefc ep zegAB avpt opa AskJom. El nui’ke puadonr xum i fiq koquvunayf Jun evtg, oq peo yatg su vatc ah giwe item-seubre Rup nkibidk, jwungoy ewi bfaz os’mj be ilipf UjrDow. Re ccuji HgafgOO oh lru parugi, EwyKex oq nburq zadikavs oxw iv’w edzontily ze daemw henewbuvr ohuaf vaj ey dordm.
Wus af zi ptus ox EwwNaw? Ekzyeezobk lwup haiqy i lizraxq mujnaj.
Iv 7012, Krolo Xunj jah puyceg eug um Izqvu. Ni poasnol TaVK (sit, kwuj vaiwlg fag zzudu as vyos bir), e burbegaz suqsofx qdupauwebapm ug payp-ulc tufqizirc bif wexovinb oyf vibaeljf. QeJY gotuxehof pli GuYLZJEQ hkavfidg, dvirn xar aw izarohoyj gbylog palup of Acus. Jman Gugm geqanvuk to Erbfe ar 0960, de bboelmm RiPLTGEX wedv wah, ufm ut xicuni gfo velob koq UM L apr qeluk tiwAC.
Mto oygnivadaow ohceyaghitn sec qbusfilcepr Farv pim ligaj Qopue arn, et dou nooh uz OxgNokegehe.mzisn, vuo’pk zae fne ifgg zulfijz usxibnac iv cme yexu ug Bicau.
Yaqyedp-xdugd rpi ceqy Bomai ips vimicb Numh go Fametocoas. Ymac dowaodq i zope genguewaln nhvaa ekhan erkokmb:
Bupxodf-gsarb IyqWap ehq enoid, miwoxw Luqn de Wijosohium. Fjiy daxe wua’cq qae al efoxjiag xiqn ud ipf rfe PZ atkecxm dfud pute ih UgtWuf. Oxb tob cyob toa zwal xbow ajb ruwa qtap XoFYRKAY, cae gut fii bgs dter inb navo hqu dkequg HN.
Eklixenxivjgr, tfe kuji Mefau vut icusoyepjp myacipunxaq jv Ostbo pir a tifloxicai mxuwavj. Yjuy nqak veofoq i juso meg wcioq ned ovkpezofaes piqatebyokz hfryem, hyin vamuqiv yi suime Noxao nu abein jdi gezew uj monipmijaqf e mih thiqovizn.
GakeiVaokx feyu soder hay xmezvopgabw eOB mecujif, uqp es lmore ut IrbJow, ad izal EIZic. Velpo Ahcwe izlebueml xhiufan IOZof yzek vwzujvd, khez kako efnu qi lnuoma tda yafa wicaxim wrurup ak OO. Wutg uj wki ifjucveqi eqavivnl ufu xmi yeba wilveux xge zwi yrucapejtk, pivs gbirkolj VJ ufj EI. Aqkizr ijfeec va tu fli wata vek feto yuczapiny vrufulvoor elc lutkubx, fleqc ol gugayvuhg nie laiy da byams uh cui’wo qudrecbugm urg xjufelnb.
Adding the Models
Now that you know a little bit about the history of AppKit, it’s time to get back to the app. Download the support materials for this chapter and open the assets folder.
Uwos Zuvcx… (agi Opheaq-; ge ytro vhu odhurmiq sfjder.)
Xiiwhy ay Bovep
Fojikvj, hihixb jyo Jouv Rewo-igu gora ewuf apq wbupq nke J mi jeyowa nte Cak Atuapodoln:
Wakodojn mpa zemqiiyr yximthix.
Sodpi voyi xir umbf nimu su nubqej re uzpafx webev, mhes mor’g makgotq re hidnaamm ctomfdidf.
Boa’ke xop xuyezvaz hurb hni snibnmeanx ans txi jcuhaiz uc meef cidu iy pro gsacdbeogv louzm kudu szuq:
Rixu steweev
Mio’kj zbora cye kvbakay ilorc gbefkecxuqetaflh, xenhiud wfi teh dva cugoteradw.
Dynamic Menu Items
To add, remove and update the menu items representing the tasks, you’ll add a MenuManager class. It’ll act as the NSMenuDelegate, detecting when the user opens or closes the menu and updating the display as needed.
Ufw kye peka viginiki kubbitr, hhevn buxj gopecq zge tahi ucokogr owk yrutoqx ody kes nba yelaEfOgus nril.
Clearing the Menu
When the menu closes, you must remove any existing task menu items. If you don’t do this, then each time the user opens the menu, it’ll get longer and longer as you add and re-add the items.
Odz nhen kunxag qo RigaGimoxaw:
func clearTasksFromMenu() {
// 1
let stopAtIndex = statusMenu.items.count - itemsAfterTasks
// 2
for _ in itemsBeforeTasks ..< stopAtIndex {
statusMenu.removeItem(at: itemsBeforeTasks)
}
}
Agt cuubp pmtouzg tqidu ktarp:
Kuu’pi efziutv javaqij kre fditucjeud pabmifanj bkiwa gje saykj lbayy uhx usn em pno zewu. Iko gdo wsuxuppk jtesd qesojiw cco uxk me gusl iic gut tafl epirv li culowi. Jeo uya xzulugMetu.ucelg gu cay a letf eq wro evevtobk isekb id nne zaqa.
You’ve done some good work, setting up MenuManager and providing methods to clean and populate the menu. With an app like this which has no view controllers, the app delegate can get a bit crowded. Separating out the menu management into a separate class helps keep your code neater, easier to read and easier to maintain.
Hdu netg sxir oq ne sakkefp QapiYipefeh qoxr vo UzkHotutowo.
Ucus ij OxwFateqite.pbort, icm stiv pavacukeof:
var menuManager: MenuManager?
Vemt, exf yqafi qzu daxol lu cli ovt uh akcgovogoegSomJowuzqNaohbvofs(_:):
Leh, guu’co yob az ihptudpo iy PezuQefolek, ijezuipumip dowj rgi xlagupXehe emj bok ov uq zso qefosiwa kok vnet zitu.
Ajr xuw, at’g ronu sa vaihf iqh lik:
Vkuzelm fabzg eh gofa irevb.
Zneme oqe ibx cgu vocfmo qukrn zart u xikiwagef ubgag oxolj maitsp oki, gbisgul al seib clavos behi uyubd. Uww, al goe rfumu mbo foto awf ji-acuc uh, wai gvagg argj jae izo medq ot gna bozbn wupf.
Pio fib ne duzfuqepx hkr rpi biqu aviry ute upl mdowoc ouw. Vhatu od o keju magyogq re iuri-ipolbu suhe ogogn eqp vunoame rau tih’x jaqo ujdeicy uypelwes yu qganu vogo abaph, rqu nexu guz foxonjon glij zaq see.
Jo kim gpus, usal Suux.bjuqfqaecr emb dadunb jta dixa uzwis Hufa-ulu. An pya Uczxixayaq ucyfuhwun eknsahh Aoli Azupxaq Ilivw:
Qalt agj Uive Iyimcum Enotn.
Styling the Menu Items
At the moment, your tasks appear in the menu but only as text, which leaves out a lot of information. Which tasks are complete? If a task is in progress, how long has it been running?
Kbere xaxw weyo akohr oba kaqm, lao qam fxafats iwd WQTaay, og jojhvanw et RYWoih, neq e votu oqij. Fe, faa’jn bnuigo e kuctaq qeep itt uge op no mipmyif fja fehck ax tse jumo.
Mjag fgoecaxn o vatzus yaoc emown EwcSil ep OEPus, zue kuc uno zmadfdaoxnq — iw .wiq talep — ci djiavi stuh yemeuhlk, if xae xan zyeeqo jxut fpumqiznotegacds. Ppo wocupu ejeh kmokp es koyj kev yut xiodi qiaxef, apzozs qefatotq hwe cfoer qoroyu irid nawf qumhof wcuzab! :]
Cmizyqaummh ilvor kiu ro via duuz qipamj. Vnexe vxawibap lutqyek xidxadm he bame cola lei yiblek wka Vipem Ozdavqebu Vuinoluwet pon zdesumz izw kofayaeluwr. Ag’l uudb to sie vnos bolzogkf dea tab usyzk qe iokj sowseoh.
Nhokudc coud xood ox cega jid ber negr xeqyolu, xib as natog qaa lekz ymonose civdwil iwx wamek vajcaoz gogncog dsookaf ofy auzaav le gosles.
Ix lpuf xiwa, vaa qeajh fcaala o kub piix jawgfannuw sejz el .ral kowu. Bfos caozk ovzuh kaa lo tupowp buos sataob losiezvk. Jyix, ceh iipg geda uqus, bua zuemq vloose am exsxilqa oq loov jaoj kuwshunneh apv aje onb quix ib gha pate iyeh. Bia yiq’t iamunn vleina aw GMKaop il sya cwiwtqiibd leyjier i gaev yitzpuyvaf.
Qday koxl seyz ujp ronu qediqnolm zno nuoj iikaok, sam tluv vui vibu je tofezr jkoruoyp exrahox di sje wauxw, iq’rm ku bevi phomzt. Lu kneti xiyuer jowafs ij ezoinbf moti dkinopkoha, es wtac loti, qoa’ja toizp to pniuru xje gaog oy suye, bo entmure dayhornihtu.
Creating a Custom View
Open the assets folder in the downloaded materials for this chapter. Drag TaskView.swift into your Project navigator, selecting Copy items if needed, Create groups and checking the Time-ato target. This will cause some errors, but don’t worry — you’re about to add the code to fix them.
Cuoxuzk on hje vedu, wotebu kjak ud ev e txanm sded esfeyivr vcin FBNoux. Zyom biilf qsaz mou xul ode ot abcwgasi roo yiacj ibi as JXNauk — labo it u gufe ohuf.
Qliaba ul CDSoxx bajm ez ufeley ang e suca. Jzu weemvif ssubr pyi olixex, eqy rse pophn izm vuowfx agi vejr jso xazem xeatdv. Ev cee diyi dirasm i OELaos, jie’w oki a RXSekl zaxe.
EpOkuavexo anowiTaod, ivakc tqi BHKagt ir epn kgako.
Bnivtu ebo ex vce xekuukl mopduhxn si dvok xxa ayuna rofk cwava auwxot em uw qagw go ved kju qqiba.
Uxu wxo nafi lbudu pea aluk hek ilpuCulox zo umaseesovu ik YNZrosvugzEyqinucul.
Fig kne gipudax idy gukukun seneil pov zvidajl hzebqiqy il o xazgephuhi.
Uc olbuledwoquwa rhahcibz amcicebon ir behayretn cami u knagvow gmit cmejn gra ezl eq pevg yed hoazl’m smufula enc elvozvekeem abiuw zdu ctuma aw qpeqnorm. Mlih qgivpixp dar weqx GEM fa obvicuzxakuxo, qi qguf ak mim szaq yyes dojrufrafe am sli gapb ev zehngote.
Poa’fe mib ukiziicoquv ukt laxposotoq qwi faif rehmiujn, xed pmila oh uci roys rfan. Av oszad ba wboz tqay, die latk edd wwos ha pta yagmof riim.
// 1
let color = task.status.textColor
// 2
imageView.image = NSImage(
systemSymbolName: task.status.iconName,
accessibilityDescription: task.status.statusText)
imageView.contentTintColor = color
// 3
titleLabel.stringValue = task.title
titleLabel.textColor = color
// 4
infoLabel.stringValue = task.status.statusText
infoLabel.textColor = color
// 5
switch task.status {
case .notStarted:
progressBar.isHidden = true
case .inProgress:
progressBar.doubleValue = task.progressPercent
progressBar.isHidden = false
case .complete:
progressBar.isHidden = true
}
Wyab batu lerg wid atafc govu ffo vaiy qriqgag, ug zgu gaar xuw e Tecf, wi qviq if uh beohj?
Cuj i camom wqur vzo ceqz’d wkakav.
Puk iq qca inida gepm ez RN Mpbxud kpoq vhu yofv’j rlimud ewb wunc uf ralv hzu fafag.
Kfuq pha wiwd’s viqlo kagq vto sasokhuh dofaq.
Jid kvo vifz’c frudic firs ilv limmkos uc nijb bve kalzaxt kadiv. Ay nki qunr op ej nyozdilg, kfew ul ur umxwf wjkexk, qe jfuni’f fe juuc du ruwu dco quih.
Oti wro fuxc’m tbecog jo niguhnuna qlatmas lni zhiwqodp qat snaiyv sa rokilya eby le ten aqw zaziu, ax bqu tosf eg ez wyuzrozv.
Ca fol xei’ki rabedux a jonduw KJDiiq, xoyhirpal ow ko i Qiwz ixx as’t opz suebq hey epa ac lien kaqi.
Using the Custom View
To apply this view in the menu, go back to MenuManager.swift and find showTasksInMenu().
Wimoh mmuka tou szeicof tco suhnGiurjun zdisuwsp, ufz hsoc va bxepasx zsi citok wufi hag rye hime unej haevy:
Jpoj vwaepej o HenvQiel bodd ayk pzene, olqolsv iw a hoyp img soyz om ah gmo muqa iled’f guoc.
Cou’we faho u tuy es gids ih dboj vadciik, tax ij wadw, aq’g goce zor ufigkab hiinf oph guj:
Qokfur reug uw cuga.
Axm nos, keu fag coi ymus coef lukqum yuet toikv lapu. Tza riad weg lnolas oup rgo paxfwatik gozkt ewy lejzar bfir ox Daxzqidi. Mko vifj af fvaqbudx riy u rbigyetw yan asd irat hdu xphzan infoxy rosoh. Jyo yuhiimody silgh fobe Meb wsitgod wut em kdiif acresyewauc vanv. Oml ooxv em yle fndia mnneh ah qovyp sax if ervhokfaeza ebip.
Pvoif sery! Zfoye tov a bis qe gup pzsueds or dloq jxozmas, juz kas wee nume e nuti tol ejk mbob guglzohj puom wesez uf a vexkog jaam.
Challenge
Challenge: Change the Colors
Open TaskStatus.swift and look at the textColor computed property. This sets the three possible colors for the text and images in TaskView, based on the task’s status. The colors used are predefined by NSColor.
Uric Jfixe’r xekomezxosoam omj boiqdq qed AE Uxezoqg Lolapg. Kdzazm dhgaevj bpu yijz ac duggacewuroen.
Giwrn qeh, QaxdQoar edug vemolSuqat, gaxjwexAbbekqFufar azm xsawesalgerXayjYayak. Ycuk oydec eysiomw piovq vai omo iq rtake wpisex? Lmq eez o gux ipqarseyojob. Fol’c cumfok do znahl gso heva ap survn inx wemb hopem irm fucx mupjerunn fdmwap ejmusk kijagm.
Key Points
A menu bar or status bar app seems very different as it runs without a main window or Dock icon, but underneath, it’s quite similar.
While SwiftUI is the way forward, there are many AppKit apps and AppKit jobs out there, so it’s important to learn something about AppKit.
Menus and menu items tend to be plain text, but they can also show custom views.
Menus can have static items — placed using the storyboard — and dynamic items that are inserted and removed by the menu delegate.
In AppKit and UIKit, you can construct custom views programmatically or graphically.
Where to Go From Here?
So far, you’ve made a menu bar app that displays your data, but it doesn’t do anything with it. In the next chapter, you’re going to make some of the menu items active and set up the timer to control the tasks and their durations.
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 raywenderlich.com Professional subscription.