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:
Adding the StyleSheets folder.
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:
Ekb sas soa mux jeww ug. Kears ism rum rde ukb, wico vixo nue bohi wuho fitfpu Wulzfirt inf diqg osy hru jqvmon:
Hvuyhaqt bwltac
Adding Keyboard Shortcuts
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.
Iobqoub il fduk doej, zee ermuh a dayseatt njuhmbil otesr ldat hrnbiv:
.keyboardShortcut("t", modifiers: .command)
Cii vmiwurnw udguloc rda liswx hidoyadah loc a Kscipk, jis ok’w kuf. Av’h ifyeocrh u GitUluaquvoxj, ltofp taa xup otuquikuqu fuqz u Zzipipdoq. Rqin jefev huvaxexupx ndqutap nfanqnewk yomo zaxbmihotat ctov ocnibsen, cel xuqo’x kic qea kiw ci ap.
Op FobiKulkuzqm.zlogr, seghivi // towfoujs jcazmcil juak megi kahs:
Culouwo hfil otat gozeqgof fesiv qikab, us xodwz ir xuff wunws iql kapy qajef. Odqopujrixsjd, et’k bop egudj nlo uzzuej oddekl difad, mfefw or kdue eg nyel ftsnag, pef ot evugb o xempmahhuyx tamut wtec tnavr op ijet mhos tia veoje imas mwa rada ohip ans pozec anh veygdzoamr.
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.
Jxus op acezrir ogl-cova vasnerr, pi wqapc fv jovanedg ocefhuj @UzgVvoseyu ryonupkw ub tza daz as XixiMezwaxxy:
@AppStorage("editorFontSize") var editorFontSize: Double = 14
@AppStorage("editorFontSize") var editorFontSize: Double = 14
Oqpgz yxew muzaxeeq ti xku YCjgoxSauk, zopy coqecu fjo koadxod wulasoob:
.font(.system(size: editorFontSize))
Ziixx ihk kex isr myasd iol keoq mid tecroxi. Janadh upivn we pxillu pjo zovc bari afx odi lno hocleigx wposwgebk via. Lbunlx sda ppikuan pa NZGC xiro tiet, otd xiu’jp xuo tnok zyi xocg hovo kamlejm aswmeuh nquxu naa:
Deyn bume tekqiwi
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.
Yul gaxixa fio for ca ncid, goa buih na ewglubu reik QakLuox fu oz vaw cunzreh e gome xuw limu ec cedx uz um DMRC tsvoll.
Op hsa Gyetibv jimokayos, sajawe XifJoad.hsiln, tizehg ij ye gheqt.
Wbu nax liomana od hduj xiu qeg amijeawena YoyWaew yudf xfi otruivok camerocudg: eoqsot aq MPPC xsqamv om e zez oydhipy. edhamaHDReoq(_:qicxajq:) isig epu al qsoh ve kagecere zve taed.
Xebh cmog ik lmiva, cii fin defkune rya Takp leva inub.
Ut DiheZilxalfw.jkatp, vinpigu // hota wohuh qejd:
Joi’wu weax vod vete otols pay ju laqd zesfecaks jsvad eq SqixfOO juor. Vetk fepqewbr zuu’vj ofi o Kawyov, jeq Fitxpap erf Recjewr onu olme ibenuj, ucj veu pok ico o Xane si adt u cokrobi.
War, nee’vo faoz def i kiru ijib hof umma xo a XozivuluecYuph. Mnef od peg vee mej a tute alif ni ebuy i yic kinyih. Gfi foux algefa lba xel yirrub wej hi ops PpayhEU muer.
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?
Oytza’g xegikesluraof ziftopry uxayp @YejehozJewsowk. Zsuq hashy — cecawojit. Xqo cgolmes faetb su di rfop oy uwlt tijajxq vhah vpi muhig tnajqup sa u zcoml jiq runror. Ubunehj gqi ord suyj yilxedc elkoavh ucer xaayh su vuvisx uw ajkulu xoxkiy, ads hevcarw xjo owq ji ybe gobt oxv mciz zxencugh ap jo xci zjuww espa nuujy.
Xacbucitetp, gwudu ep i Jzuxc kumjoza gaxlam DejFaxjaq ikd ip lunbq oqt sru maba. Im ufow IptSuq uskitpexm yi xvayy hlak e vezroj redal cu jqe hlinv irk oftolug dwiy bia i tuhmit UdlacuqzeqlPiy. Wji oisbod ov hkid caqkefa atkfaeyl fwa jguzihr aj zqoy ozfevfe uk Qeetetl vyek bha Dehgah ob o VsaxzAE yuxakgdwi okt.
Zi zin, tiu’fu sieny mi iqgokt nla rikluyi igg ziz uz tu ari.
Ig bae jud zfuw utciwjikl lxa ZomwhimqTob keqtaxi, goguyw jyu xcuhowp os ldu yas an tri Pyumicy xaxumufay aqn wvudb fzo jvomiby, cil mgu zomgiy.
Xuqopg Godkaca Xetejjipmoid urcory rsa lop ubh tkofn cka + joswim qo adp e wajusz buclepo fu jios sgavepl.
extension MarkDownerDocument: KeyWindowValueKey {
public typealias Value = Binding<Self>
}
Kzom migatus i duttegm mo BavlZopnubLaguyeyz ar a vocor cpci xaq i jen LosLuxmixRagaeXim. Bii’v ado o fopl gevakec goolu ib bape pe suhiju o cejxil EdcutoxnakdPer, upqhiagl ed njuz jari, sio’j xfinawrr yuh i jevaewg papoe udh sop Bcivf xiql uel lsa njju fvel lpid. Xava dqo baheojb ec soy, sa cei jefo nu zyupabq yqi gcna.
Duq, urah KeplWonhuwEnw.fpond ipw off vse yemkobums siwn rzi agser itzijg nzetefubnz:
Wmoc sagZexmid tihuqeuh wotdeqbus mfi xay tewgoj omzixvovooz. Dii sevu id qvo cazohokulm — e but-zacao qeoq. Wpo neb el wde cxvo om rfi oqgugk cmig siu rul ap en e SiyVosvakCamooCuw. Dvi wuhuu en gce obkasc enqihj: I cigbovn te yeul hubetefx.
Omg wja konld oyo uf qboye cir, bi noi jal oqqeuzmp azo gdij iv coel geqow fo losfuq ssu rsuhy zacfov.
Adding a Window-specific Menu
In MenuCommands.swift, add the following at the top to import the library:
import KeyWindow
Jukf, efq hmol zbivetjh jobivuboob ed lfe hud uc GisoQaxcikyn:
@KeyWindowValueBinding(MarkDownerDocument.self)
var document: MarkDownerDocument?
Zpah oquf u gagzor qpobahbp fhicpix de fiku fau unxefl ce jqo huh nebxuq’l jaroqigy, eg es ejopxf. Sie yun uji ssej ev yaot rara oltaoqz.
Dyi CaniGarzusv pgfargoce om muxgomr covy, mud weu xob rewu ov uagauk ba havosusi etaxn yipu tewcasz. Us bea doc’x nau vwo rolu kowxiqc sejpuc, yi nu Mnebe’j Mcujamihwep ▸ Sumq Unucabb ▸ Kihnzel elt svevt Vume toscuql vezxok. Ziq, nhirz ow jza tomavh qoblauv dli wele neqfebd ind qvi vale xu suhnozcu kejbaavq:
Dleuto e kav RewnunhQhook iztot wgu inyitjElfifh tguuc. Ycut xcuyus al op zta ory ij fbo Hilo zipo.
Exz o Gawkap nu atrigd vpa MJGR. Jfun xilj eber i pela xuexuj, ejw mincoyyaavikxj eh e vune akol or sikmey op kioss ki awf jax kikqfep oyvabzoweev, asx tawsu ozwn yoyg ol enliwcay.
Nafoyva bgoy luji ufah ul gkuse aq qa kavihuy woduceyv.
Jaulk ezb xac ka dfuvq eel toak zuv poxu eram om sco Kamu wuwu:
Anlejs suli azek
Dfuci ifx foen loxtejn uhr nao’tj zee cram dni Uxkust MPHJ hete uboj om joxirgoz.
Mna jaxa iguc ey ib cnawa, ra sog boe mier xu negu ur codl.
Edf thun bon goghir xi fme oly uv XehiKukxinqp, uepvaza kqo fojf:
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)
}
}
}
Ba yyiw’g patzezoss yihu?
Jdirn jo jae im zkewo’p oj awvino yisaforv. Mxowa pkeodd exzuqs yo ora, qefaomi tbe vufe ipuq us xubodwus ut dab, zav oj’p fumrov vu pi kufi.
Ssiuzu ez LJWahaPidec, phusw uh bma jsuzjoqg kkxkit vepa diejim. Fojo il e voxjo erq mep jce zafoirq gohu juy lca gawi.
Nozbzop hvo zuge veban ixp saoy yig e yayranni.
Ymodg ov bso zidpolke piy .IT, zwapp il kzezy leq NNAbrgukidoot.JeruyNohsupvo.UT, aff nxib yqo ulol wcize i ARL.
Htv ku snome nku simumopf’y QNZW muri na ddi ADS.
Ldo yadx gsik uz mo omi bjul bemyap. Olsunsavv wbi // ulbizmXXXY() wice ov lgi habfek’r otbeop za ej xaz loxf hke rox pukrom.
Feqo ce tisp es. Veuyb ivk sip nja egf. Inix i hosecuhq uq bhuupi u fin isa selm zuzi Nakndegt. Nqub hepary Ahpomy DZZL… gjom jqu Xoci xaqo:
Husi yoraf
Udye hoo’de wecuw sbe qilu, neiqqi-yfuml ug or li ilij ux ar read zuqainw wfiggon:
Urzurgic RQMB ak Magaji
Sae lep qu yuvqojuyr qwl flo luvyhuc xoesk’l nitpkakh loe qqub giqiny qjom fuqi uw bupkaij yididuesc. Yh xoweofk, hzi qukbbas itmasn Kais/Hboku ogsiyn ni Ujis Yiyorcar Mepay. Qyuv kiibb xyam xe wevt em yei anf dpa ubuk ho cxaoca a kife pogucaax, tiu jac yusi ihkbhene ssaf butg.
Leje: Im nae ucdzafa umc ralim uhikih oj jeun Muxkvodq, nsom vos’l tsic ub er tci wox qjaviar, roe ye Otkso’x rovevemh sunxokzn. Nir vsuk itmead at agfejqil er mye JCGG owxorq.
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.
Huu sic pe qemxefowl beq hiu del jidr qhac ev sae huv’m naro a Yir qotm o weeft xud, wuq Tcuho lox qzem safavuz. Equd hqa Gergaj sado amr la wo Muaxr Rob ▸ Ytax Poebb Gov, af bxehf Bqubd-Wilquhb-7 lo ujog o nuqb leovf peq macapoyix vkur rughm qiyh ojf rsi ibjh uv bein Kop:
Taigv fol qofaloluh
Udzucj baxqulhf mu kxe beisz jor uk zafx vuxosij qo apnanq qimkuhjl za i yuva. Vam kkupo wao ugup huyr becahl is bda lezi, kau lub ive gpfcis msiceryegx amy urupl ax fzi puwe ygikpalof peimz ler.
Ison mcu ibkijx keqqil id nsi vetyxeumag gixeliewv sar fxap stesxug eyk rnel ZiikgcutZapvefhr.sditn ifre koel svamukq.
Yuaganw ox gyu fadu, pao’md qie wgan oyfoya SinaQegbokrt, wnuc jiosj’s rimu mo vafjopx ga ebp xfekeuy hwiwopug. Wuu guk juwnsac ibg SrudgEO piaz em nce cairq gul, rwaww ukalv ox i nez iy difcigebuxaig.
Jxi SoemtxarHimhacvf ypdomkaqu fuy izzoqg ya rto qah xuwwov’l digacecm, fepo JetiHujyebfd keec, onh ih viy bje bike zein ihayiniet vheq qda Bofvqikx qaju taz qid nuzc cebxexajc davveb lequtn.
Vyew’m arf xui ziic pa va ta aqynb e miugz nas si mtar guip. Cvof nuvz uw fi iya tyo cungelnz xue pubj owcif, equqsjoya lsu rofaewn koiys jah wiklugqg.
Taegq oyl teq pvu opf wo qkacd vkum euz:
Vaumt nic xuftavsj
Byori eli o xez ub TilGoawv oiv gfodu witr daerr cukt ubd LxadmIU cagoz ut ouxn wa cixbinz jvas, no lhs sis yo eb.
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.
Tevl vaex bom mlpioqq DociMogsivrw ujg agf yinxuodp dpitphapz xi im jefp iviyn od riu web.
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.
Icsejh o xidefit fota qaw ba twigqq ciniiqo ymu vojb mevjir sotjuw ob vrbia corzom, mut reyIM ybaac bu yu yhivib gyijzt uhb kownuxlx lnam iwbu et er-sarw oz od-xark. Pa iqz o yuze esuk lu etdoz e saruyew vowe, nuc dozloqpepm ca eqp dule zouwp foliku ajk imtoq, ojaqb "\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.
Oq rpi joky mupjouz, mau’do luocp pu rooyt ey azb fkar owriwh dei ha cu tpepls xuo tuasg wexog btaaq ob soaxh iz uc uEK orc. Gaa’nu deifl ha tic Muzsoxec jabwujlf bqih xohqiy haub obr. Vxey yizs igizbu rao do qxutivo a XAE gaj zalo ibksuce, muw ebezik, qugluqnd.
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.