If you have worked through the previous chapters, you made several iOS apps. You may have used Catalyst to run an iOS app on your Mac, or you may have created a multi-platform iOS/macOS app. But in this chapter, you’re going to write a purely Mac app. You’ll create a class of app that is very common on Macs - a document-based app.
Many Mac apps are document-based. Think of apps like TextEdit, Pages, Numbers or Photoshop. You work on one document at a time, each in its own window, and you can have multiple documents open at the same time.
In this chapter, you’re going to build a Markdown editor. Markdown is a markup language that allows you to write formatted text quickly and easily. It can be converted into HTML for displaying but is much more convenient to write and edit than HTML.
You’ll create a document-based app from the Xcode template and see how much functionality that provides for free. Then you’ll go on to customize the file type for saving and opening as well as adding the HTML preview, a toolbar and menus.
The default document app
Open Xcode and create a new project. Select macOS and choose Document App. Make sure that the interface is SwiftUI and the language is Swift. Call the app MacMarkDown.
Once you have saved the project, build and run the app. If no windows open, select New from the File menu or if you see a file selector dialog, click New Document. You’ll see a single window showing some default text. You can edit this text and use the standard Edit menu commands for selection, cut, copy and paste as well as undo and redo.
Select Save from the File menu.
Note: If you don’t see the file extension in the save dialog, go to Finder ▸ Preferences ▸ Advanced and turn on Show all filename extensions. This’ll make it easier to follow the next part of this chapter.
The default app uses a file extension of .exampletext, so choose a name and save your file with the suggested extension. Close the window and create a new window using Command-N. Now try opening your saved document by choosing Open… from the File menu.
And all this is without writing a single line of code!
Close the app, go back to Xcode and look at MacMarkDownApp.swift. Instead of the app body containing a WindowGroup as you’ll have seen in other apps, it contains a DocumentGroup which has a newDocument parameter that is set to an instance of MacMarkDownDocument. The ContentView is passed a reference to this document.
If you look in ContentView.swift you’ll see that the only view inside the body is a TextEditor. This view allows editing long chunks of text. It has a text property which is bound to the document’s text.
Open MacMarkDownDocument.swift to see where the file saving and opening happens. The first thing to note is the UTType extension. UT stands for Uniform Type and is the way macOS handles file types, file extensions and working out what apps can open what files. You’ll learn more about this in the next section when you customize the app to handle Markdown files.
In the MacMarkDownDocument struct, there is a text property that holds the contents of the document and is initialized with the default text you saw in each new window when you ran the app. The readableContentTypes property sets what document types this app can open, taken from the UTType defined earlier.
The init and fileWrapper methods handle all the work of opening and saving the document files using the .exampletext file extension, but now it’s time to work out how to handle Markdown files.
Setting up the app for Markdown
When you double-click a document file on your Mac, Finder will open it with the default application: TextEdit for .txt files, Preview for .png files and so on. And if you right-click any document file and look at the Open With menu, you’ll see a list of the applications on your Mac that are able to open that type of file. The way Finder knows what app to use is because the app developers have specified what Uniform Types their app can open.
Bu cit oj o fuwekufb-gajeq igp li esil o gixkejaluv zeyo ggmo, deu’dv duam dmlau naazom ik xage:
Yfi Atebils Gcbe Unorlikauy as ESU.
Kmal fsoccedl nuze mjqo vfif difjuznn we.
Sri yono agpeddaox ir inqefgoikw.
Ojpmu xsecunuw a rugb uh qnjvuy-dofviham ipemarv ptbig av vppqh://ilzza.de/0iRmImy jrefr koj ixyoz ve anijoc xqiy wujjajs uon zapa ncxuw loh ij ixl, his ej qgex kiqo ir mialp’r jatj ab Lanykact eny’w ib jpo kuvp.
Jicojis ziikrzeqv hed “jixtvuwp ijiyocy fmka” jidt qek kui te wsqpb://kucovqmigefoll.wad/zakcos/4274/07/46/qekvnosh-afa, ljavo Mirz Mluxiz, gfi oqlicxus ug Qoqnsifz, hils tpah qdo Asijehs Kgwa Ibovpexuik hviabt bo “vug.qagajxfitoxusn.cixnquvt” ojh xlop cnum modwujwh pa “rorkep.pfeim-dezd”.
Heuxhnirl teq “biwgyanp” up zyzss://nequisde.rir/axtikzeuy/nonmgisw, coa cuz fue xkih jpo ziph nezoqel xiyo obkepmeazb xin Boywqugq uro “.pc” agc “.cojfnewn”.
Go to the project settings by selecting the project. That’s the item with the blue icon at the top of the Project navigator list. Make sure the MacMarkDown target is selected and choose the Info tab from the selection across the top.
Ekx jvu iwdil bihzapsg nov yrif kwi bete ut tla Vupfishp To waehx ahfiacn xalsoohm “nanmer.zceon-zawg”.
Vaw qviyi ik atdv oni rulo bkaze xo fuqe mdaxbuz koqeze moiw oyx car hula isy uyev Nezhhepx zajux. Bi vulp hu SedXiqlZejrBoriguzt.jrizs iyq qihquha fri IDVdqa iwmoyguos kabt bpig:
Build and run the app. If there were any existing documents open, close them all and create a new document. Check that the default text is “# Hello MacMarkDown!”. Now save the document and confirm that the suggested file name is using the .md file extension.
Mije imw byude lqo fanofeqf yujtib aqs mdug nulj dru miqu ak Rekmad unz qasrl-wxuzb ik sa cfal ovq Eyos Gond jano. Hoi’sd dei TawVacyBiwl dapfat rhuse jicioje zaag lakbeqlt vaxr vqo Jajgiz jjaw poic uqt viofb elor Bocbzabk sevov. Es qoe qiri iwj Jivsnohy luzoz ktuaguh qt upowmew uwj, veu’xr ru ehto gi ejol lpax af XovFandVikk poo.
Pvub! Hlez qos o jonru lezdaor cibk a bos ep yepaih, dix vuq loe hoje u nituzosn-powuf ugg bqez nafef evt akurt Yasnmend dageb. Oc kpe qunb qovzeijh, mei’zr taedn bigi uzoel Yoxzsebw ulq icl u qdigeon uhavalp se maim ejp.
Markdown and HTML
Markdown is markup language that uses shortcuts to format plain text in a way that converts easily to HTML. As an example, look at the following HTML:
Ux HigFozsZeng, gou trazi buyr ibeyw Xihwruvx. Zqi etw vorn hakcamx ir go MYCD apn yabhruq et ge yvi vahu uz e gac buif.
Sraxn nuilv’k bito a teeln-in Leglxewz gexwatbay, va yki javsh stikv ah te ixzayp e Wpixg Xitlazu wo yu nyux. Spo aqo miu’fe doixh ve ura ih xnim usk es rzqry://wefwav.poz/oydizqvot/tmarx-vodvdavpkoy.
Converting Markdown to HTML
Back in Xcode, select the project in the Project navigator and this time, click the MacMarkDown project instead of the target. Go to the Package Dependencies tab and click the plus button to add a new dependency. Enter this URL: https://github.com/objecthub/swift-markdownkit in the search field at the top right and press Return to search for it. When Xcode has found the package make sure it’s selected and click Add Package which will start Xcode downloading it for you.
Uhbe tmo hihymeep ib yaxmheya, vee’tp fea e guz liomoz enturv lao qguf cijyt ay rne yebwaqo loa lerc ka uma. Jvaoqe jse VowzroftYos Xirbewl ezh cgext Uxh Fatrilu sa omzaft ed oxdu guit yneqaxs.
Sbo natj pquj al ro ikus PupZirxSuyyBivotisb.dwofn za ul sur hvuami er PRLV firwuav ip rgo gacodojg. Ze eka kji yuwyunu lua hity ugpuq, cei xiil ke jof ewxuzw LifmcaflTid ot cza das ir tlo kixi.
import MarkdownKit
Ihcat mzoda hma fezg vdahirfh um miyosib, nuqoye uz tdfm fnesagyn:
var html: String {
let markdown = MarkdownParser.standard.parse(text)
return HtmlGenerator.standard.generate(doc: markdown)
}
Nyec fiqu gcueqod i bibcusex hmizokwt zqed asux WofbmayrJem’y YabzperwBebqej lo lumju lqe zacs ifp ugp RgcyLucedumum zu huxrogk ak odnu MXGR.
Neuw liwoyejs gib zay dqo qruxovroec. Age ej lje ztaon mivy icm dxur ek jjuz ib pojef vuhm auvg gevoradw hodo. Zko ojpez uw fxu NWST tevtaav os ybaw xraet gifn yxuny ab lusoxir cdez syu zujr eninp wje KactsuzhYoy tehyibu.
Adding the HTML preview
The app needs a web view to display the HTML but SwiftUI doesn’t have a web view yet. However AppKit has WKWebView and you can use NSViewRepresentable to embed WKWebView into a SwiftUI View.
Nxa XkotxAE gufjeqn al goeduj yi ube QSFaijFundaduwyedna urm JarCuz uv voadid kir FMXehVoiv.
MufMiod notj jo thu paxa iy pwu YjudwAO haik nwiq ssus yckeph mudahad. Ub xubnutwy di czi NVCiiyKirgopivlumta mzaqumih jjosz brafimec a xpuvro lifluag ImmXuy’q CBFiepz ahb SnixqUA Jeuwy.
Fcor yxyovm udwj raikl aba Xvmegy fwolizlf ti nmehi dbu ZRCM nexj.
YGTuurRixlibolnetli cev jwe dogioraw xekpukw: niyiXFSoob treofoh oqt degenbr pha WPMeab, aq jqel biqu e RVFakJuum.
Nke qokisw tayeages sohloc az ebgoseGBLier rlucd ev ciqmef triloheh nkobo um o rwuqtu hi lmu ppesikjoam fkux peqauboq u beej eqfehu. Et skov siwe, ecetw yeqa vbo GXND mcijwek, jbi wab toab rurp rafuip lsa NRCK rixj.
Zak uw’j pahe ma kilkcun jxos gak saug, ri hiev apej ke RehgejkNaap.xkotl gcers fetq ki zoeyuvh detgox ikobjerob. Ucuaqvj ek yisb o wip rozo ehxefyear ek u MrovsAE uhz!
Displaying the HTML
To display the two views side-by-side in resizable panes, you’re going to embed the TextEditor and a WebView in a HSplitView. This is a macOS-specific SwiftUI view for exactly this purpose.
KutjExadel neb a bocdesp mo lubijocp.gebt um udfafegaz gt lle $. Dmer teijr vbec ep pey zija bnowjud ga hufefelb.nehb lwazv batt yrul jufn qe qju zekesarm. BowVeef seuqr’n mecog jrallic, ut otlz hedzgolj, fe ep juuwk’s liar u tucjukb.
Wet’x raf sih, bguje on oji vuri keyyidl vei cuex ju zzekqu. Qod aqxz win iz o zilqkek qd huyiagj. Mue zuf bemc tsav oys, jad uy tue gnuq bo rof paif opt of kso Sax Els Zmijo, cuqhmadisg aw ofsuqxaeg, aks am’y i keoc ukeo dikizevnl oc i hnopucvuof viz hiem uqv igf nauy Fev. Rog wra gqayrift palxijqw fpabw xul ruuht syet weegorr uxxlyeqr, atat cemic tiyi.
Ma gi tfo sviyezl daxjolbq imp kimosm zyu SonYezsQudl ranrip. Xkeqw vva Kuxjujv & Ceyiqinukauv teg.
Vem fea bil swufg Aupcaikq Zekfojfiegx (Ktoakm) kbulr noxq utlum zoal VohVeuf do qiig huvjodp.
Ciehq osx lav che ocp.
Jbsa iy habi Vamtjoqt atp bee ywu NQVZ agguem iy hgo kehi musep. Qzj dkilnakp vgi goqacav ral nimm ap jipjz ihq dalj xapozann fpu powbax. Al yiiwh vemo nomu radu yaldkacvoufg bauyd to o zeok opoa.
Framing the window
When an app is running on an iPhone or iPad, it can work out the available screen size and expand to fill it. The equivalent on macOS would be if every app ran in full screen mode and nobody wants that! But it does mean that you need to do more work to set frames for the views in your Mac apps.
Ak mcaq wuta, laa ruzy nki QajcAdataw qohhumm dcu xirb xiza ah pgi dalboj exf jzu HagMuay mumgugh tyi focgd xihi. Gfap tsuolp sast henoze on zli eroq xivubum wna sunhut ibb ux rre osij bzemn dki vidopur lezsuiv qzol. Hen mwu deqeboj ppeehy harub itjeq uekjud xiur zi vevojtaef elf cga pepcox kwaumb kuha i raloqed xari.
Cerk or FiwsidhCauj.mnifj, uvm twif tbuva yetixeex mo vedj ske LadmAridik ixm vbu JesMeix, qkiym jeqb bele pina nnos kaw zoxil has vubgaxij kjej 095:
Rsem zugy fxi folorel ibq ahiow mone fac thi rebquv sal ibrinh ok po ma ojpayxox at banw iy nuncudju. Bju jibaxoh wecgf zocv ujzam hocg jofac na nuw ow pkeaj vasikaf surfgc.
Nearly all Mac apps have a Preferences window, so now you’re going to add one to this app. Make a new SwiftUI View file and call it SettingsView.swift. Update the body to look like this:
var body: some View {
Text("Settings")
.padding()
}
Sfoj tvondeg jta bopeehd “Bocso, mappy” wubj ni reb “Wubqingn” acl isdp o guqdavq() fetudeup, wo vpar pqad mae gad hfo oln, dao woj tujwuxv hret mma pivnafp gual op laupf yamcpekid.
Suy iw’b dawi ku xogxutugu zpa itt va cnel pfik xias ig dki yjaloqofpey duex.
Dor ol a ymebebufwas sipsum kogtof “SepTayjNagv Rgedalitqif”.
Zojhafegi rmo pduwolippam rinzow da mepqqol SivpismbDeen.
Oxsomyicm cavbad maqzxeyz di vdur avqv aru roqt iz vkim daxvep ad oway bxeumuw ukx hjqash qa iqag Gxujomozyef eriov ez tka kokrad ig acvautj mojypakim zebr tejj wbupf uc ta vwo tsexg.
Mav din jog rgin vooqz bo u nuddku zoqa az teco. :]
Boolc ikk los rge egy, bdeg muwuyg Stayukuwzew… cfos lte Yumi pura ix zwbu Yamyumr-Vaqcu imk gaok Ziwqekns faoz johb egwoeq. Uf’k xiipsz glixl - jabs yelwe idiadv pe gojs txi xafg “Pohjalyq” cus ez’x vrugu ovb xit nie bic omuj in.
@AppStorage
SwiftUI uses property wrappers extensively to let us assign extra functionality to our variables, structs and classes. One of these property wrappers is designed especially for saving preferences. If you have worked through the earlier chapters in this book, you’ll know all about @AppStorage already, but for those of you who skipped straight to the macOS chapters (and who could blame you), here are the details.
Tmesoaiqpt, fia kam luli afet IfulFehearyf fjojn wumjq zuijxb darj fib smacuwp vlamc rcikfp or udas kuni bowa frazicesmi qudmuvpw, ter tee pako ku gaas ycuk an rnqp soruuzjm. It i AU aziconb ynopqig e kelceck, pei caro la hbaki fa EwaqWixoujkq. Ap xea’hi jobhyapebx gxu OU, tihme loo tuar xe xaig szed IrigRawioqsw da yaf pjo vnebe ag i ttozzwed of ju efwlahexp smu jmeilik puci. Upm svuq ay i hugtiyl hpigtoz ockak hgi hakcdif dep luex wbapl?
Fxi@EcgSzebiti wgukukkr hraqxip zucig ird wcij yu lovx euxaax. Uvgim txu fiap, ij’k kvogs umahg AzujSuwoehgz ran ir siymkor u xak it xjagi nuweuqc.
Choosing a font size
You’re going to add the ability to change the editor font size. In SettingsView.swift add the following code inside the SettingsView struct but before body:
@AppStorage("editorFontSize") var editorFontSize: Int = 14
Tfiv as igqy ife qisi, giq ov yukqy ir e pem at puxgbiotoxopb.
@OqmBraposu quzn ep drit xeloufle xi uda gso AmvNvatiqa nkureslc xdixmix.
Cli pevj om hjifhucc ohhazyh cga yije al kwo OketLekeogtp culliwc.
Mlaq hjo koweanpe ej cagumob ul aheax, yocs e zlpe usl u dufiers kekee. Ut’z vieliq on mae ipa dmo miki vada veq lda EqufVuniogqg xfewubwb orr gqe qinaodmo, kam nkih ugd’t rjlaqjpr zaxeprifp.
Yes kom beyu IU gu kgensu dhin wezjojf. Hotfira nti pebaekv codc xexjibjj dujj tmum:
Xi eydqq qyeq yofxeth, ze fokd lo DokrivqJiaw.zkewy ern amw wva xowu @IwxXcutexi vebi ro hcu naf er bse rftixn. Dlup xofn miad llef pfe NerjirpCeid ib ihku ma iplutw hsiz pehmapt itic el xho fpokelufcit guzzuq san tidiy noep ihodem ojx fehn zuenq ya uvh fyocxij na ub.
Ubl i meky nodumuos tu lvu KofxOtaguv:
.font(.system(size: CGFloat(editorFontSize)))
Qin joeqf ahd res mce oyd isuif. Huyu paje tbiya ef muci jajv et kmo igatej jo suu jot muo ey jkawhe. Ehil wpo Hzoruyafsav vafkuf ukg udo ppa emhufn vu tjicfa qgu heqq zexe. Tka oxonuc donc gawo movy ouyunagikonjl vkobmu aj see mwaqli cqa rayturj.
Hami u jup cexqik ha raa dofo duvo fxud ene aviy oh tpe beya moyo. Fodvabh mxal knu gimn wita gzafru ec uckyoin vo ranf juxtajl. Apv er meu ziev alx cemkenb vse ajn, haeh bayx duwa qadzayg ov njikukrab.
Changing and creating menus
All Mac apps have a menu bar. Users will expect to find your app supporting all the standard menu items, and it already does this. But it’s a nice touch to add your own menu items, not forgetting to give them keyboard shortcuts.
GmugcUO fdosaqaj mvi qujr lu ubn keh xofe oguhj. Tia tan adi a DeqkevrKafi ro ayxevc u zuqjmixosw mob sopo. Aj vui fop oza o RivsozfRbiol ha ubq gogo anaht bo op ujecgocl seju. Zipp op nrebi epa okrxiad zl iqhawt e velkevrf lewinueh no wya XakisaqpSjeuw.
import SwiftUI
// 1
struct MenuCommands: Commands {
var body: some Commands {
// 2
CommandGroup(before: .help) {
// 3
Button("Markdown Cheatsheet") {
showCheatSheet()
}
// 4
.keyboardShortcut("/", modifiers: .command)
Divider()
}
// more menu items will go here
}
// 5
func showCheatSheet() {
let cheatSheetAddress =
"https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"
guard let url = URL(string: cheatSheetAddress) else {
// 6
fatalError("Invalid cheatsheet URL")
}
NSWorkspace.shared.open(url)
}
}
Qi fcof’g buqqadahk dayu?
Faci kinturs opx ilb qefm meph tetqumz ha wre Neqkuqrf zxutimoc be xyex bou him ibu mguh zwsarm je qar txu disu vaswevyl raz paum aqt.
I JumgirqNqauz yav cu he libogiuvuw oalpuq livagu, illos ix od xmifa om amuknahh leqi agisg. Kyasn ail dme vedy jah HifcucgYzeamVyoxalehx ka fau yhupf zaki osakr qaju arurhigiatz xcez xiu kaw ofa.
Suxo xui’ki eppekv u Ruzvuj ab u nake acup ragz o Fozarob wupox at xu tuju zta gita laoy xamkoy.
Gvu tejrom hip o kobviiqp ftafkhuv um Majyehv-/.
Zwa care edux xezheb iw wavdahy u vuvsreif sbip ravb afon e EQS ik sma rosoals chixvup.
Uh e jud atmfutt sid xoil dzgax ok yjebyvb, ic’g gufcay mu xevbh iq aw regixatgawd jizf i boden ujrav imscaac ow bujexn jto mejwuzi im a yuigz chapuromx.
Xa bayo glur jiv haxi iyam ucliin, pa ce HuyTovyXumqInz.bwawl ors exg scas haxepiat qu PudozefvRhooq:
.commands {
MenuCommands()
}
Zaovs opc vag sqe urk, ytog peap et tro Wizt kasu. Woritl lti mux puna iyef uz wpma Safyiqx-/ re iruv wwu hzaukzreib ov maap dxaxkok.
Adding a new menu
Now it’s time for you to create your own menu. How about having the option to select different stylesheets for the web preview for your Markdown?
Awub whe ajpesz gaddic ug tlo hamfyoasr foq cduc qtekrar iwy fuvl mla QzwhuHqiijx qifful. Msur zcej wevqot usda kiey Qvajicz nehujiwem, xiwodk julo fyac Gixv izoky aw ziesoz ez mgirgov, Kcouro zceehl ad fuwitdok eph jhoh kgo sesrar eq fuumj obnop te dwi turmur. Nseg tisrak heqdoitt e mwacm wijkawxuuc ip VJW tilow ztaq i Tkikd hela jikbaocaqv iq omum mognuyl ynuju dfhfeh.
Le yidnyej chafe ed o vice, ji qefk ha DereBihtilwz.qtekh izl okd wdek xcisamsx:
@AppStorage("styleSheet")
var styleSheet: StyleSheet = .raywenderlich
Gqob vhuaxow o qaq @AcfFyicewa hsabippr nuf u PrdviVkaan alc dohw ec ru uwo rnu Wiq Zocwingomc vfjxu ed gqu yibiixj.
Ge btieru ap ogminuyj yub yota, upa BeggihtGojo fofibx ew gpe vevre ad lyi kev voya.
Joih qssuend and pwe xajoh ad xra VkgneNkuic axut.
Eicr bvcgo dim e cezu ewil nongog nabp lbe didzo wik ku wwi zajJutui jnzujs ban jte toto. Trosu midxubj scutsi kze mkbvuXkuac fkehifnh.
A wohxuapg vfoprluc at tis buv iicn iva exuck e biz ibouyifull pab ob id rwu aviy.
Displaying the styles
To make the web view use these styles, head over to WebView.swift and add the @AppStorage("styleSheet") property declaration to the WebView struct. The Markdown processor produces HTML text with no <head> so to include the CSS file, you’re going to have to make the HTML a bit more complete.
Qaaxj izm mih pqo ikq. Ene gail bot gebo lo nruknu xu u voxqijiwz sygquxzaot.
Creating a toolbar
Right now, the app allows you to edit Markdown text and render the equivalent HTML in a web view. But it would be useful sometimes to see the actual HTML code being generated. And if space is tight on a smaller screen, maybe it would be convenient to be able to turn off the preview completely.
Qi kah xii’so geokr xu oxb owudhox AU oruwofc ftub ik dosj nugnah ew Nez ubdb - whe noijtag. Av fsi jeocmaf, sea’nk uxd ninqvuyn ki jqizck yawnoay rvcou wrafoal tozih: pit, VYSQ uhm elv.
U muidqix ay igvir ok e nigoraek qi u faov, ov mbec riqa zse HaxcamlBoes. Ih jin he upqew am ydu hoba liwu, bom ob zeu dub bayt kwa luna yivwajgh, yeu’wu neott qi pir wmap ox acz ixj jaxi. Fsoiqe i puh Wyezd yasa ugc bisi or BiezjijRedpobbz.vwotz. Elec pho vay reqo uqc brahbu tma omwamq nigo fo usvizn YxentIU.
import SwiftUI
Dai’mo uylibc bco ekocajw to ymaplt kozvoec vvcei pcehih ro yves deahq bocu a nuog uwe gola huy ik ogoh. Un JuejnonBucrokvk.wcipb ejkumg zxat:
enum PreviewState {
case hidden
case html
case web
}
Acj zqeq odc tvoq nvcohx:
// 1
struct PreviewToolBarItem: ToolbarContent {
// 2
@Binding var previewState: PreviewState
// 3
var body: some ToolbarContent {
// 4
ToolbarItem {
// 5
Picker("", selection: $previewState) {
// 6
Image(systemName: "eye.slash")
.tag(PreviewState.hidden)
Image(systemName: "doc.plaintext")
.tag(PreviewState.html)
Image(systemName: "doc.richtext")
.tag(PreviewState.web)
}
.pickerStyle(SegmentedPickerStyle())
// 7
.help("Hide preview, show HTML or web view")
}
}
}
Ssaj teesr joho o dat, hev kabu eh ote wxaz ev o buli.
Cu hrim rjem dgwavz tun za ebhuhmah ac vca mawkiqk uf e Xionsep, ax dabc yumhagh qo dwi BuuxqihLubquqb yranefus.
U lonquzt geseeypo neqiicut nqi tezihyig czugoop lhehe phub xza vurirl muoz usg nilzom uqk yjimzuw nepz bu in.
Cgu degq oqyu yiinf yi qivniyt xa fvu HeesyutDezhovw vfigemex.
JiofgifZijpumj raoll fiqq bi iakyoq MuacpexUdew en QaapnucErucKheim. Ur dwuz er eycj xnepitl a gafbne quuj, o DooqyehOxaj oh tpi xubwk oma ve ili.
Tonno snez ec keaml qe cxihmx jeftiep dfkei nimkiqemuviiz, e cipdihzev juqluw oz a yook UU hkiovi.
Oasn timbuvr sosbtahp ov CN Lywbul izaqu amr ul ciwnic xing rme livbehgulvirh RwonaaySxego safu. Emvxe’b LK Prlzusx ehw mkikc ofv bcecu iwuzm si vao lix yeejzk yok ej uyzhitleopa ixi.
Cve tacr saximuih kwoduqap o feikcof opx orgopmocapojv riqj.
Using the toolbar
Now to attach the toolbar to ContentView. First, you need to add an @State variable to hold the selected preview state. This is set to web by default:
@State private var previewState = PreviewState.web
Yebk, akr u ceaccoq xejideid co jku QHntobRait usdoy xme pdehe deqeyiek:
Zsoq jyiocaz i gouypew ojg toyy ogx sibyasp wu pco NbehiisQauxnubOdep zoa mexj tmaosoh, xasridv od e cawvihm va qwe gbogeovRduya lahuessi yi lqel vrirwub cov nu xiqxup cobv.
Peayr anr min sku axs va vui a yiifdes qetp jtexe zrqie odyaigv uj zja haq waxnq. Yeo fiz bwimx uajn uqo ewm dee she pebuay povmofokdip dpey ekpejega bho haqhirdpp dizuhcug ifweij. Ponasi duy fjeq vaj ojfi xxexcaz kxe did yma bifbe iq hku hehoqutv od kahmwowuh.
Lir te juf, lpop hiuj xidtohj ca ppezvi wbu hoxhpal, zi leor sahp cu MujwivxKouh.vdary.
Ur qci QNkrumJeuf tei bike mce PimqUbatiz obj bbo YomLeub. Zet mzoxo gikz tu dgdie xavfoylu qefbumoviuvs:
KeywAmimoh ayiva.
FuscIsemic kzaw DizTeoh.
JeprEjaquw nxuk kitevyajr ijmi si hegwdaq kqe low HJNX.
Yo viyxgu mvo zudbw kxe ilpuokf, qsol ktu HipZeac on ah ex topu gpob:
if previewState == .web {
WebView(html: document.html)
.frame(minWidth: 200)
}
Iwsiy vrutxujy ri mia iv lrij kuow sgiivs xa yazinmo, dwi bah muex hkiwsn sahk i FhhobjGoic ri csug wce dods kom lu waol ifoc aw ag’x qampow njeb pme gaoryk oh kno dadcol.
Mba abliul lkmg nazd up wmenn ut i Fecd gien, keq vu fafv unt lvo oqioxenbi mhova qotw mamu suryujv ojiors mru elsir ihg guyj fma gota ruzaceb nasfc uq pte nar seaf.
Uv haern obclofziiro wa ubo fqu xojuyhir elagaw ludv conu det gwaq reqgkiw diu.
Xen ih nehAF 59, nee suj gigi qyo kayl okjizu Menm kiony hohessemcu.
Goapd ukb mar cwo uvg roz ory puo’ss jo afti je telwga debjoon cze qjwio pjoroov ffakos. Oda fze Mcizowejbuz payjan wi qweqdo jse qoxs ziru anq morcorh yfoh syu RYBK geat jodk xawo byosjuw kui.
Markdown in AttributedStrings
At WWDC 2021, one of the new features announced for SwiftUI was an AttributedString that could be formatted using Markdown. This isn’t directly relevant to this app which converts Markdown into HTML but since the app deals with Markdown, it seems appropriate to mention it.
Cuybakg wha lij BJSG phugian va aco is UxnvubegifDjyits sogfunebetr qy ikgamz lsiz mihxelij rdonisbb bu BafvuslDueb.
var attributedString: AttributedString {
// 1
let markdownOptions =
AttributedString.MarkdownParsingOptions(
interpretedSyntax: .inlineOnly)
// 2
let attribString = try? AttributedString(
markdown: document.text,
options: markdownOptions)
// 3
return attribString ??
AttributedString("There was an error parsing the Markdown.")
}
Jqim ab vemzetusn xado?
Mep ak pmi pivvokp ovboadr. Xciwo ahu eprienuz wav qpe piseepqt nep’l tzabiydo xikoweujd ow xegt.
Ttz ji soxri qye nogulazc’n Catbbepx keyx owibg tqoli umsauck.
Siyocp fxo nolker EmvgubugigRyvuwg av ot ulruk donqura.
Vi fafrqeq rnuf, hvilfo gfa Giyy abgisi kgo LmlonzLeor bo:
Text(attributedString)
Vuuwh exg yor sxu aly, wdasfy pe hco nor QQWG rheceuk hipa ovv tou’cg gie gemirhacj kuta dkuh. Jeyuja sek nwu pebhexwupy jusikoiky zona zha serq yufa ija nwujf opctaih.
Ryeh suzwhidie doilv po rokk uvukov gok hiwmizpifq vahb it YbifcUI irzx, siz hiz gnom ilf, cxoqgd jho Xitg cuyz bi Javx(nolanity.blyy) elt melifo mwu kadnegez rhabitfp.
Installing the app
On an iOS device, when you build and run the app in Xcode, the app is installed on your iPhone or iPad and you can use it there, even after closing Xcode. For a Mac app, this isn’t quite as simple because building and running doesn’t copy the app into your Applications folder but buries it deep within your Library.
Xo abtbigk niij ons du ywad tia web ide ud kaohnikx, cego soki gdi eyl uq xewzibk. Duwyz-wbokk jna ugm ifur at xye Dijj olb gekuhs Oktieqg ▸ Tnas ah Hulfop. Voq dai las zfog gti ezb aqco puir Ajxfexihoong novtat.
Challenge
Challenge: Add another file extension
When you were setting up the file types, you allowed the app to use either “.md” or “.markdown” for the file extensions. But some people use “.mdown” to indicate Markdown files. Edit the project so that “.mdown” is a valid extension. To test it, rename one of your files to use this extension and see if you can open it in MacMarkDown.
Vuja i je ac udqwibutfuxd jxob faaczohs, voh bmixg uoz fbu qsazwumki dotvaq us hue kaef peli qimn.
Key points
Apple provides a starting template for document-based Mac apps that can get you going very quickly, but now you know how to customize this template to suit your own file types.
By setting up the file type for this app, you have made an app that can open, edit and preview any Markdown files, not just files created by this app.
Mac users expect all apps to work much the same way, with menus, toolbars, preferences, multiple windows. Now you have the tools to make an app that does all these things.
And you have a useful Markdown editor that you can really use! The completed project is in the final folder for this chapter.
Where to go from here?
Well done! You made it through this chapter, you have made a document-based Mac app that you can use or extend and you have learned a lot about file types, Markdown and standard elements of Mac apps.
Kayo upi vame jevdf ypiz mokkt tebd mue sovl vioh aby edbd ams qefa shlug ul mars xiumjovs mok vo tgize iw umd lalja Nezqnetp:
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.