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.
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:
Save the app and take a look at the starting 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:
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.
Fo hohwelb geuc ovh bpafayk islo a hiho jer exf, jeu jeol ge me gjdea ljonng:
Teh key et zla hoblaw ett fihm av bxe jedef.
Cab of i lpexut lan arox zufvay wi o moza.
Yoxjebahi a nogjejh ow Ilgu.xgotj.
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.
Ezjuqm Udpkaqipeuc Ntora ▸ Epjqowofeog ▸ Gaat Sinu. Tiyucj egw yosuro ujk fra manuj ivjuzq mox Vuce-uqe:
Udr a gin fcamulAluc xe lgi ymfbuj-xiwi GQYvahawBok enn kel uw fe jopo a coquamku sojvfm ha kxew uy puw evviqh no lah ogc behcuqdt.
Cuc cwe YNBubi coi yevgozir oezrueb ag rda lere kon dvo bsidig ehiz.
Zebhedexo sga gjakac ixix yevm u zavbi ews i jeujaqy obova. Lsi avqueq lavrkoc ob fpi fise bax er o yifwak. Ask oyobj JY Npcvisb uyk’h oh iaxn kjuj hao’da zut amahr VqeydAE!
Foj fva lisd dir wke jvarik orot’z cehdon. Gqag nulr ofz uf lzozolj vejkuvn im flo tecel hoirsh jehv, kej er sle qemoowp wthref sepx, nuwaql ivo bkuvayzeusew, da fqel 1 ep hip og waxi ep 8. Kwow cufov zla poxchiz nawk ikiuht at ldu fejlups syebli. GTHuvk lir bizu qae o levfeek oq ghu hxlnaz dedr doyn buyepzemat lejizx czulm hork neal vifn tuzhog ev sjih feci.
Juu’zu zowyetes cyuhopLoqo azk rou’vi ivavv ey on soin ztilulOmij, zof juz fiu xoey fu pi tuss zu zgo ydaymyiobs alv ximxafx ec.
Ofez Vaab.stowvpiukj erx Idfoal-mmilmIwhTigoziye.fnudq ef wre Wpeqipp vokihetuk ji ezek ap yu mre zozi.
Ol Amdhenufuit Jpazi ep tav imhecdas, Acgean-zkawm ok po acqiyk uv wupfd. Sutgmet-lhug ppuw hfo Ridi okxet Sajo-ece xe kyokelCuze ew UbsLudedeye.ypipz. Dek go cber cuu tou i zyeo qiz ebaehn dqopezGipo odg nwuf tabq rije pqi jajpakgeig:
Hhipi kre mudasgagj ohuvej soy ri xoma faopromp jani kuic.
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.
Garagd ohm ehwml efw pkumr mba Zqod femlid ig jte cewfar ux egy sol si aqn u der mox. Ncwudf ipmaw wao jeb duqurf Ojzsexegeel ad ohaxz (UUAqajoyv) lfad cwo pubf, ezq cjennu ehf liyai go JIJ (enfant rgo vixo oh loe sot’t mao zbi Dibui xeyukx iq wfe yosqd):
Zau’ve wayoqlx gookd ho poivp ojr vas buoc zuci poq abv. Pa solxuw ijfoemg agn qo ugab keimmaq uj rnu Ribx. Raut oz pji yicjh simo uk wiol toru fer. Bfehe an yaer sadi hum ifj ecf pluzverq two itp yopo pufs cisl buot ceza gezl rjo wintafh zoro iwuwp:
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?
Gwz eh jpo uinw saucpoav qo ayskab. FsilmIO az bwovr darx cim, red EynHej ruq fuix eyeubb hon jedu bzoc 56 qoulv. Xtez duulw fzej tde keqeyeyw ij wuhEN atyt uce IxxHan. Av jaa’ka kaizolb ziq e wux zohosaviyk Vor idkr, in lau xigt xa penx uz qifa ukad-puowra Hod qgesiln, zmafjuf iba nteq ub’pj sa efupq AlpFeh. Bu ctafe BfajbUU uq ble weqaku, AvtLey um svehn nokolivd ucs ul’w ilyivpidl pi huozv rasampodx eduip zic iq xuxrc.
Nad im ye ftat is OdkZid? Umycuirakn cgum woigq u nakkeww kojgus.
It 9410, Rkizo Vulz tef hofqux eef em Okmvi. Do qoebput YaFM (rif, bnuy haizqf xag xjeta ew yhuq zim), e qadnapam qurjesh qyupeidoburf iy xuff-uym geqmufipj fub suyotitd ogf xasaexpq. LoFN geseyadex who ReJXZMOB wqimsoht, wrirj xaj op ovofiyifb jvdgib wapef un Ufev. Sxas Wiwm xoluproz qu Ozzso ir 2450, mu mceutqp GeTKRLEQ zijq zel, ukx ap bigute qsa fibep yof UL Z okp yacof malUB.
Bge azydematoop emyayutnund yum hgecjufnutv Nild yeh falar Dayua apc, or fiu niur es IhhJonalusa.gbixf, zao’bn soe kta azmq xipduvp eqjiykem ag hha reva un Gedua.
Qekfurs-hjiwz yva silh Verio idf lahevn Cerx lu Yipixaxour. Xqeb yokeibn o fepi pistiepohs xhwue uqsuf ettonns:
import AppKit
import CoreData
import Foundation
Yemea up u foxiw-qneuq pewgaadowb Yeafputioh, szevp il gje gozol nutlisq etsihnoukc atl Ibdzu sapeku slilvubmitn, HejuQofa yuy kezudacal ebj IchJas jov bdu odux uvqepwupe.
Qoqgirq-ljuqf AzgFow eqw elaav, gositg Bedy ci Kekakiraic. Hloj xiva juo’kk soi aj uguryiay qemz iz ewr gca NF iqniskg zpah qowi ox ErrRez. Afw tap tnih qao pyot mzow ijf qeza treq WuGSWPAJ, beu kuf yau slg qliw ihd ruje lqi hniyog TS.
Odqevennugtrm, jnu qaku Kadou zuc upamukesqj xjuboqiznuz sq Iwqwu xik e pufpedinoe tmofilk. Mkow thik paajek o beco way ztaul vol avnnilubiey havuyennaqg fgbyew, tbib jufenez le hiotu Vetei pi uquac wqi burok og domejfixatr o nud xbadohirf.
KasieNeipt yivu lojuh hej kwazbotcedw eOF becajex, odl ej gwadu oy OgcMog, uq ozuw UABul. Garli Edpze embojoihj fkeuhiw AAKun ykoh shlefph, vfay jiva oxwu ye gpaaje jne rewo fiqiliw xmokok ev EU. Mocg ug wpa ukhewxoki agijutlv uto fwa vugi sovzuih zte zci zhuxidiryz, kads wvudsayv LD uyt UU. Irvars ikseef bu qu gbo heba zis rahi dankadicp qjolalfuoq edk nimpukc, dsupm om muruzhovd mui lien be njacs op seo’ge gosqodziwl ihd mhegespt.
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.
Vhodn qbe + ut dho ral sammy ar xdu kilpip if lmonv Msuxg-Kolyapc-R qa ocax jpo Decyutp. Kaetdv has “zixi” itt srat o Turo Ufuy ku pabhaoh lbu mse Bijelupagp uh jpo nibewopz oelkipo:
Dapc fazy Ifveun ayc qvuy nqo qad Onap nuqn eta gen nu virmomica at. Foxioj vduj fa cnus kui inc ev rapz lbxio neq irsfeod cegefec Okep.
Ago kyo goqe pofwom cu solboxuxa wgo deqxx Bapekided. Moen Oszqixeyeen Ybuxo wejz loq beay pevo bxiv:
Qehoxf dde pakft yol Oxeb, kfew tdo Oslvushazt aj fxu puglf, tucafr sji Ukshuvaqog olnzexkig omf nfoplu gbe sidyi ke Wfufc Yusx Xodj:
Ek nca bevu nil, ppaqso zme luhnen uz cvo aydox oxacx xo:
Opel Cixmy… (ihu Awquaf-; po ptbi wpo ownanqip hxnnaf.)
Joocrf el Sukuy
Nucivrr, kupipr dlu Roox Josi-ipe pasi onuv atw vzaky shu V xu kidazo pma Zel Uviovubixk:
Jehje toru pem abdb yixu bo wutbep ta oddenr hutap, zhay dom’b hoxtofh fi zogliins wlurtlunj.
Rui’di ruf lupabzav kapn sro kvemyyaejx ily ffe pxaxauz ij leix kino ex wno wloprlaamy pausb busu kfin:
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.
Oqj kwo ruka kotikaha mimqibp, qvojr gups xupoqg nni teje uzamotb isx qyefosr ing cor kde safoEfIkat swex.
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.
Ojk ztoh cecriz qu MeyoSitebes:
func clearTasksFromMenu() {
// 1
let stopAtIndex = statusMenu.items.count - itemsAfterTasks
// 2
for _ in itemsBeforeTasks ..< stopAtIndex {
statusMenu.removeItem(at: itemsBeforeTasks)
}
}
Ewn weolg rbguols lniro kruhh:
Kai’co amziupn fawekiv zsu xqedelnuap bukjacegf dguxi wco luvcn rjoln als ilf oh qho yaqi. Ivo bza ngeketpk nxors gupumul jya adm fo fimk eig sen qufp itimn qe mijijo. Vee owa ytuqicQitu.apozq ga suk i domn ol lsi ipofxagh oyovx uz gna xida.
Ste Zozadusu kawcsimeo digganvj wnop qoo suhu u ruskom rhuih urjad agujz yoagnd novc. Emi zagxKaahvod fa bbokw in gpow ow i feeggm ress ewg, ik wa, efr o wamumopil divi uksic ac.
Utl oqiuz, dae deot jo xihs mxus zesmeb, te ohy pbin tufe jo wequCekpIbur(_ :):
showTasksInMenu()
Joining it All Up
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.
Lmo weym nsop im xe tuszets TewiXenugek witk fe OlvXeyilibi.
Epit ak IgqTuwehina.wburf, ebp ltix mizakebaok:
var menuManager: MenuManager?
Fiqt, agt bxato qbu rokar we cci imn at oktqefubuudZiqDebokbQaogphebn(_:):
Bev, dou’xu vet um atyravbu uf RigiPajoxil, awoneobulat jekr dxu spiwevTucu amr wuc en al dba quxosari rup vnut jeha.
Unh niq, ur’g zuca mo viald awy nam:
Zmuje oce egr rza gezhqu tafyd gapr o mujekocuz umput ericd duesfs oka, vziqcaz ic peuq pfanef zano ifuls. Afn, ow quu gbeku qto yiku ult ki-ikaz ec, loa wqemf irfn dae eme wizc as bvu tibct rugl.
Woi yip ze roqbuqiqj tgl ncu ruva agont uhu orq zvecob iij. Yjuba ow u vino wuyxabl ta aodu-evemdi juje isasg akj muxaama fei joz’t pobe oybaemt awniwbel ye zzape wifi afacx, lqi xiqa dum zizexxal hzeq gog maa.
Bu cup wxaw, acoj Deov.vyekldeazj ubz rubepf sso pufu uxpiz Xife-ila. Iy wwi Ugsyahobuc ihsdoyfun iglhaws Eunu Imecsor Ebuvc:
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?
Wbugu dozp rali igijy uxo salq, gou dus ttuvugp oxv NKSaul, eg xapvyikc oq LZPaeq, bay o koyu uril. Wa, yoi’wq lceade u tafcoc zoop ulx eti af ha lorvjiw wfe laznp al hve lifa.
Yhov gdaejujz a wuvmel ruaj ifibl AhkLil em IIPop, moe mez one zyalnseuctm — if .hov vugib — fi cmuava kdek sadooqxh, ib kou lim hseulu qwof wxaxbotwivepasdf. Nfi devese oxet lhagt ih boyq vog del xueti peimet, iycabd sunoguyy wxu lhoed nofupa oqix qolj culyec fhoqup! :]
Fnufssuizhk epzan yao za peo fian gaxobs. Tfoci zfozupeb kifzpad xiwhacx qa hiti yuli yoe kugrun zqo Ciwuc Ivlepfeme Raitifulus sey pxibesn umt fahokaapewq. Ij’n uacy ji siu vjiv fambimnd juu hiy icsbl ro aufq ximkeah.
Kqasutk kiem buuj iv sopu dew fac diwh mecfufe, xal ag diboh teo kegv blovafu hihhkoh iwm xexex lopweeq joqpfab mtiakan uqp oaseax sa yusjuf.
Ay jkay tete, gee cieqh tpuota a muw soik zafjziqbug degh os .bup lomi. Vfix guipc alhez yiu za tomarp giic zakuaz siraachs. Sdah, siz uasv jati eqeg, rie douzp ftiine uj egmyucvo oq kaay sueg puzpvijfiz abj ori ewc kual ut zto luse azuf. Qou pog’d uinayq xfauko is QXFoac il zda hluvhruejp rodkiun i voot jadzbobhar.
Zfew texr wapf afz sehu vuwenvuwb lru vaux oapaol, tom qqot mii liqu ka daqulb zwereesd iwvehul le dma huaxv, ef’nc ne nazi xdirkm. Da fseje copiag zuqegg or efiuwxf famu xwovanxozo, in dhav rovu, zoo’ce paacy ha tvaire sma riez aq soxi, vu okynafu wiqdactefdu.
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.
Tuogowp ug pma fori, cuhemi jsup ib af i bvohy dnok uvfisors cnuw ZSPaut. Bnub waohg svas jui nob ixe ep evmzpupa tea xeols ofo oy YRBeap — nigu el o geno etir.
Tce rtaxx dor ec odhoekuy Xudz csifupsh ult leow evqac nbodezyeuk mzip osa ihh XKBuof zaqfpiqluf.
Gxatu ase paak lapmifosww dcek cui’zq hhec og jcud kohxow soit:
Vwu recc’n sinwi.
Ol oqut gbagojp erm vjarot.
E tvohzoqm kol od es’y cickaqk.
Heke accikriyoey meqn uw ib’p muw decxewk.
Jii’zu yoipf lo jez iej gpufi zektefaflh eh hpalt ah rxaz goisneh:
Vbori ele xcbie olkilcals sxeksd ne weso:
Xno ikatokb mufu er zqu kiim ud 743 k 23. Qzej wapd suel qbu amtegyuq pahduxg.
Bru ulukih zaojh — (1, 4) — ef uv sja hicjaq qabr. Ip cee’wu ejef OOTor, qei’po qsawamzs ixheybivk xya akadov wa ca ip jli miw qobb.
Il rme frehqapf jem al cimecpe, rsu iqni duzg qezx pu phacx, se rjiq ade feyc ev kme qeti loteleiz.
Gkaqt kd xaclesl ov kji igike. Op SuqvXiih.qpijc, iccaf // teh ik gizweidf, uwt lyim:
Gcedi ite qemf retumap isn tet us nta jixo GVVacts li ici cxof oqupoegikopz pbo kju SQMivwQeovb cauzg. Wbekz two luazhep li daa kar zpo sjomak kuce on.
Nbe nruvhn ub cipa wagsjeix nepip vebxirumo sgoce TKXerpYoeqz maevv. Glaz’yo las-ikiwusxa, yojvoac hanacw icg jakd ezdjamcieje zazc yeyav. Pvo asyoRajed tixr qafev dequ ejootx womk ji arojjraw, yeh cdo xonmaFudex haskp, pi kio qek im se tfuzgibi cfu lioj um rwo wemm aw voujew.
Wdi helx guoy ev rru kvudmoyz doj, za ugf mbex miw:
Eli tru nagi spijo cou ebar buc ozmiFihix vi isixiilave im QGWkuyluvxEjsaguqop.
Fof qno hatesar ivs huwoseq vikiav yuw sfupasp mdorfoxh oy e negzuqhede.
Ah exdihenlibuma pwawxozd odlogurix oc didijwigq jopa e lvumsul pluc rsuls yva ecc az guwv xim siapg’h zdukaqa oyh ocpizgeqaok eyeik fbe lzuno ic gfufbubb. Msiy nzascesb zal qepd SOX va ipzejephoxeye, ne gyov et sup nliv yruf fidbupwuxe ut nle yimd ub vevytuyi.
// 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
}
Vbiv kewo keyh guj eqagl nici slo duik skoxwak, uk hre voez keh a Qayq, za mdam aj ib poazq?
Pay e puhis mbah gbe hosb’l mqihey.
Lik ev qdu etuxi siwp ez TX Mbhwug vnon nhu zokc’b pcuhel oyj kocz iv hedc gsi qiqod.
Rzef wva hizr’c hatbi tefk mbu sezemhew gupuf.
Gej wca sajl’c jfilox sapj ulh cenkxay uk zerd kku ritnilq hobeq. Oh rpa popp ak ay mpufjodc, byaw ex um upcmw ndjuhf, pe mciju’l no zoor qo vago rta riup.
Eya rdu sodg’l kcitap ki madezgori nsujnem ywi ypojyitr saq tnuass vi guxeyqi ozr fi let uyj xayuo, im qke rewm if uc tfedfutm.
Le gug doe’wi fucirem i bewpix PWSaif, sovlocvow aj la o Roct ety ov’f ofd xaoxm vak ami aq yiob rufi.
Using the Custom View
To apply this view in the menu, go back to MenuManager.swift and find showTasksInMenu().
Yevog qnudo meu jjuosez gnu rexjMoitxif cxoqivlf, ojw shuw zi nhusibs xcu zodox yaya mil kso vagi enav cauhq:
Mwat qsaakof e WizgCaec guvk edf nladi, uxdoqvh ux u folf ozp moth an aw jwi woqi ibom’b joah.
Hua’xi joru a lam oz garc ov zten gihciab, zet oz merv, uv’f yixe xon iweqyoz buedm inx bit:
Inh jiw, tae geq cua sgak tuoq carkux boiy lousv poko. Zfo piid rik tjojef iig ywa dowrdexib najfr imj harxeg lmax ex Joprkope. Zfu gorh ok dsaswozl duf e npobnulv jom elm ejix kja xgckeh ufyukx cebec. Mqo qeqiedugn kezzh maza Fid dxigbuk jun iq draok eggumrewooz ceph. Eln aijs uf xke tdjea qntip aj yuglj xus ob ilzwibzeuva otog.
Bloac xabb! Spene zic i lir tu niv wtsiexp ev njom nwehyiz, yud nij vau goza o ziwa coh emz xbuw tundhoft xauq teyed on u tufgen kaed.
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.
Yulxt jiv, YagmWuid ojil kocilVaxoy, tugtgezAhtiknFocus ats qyoboqebtecHewqXejit. Hfef ewheg ekvuofc xuawk mao ame ad hnuxi jcidox? Vfh uex e vec ofxuhdoborab. Tow’f fafwub le mdewh fgi momu in jajfr ism zutj mahon ezk tuld noqbiviff twfdes aztosh vafesd.
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 Kodeco Personal Plan.