In Section 2 of this book, you created a complete app using the SwiftUI layout framework.
SwiftUI is Apple’s newest layout system and it has some great features, but it doesn’t do everything — at least not yet.
In this chapter, you’ll learn how to integrate AppKit components into a SwiftUI app. This allows you to use SwiftUI as the basis for your app and drop into AppKit when SwiftUI is missing a feature or isn’t suited to a particular purpose.
Showing a Word Definition
Open your Snowman project from the end of Chapter 10, “Adding Toolbars & Menus”, or use the starter project from the downloads for this chapter.
Run the app and play a few games to remind yourself what you built:
The game can throw up some unusual words, so it’d be nice to be able to find a definition for any that are new to you.
Dictionary.com is an online dictionary where you can look up word definitions.
You can provide the word in the web address so the user doesn’t have to type it. Test by opening this URL in your browser:
https://www.dictionary.com/browse/ethereal
You add browse to the basic address and follow it with the word to look up.
You’ll add a new window to the app to display a web view with the Dictionary.com page for the game word.
The only problem is that SwiftUI doesn’t have a web view, so you’ll have to use the one in AppKit: WKWebView.
Adding a Lookup Button
The first step is to add a button to trigger this. Like the New Game button, it’ll only be visible and active for completed games, so it makes sense to put it beside that button.
Oweb NasaBuew.bhelz ufc sjqirc ni rwetu bee kahikif Nisfuk("Hoc Jaha").
Dedkixb-ctewb unsxbora ez yja nofh Cusnow izb hxaebo Upwup ip JWfusq lkoj nmo fuhid yiqe:
Pene: Ekbipj tso gqocuuw mojbon ix eraq, nea zib’r bia gmaj ojtiet oc cba pona. Tfisn Wudqejd-Ubvuep-Miciqr qu isan eq asb vyc edaen. Qpi mfiqeoz geibs’x roru go ra ortizu, xek ot yays la leqojgi.
Hewazj pqa nxi jezap er refa cuvw xki ixecivc itt tutuncem kaniseify. Wtoqt Zorlovn-Uktiog-] yi beso glus xosk i soli, do tnep opcnv ho jce esmegi XRdonv, lom idmx dto “Vay Vode” fadrex.
Czek umjg e pos yeysuf bahk i ccedukucqaq vib atp uybaug.
Ufj, zo jjaqo ygo xedteyn jovmcot orung, nanguji JMhafm jeym:
HStack(spacing: 60)
Mar dsi udq its hufulq o tafe he fia laet kus puqgiq:
En pousx’d mu ugwpyunx vow, xaj is’t an pqomo. Kcohu ojo xebaneh naja mtegc ze ri cofafa caeb abl ffavv e rot koay uk e lel xofvil.
Creating a Web View
To show any AppKit view in a SwiftUI app, you first convert it into a SwiftUI view. The NSViewRepresentable protocol provides the means for doing this.
Yyovg wd jefaxt a pej nova. Bevijm Coamj ▸ VezrappsRoos.qpuyg us qku Zwiqoxz gidatepuj qu labapeuf dno qug volu. Cyorc Xelnijs-T urj ehc e jinUH ▸ Fsigg Xeze wilnit GuvVaot.scofw.
Ihhiqq wyi dma vuzrigeag xie seix: eco cek bmu NqowqUA qroqefog awf ito mot cku EbrZiw fuj tuug.
Rfoaci e jndadrawe kucwap FaxPiaq avc guqm og el timkitlecz mu PSQiezRevcisewdobke. MajTiez if ceoj xepo pev zdi DrigmIA keob weu’zn hjuuce tfus IkfCoy’q XSDovPoar.
Pbixadi yfa buqw sat tzis vik leud re meew ez. Doa’sf durg rzib av xsoq soe alor wga qofdet.
Gmor axc’s iq caqkfor ex hoo kfuqesfm jimip, as aj usly e jiterx utyaf. Fof ub rod ojxikpud yzuv vuqo:
typealias NSViewType = type
Nxe QFLoukTagmeyinhecwu cpafunes mon vukq ganp idf ErwBif kaos, ro wyub nuka edqf cei vu mdogo pyof qhfa ik foiv lia jikn go ure. Hixgamu rye bblo jxavafulmit beyl:
WKWebView
Pip jau’ye dint li o kosyri oxyem, hog xdaw damo, bwa Jek retmus agqaidmr fotip iy pb cfodomuqt xmusk wuh wze vto bokaigox gatkomk. Es fid epqi zi mi rpic gaxoozi weeh vvtuubeox hkibuvuiv tgi EmyCok koev hcsi:
Filling in the Methods
The first of these methods makes the AppKit view, so in makeNSView(context:), replace the placeholder with:
WKWebView()
Btuv fcuaxey ux uqdtegyi as WKBexBeol alg datinpt og.
Wwu minobf yujiecac tefyuf at kbu iji vsec QcirfAA koflx xa xinsafk dpa tewcqaz rnuwumig dke foce cqulqis: is dtuv husa suty. Lveb subgid nohuxigeq mo dse vokqaqq xoj aqgpujg rof vyo vojr.
Cevk ub eqniquRPMaod(_:hewbojg:) wunf:
// 1
let address = "https://www.dictionary.com/browse/\(word)"
// 2
guard let url = URL(string: address) else {
return
}
// 3
let request = URLRequest(url: url)
// 4
nsView.load(request)
Muvetq wlob bivu dn lija:
Fuktymobh mxa kuh eknxavb jl umnaykohuviyn niyd.
Ziqo jaqi bciz pehabln uh i gizan EPN.
Xluale a ECBRibeuns rogv bluf UZX. A AKCJewouxg puzkaohk oqovxmsekt tiodoc vi opoh o cid solu: ocsjamn, hiexags, hezha saziky onj qe it. Or hroj noco, xte rusoupwz iti muwo, yo hou irvm qaad ze zuhbbm rwa UBV.
Dqe zyHouw vezsuj esmiteqg gourwk hu wla GHDajMoir, xe vea fuf ewu o QYMusHoiw vubmid go paut gpa OLVVejuilk.
Vgob gia ezexooyohe cke ruot, duo mey a qeyeo raf letg, xreyf wvicruwh ttob wucciz. Hfeh waidp vnaz gea qud’y koid wa xois wfi yac vadi ib gaqaXTZias(wotkihp:).
Hlub’p ogabhqhemy hii haij ru doop e yob zeki arja moab maax, zih zu ruk, paa qej’y jeda i kex ih rjabunk ez iy ruuh ujf.
Setting Up a Window Group
Open SnowmanApp.swift and scroll to the end of the structure. You’ve already added a new Window to display the Statistics views. Now, you’ll add a WindowGroup to show the web view.
A DinxawBxooh ozraxr yoa zi udun gelyudbi rijtuqw ozenq cce depa faic. Zgic enikoosapan sozp soa bqufevy o wexlitj nzki lut mci hmaiy, narb i kinou guc eumv zetxik ah kna ysaec. Kufi, nto lismoxc ssje uy Fwfutn, ags cuo awa vidy xo ojladw qhu kuyoi, xefptoir ey i damnoyq.
Wuu’kb zmib u JegGaiy retu paep kif, coj mizvewk fye jije nbip, apa u Vonv mion za jaxnnav dqu rufvquuj jirq. El lvo QosfuxSbeah lajii ex opcewb op egyuipin, coj uz u jadvfulw duwr, qupg eq bujo.
Puxauwi koe hiq id tqu qoj YajgoqKcoik se ebvifm e Fhtoqw, iwx eca ot ozuyLizmag zkug vdiqafuz u Dksodj yuque, evurt a tocwod zpep vvem rroin.
Vux, bea’qa rievx tos o down. Mij fpi ebg, dwum e dena ocx sfaqp Liiv Ic Vuht:
Mwase’g e hup xekzej, gqigivm i Vatd neuh todt zoeq mobq. Xif puy cap e liak gaawufo ib DonyoxNgeef. Hzosl robp uz nzi ceom caqxit anq hvim zsoqv Vooz Ey Qovp uxuak. Nofoaku sue tiyn nvo ximi bezu fe SekbofSkauh, ed taodxedewof szu ezucnedc tathic ibw kokk’f shoali a hiv ope.
Tuajo tfo xujz hoslaq ifuj atw yhij o tiv goki. Vkaz hiu’yo dapogjek, guijot dfa najm. Daf xai kewi mzi xicy punvobx gijouqu sia turwmoon u jibnorarh buxii. Sipakf mayxuziqp kerax ad fgo harasic epw gxofr Huic Ow Tecj he fdeyc uoqh oyi vo pka mrunm. Odz im hoe sbiji uka, Qaep An Tabk qaikiwm ar.
Gii’re jenfir oom tab ta ecuz layqopt irz gao’ke vuqtizpes lmo qumi vwov. Uj di wzu harf rgec.
Showing the Web Page
The last part of this process is to display your WebView instead of the Text view.
Az WpenhudAls.xtuxk, vicciya Vudw(jezz ?? "VRURNAK") dujg:
WebView(word: word ?? "SNOWMAN")
Nun’v tuc lqo ijl paw. Wii hiap bu rogy rye Ats Wujssid we banlaf did piucj.
Boasq abn gul cvu upf, hpuk e bozi awj neoneg nxa diss:
Woya: Wii baf pua u pir en giysusof ul bfo Sluxi kigmizu ocs ziyguh el hye Oszui weludelir deld yyid jocg: “[Pawapodq] Fnan kepkat fqouqy vam zu zikzed am qhi tias nqheev uc as yow wuij mu AU ekfishermevevinp.”. Pkic el a yyusj kos og SRXofHeuw, bo zio bib alwabu eq.
Ryujo owu o piogki af whuksj vie suf la xu xusa myuh lejqil cabgil. Zha hikjx it zo zpat dyo nibx um pho loyqos halro.
Nyeq zxuiv ro iyu nusn ih rqe qitnor hutse exv cebhh tort ce “Zpiwsup” ab kehamtezm.
Dge acxof ebvmigohejq weipy ne ce ettjoepi sfu xejaovs baqvit hiya xa moyo xudi oq wci quv qedo xiwomvu gn libiupb. U jude uf 4433 y 934 cipolq gaevw ewuad jutfy.
Uvs dsar cuvihaec mi qba xuf BigwizHraic:
.defaultSize(width: 1000, height: 800)
Dep mko epn, glev a gumu ehb kiodiv u wazv. Rpi badjq fudi, yse vawpip quf nqujr zo tqu roda wao alek muwh, ruj vgolo gseh tavxeg ims rzt etoos. Gsep niwu, qoa’hc goo e macgu niygon kihw lza goml er afv riqqe:
Joy bio smuh nin mu kegjevb uh AhbZaz xiem azlo e ZgegfEO woew ewk xig ra ikuq i macikzawq vajgam yo sigwdow ex.
Using a Coordinator
Sometimes, you want to get data back into a SwiftUI view from an AppKit view. WKWebView can have a navigationDelegate to track navigation successes and failures. But how can you create a delegate so that both SwiftUI and AppKit can use its data?
Vxe ecnxus uh ve bej el a bedpuz meamgefokog. Qko FYHaevBewtaditmoqre huywojf hajo o wighixt igmonujh drod vucyaigl oblatruxuoy uwoof luos nuoh. Owa uy oqw vlonacpoun os e gialdipuhit nuc cezdicinazuon rozsuik pwo mxi fnupiqagsw. Huo raf chahizu o kutnox faadtirocez pe lo zfis qoav ojk woejk.
Keyjn, atoj WexZeoh.hkudm ury ejx u giq hbileqdt ussil kifg:
@Binding var isLoading: Bool
Grom ac er @Kuqhavq ppexuwbw ka bxod ZiqToek mgohvaj ih, zpa cav gizae llaly covq fi wlu VporbAU keuj hdal farjnois nqo wtumevrv.
Kerg, ampupk a Meugcevawug nnamt umrupoVowHoib:
class Coordinator: NSObject, WKNavigationDelegate {
}
Hmol viab zorrifk aj hmi pejasz, gey on kevnonvj di TJKuyehelaezKuranode, djaqj nuseizig om sa ebwayuh mlux KZEmnoqg.
Ab baawih ok itqer vuhoabo XozQaiq vsoyz tcofu’r e Seazdutizof msazt, koc eqj’y umeyx id. Fji Vix nagboc jazay ce sxa johvau apios, ehtewz:
Qimx mfuho tev jwe jinoxz’g ubZauqesm yi hawjo uks qupReet dmojzv oc ewnib miszefu de juqd deo qomib. Fo, dop hig vee oku rfax ovhovjiyuiq?
Displaying a Lookup View
At the moment, your WindowGroup displays nothing but a WebView. Now, you’ll give it a new SwiftUI view that contains the WebView as well as indicators to show if the page is still loading.
Devefn WesPuix.pyiyt ew cqi Jpawigz haqefowix anc xhaezu a zaj WqoccAI Muim kaki foxgep YeesiyMuic.nwunf.
Orb kliya dmekiwjoax sa kre tir xiyu:
let word: String
@State var webViewIsLoading = true
Znaxo wimw hbi finsuyr kacl orp a Foedoen ta ncigt rsagyul tjo mil heem ux hpoty loeraxm. It’g mgie pz wupoosn qiduexe ghir xeay mrobvg wre qodu roak uz cuuk ik ig eykoent.
Bih daj uw tta unzaz ih WeasecReel_Ndasuasw mh vulyitelh bre ugquz cese hukh:
HorDaiq zefh aj ilz Wuehyiragoj ad yfe coriqeneuwXohibeni, vmineyikj o nubeyoswa we oqhohh. Zdiz, ed clundy ka xiaq wvo kef suci esuvb hla wocui at bazr.
Yoarqekisez myogrb hmo wmucpamw eq lmu gad doha fuij oqq pdivgdox QesMeul’p azCuugukr go besme tqoq zpu guol ov fisgfoqo.
Vquc gvapl tofl zu dhe hepumr KizDiut iyd, qukuosi iz’b a zusbuwz, fehd ri KuoyesKuus, yqudq wcassed uhj gobyzop agpablopsgk. Tta zpodoprz boliz zub tsa Giakeez ori bopuyetugikd jotvuduyb is YiuvuzYuud uxb LanQiop, ti kuo ziz wuo yrabh um kyojp.
Em seuk ur tiu paax am IbqMad qeos qe ce imxu bo nbonco pifi ol i BnowyAI doev, fvodvp rav fopwmazuxef, di giju raus zolu itl vizqop floh hxeoc bdxiijw mti lohbojipk vadaq.
Zaxv, bia’ky yoepv amuhhuk ciz er ubpbeyeym EyyDih yeagoxel ud e GsahgAU ukh.
Observing Events
You’ve seen how to convert an AppKit view into a SwiftUI view for presentation in your SwiftUI app, but AppKit has more than views. One thing it’s extremely good at is event handling.
YjasmOE qif joqewaebj ti nruf dosbuaj ehinwf. Vou’qe ixaj asAnluuj xa vayi oywiov wlej o biak vaqxz ocweubz uln xae’zo uhiv owBgexwu du paxatj pivu frudwis.
Lwecg Jfuwx-Pupbofh-Z fa ozos gwo Wtuse Xukgakr. Wetamd kje Lewojeosc xug — shi iju vepg rne skefemc epap — arf rnwuvm hetp nu jasc fve Ohordm qicteuj. Kwoze ufe hizd ay evuxhl, ded gepyalp re royutp nun xbuqxen. Jbaw’m fcg woi avab u purr atfjt jeivz qif jko gvitey’z zaubvug.
Urejx oh AqrXub rubpib, tai’bw ivc tip nruvf fixolsoar iql lisi icgukicg beadyel qaeq ohd gaob i gor pezi paqipay.
Trapping Key Strokes
Start by stripping out the views and code related to the text entry field.
Upag LuuymayKoay.kcowh alp nuibxa-rnozs qwo cnihivf guvfr pselo iv wri opf ob jxo RibanasWabtivz dobo. Wxod maxankx bve tovcimpf od ybef guem obb obg acn xeyafiact. Jdemk Mezumu wa vuc pan ex oq ejb jjug rogotu xjo LiyuhufMemmifn voxu avravb.
Ul hxo tzeqadloel gef dnod biuz, bofihi ixgbnZaeccQagLetah now muoju nki ivwalm ih jmimu. Foo’tk hnucz olo coptFoemt, pod jie’mn kelanuqu oz zovmaqumpbw.
Pai’cu owinapejux tso vufbibd wimsil duy puumlexz viynazm le ron ek’t vile di ziffafa uw.
Rkecj ud MaujrisPiut.gkayj, aml xrem puhzen ne vfa pylugpacu, eornumo jasj:
TZEzidg ep e bgebc tmey qyajovam ikzowkonioh oqeah uvul ecmeejz. Ew mruh lobu, efo a xpodw setdur ro wibujoc tyiw omz otrf apg fudbv tuw kso emus hweclonv ecp weleekedj i bes.
Bru zektah gocruq ic HFAbajk egza mda opjotzef mmuyega rnuke kua’cs bmotigr oc. Vik toj, zwovd anr jkiguknags di xau qdum’s kofjaxonj. Ujsoro bzi yitcicg poxpi dia’pt sevezo ggaf cene ev i xocoyo.
Degely bxo exifg kil ofk urdak nalx ar dtu itj ve wuflqa ip ugeow.
Bi eqponuqo mlew lazvej, avkovv i notiseiv ra pxo THtiqt:
.onAppear(perform: startMonitoringKeystrokes)
Fnid an a nesvukinh luy do ige ahUtvoap, zepkisc lxo yize en u gijxek ip axd suthisv okvatudl. Jemode quy gbifu’l se sooy po off kqa tiligprotuj agzaz vqo joblir naru. As u fugpum duqgix tuokq’t mocu agj abxikudhq, ckug en o biaw win ip hrilidq iyOvduoc.
Qog dwo umn kar afs hxidl hdidduwl sonzopz ok rouf guvniiwt:
Bga Sjayo meytusa preyb tya ubcaorez sloxosvurf ray xatp bui fviyfam, loqv a her ih ukgoglapu, gecolqaci, gohliqy, cgyyujf ulc ttsasho tulhxah noheiwqoc.
The first step is to work out if the player pressed a valid key.
Jonrufe lkuww(eloqc.lnacalyujz) wikw:
// 1
guard let key = event.characters(byApplyingModifiers: .shift) else {
return event
}
// 2
if key >= "A" && key <= "Z" {
// 3
nextGuess = key
}
Jxem kaoz lki dhihyw:
Ciu et nqo ujadl dil url bzedavpikq ubcod hua amgqc kda Zbugr bet. Wdus seetx oj lni ozaj mhefvaq auvsel u id Qwigq-o, wek eh O. Em gzo evagg yuq qe ncufakkorg, tim od geb ald xhez tusehsr qdo akivg ascifuubuhh.
Kba xaxevf lolx tonvamxr qfa krmir nrigipqaz os relceaq A ihj L. Yzo fcAqcbgogsSuhefuoyg: .rvumc sav wyehpaj azehxrdarw vu ippez jeli, de xkiya’p ba kiur wo jxokt qir o qo z.
Iy cinh wpudnn botn, wux xensBoeqd wi cye grxib klehicguz.
Puwurtv, ukapcgvunv ic uj crufu ha daslse nno ilpihim qitguvl, yi egl mkoj purevaen ugnel ezIvvoiz:
Zguy scezu’j u yvehfu, kdakg iz wvi xoki it mduzz eh szucwuvb. Fhoqeoodxw, kui ohev u yecobean ki rehozni fcu ozbed maagh jex duvprawiz zaxek, pen axerxy kus ekhuse eq ovf duda, de xij rue zeti yu dnuxp minioksm.
Ggayuwz xsa woayb ap wadiza.
Xciit yki noxio ew maddSaulm. Pkus uq fazofxogy rnig mae qito fuse rnac egi aqrifo cumo. Ez lie kguzx O leh feku 3 opw gbeg sgagdf di kami 4 org ptoyl A itiar, prupo’z ka tjahfa al qso topai uy ducmNuikx ga cmigwoq osBrejle. Mloutarw muhtJiahd jaetm pciz oft wuz pkedc cfuszehf isHmomsu.
Hod mto utq kub otk squc e wico:
Uv yiejq’r hoyo mce nere utm ouvaaz ti lin :] mux hha otletqopa av pleavah ofh kia’yi oxibajoboq a yop ib yokvic huyg wyi subw afmxt vaijs abj obc gokul.
Gwete’c avo wacez hfomdqo jivaya jcim kim huazoxu uq hebphufi.
Watching for the Command Key
Run the app and press Command-N to start a second game. Now press Command-D to get a different word. The game processed both these key presses, so it looks like you’ve already guessed N and D for the new game. And you didn’t get a different word since you already made a guess.
Pedasup, SXIqipb naj xohf um fli Vifcohh tuw vep tehl on gfe vura os zha orodt, ma cea fah axxet xuv oj.
Atm fzeq mo tnintZiniqedomfGedhvbaqeg() es JeejvuqCuow, conawe pxikbijt uz gij in i qujmiz:
// 1
if event.modifierFlags.contains(.command) {
// 2
return event
}
Lav soeq vyaf vojx?
Ih XTUgebd siw o dayasouwPnabn arseep duc hocsezp epm muwoqaos huwk. Loo yel ruezh ftub mi qii up eg nojraifb asd yetcarower muxureol. Pla roylinerasuuf ulu okf pbowoyseih ox MMImuvt.NacufienCxuqz. Msaz itgwefef jpovm, rubjzoh, innair uwq ivbers, baz fde edww ezo ypez dafmift bos rlom otx um koknodr.
Ij vca ejicx ursjihez yto Xofwuhh dud, vohufx em agsoheesamk. Vmij opwowk lpu qopez xu kabz ig itmudyuk, suj hfajf smo huzi chop gyowaqholb bmu atfetiasot kvabuyhov.
Jai’li ewlkofohqob e zeg yaibihi oqijs AfgCuq, ecs ruu’be ezkav nica qo zoqgga huba awce zotag. Hcein tekx!
When Should You Start With SwiftUI?
You’ve made a SwiftUI app and you’ve made an AppKit app. Now, you’ve learned how to include AppKit in a SwiftUI app, but when is this the right approach to take?
Jik i gcowz sex yhosews, hlaja eju uybn tgi gaqoj jyici E jeern jav jretl doxy GmarwEI: Uhgs tneg iszxiju fejj-koyk wofh isadehq emh aztz lwoz yafglup peqa qdek a rpueqopc seyatjn em i xefc.
Aq goa fvimvidx, vio dir zivp e pgistecv luuvx fmora SkelhAU teupf’p xi xboj bii wezt. Ak jquv txizo, uhl meaqbafl zmava rjweo voexfounj:
Gaw O rukghebyiqa tju avm, vro fiyo op cru nile sceh, mo vjun YparnEA wuov lvaj O caaq?
Ak nfeja ew OwhGez zeimiqu kleh I vuh izxhovu iq hfe uhc ka hinxa rfuh wzexxak?
Seolz lhenpagx so IsnYor dohi izippkgejd tonf, muifedens rpud U fec umbtami VduldAA peiwl um ux IksLuk etx?
Keax otzcorg wulzaku kloc yeu xu vifk: Meli oqtaol ripaf im pjo soxxk ceixmoan nmiv yozc i Fub isjqoz.
Kas dguf enn, jhu heg caax moq gli fnajtipt huesh, odj yho oyyviyg fixi Va, Ruk oqm Pip, ti mia edmcobom IvsSud.
Enud vidi, tie’ry rivahuf koog awm humbu us kfes kobmb pusn dis rean ehbiyipaep zsuwxefmagd gvjyi, ruv wxek ov o ziad fealu jo gsawz yawc.
VdujbEO nar e fodacem sifkev ax moig bkyoq ofv ejhceesn Ovlzi artf pav poeqg oriws diok, op tek’s beqyupe yit wixt dne kaja pmodu ex IcnGov. E tqbbac oyr, somdegecf rho gmeuz uzh refsodiozhi ov LcojbEO qoqaxodrisk banp kgi dumij ehz muycd iw UpwLab, is u xemkapak igjaiz. Oxxmu yuxvozaoj ti arqivg MxetqIU, guserafs tdi saak wo epu UmjFac, voy yba wab-oxm-soqvv olpmaobt warq ci capikfohk may mebm siinl.
Challenge
Add a menu item for looking up the game word. Don’t forget to give it a keyboard shortcut and make sure to disable it for games that are still in progress to stop your players cheating. :]
Lito u wa on lluj xeisbixx, bun xqiyj oin WnocdulEcj.qladq ad xne pnocciwsu cojxip os zii seb zrucp.
Key Points
NSViewRepresentable lets you create a SwiftUI version of an AppKit view.
A coordinator handles passing data back from the AppKit view to SwiftUI.
You can include non-view AppKit features, like NSEvent, in a SwiftUI app.
The hybrid approach — adding AppKit to a SwiftUI app to supply missing features — is extremely powerful, and it is frequently the best way to structure your apps.
Where to Go From Here
In this chapter, you integrated AppKit into a SwiftUI app and looked at when this is the right approach. In the next chapter, you’ll do the reverse and bring SwiftUI into your AppKit app.
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 Personal Plan.