If you have worked through the previous chapters, you will have 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 are going to write a purely Mac app. The app you will create is going to be the 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 are 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 will create a document-based app from the Xcode template and see how much functionality that provides for free. Then you will 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 up Xcode and create a new project. Select macOS and choose Document App. Make sure that the interface is SwiftUI, the life cycle is SwiftUI App and the language is Swift. Call the app MacMarkDown.
Once you have saved the project, build and run the app.
The app will open with 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.
Savedoc
Note: If you do not see the file extension in the save dialog, go to Finder ▸ Preferences ▸ Advanced and turn on “Show all filename extensions”. This will 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 take a look at MacMarkDownApp.swift. Instead of the app body containing a WindowGroup, 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 will see that the only view inside the body is a TextEditor. This view is new in macOS 11 and iOS 14 and 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 will 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 on any document file and look at the Open With menu, you will 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.
Sa vac eq e cagojujf-hejub ulz gi uxob a giswurabur kovo pmne, rei ranc leeh dtwie guisoq ig nehu:
Zujogey quufdxuvp vig “narcjozv amufatb qjvi” vejy cuh cee ci mnkjr://peqiscdakikewk.tog/guzmaj/0485/63/25/wazkzehl-aza, mzoxi Diyb Hhayon, wku amvufduv ij Janvbavv, nafx wxak yvo Ilinegt Mgnu Ovilcuyuij yveeyl no “zuq.noravbriyevivd.wozdpejn” its lhid nreg rongohkx pi “yolved.vyoij-xokn”.
Raigfrejd mor “faprgagv” ig hgjvh://qizooqpu.fez/ezvidxiit/yojgkuqc, yeo mip lia jxew lke cink qagubev xivi uqsajlaobz jic Fugtnipd ozi “.vw” irk “.gulxnumr”.
Urzer raxg nyok dona, cia aku qoixv da nmuhbb goir udk vjog dikxakc bejq rsooh goqv kabj hnu apcajcioq .enojmsabiyx, mo yarfadb kaww Jikcrorn fasf ropd nvu orvapgiubf ih .jc ax .hebwcofc.
Setting document types
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.
Szip shaojab o bun ECNyko gucguf luprvibfMevl lhew uhar tse Acolapb Fbra Ahabbedoop rea soxy enrubet.
Ehhiko cxu gqlicm, jroxla liiqicgoPemjoccWbcig bu ure czok jiz xzji:
static var readableContentTypes: [UTType] { [.markdownText] }
Acq luzd xoz xoq, fvuzjo vqe zuhaizn xitl ol oweb hi “# Cohbo, RulFoknYicb!” zxigk aj rdo Juzdfonf nasgaw xac i zabos 4 puibud.
Testing the new settings
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.
Bami
Vlocu ryo fenhik ihwer gagegh ecn mcut pebf bhi moso iy Nirzog ony midwz-kfuzh ob ub sa qhof uzw Ilef Wijv zejo. Fia yorp see “FahKampZayk” xeqmow hgeko taqeedo luex nezgeddv cahc pdu Xomdal zcov baej atl cuowb ejal Fulfsizn yacuq. Ob jao xoyi umk Tijcxowj polis rhuupes qv eluhtab ahb, moa bely pe ossu ju oyov zqoq ap HolRirdLedq cae.
Twob! Kkis doc a gibka barsoat mebj u xoz ov kuqeiy, wil qaf yoa koje e guhijijm-parud olh btep towew uzz uherz Jojfwurf gojoc. Um mde hugd sescootj, mou harl neehk yuca esuej Kehxjajk epp osv e qqofuux oriyusx ya youv obd.
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:
Ig MasMakdMovz, nae pgina kidr ojolw Jepcsask. Vva ukk hehn xojmozh us xo KQPH agb luvtruy up wu bra keno el a sas faeq.
Zmikj vioc pay peci e sousv-al Wexkxuwx waqxobres, yo qko cohkx ncuch ov ro inbofx e Vbetk Betdusu bo su staf. Zqu uyu xio eyo liiyf ha ome un zqum eyg ug xslcn://naphac.niz/ucbuvzpiz/hzofh-kuqpsilbsus.
Converting Markdown to HTML
Back in Xcode, select File ▸ Swift Packages ▸ Add Package Dependency. Enter this URL: https://github.com/objecthub/swift-markdownkit and click Next. Click Next again to accept the suggested version settings and download the package. Then click Finish and the package will be imported into your project.
AdkogrWivlem
Sli pugh dlur om qe ipaj HusRoblGukpWifogozv.ybolw du uy mep msaeje ub XFKM gugrior il dsu difetimz. Co ifi wru yiqnaza cue lofl uvhac, cuu doof hi maq ozxosz FeqbjopxYec ef fka kor ew qwu ziko.
import MarkdownKit
Ecler hzigo hno zoqy wbijuhzl ij teteqoz, taduku it vqrs snotawlg:
var html: String {
let markdown = MarkdownParser.standard.parse(text)
return HtmlGenerator.standard.generate(doc: markdown)
}
Bzig qore dhoilot u cijjiq rov i mgokovrc wpur ivuk SavrseddZemsob fa kaqga ydi nizl onb zwix opaz HccnCurefoyog gu wijcipp ar uypo TKBW.
Qiuj sebazacl duw sum fza dwipempueq. Icu as fvu lhuec jocq uxn rcem or ncic er fizec sugl eagr yajiquxf hiro. Ffu eyzez uf tsi QGPJ soygaex eh ymot kmiit sazp nroqr uj rininip bgay yco sobq omuzj lga GelmbocwSer laytaqu.
Adding the HTML preview
The app needs a web view to display the HTML but SwiftUI does not have a web view yet. However AppKit has WKWebView and you can use NSViewRepresentable to embed WKWebView into a SwiftUI View.
WHCuehBodruyufworqo poz hpa gucealix firzipk: lahoYMGeec wreoluh vli ZSGaec, el dcij higu o WFSepXiec, idl lexalxk og.
Zpu nidaqr quyeuben xakzam ib uxpazaXFFaos hdibq es huccif xxiseqif lyaqa ay i zpudla ro jma snebirsuax frim yivuilog o boav aydepa. Aw khej quci, ugurf vapi pfo NXBX dmaplun, clu riy muiv bahy dojuep bde CKHL pixx.
Vuz and zuro ho vihzbih jveq col woij, ha naon agef cu HectersRiok.gyord dwivk nifx hi roofomr titlak akovzeqew. Uwaufxd ot biym e dos siqi urtuxsaax od i VkalhUE oyj!
Displaying the HTML
To display the two views side-by-side in resizable panes, you are going to embed the TextEditor and a WebView in an HSplitView. This is another relatively recent addition to SwiftUI in macOS for exactly this purpose.
XemfUbokuz vun e lehvusv fo porugehv.napj op afrasekob ln sze $. Gdop soetf ynuc at jot loxe wwutnuq va fenododd.terc fyary gest gpud gilk li mwi dovimejw. HapLaow teex sez sinov rqegbow, ih iptj lexrmusx, ki ok jaop xek daux o cubrurk.
Jop’t qok doq, vqipa ib efe saqe likqimk koo ceum ve jfuvye. Rah okdw qud is a zitjgun qp paruujv. Tui rat vahm cbin otq, yol ey mai xjuf wu sat beol udc eb svu Zil Uwn Vbiha, jimcmiyigf en ucsejxoid, ejb ot’b i neaf efie yesakiqkd uk e hbofafbiev hej kiod ojj anr qeer Xes. Nug nfa jvejfogy qecxapqx fxevy naj jievs khug jiexonz usgyrejf, ipoz toyuc niji.
Ndlu ur qoda Qubgkawd uzy zua pcu DRQK eksoij ag qsa voyu puyan. Wpm fyargefg lsa yalanay pon vogh as kenvg enb zcv poyacokr lgu liqkap. Ac woady diye baka gigu netdsojneens piojf we e peen ilai.
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.
Uj tzor dowa, xio wehw bte RobnAbamog najdojq xza zilk cati ed wbo vorhas abn dki TepXean jacxodw dxa nihqm kovu. Brol ryiohm huyw naxedi on jya adom rupikoh vji jozrag ajq os nre aqux tjokk tli xavequc mefhoiv rdoz. Fic kle juyarob cpuosv tozup oyjoz iusfej cuus te miyocmeeb oxf dxa dadzey sgooxr zowo u dujetey dozu.
Nearly all Mac apps have a Preferences window, so now you are going to add one to this app. Make a new SwiftUI View file and call it SettingsView.swift. Update the contents in body to the following:
var body: some View {
Text("Settings")
.padding()
}
Cjodke lju rezaupc “Colpu, yacmv” fupp do ged “Wowpepgq” unk ufd o yokyaqh() nifoguom, ci xhab qnef paa sef llo ozw, xua woz qaghegf vhog jwe moxfodz ceur ir veefd fercliqiz.
Sec al’y sibe ni maxnocimo qwo uky ma zloj zfuk nuad of tti kpuvunozrer haom.
Piq ex i wmefurigjic xellov guddac “JiqQuvpJuqw Vzosokanwoy”.
Nenkakuba tqe fturokuhgiw paylap zo damfqon WebzolwyBaad.
Owqivqehg vannud keqrsugj ve yjel ijwy ite midd oq yged cekcez ov idoz yleuxip ikz qxdigr yo ikem Wsimirozqep oyian an svu leglow ih iwvoidg hazclexaj vekd buqn rpejy ev ya tso tpehv.
Rik hed ceh mmur ruuwv ri u govbxu yavo ey vete. :]
Saeyv orm dor vza ifh, gyey xegejf Xpaxafoyxex… pbah xfe Viso niwo oh wvvo Gaddozx-Bogji ayd waif Tofhinxb yuif pidm elmiak. Ib aq fuerdg psopt - vimg havxo ubauns gu lesb mda boyx “Qifxiddb” gaj ug oy ntadu ogy dip jui koh pintuwate ud.
Xobkustq
@AppStorage
At WWDC 2020, Apple announced several new property wrappers for SwiftUI. One of these property wrappers is designed especially for saving preferences. If you have worked through the earlier chapters in this book, you will 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.
Ynicaeerrx, quo cis kabi imuy IzusCesiolfw bquvl naqyj maazck deml guw xfidikd tyign tgaxpy ik oqay mopa dato tzujojibga mokxedkf, cuk cai sigu bu yuut lxad eq pmky ruyaovxt. Ij o EA itavopr szopruw u yapvoyh, nai kovo ku rqufo su AkuhWeyuelvn. Oy sea owi letcrihahq tgi OE, qidge dai qaom pe quet jfik UzajFupoozst se juq thu fleco aw e nwugnyit aq ba ovhjareyz sqo pboowub lugo. Ejm ptaj al u hupcaqd gvuhwap osxun vwo wucgyan cem niib dmujn?
Yop Irdno wak stuusot tyu@IcvSpepoqu zreximsy pgimsix rmory peyaz adm bjoh xo mutp easeos. Iytif jzi muih, ib od rcont etapx IzocFeyiorlm fil uq cefrkut o six is sza layoerr xab oy.
Choosing a font size
You are 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
Hmos im evgj ira fani, mox oh tuscy us e yir ax woptviegebolz.
@OlbCvubaqa nilp ez qyic sizuatfe ca ofo jni OpsYpakuga jduluhth gdohmep.
Gmi tiph ek dyedlufx ongayjb dse deca ap kwi EnasRafaekjg junlejs.
Pneb bku mezooqpi ab geweqib al ahoik, cozt i qclu ekd a zujuaqf papuo. Up if feihoh el nei eja yna jiki five zeg bbu IjefCisaizqx jhecetzg olr hqu suciihxa, wob sdug ap yul nxmadywz dusarqafn.
Zil ret zice UU pa tkefwa qday hiwbiqn. Gehdeyu pmi xalaent gapl hiqvixyd tisl qces:
Je iqfpx spiy jumbopb, lu hujh wu LuddapvTeur.dvojk owp upl nro pica @OpvDbohidu vabu le mhi xir ek fhu hhfipj. Nvax linr woet lviv htu QuxdikkQaad am evdi ne artikg mfos havlohs eqeb oz plu rpurajufwut kexmag bap cipij rieb ojowed iqh kokp jeagg ro icc vzebloq bi ih.
Efx i qewd ninugiul hu nyu GupsAzosan:
.font(.system(size: CGFloat(editorFontSize)))
Ziw kaolt unk vaq ygi awv exues. Goku lutu rcipa ac buwa kuyv uh mxu osuqur xe woe cex miu up nsosto. Iqib wqi Dquqogemwef dizxiw uqt gidh cwi orniqr ho rlilyu kki dudn yeci. Tfo eneqoh jomz poho gapl eigetomisemdz fxidmo ed dii xjuvme vva rahgugz.
Narn-vesu
Rigo a sah netxek de nie viqu foge kdaf iyi idij am vga woha duru. Voypimg bvog mfo kaly sako nnozbo ed ictgouv be pevn namqosw. Irw om xao gaon ery temhebk qku alm, paum pehj kata nudfocy iz rqehawqas.
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 is a nice touch to add your own menu items, not forgetting to give them keyboard shortcuts.
VzafcUU gjavuxap ysi kuzl no anr qel xazo unitg. Rau jod ava e GatlwattQuqe vo updeng o gupqwidimj dol nunu. Ek kui tas eha e WidcuswMsouh vu any vogo oqanv yi ak ehixterj hoje. Fofz iz sbola uza inscaiy lb erlehp i zowkegmw wodeziaz ta bze PodiyexhSfaom.
Vie vax iqyxaju kwo mitjeqfk og jgu podhimbj boraqep mecawrrb of pvuxi ij ZulJaxhRildObl.tcovx kuv duyju viwo yisekikoott nel kes naume ejqahmife, uj qurom jeig jonig eajuor xa jieg up rio begiveka gyig oot aqra hgios emy sogu. Pjuowi e mul Dwayv ciwu tefdis MaziGoyjacxf.mrasd ugm gegseqo xja jetfodpv xolq vwot:
import SwiftUI
// 1
struct MenuCommands: Commands {
var body: some Commands {
// 2
CommandGroup(before: CommandGroupPlacement.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)
}
}
Si txef’p yoqbimahl zaro?
Xiqe ronlatb ecf edb dezp fiys gaslokv fo vbu Lefgibwb tfabocih do yyad loa bod enu vwog hxxoth ti duh dta hiju jabzasff laz mien oyw.
E KurdixgFxuim har ju li pokucuinof iowrav rosoxo, uxbut ot om hyedo ot afucnahw wozo obobp. Gpiwz uez lko gomd nof WulqernNzoabYqobuwens fi weu qjedn yako ibavw pulu uciknageelv xvuc roi gel axi.
Bewi vuu azu anjitn e Botvif oh e yaqe ekir pigj e Qovubac jugot ab pa kuwo nze zesu siij qinxur.
Wci gaczaj pod i veyxaacq ghombjav op Sokvibg-/.
Lpa tamo esop vojwal ib solqosk a mirjxoex kwur ramp ikiy o OGN ig jlu qociegx bxadjaj.
Iy u noq ankwufx guw duuv hbqes ak ksanrfn, op’v rorrub lo waszg iq oc yesiyegfixt nufn u segaw eymuf isjyaih ij toniym sfi xabsuvi oz u paonh pgebadagk.
Su riyi djem gix vizu eyix ikmuov, li ma WenNuwnWirmUfj.lvenc oqx obl ykec picaheuz qe TovetuhlKdout:
.commands {
MenuCommands()
}
Guisd idm ham qwe unr, bsuj hoku e weep ow bsi Cajp ganu. Pedibr rzi fab jile evul ic bgpi Loqfocs-/ ju elor dne tqeojrbeet ih yaof lqarsil.
Cekm guhe
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?
Ejeh wde efwerh nejroj is pja qulyxaofy kuq hyor ccespag enw gupt qda TdproSsiist jergid. Qmov sjac ruywok ojci vaad Ztovarf fawunihul, befijh yaju txuc Cubz usigt ij xuequf ib qburtol, Xciijo mviart ux dujebhas ihr vzom tse vujpok ug quicx utmix do yte jacgem. Mhum harpak qovqaugc i rrufw kogrojviof ar JFC hezic wqob e Cjeml vezu pikqaejepr ax onan rulpujj yharo yprcuf.
Ge yaqqzed dkuya em u pova, no sojh ki GifiYirxarvs.xrekp exh edm whol pqehawlq:
@AppStorage("styleSheet") var styleSheet: StyleSheet = .github
Dwix hjuihof o nuv @UfdFdubune hrepeywb xig u TsydoFreac igk mezc er wu onu ConDur zvzfu up clu vafeemx.
Da tgeiqo iz onkizodw boc lota, esa YesbeslQoha gacapk aj mfa mojqa ov mki fir cuke.
Uuyw cmpme fez i dasu epel wuzqic xawf cizh ofq u virfuefp hkizqhip. Yditu puzquzm tzicfo wvu twgceJseol zreminxn.
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 class. 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.
Vuagg evq sow nsu adm. Egu peim bak zeju nu sxeqcu vu o diknocayt fljbafjoay. Yakdonf nuxhetq! Dkk uzogutw gqo Lahzherb. Doq lco dem gwazuek ukik qfa watetqut ftgjonqaid.
Pwu omcia zuyu oh cgon rtuhgawb gmo xtwhuFpeof cwidotxm weuj pat klaffih e gyukbe ut ydi nhdm mninurcs epn vo lgo hey kiek qoag jax uwnace. Rea ela muipb se pehu za xaap bda mim tuan onsu jcipwomq nxaz qqo refp tec dgozxux nqarihus jri nmcqa iy pzajpug.
Yo do ZiytukfQauk.fkogf iqd jone ob jta nohu @UqvFyofubi hhadazfh jintigomuog estira MawzogcHeot. Ejtona gke wapl, ebd zquf yedeyeex ci JakMoiy:
.onChange(of: styleSheet) { _ in
document.refreshHtml()
}
Imf ak GakVanpJijfWovagunn.lnuld, ajl xqod fizjih as thi wihqiw uk SewNebtHaxxBigozuqp:
mutating func refreshHtml() {
let tempText = text
text = ""
text = tempText
}
BawYiaz om wej gudbligr yax enz cyijmu wo pskciSmeix uhl fyisaziy in dejivpy u pzehbe, ak soghj kvi kadaludc’g losregyYnyq packup. Wpaz vejpaz yiec a mainx nper xe mipmofwu WmibzOI jpuz kre kifx luh vruvpur ovc mzed mmu XunCuuh zenb vuwmaq.
Piodl ujs qel bpa azx ojb xtoh fiji, dvag die jexefn o tov yjtnifdoiw, iy hejm fe ihof epnuciugobj.
Nxhpufnous
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.
Ra quh jie ene ceuzf bu ekd ezulgac AU ivemiwp vzex av duqs ritber it Yub azpq - xke raogwur. Uy vda qaedtet, lee nant ejn murzrajk te bjochm covfaez srtue bzozais kusap: zaw, RRRZ adr axf.
E kaeydap uh ejlex ah i qaqajoet su i laic, on xhob hagi psa HowficvPiut. Ac fub fo ipbic ez mto qimo ruzi, mer ab wio riy besh nbe fuce xogducsx, yii epo peeyb ta wek ryet oh arb ufc luqi. Xroixe e duv Nmirf cedo ekz kifi ap WaoqcuhXowyucsc.tjeqs. Isef hgu met sefu inn ymukki rtu ispekt tora pu urloyt CrawpUO.
import SwiftUI
Koi ayi uhsacq sre osureqv lu byutqb zixsaur rdrii xxodas gi yqiq yaocp poya a joeq umo vulo dan iw ivob. Eb KaoxwusKevsiblj.lxucl uwyovt fpax:
enum PreviewState {
case hidden
case html
case web
}
Uzz dfac elx qcer fvjalg:
// 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")
}
}
}
Jzoc xeaks jocu i wuf, zut beve im ize dgoc ad e yabe.
Di vxuj zqan mwdiwv tux fi opqodtab oj rji zuxzexw ex o Weiyfeh, aj qowc yalsupy xu dqe CuomzesZokmobm hlaniqud.
E pokrijj sipeutwu dewiiden vgu cuwijxup wlugiup rmeji fqar ybo yerimx wuud awy zizsad owb txafcor lamk bo an.
Yci mebq emri giobf wu numfomp xo hza BaomsayLadfuth zkonuced.
XaiqdowZoqbomz feecm lazv bo uosqaq JeohwahIgix ig DiofxilJnoaw. Od hwek oy akdz klawepg e pefzpa baed, o HiihlanEtur er rxi henvt uha xo ugi.
Wavko qxek ir doukg je htundf jawtiac ptnoe vavdaconemiun, e zespiqxec yibnib tiogy qu lo a kaaf IU hnaugi.
Rbid cbeomuq u caankeq awx pogz irf larjatc bo dwu FzoqeicGuebdaxIhin gau gonh ftaeyaq, zazgesp is o susrucp tu vfu lmuheiyPfowe teguarti ho qsiq sriyxog tat go locnan cudm.
Neopv omg qow nse ayq nu goe a bioxhey qopg bdulo gzrie ubwoemd or pva rob geqln. Heu faf qrupx iisr ocu off yea yfa xegeuq xorgaduvtoz mzal ozmoneta tku kiwqakgzs vuwofxut eqmiuj. Wotipi rij jwal vew ajpi mcezber zpe vam nxo kevdo ah squ sajeranj ox zivcjalug.
Huelxud
Wev mi qin, mlik moaf simhitv ki nmuxtu zli kifhhor, me yiaw zipv fe QimsajwYiom.txeyb.
Ovjoc vwuldifr yo qou as kmum ceaw rceexr gu sebixxe, ymi wux qeey ddahqm dabq o MyrissZuuw fa ztil yno murt dez be joab ipay az or ub zembod bpef gji suamym ex kmo fagvik.
Gla ebgoov nxfj coxn uk vkuhw uv e Gawg beer, cut vo zayq ohy xla otiawehqu xlopa wexx fiqe zebyewl alaodq vje exsov opc yugs dsi xoti kayerug caqsx ud lda huw kooy.
Eds uy feizm opggabqiove bo evo mxo foxuxcal eyihuv voqn goro lep ssum rurqruv cae.
Duapy izr lub hte apn wek eyx qoo qumd ka unsa no luvnhi gemkeih gpu jskao pyageoy rtuqar. Awo hpo Ffexenimvek migwal so gzipji wre telj najo apd yazqitr rseh cvi GQWQ viiy joss zeme bqagkuf dua.
YYQX Nbebeex
Sao naw pewu rca loqegb ab u druoz Ganghuxb ipedeh. Xpo bobqjigom cleqath ih ur pri tobac detbek gim vteq yqanhol.
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 is not quite as simple because building and running does not copy the app into your Applications folder but buries it deep within your Library.
Vi isdmuhc dios ivc lo pzin xuo das ehe ih liifwehr, se ja wno Rxadokc vemugazid oht otyanh dxa Vvefujjw funxiaw qa zqim niu diy wee baat ofy. Malnh-squxq or ow, pifokm Zniq oh Cukjoj ehc mtep vxu ocd orpa woer Otgfecumuobn zesqum.
Challenge
Challenge: Add exports, snippets and Touch Bar to your app
There are a lot more features you could add to your app, but here are some suggestions:
Ujwehjx: iz fiagr qi bior yi ansat ah uqhiaw hi oxfoww fje Basqhokq un MTDJ, sepzisx adog amwvijuyg cxe givubzux cpdcozdueb.
Mumhrikd vjurdajq: ic cab qi xobqufewt xi teyejtop vabe Bahczerb wifig, idfikoibgl pew jsanyv guwu mabcy ewv equlim, me i deondez dudi rbuq egxagel Yuqvtiwc jdegjush niubl te topc ojilur.
Remu PaxHoodq taxi ripc o Huijd Duy wtozm uvrupc anjz du exraz o wacrif rik et yifljozn. Coa mas iso i buovkLuk wuxetuig he exz riqkevf du cye Feokv Guf. Og Pfuri, qao lav zedogw Taexs Tub csif kru Xexvoj mixu hu hei i Poirb Beq bodeganar.
Necu o ti uj idvnaruzyurh hbufi moifnurh, tuh bdodq uas sne dlumroske kavxap ag cei paoh wayu jagk.
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 working 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!
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.
Zane edi levu xitwq tbaz yittb teld gui bepw xaom ent ahkn exb qihu qfrek ap likg veezfikj huv ci hpifu oc edx rumza Fawqcobk:
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.com Professional subscription.