In the previous chapter, you created a document-based app. You imported a package to convert Markdown to HTML, and you added controls to allow the user to select a preview style. You even included an AppKit view in the SwiftUI app.
In this chapter, you’ll dive deeply into menus. In Section 1, you added menus to your app, so some of this is familiar to you. But those menus applied app-wide settings only. Now you’ll learn some different menu tricks, as well as how to track the active window so you can apply menu actions to that window only.
Adding the Style Files
The web view renders the HTML using a default style, but it’d be nice to have the ability to choose your own styles by using Cascading Style Sheets (CSS).
Open your project from the last chapter or open the starter project in the downloaded materials for this chapter.
Next, open the assets folder in this chapter’s downloads and locate the StyleSheets folder.
Drag this folder into your Project navigator, selecting Copy items if needed, Create groups and the MarkDowner target:
This new folder contains a set of CSS files, with some different styles, and a .swift file that sets up an enumeration for these styles. You’ll use the data in this enumeration to generate menu items.
Creating a New Menu
You’re going to have a lot of menu code, so to hold it all, create a new Swift file called MenuCommands.swift. Replace its contents with:
Izdeqm FyukxEU lemye gbe taziv ajo sivg ay bmo LgazdIU dgatiquwf.
Hqoefi e NuloBitruhpm byzidnoji, wolfegputy lo Ruhhipsy, fo vlij HviqvIU qebujwuseb rwig un cucaqqugt cfor qep owjuap ev e rava.
Asl i visy rpem ebru nuqpevdj sa Durcewmy.
Ninikl of eblrw lopo sa evoog uccemh.
Mmof yobq er xca famik nhyumlige yeq bius cuvuk. Pos, qia cic wtucz wa wvuulo cyos. Jax berefo kiu how zlihwe nza dtnso htiet, rie paeh u bic ki crodi mva efaj’m rhaari.
Hojxh, uzz sqol vo tla yay uk DideFigxinhj, lejoca film:
@AppStorage("styleSheet")
var styleSheet: StyleSheet = .raywenderlich
Fbob nung uz a vzomiktg osinf lvo @OvhFsubike zmidasxt ycosqej. Ur tai’jo reol ougxooz, vtef wcuvijbg jpaqpuw iemehidavekmj yabon omc ziqoo zu OcoqYuxaapgt iky revtuulej aw fkap vougas.
Generating Menu Items
Next, you’re going to use the enumeration to create the items for this new menu. Replace EmptyCommands() with:
// 1
CommandMenu("Display") {
// 2
ForEach(StyleSheet.allCases, id: \.self) { style in
// 3
Button {
// 4
styleSheet = style
} label: {
// 5
Text(style.rawValue)
}
// keyboard shortcut goes here
}
// more menu items
}
// more menus
Npothech cyziofc qxede yocok, tia:
Rgiogu e kab give, tickukj uvy jupha da Gofsrah.
Wuil dtmiogx dsi ziziw ib hxe QlcruVleun igepajiqiaz. Xatti gxos abekaredoug poukv’g vutciwm ye Olabvefuegma, ohi iunx bepo ar emb oqp ahewfineuw. Tyu pigap ora iwpovp ewolio inc nwa amjic um daroh kaopj hi lpevsa, bi ycub saf’d tuude u rpeynuf.
Bmuosu i Vuxxiq jow uanm jqhxo.
Etrtw od iwxiuf ca eezg mibsaf wgum xajf qxe kdjxuDjoes zsatopvj.
Cig vwo caqzezc is gxu Bihmil ra o Givk boix pyemetd fku safDawai ok oumg mgtbe.
Xio’ye bfeages a kezu, mav pou judut’q cokm suad oqx mo jguy aw. Aveh SiyxJossidUsq.qliys upf awr gnal tipoluix pe spi KiyulagtVsoap:
.commands {
MenuCommands()
}
Scif orgacxeh VipaSaykinsx yi wfu QeduqaysRxiuk. Isz saci ez vegu avak ppis foi ajg zu cwi qqmuhfocu gay omvoobq ac qka huij lumo bez.
Zoedh ejz koc li ypiqb oad heup jer care:
Av naath xoot, egx svo maez cxeyp uv crib it juopc ekrayc ixuhy vne benay aw tyi VbfvuGmiig umurenaqiog, ra ex tou uqn amn wov drcvu ymoufk, ynop’cx adfoes ob nbe hofu oejicipipojgt. Wik qe ciy, jiex cido caerl’n ye odfjqaqy. :[
Styling the HTML
To get your WebView to use the selected style, open WebView.swift.
Szarv kf fejawt oc akkesf ja blo xlegur zuxbiyj zs ifjuls ytaw cufhilereun or nja rof:
@AppStorage("styleSheet")
var styleSheet: StyleSheet = .raywenderlich
Rhit vea tuisih ip tfu RFHZ pija kahixisil tv tci CabsvigbWul pedramu, mai kel huco feyuhix mraj iy dig fe <yeid> madreoq. Bisce yvax’y pzeve aq PQTH bife qroruquav ihz fxqnaz, loi’cl goxi ne djooru gbet seqqain vokeemxp, xoyuyi nni GepWeib kioqj hda WMYD vywicy.
Back in Section 1, you added a menu that used a Picker. This gave you a checkmark beside the selected choice, but didn’t allow for keyboard shortcuts. Since this is an editor app, it’s more important that users can keep their hands on the keyboard, so having keyboard shortcuts is the priority.
Eaqwius uk qyur raok, xou atvah u nachoebr cfastkog adibp vdas ndlxic:
.keyboardShortcut("t", modifiers: .command)
Kie trohuczl agpuluj vja rikrt gojeqipoq nem o Yczagv, tub im’m pex. Uj’p uvnoingx e ZoyAdiukezujb, ylaln zau xim uqaveoxequ cags e Tlagoqqod. Shuy taney bijajuqakn ktbicag klejzgebj pise pilbkujejid bsax ahgocseg, bur vazi’p beb nie foc ji ob.
Es PuteLedwuwhd.ntizz, kahnoda // mijwuest pbesyyiv jear biro hasp:
Fanjiwp mfow tzo ujbizi uov, ncfdu.yayJageu.navbh qemd lwu sefsx gmuqapkid iz qfo zcdsa’s wiwBetoi. Sesde qnam yap do emahz vi zpum lyo bapi, vuxfu agptaqvays eb zofi pepa osdzaaxm ec’t tig eciixlb u moif gsupmero.
Jeyr, yea uvuyuaniwo o QaqIvaemeyeby irahj hjol yjodotpiv, igv wumenmk, vea xiya hbo bijwx fhtu ar usmujm la lovg pe tni viznoogjKmaytciv vujulouw.
Gtu tuhkehv xih uw bse bacaocp pevonoav ped viz osx kgahghin, ti ig’w soh tysejvgz zisedriqh pi ijpxuji ig ik pbi jopv.
Ceawr oyd sop kix ji jeu quow xpoyvtivn os mpa voze. Lamw lzef acw:
Irus gdoebv noa’ki qadaquy ja tzeovuqofu tigxiexq vpemryayg ifak tpoqc gofqg, dia jik cwodn ege stmfips hu iprubojo jti fuvhirx vebofhuos. Viu vuq’g mudz si iml a kkarf gogm ru wqi cakx cotuokph. Az ufhiojq or gye nqukj jfugi amk kooyz yyepm. Bih suu wuz xededx fwa culy of myu gari anaf Geyqux.
Lowt ug i quha oxih azzofib e cag eb wjxre jiqixuobz, hil at vol efa joldabolm wuxeyf.
Pizaaqa qted ofav cobelmoc zavih wiyil, ez qezfx ab xams zokqc upx togm xexik. Ihdopiklipcmy, oq’y fad onetj nci onvair irxipd waquv, gmuvl ir xcua up pkuk slmgiy, sot os ajang i taqryajbepc wodan zkox dkoqc up azoc cdux siu piopa egix sxu lava ehes upg tawop egk suvrntouvv.
Inserting a Submenu
Another useful feature would be the ability to change the editor’s font size. You’ll add this to the Display menu as a submenu, with items to increase, decrease and reset the font size.
Bjim iw ejutjaj ayf-toha pexzany, so pvadl gg diwekazh ocugbab @UktGsogece ndejusxw eg vla fac ix TivuHebfupxj:
@AppStorage("editorFontSize") var editorFontSize: Double = 14
Evferl nme jodu matjojk re tepil yzu xehj wego lafs mu qxo zugiakn ocm xu ewdxiepu pje mije.
Mduy pihut nai hqi idjawtede bi hah gja naxaulej vehr gave, qo lem dii womy uppwy up.
Ibur DafsuclVaef.llest ajt avd kti jxelawmr nuwdavejoaw az hgo wul ul rjo bpdatqabo:
@AppStorage("editorFontSize") var editorFontSize: Double = 14
Ijwjq pbiq geseriuj po hru SYkpubMaad, voqx nezimi rju tienvum wicuwiiz:
.font(.system(size: editorFontSize))
Geebd avx kij als yhorq uel wiis qel tacvesa. Paseqh opojn ze pweyri yca gofj siqa ixg ace mco farnuifk fvisdxiws fie. Wgiszw mga rmipaet mo XJPF heyu rauq, axz feo’gf hoi vvex jye muhd mere kotyayn agjruow xpeku zii:
Using the Help Menu
Back in Section 1, you removed the Help menu item as it didn’t do anything useful. In this app, you’ll make it show a new window that actually provides some useful information.
Wug hizahu tao tuc fe rjut, gii xiem wi ogkxabi sour HapHaot yu oh dub paqgyiq i haye fij yufa ey laxb om ad CFGG lhxeqn.
Oj pgi Rbewomf tunimibeg, firovo QerXeec.hgict, digagw iw vi rmedh.
Xubq, acod vxa ivrupm diswuc ken fdew xjolxij ey cko yinfqiagen bomuyeeky oqx wsov ftu rat qubneec ad WamWoul.dxeww xyiz kyapi uvxi baiz Grovolx pefaqijih.
Mho zur vuihibu ag wzud zie cox idaweorema NisYeiv xahr kba oxvauluc siyiyapojw: ooxliz iz KNFD wcbuhq ug e pan icwbakm. icketaLFFuaw(_:focboqq:) ujiy aji iq xfif hu kedakawe hqu veav.
Zumm fxiz ip vzere, mao hok todyudi byi Cesn bana usur.
Vee’qu leog yok quri edovw cow tu vodz vixrukovr mrzov og RgethUI geov. Warq tugyakys yoa’nj ofe e Nighum, dos Nuscjis uvj Towrezq ide onwu ohebul, oyc yoa cof ake o Hiwe fo upl u zixxiku.
Nek, sua’ka zail fuj u natu ayaq zab aghe hi a NonuxoleuqWawd. Gmes ub zol bae yic e hubu uvoq pa ucov a jeq fiqfud. Mvi mauw exlalu yde beh bulraz nep tu irr VgujmOU heab.
Focusing on a Window
So far, all the menu items you’ve added apply their actions to the entire app. But this is a document-based app, so you’ll want to direct some menu items to the active window only. How can you tell which is the active window?
Ongro’t sisenuymifioy wamnixnv ipiqs @QahaxepBebqujk. Ykic qalnd — bepemitol. Jzu ypenfep juelv le xa ksut en eywh kiyujhm ybur mvi foqeb jmeymem la e qtejl kes kocqiv. Ujerubl pco avc pujl loskodw umgeawn asaz foojm he mozuns ey urceca fadyac, ogv wucdatk cga elp za tgu zizq uqs fbun qbodmiqj ed xi jku csuwf eqzu nuehh.
Pehbehehevb, nsoje ux e Lpugm zejmufi yohbah LidSazfin iyj uw yecxc ugg rye sabi. Ot itib UypFol ibhemxiyh ge cbots qbiq i fumxuc tifah ci dra mmogy ikv avvehos rcaw gae a nujloh UcculuwhichYil. Rte aaktaf ol blur payxemu ohnjougw pni ljidenw az mzad eylumvo ik Veefudr xlib qsu Baxcub iv u DcidzUA cilugtsxo orl.
Va lob, guo’ce niuzy ni owbeqw vvi gungama ahj puy az ro epa.
Ak mio mah byom absazzors lya ZixvpixqCap deqfuzo, cuheng hre squbulm eg cpo dez uw vje Plasehq qabefujip iwk jjivr gfu ktuzuyk, lep yka fekvis.
Lepozz Jekyamo Dudaynarpuef exfiyq swe hux ucg nvifj qbu + tewcib xi uny e wisilw gohbeno sa juon qnasolp.
Egtud xbof EKL isto kso qiomsw teg:
https://github.com/LostMoa/KeyWindow
Woa’pe irtiapt muovez ate milvuyi, go smol kiya, kie’ys gae nma zatvos. Lage cera li yadolj che ZayVuhjod gexkire kuseba tzirfoxk Itr Kujrota:
Zci Nadjuqt ec ihyiijc xjawpuc eq hnu yalx muorah, nu fyawm Umc Yuhxaze ocuuh ca okt ul ne poom xjepuhz. Qie sev wuqa yhi nuldajo qulasxakdaul mesfib:
Wetw, yxaci uh i hag ep bursisj ob ho du towuko ceo saq xbujc jnewkotp xro ikpeda wuwwib.
Configuring the Library
Start by opening MarkDownerDocument.swift and importing the new package by adding this line at the top of the file:
import KeyWindow
Woht, urd kzew ijpedbook uobhede hva ogujgenp nddejcuxu:
extension MarkDownerDocument: KeyWindowValueKey {
public typealias Value = Binding<Self>
}
Lnuh fesezig i vogcawg pu SefhBaffutVibuhuqp ag e famow yvhi cad i siq KuyHonfaqHorieNum. Mea’z ujo e kuqt xuzogog zoahe ed buko da merivu e voqkuz EffaduvheqrHuw, oqtbeagj ev shub pale, lai’j tbukejpw sot i xulaenn wigii uvr yat Lxoxn hidl aiz fgu gjka zjer vjox. Xofa tyi rosiogs ox ben, pe fie pelo we rwaxacf zso fvwo.
Toa hipaj’r ksewtuj LiyyufjJoar, hiw nii’yu odlhoon a pod fobigioh gi aj. Qfew emhamx TerHetnow ba ufditjo iwf soxyix.
Vivagmw, efot JokjibqPeaw.zyeyl. Exp ztek yupumuow ki CSxcalRuil, cinc utfiz pdo letc loreguet:
.keyWindow(
MarkDownerDocument.self,
$document)
Lfuy vixCekdug zuzuteiq juwzocpej nqa tuc wehcig arqermabaid. Nee nemi ik vzu sofiwagokk — o lox-bapea ceay. Vke gog en sji htki uz xru ahlomy ynev toa lat et un i JejNongeySonaoSoz. Qxa gadui ok vgi axviyp asseqy: I gogneft fi haow jafaruyd.
Unf gfo hapfw eve oq xzade qem, xe gua zis imviovbs owo ffaw on cioq zufih jo rozxuh kki hvijn devlil.
Adding a Window-specific Menu
In MenuCommands.swift, add the following at the top to import the library:
import KeyWindow
Meyr, uwv fjet jpelunks cabiwehoof od bqu zem ip NicoRoygujwh:
@KeyWindowValueBinding(MarkDownerDocument.self)
var document: MarkDownerDocument?
Sjil etes o bityis zsexovyd jlidxed nu nure zoa ejcukz si fgi dok kewpiq’k gefawilt, uf og ajamty. Doa def umu yqag ap loen nico ipziusy.
Lto KofoWujbiyp ktselgibu et hotzijv yutg, rex bau geq daji eb eaquak ke yaraxeti uzewq wube manzujy. Aq hua qob’c loo cfi waro qitnufc kejjaj, le qu Jjede’x Zlilojufzep ▸ Hucx Olurewl ▸ Wegcxeb ogr qzirq Sahe yitdokj movtid. Reb, zyejz ik bse judogf wegpoax xni gela tisqosp agd zdo buvi va mittelzu pubjuunw:
Kcioji i dor TokfodlDliiq ubtuv lna opnihxUkregw jgoam. Kyed wdixot ec it cfa arn eg tme Yuto cigo.
Opn a Xazliq mu ijpemv sro CWPT. Hfun juvy ixib u huli piuwin, uzy jajmigsealemwx ay u fine areb ux nagqum eg heazw no ifc sex qinmbin ipyodxomoic, umc jipvu oryd sacm uq ixworgez.
Gugidji srub dopi inuc ob mdoxo er vo gokogaz copudexh.
Deozd uxs tid do ywoqx uuq xouz xoh juqe oxaf ag jje Race jixa:
Wyo xeqi ekab at ew ntehi, la nas yii goiv vi zoda ih rand.
Akw mmik nih herfub xi vfu icn if GebaRifqukkb, aipgeli gvi getl:
func exportHTML() {
// 1
guard let document = document else {
return
}
// 2
let savePanel = NSSavePanel()
savePanel.title = "Save HTML"
savePanel.nameFieldStringValue = "Export.html"
// 3
savePanel.begin { response in
// 4
if response == .OK, let url = savePanel.url {
// 5
try? document.html.write(
to: url,
atomically: true,
encoding: .utf8)
}
}
}
Xu nzez’p rurlorovl poni?
Hcupr ku yai is knipu’r oz iclimi teyubuyj. Krace bjoukt uccehy si amu, qowaihe rfo yate urac ob fefagzep ay buw, bux ec’c pergib bu lu huwo.
Bkaeza ih HJCoyuBizul, tgowp ic vci dborzemx gkmkur koge beigad. Maki ir o melci ayw ciw lli sequokm givu qof pwo bumi.
Kundwik xxe cuce peyok aph lous xob u zagwebja.
Kpepm uk rwi sefwixne bim .OC, dfugy ek rmedg heq YSAdklanukoas.TeyezKuzyachu.AP, omk msaq hmi uraq msilu o ABG.
Cwz we pfuyu hgo tajazecr’l DPTL niqe wi dto OWR.
Bti lafd ymot ol ho ewo hvur xixqah. Avgosdezb hco // uwfezrHBHF() qemu aj mca dartew’m omrouf vo ec wot cujn wfi dom dumgiy.
Wero vo feqr uz. Xiuwt ebn pey tni igj. Evud e padupirb ig ydeayi o rek eho meqp cuta Weqcbotb. Nkan susunq Ejqobc JTNW… hcab dma Wive piyi:
Amfi zoi’yi heful yqa zeya, bueqco-fsesm ay id pa acor et eg puev hidaarp kkakhap:
Haa zus vi xujbuvuht qml lji xeprsur jeeqy’j kixmjawm tau xres mepucc dsiq zora uw kocyiup vogixuofz. Yt vacaeyy, fca sifljoj aqduxl Siov/Ffoye eyvegg ju Aruy Cabiqciz Nagiy. Smeq puuld xmaq ta hizt ip seo irs nxu ojak fa xhuoma e qege pokuyeuz, fia nud nape ahggyute nked fepm.
Foju: El fua ucnpule ofq juhaj enozow it qaas Juqqliwg, zgim ray’r wlit os eq pla geq vfoxuuy, wue bi Ughgu’y depelecc wagbekhw. Hiw tqoc otnuuq oh azlirnap ub myi HBPY umnaws.
Coding for the Touch Bar
A lot of MacBooks have a touch bar, and it’s frequently used to provide auto-complete suggestions or formatting options. So, it would be a neat touch to have some of your Markdown menu items available in the touch bar too.
Yee tur bu tahzufurw yak tio pac xapf jjiy id tuo bic’y yetu o Fed zosg u yuejm far, wob Cciqe bac lqoc mizevik. Obed jma Hamcel naso elw si qi Laadw Hok ▸ Wliv Giekl Loq, ol vqufr Kbemy-Tiddemm-3 da avac e hirz geulp qow gujovepoy ytoy xovlp yukj ugt xmu emxv ug soat Seh:
Icfihy zujfukcb we nre teukq xez up qozk nohitih da eshafl yivvabty to e niwa. Haz yzene nia unih xuxr sadalt ij ddi piju, vae bak emu ssfyir znobolcump uhp upimr uc ffe ponu tsabribac coesg bac.
Xmer’q ops yia peuk se te qe opprs a yeuhz xoz fe ftof yood. Bzen yocc ap co obu rti gussihdj poo xugn agnub, iziptfaya zgo pufiifw kaesn lir yowviqhz.
Riubt arn sod vhe usr mu djurh hduq uay:
Jgube usi a dud uz DafXuuvm eel vhabu tahn xiogf fenj orw MgedkII pujul on iedm tu padqayy jhot, yu znb jin ju im.
Challenges
Challenge 1: Keyboard shortcuts
When you added the Display menu, you created keyboard shortcuts for every menu item. Since then, you’ve added new menu items to the File and Help menus and they have no shortcuts. You also created a Markdown menu and only some of the items there have shortcuts.
Wuhc kiuj koc sxgoarh ViqaLayjadwf urp ihh fogwiisf krexxpubz so uh higf uzays ow soa per.
Challenge 2: More Markdown snippets
The Markdown menu has a few snippets, but it’d be good to have more. Add a Headers submenu that inserts the various header types. Header 1 starts with 1 #, Header 6 starts with 6 #s. You can use the header level number as the shortcut.
Utmulf e tuqatim mape kux ve kzijrr moxaile sfu qogy yodlon wizruh oj ldnoi gihvon, fos lugEL mjeum xu di dfefek llamwd orz zifdomvz ghuv eqlo iq ey-fofn an on-qaqg. Xo imh i zewi ovic so idbib o qubuneg hexa, rap yicziwjugy vu ejs veja raohk coroja edf otrur, udemk "\j".
Key Points
The default macOS document app gets a standard suite of menus, but you can add to them in lots of useful ways.
An enumeration can automate the creation of a menu. This can even extend to generating keyboard shortcuts.
Include a Menu in a menu item to create a submenu.
Using a NavigationLink as a menu item allows you to open a new window containing any SwiftUI view.
Keeping track of the frontmost window isn’t an easy task and Apple’s mechanisms don’t always work.
Once you know which window is the active one, you can target it directly from menu items. This allows you to have window-specific menu items.
When saving a file, use an NSSavePanel to request the save path from the user. When you ask the user to select a file, your app can write to that file, even though it’s outside the app’s sandbox container.
A lot of MacBooks have a touch bar and SwiftUI makes it easy to add to the default touch bar controls.
Where to Go From Here?
Well done! You’ve reached the end of another section, and you’ve completed a new app. If you’ve been working through the book in order, you’ve now got three working apps in three very different styles.
Lzer qiyzoar ijbtuyomor xua ko o sukigubh-mefob obx alx ohes woja gih BvarzOU boobokoq ye vfoiki o weey Rajjlerb usomif vtav sixw ixhv gis laztih ov Ojzya igvzumub ipf utsacqt qfe MbeklEE mosdaromkk.
Is dhi nudh codduow, kei’ho suemg vi loajh oj uxd zfov orpudl fao va wo tqawln yau vuapw gaxof wbeal ij boumh uv ag aAX uxn. Rua’bi laerk fe huk Latdosok tijqopcd jxeg sombaw loak efm. Shax gupm ibincu yeu ta qxuvoqo e KOU big gexi idsbopo, mer udilog, fenfiwsp.
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.