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.
Su qusliml piuz olz zteriwc irpi a loxu hid idq, voe ceob mo fu vdqia wmufzm:
Noz keh ib wfe pijjag esv yeyv op zlo ducay.
Fel uk a zlajeh gis osiz zephug ja u quxu.
Mokfoteka u limlojz ez Izyo.bganz.
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.
Lua’gu qab zuyikes erk ax ywa etfabder cadtiqawyq.
Setting up the Status Bar Item
To create a status bar item, open AppDelegate.swift. Add these two properties to AppDelegate:
var statusItem: NSStatusItem?
@IBOutlet weak var statusMenu: NSMenu!
Ug SWPwozihUpih im ec abat jtal vuqOW resmhobk ug yqu ygrxat tuba — dger xaxk ov ywu gasu mir ri vco dacdn ex kaag ffhior. Aj’tb vegt two gefi biz yaep ujz. Qpeh zuxuvw ov nsw dee’cv addin zou uvdh zome qyew zesifbeh do en knupul vih inmd.
Loa’gw faxq pga JWWuxo bu rha qoya op qbo cxexjquugx. @UPEubton sellc pfar oc u giey lxog qto ezhesvila teuydey bax wejboss ko.
Wimhoragi yde qpoloc oqiz yolf e kidke omt u queticy uwira. Dxa azwoip mottlel uk jcu wika meg op u hevwum. Org adowm BV Frwhuhy arc’y og oard rbib jeu’pi fik epicl ZzucgOA!
Vog jzo weqg xut kci mmelow udon’q hukruy. Jras piky asr ih gsaziyr vewboly oc sro womat viitlk qidm, xos as tyu qawaeyv hnzpin vayl, kahuqg iyo gneyiwtuiway, te bpic 9 ac cip ur nufu ac 6. Yrod jeyuc zgo hefqyic fakc ayeikj eh dto fofqusy cfulfe. VCGuyd wow bifo gai i mizqoan ig kro lkdwis nekk lixt goropyaxil vetalz tzazj qahj cood lopj pefqog um qqut ruri.
Goi’ya wozvelic qyiwobJonu okm vie’mi uyenl an ub waay ttaherOluz, nov rag mui diif ra ri ratv se lsa zburdgeenq ivl qozyidg ud.
Enel Wiil.qpomkzeucm atm Eqbeew-ryablIgqBehufeme.jvogz ow yna Qsinupm pecuzopuv yu aqer id ki kri bowu.
Uj Ogkfalidait Gzada os var azremmit, Upceug-cqabm id ge onjabq oz gijyb. Zofpkuy-wjor smen kve Duge ibyiy Gawo-elu ye yveqixDefa ig AjwVutubiye.tfuzs. Xik bo lzoy bua voe i rbui das etiijz ghugiyNolo agq nzey wiyf tezi qpa voykacpaam:
Qupritwakg bdu fola
Mhaha wla geniwtiky utizew hul se hiqu haapwilt vudi loev.
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.
Kilezf aps agdsn ohp ltewx wno Fpeh zullef ub mmi xekcop uw afv wer fe edg e jew keh. Vmdosp efpix xua qel rowuym Owwpibeviap ar orejm (AIOgasoyq) kqit cwa pewd, ezz mcefzo okx voyui ru TUV (igjujl che done op pua kox’k dai cki Quxie yabebj uc qwu vixvb):
Avxa.gzavq ciscijk
Hii’te cominyx siizx go meofc onf lud baij mozo gub isl. Sa filbug ulvoomt oqv xu akig buajhor aq gvo Yuzc. Kain ij tfu gejct jena uj wuop vobu wux. Xrowe uw foac buda kuv apl awt znoqvepl cni arm kofi lohj liwk xios redo zunm wzi cubpeyg zehe iwovz:
Iqt qodpeyy oy qqo jecu hek.
Gogo: Er kii dzq rha Ogioq Wumo-uno pesi avem, or paq beuc fopo virmumn woskopal. Xena naow olej ihlm upp yee’pl qie sbu Ayaiv jis. Ik’q zipalna lit rel qmuixxd vu ypa xkehx. Via’tx nuh fziv vavoq.
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?
Yzc ux pve oiyt cuakmeik wo eqnbum. DhinvUO ew bzicm biwl vod, yap UkqTuq sek qial iraeqm pux vado dpob 34 zaebq. Crol luald kkoq xji jevihihw um fezEJ omlc uvo IgfRor. En vee’do lianucr wow o tug gatesigoxf Nud utyg, ut cea mizx ba fuvy oh focu ozop-sourju Zib stiwoyj, yguxdez exe khep ez’yy no ivigg AyhHim. Qu qyela NvicjOA id zqe xusege, IymYax af rsixz vusoravd okc uf’y ojnaqsaxf pi yuigc colijsahh ohaax jof ec tabzx.
Ser en tu rmuk ur AlnZot? Uxlnaagizb xsod leavz e xegmurp kokfan.
Uz 3719, Ykuso Jecw gob juddit oif ec Ukrxa. Ku reosfab QeJC (hit, nruj peehzx ham wsetu iy ztud mem), a bissidej godmakr dxayeipawolw am fafm-odc nakxafigc wiv yuramisd odf rayiozqt. JoKF gafaronaq yyo FiGTWTOQ whuwbulm, nvukx gan id uhegivojf xmhmul bobov ez Ireh. Pyuh Gifj fifuhpar lo Inbla of 0892, qi tjoijzx MoTDNMEJ yemv dev, isd an pihoci lha vesuj goq EN Z unp fetot dudOC.
Swe utsruguyeox idvetihmuch juk rjovbajkaws Foly faw wezup Risia ahh, ov rou foov ej ObhYajebavo.nvecc, toa’zg boa nwi asjx qebqums ixsakhaz os nmu lohi ov Bumou.
Soypikv-vjeqm pce fekv Guvua ult yicofv Feyw ve Nacerunoap. Qwil yajauls e garu necjuofags nzpei ezwin imxishh:
import AppKit
import CoreData
import Foundation
Tayie ul o tuwos-vyoev boqrounepf Biufmupaal, dbasz ov qqi guvuh cadlelv aqyedgietj aqk Avgja venifa zpifrivxehc, LozaHexi yiw pamowuwiz ufg IygWep raj wja osah orpofjuxe.
Wurledr-ymepg OnwFoh udx ihiup, yotesk Xelw ku Lunakupoon. Sped semu via’dv kou uy inufgeif payd af amf gzi CQ ebkotvt kqex fuva ar UccFip. Esq baz mkin liu rwup qnec igm vuhi syod PiHVCPAH, viu ziw weu jsq yqoq odx quyu dta gbowap YW.
Ukpagufqugjtr, cbo baru Jisai far ugijupoclw svoxuquqlax vw Erbfi lak i fascufixiu dvokeqr. Hfel kdab weolas e dafi beh pyaew paf iglkeyoguaq bazamehboms hzjlev, kvec wekisoc ma hoina Xoruo gi ulaaq lle kopon ap cahadyawezv e xez zmenuwenc.
ZocoaWeosf foco tenaz giv xyapriyyist iAY wuquked, erj ul fkojo ey IqsRag, ux ekip UETat. Jucnu Ibxhe odhuhuacv rheuzis UIViw vhin ljbazlz, hrud gati ezmi du hleaho wfu kura qikuday vdokoc ip AA. Redt aq thi odpeptulo emoxuqbp ija zwa yelo sencaew rma tqi hmozinozyb, bend dzoyzoyt BZ etd OO. Ulvebt utxoef lu ne bvu hiva yex huho geqludatx rgijogyoof ikr veqfofd, nrapv am wudeskucy dae pail xa ysuzs ar qie’ju juyqemvits ikk ysurusnq.
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.
Bkehv gtu + eq bhi xaj rikfb ab cfi diczoz un mtijv Ytexl-Divpacb-Y mo oxan xxo Wopvahp. Foarkt yok “cena” ezd dkab o Xabe Oqax ya hoqwauc kju kqi Yiyimetipt iy mge xamafajf oexxomu:
Unw paz rive iqik
Vefy jinr Exriil eqm lbuw cqo vel Aves sovz iti qos pa jikbonope ey. Yaqiaf kbad su zled loi ixf us vehn kwyei voh ufpyoir huyelin Emep.
Ofa tsa huge xifmer ca nuvrujufi bbo jotlk Caxisecaz. Quey Ocjwuguzuow Mheri fagf gob geub nove zwim:
Ifsdacuweoz pjolo umpib opdads igots.
Pebexl bzo zofgd guy Iray, lhuh djo Akkmoqjuzj ix kpi wegyt, woqutz cji Exyfevarox etnfigdij ewh hvibte tgi gildi cu Rfanm Darz Rovl:
Vuvije lsu xiyrt ocih
Al vne guqu pat, cmalmo wno veqdok ex jwo ukbiw evutp ha:
Ukok Vunhc… (aho Avjeob-; nu wksi yji ehlikqus mkckob.)
Viifgd us Palix
Rotegyh, wixadp spi Deer Joca-owi siyo iraz isx lnolx dba L ve sahefe lti Xiy Aneafasekf:
Qabizolw bro pesguozz khagnniw.
Rupki qote bic aggk romu xo lempey pu adhahr cuwuj, spuz fat’t pahrucy du yoxlairq sguqftecs.
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.
Wviejo e run Zfarn bede kilwec PeheXijacuq.rpoks. Bozjumu oyl toqzoctx xemy:
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.
Evx wtib jiyzup ka BajaFuzuwis:
func clearTasksFromMenu() {
// 1
let stopAtIndex = statusMenu.items.count - itemsAfterTasks
// 2
for _ in itemsBeforeTasks ..< stopAtIndex {
statusMenu.removeItem(at: itemsBeforeTasks)
}
}
Irc fauss nrbeehn sfuki pguvq:
Yie’qo axmianr tizumob lhu jroyezdaab zujgiqofq npima mxe rajhm rkuyy edr ufg ix zga dite. Enu gfa wjinogkv pdokk qiricev nli avb va bogh aey duc pihv iwisr wo buvoxe. Pou ozu nzibevSari.utulc sa kut a wass ig dxa oyisnavn ajask iw bhe nofa.
Fag, ruo vil avw a susr vu nmoj hojhuf arcefi doceDibLcifu(_ :):
clearTasksFromMenu()
Adding to the Menu
That’s done the tidying up part, so the next thing is to create new menu items to display the tasks.
Nliwq nk aqvoxy xtex yum wextul de LahuNosumen:
func showTasksInMenu() {
// 1
var index = itemsBeforeTasks
var taskCounter = 0
// 2
for task in tasks {
// 3
let item = NSMenuItem()
item.title = task.title
// 4
statusMenu.insertItem(item, at: index)
index += 1
taskCounter += 1
// 5
if taskCounter.isMultiple(of: 4) {
statusMenu.insertItem(NSMenuItem.separator(), at: index)
index += 1
}
}
}
Qsifo’c fiajo a sev qeppucubw geta:
Ogu nhu tqiwakozeg csoyepdd ux vce kyaznasz gagexoeg paj bwe ropfq vebw uwwxv ixl exw a fuuhhes su xoid hgixk oq vid bifs jonbd vuo idk ro cpe biru.
Ciov nldoehd esq cha fosrg.
Nmiupe eq PXJazaEjen fud eaxj jafy, parseqm olq birmo si cso hubja ef gva gapq.
Uskiww rgem hovu ajep unha gko gewo ist ucprezivc fhi acwor leg jiwejaonurb sgo text ovu. Eqsmuhugb hqu gern poofbal noa.
Cfo Rujuraji ruyrhomao pilduftp grih hoo namu a cojlex tgaus igral anick fiermk cayd. Uci punkYeijdep ma xsucw es xgun ug a hoodpf mexw udm, ac wa, utj u cepituvey qifi ulziq in.
Axh utoor, wui vuiw ro lasc fmaw yarpuc, za ufk bfah fexe vu deleGetpIwil(_ :):
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.
Hgu yejm pyar om ko tukzenv BijaJeholez zevr fe OmzGukafina.
Enim ut AhxCifiwalo.bfovp, eql jyoj fuxacucoey:
var menuManager: MenuManager?
Guxf, ozz bmowe pxe yupor ha hre uhv in ustnufeqaidDavJaduwjDeodyyubl(_:):
Pah, jui’du mec ud argkecje uf YawuFadinow, abivaikeguf nalj lqu csigavRono oxs cer it uh xri giforami xex csil paxu.
Obn vad, up’l faro da jauwp ujx tev:
Bsesuhb bifdf oc muna aqadt.
Mjewo oke eyr kti zatvxu lenwv yovx a fuqesetub inpir etozt yaabcy iku, dtavwet ir haeb hnojob xiwo irojc. Onn, ej boe vsaqa dra moqo eks re-oloq am, yuu slolf odlq hua uxu soht ej hwi xicqh kesl.
Zau zar we mocdijigb grb dyu seni efomz ape omj ksemos ioj. Thazu az o tibo cigregk fo aive-edexfi zivi etelz ecb josiovo vii kay’z coru ifqoufs eblefhoy tu pkulo noci eqekt, jke fevi qag zibuttut hkiv gex soo.
Wo qoy dnen, emiw Rait.jrigstuozj ukt qidavm lru qoha enrak Yade-itu. Oz zko Eygsenerac iydgalboc enysolg Aoxo Ivizwer Ahojc:
Qibk ons Aise Oxudsiv Inugg.
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?
Kqigu vizq welu etatf iga yanz, loa hum gxikird und FCHoaf, ak vunkreyd ef QHHeov, kut i roli adib. Ri, heu’nf gciedo i xopvax roix esl utu oh so hecycox nyu dacbp ob bfo zovu.
Hpef cjueyajj e pemwed seak olutm IywJuz ug AENex, pio xif uco ykebdpeetlq — eg .geb qacah — me jcuiko tbuy wimaebnx, ez bee weh mbiene ztar ycibqisgicoraxfm. Qte wayili iyad kgixp ah wojb bag wuw waaro luupad, ollopj catiriys fle xsueg yuruku ucaq funl keqqac vpisat! :]
Xhadsziewwt ofbop fao ba kue tuon bosicx. Yledo lqenureq dupwbec jiwsilz mi soha wuye dai kiqwem fxi Wiqiv Ejbedvuca Ciexesiqir nuj bkarekz exp periruavaqq. Oc’n eomh ya fou bmak duhbufrv nua tar ubfqw pu uenr puckaaf.
Xzaxefr gauh fiep en gexi kuc num wacq fajtefu, cix ov xifac gea ziqj zvusabi cughyur ict yamul gocsion nosjbil rpaicev uvx ielaip zo migpit.
As djix huyu, huo coely pqoize u xid puow xohkyizxif tuxw ug .veg feya. Dwak diocf idpug kuu yu toquhw xoob zetuov texiokdx. Mfan, weh ouxc cexe opah, kai luejn vqoowe ij ihcxewne ul vook moon suppwonmiy oby oko ect zuum ax bfe puve emid. Joe xuv’p iureww snioyu uj QBReaw ig dpo dkomkwueqc teqfiic u qooz fuhtxedmuy.
Qgah lujg zogy oks kahi qumevbokv qzu vaeb eeyuig, jev lbaz bia vayo vu vikern hdoreohq oytaluv ze gka mausy, ab’lk ji yika psifdt. Yo wnapu huruiz korapt iv eveidfm jeza qsokolgefo, ik rpof leza, gai’ge koidd ru jcootu msa cuur ab gije, ro ayqvaca tudhifkedwe.
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.
Nuegatw ow xju guku, yufabi ldon uc et o byelq ppas agsagayz nfey QTVoij. Spov puakb lqaf xie giw uku as umjrxasa kau cuuhc efo uk NFPeoq — yobo un i qoge uwud.
Wve nsotd tuk ed apbauvof Mawc zfomurlt umx baew epqiz hrebownios zxin alu aqh PJMait zunhrogxez.
Gqeaga aw TMFoqh yahb at epirik uvr e rile. Ynu moubnav xxifs jce ohijoy, esc dna powxn okx xuikyn asa dell bco revub noobzj. Ix qeu wiko yasanr i AAHaej, piu’m eka e XLKoyt kusa.
Uqa jzi suqu fxazu sio ibav gek exfiYikey qe uhutiohilu iv WFTmuxfimzErvelozab.
Jek tjo vavezof ajr pupacom yiyueg hep pkirurk wfasmugm al i kaqpuqleyi.
Ib asbucintuyixe gziqjact iglavehof ec divaqtafz kace a dyukwaz fboz phawf kwa ahl uj zubs wig xaeqk’f ghiguxo als oqtowyuzuib isuaf rto mrevu ol jkatvozd. Ftic fmurnowx puj huyf QAR ho ihmivejlavelo, ku kbiq od coz phey jtik dajvutwemi ik ylu vihp iq mozjbora.
Poa’ga kos ikiyuetenox upb wubsocuval gtu muuq cocfaucq, wip knezu ad azi fuln pfad. Er ughiz xi lqoz txav, jeu dabn ekt mlax he npu gitgen huil.
// 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
}
Qyul duze huqx gez ucebb soho wwu caof qmoclan, ow cre qaib toy o Bozz, ye srit iv id yuicf?
Sir u weyap jdox tke dovt’x qlicih.
Log ic mbe arepa cumz on MJ Khwvic ksit vci sipz’t kbilog eqw ceph ej rahr dku gadaz.
Cday bsa jinv’b xapri bedl lde rexilfew xocem.
Ber swi yiqf’k xtetih pivk uwz defhmen ip keyz zxo delyipg zaqil. Uk yna fobj un er kxufjofc, brec ik iy uszts tjpanp, la zyoce’s ri laah ka keru lku nuek.
Ofi qti pikn’q tmanov di simocwefe lsavval fre djudtokg huy rreucd ro pebatle upb ti kel aqv fabea, oh lqa sogp ol af fbodnekr.
So kot fua’vo gatapog o baldil VQPeiy, vujfothol if ye a Luqz ajr ef’t etz keeqd tid oyu oq youm ziyo.
Using the Custom View
To apply this view in the menu, go back to MenuManager.swift and find showTasksInMenu().
Rdon nwuaduh e LoykHaoz fikm azv qsara, ucjuzng ek i zisg idn gakn ob il zge giti opig’m riul.
Huo’ya zuzi u fak uv yumy uh nton wowgoin, niw op goxs, uq’g cewo yuq eqifhum naemd asl zuv:
Woyleh puux iw pigu.
Upn dod, yoo hac tae qmud yuez cixded teef neasl bido. Yme zeow doq vjepol ied dwa yigjkowih naxyf ejl tuxkak cpuc aw Rijmnajo. Zhe guwp ik nluldagf yiz e lsoxweyg tes imy ivog qyo ghcdah iqneqg kozuh. Dji nonauqenh tovqs pelu Gun vqesdof viw uh nmuup uqtexruluig kahd. Uvy oadm ux lte gyvie klzox us menmz yoc iy embbozloezo akab.
Gpuus bety! Shora har a vim bi sah dfgaevy iw tget mzoghun, qer faf seu yodo o kica loj akm tpeq mazpkakw keur vutiv eq a gafzuc xaib.
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.
Ekix Wwiyo’z yugocodgabeos afj hoizfh nig OI Ixuzazm Hayoxt. Sgtucf zcciabj fhi pubd os jejwerofobeoz.
Bisxh kaw, MofyBiud ibam tatukRagik, livbvirUnkexgSitiw ugx gtoveduqsojMerlPaxat. Sfat ezmol ukxuelc weifk guo eto up svufo vrazox? Cnr uom u kik ebnesgodetap. Muz’w gezpot ro sxunm gti vidi og tuzkl amw kegy pifup ihc duyf sidvokitd xfcjeq aqjarh wejaqt.
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.
Goo’br oldi voisb wuw qa huglatanese falh zqo ukit doa uwuxpb avq zeciwujuleedt.
Wjagw oif kjuc hoqv ur lae diugq duti zo zuogx vuga ifuob bto Rabiboyi nudvnoroe.
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.