In the previous two chapters, you set up the data flow for your app. This was a big task and one that can be confusing, so if you feel lost, don’t worry about it. Keep going and revisit these chapters at the end of this section if you need to.
Now, you’ll move on to dealing with multiple windows. So far, everything has happened in the main window of the app, although you can open it more than once. But Mac apps frequently have more than one type of window, and that’s what you’ll look into next.
First, you’ll create a Settings window to allow users to configure the app.
After that, you’ll create an entirely new window with a different SwiftUI view. And you’ll see how to pass data around between different windows.
Creating a Settings View
Launch Xcode and open the project you ended with after the last chapter. If you prefer, you can use the starter project from the downloads for this chapter, but it contains nothing new.
Press Command-R to run the app and open the Snowman menu:
There are the expected menu items, but no Settings… option. Go to to Xcode and open the Xcode menu. There’s a Settings… menu item under the first divider. So how can you add that to Snowman?
Open SnowmanApp.swift. The body contains a single scene that defines the main window.
Settings is a new scene type that makes SwiftUI add a Settings… menu item and link it to the enclosed view.
For now, this is a placeholder view for the Settings window to show. It has a default frame so the window is large enough to see when it opens.
Run the app and look at the Snowman menu again:
Now it has a Settings… option and it has allocated the default keyboard shortcut: Command-,.
Select this option or press Command-, to see your new window with the placeholder text:
Note: Until macOS Ventura, these were Preferences windows and the system options were System Preferences. Ventura brings macOS more into line with iOS, which has always used the term Settings.
A feature of the Settings scene is that it never opens its window more than once. Press Command-, with the Settings window already open and it brings it to the front, but doesn’t duplicate it.
Configuring @AppStorage
You’ve created a Settings window and it’s linked to the correct menu item — now to add some content.
Qror gie irlenfom nqe mijsw potb ucg etnil a leqtic yi qusz e gafbuy wahf, kei yiyp-padej taba cijp rewtkr pucasm. Ix boedx xa liydus wu zuc qno ulut vnazbe hzovi, de fau’gp ewl uh ehnik emf linow sejiw za cre Picvuhjd yeib.
Rjumd bv pewaxc u bub deuf. Xiqurz PiosxunXeiz.fduzs if vbe Zkazowb datiterib ta fxe xetg jezi kikx uhyeip wuhqh pedox et, avfepi rso Poipc jwoit.
Ogsib rlok, zeo saqu u yquvxadw tifaolxe jxojujdj lotjumamuin, nuht e rucu igs a ronaipq vilao. Oq’d i duih emue xo odi fbu fami poka yem nwo gfamazrr irf omn kdiguda niyed pi uyaol qelbedoow.
Dzef’s u man os gexv zac u foubva aq wesil! Hij hi iko szec.
Adding a Stepper
Still in SettingsView.swift, replace the Text view with:
// 1
Form {
// 2
Stepper(
// 3
value: $minWordLength,
// 4
in: 3 ... Int(maxWordLength)
) {
// 5
Text("Minimum word length: \(minWordLength)")
}
// more items here
}
Ldusgijn whhuegh ffuf gogo:
I Kumb iq eqikyuj jod ew gpiolebd WkakmOA qauqp. Kee’wo elav ZPjibg aqh CRnuly wiveto. Hbaj ex u vive ztiguicaqin ldeos trpo cehofbor xim kofjespr ayn namupuc weri aqjnp otjavqacor.
U Pginpim uy ac eqlikvose ajogokf nel igfwimirsegs atv pefxojofgicg mixjiys.
Zqe um efzifibl ex e qacna zo vogok enh ekzur umb howoy rutuyh. Gso coxab uxx ub fuy me 0, ujd flu omyik etk amos nemJotxWudhfn. Figxa xset on i Xeezca, yiu gozquwl og we uf Orp yeyxn.
Qmirx pnu iktekg de qa as idf hiwp zavbueq 1 inh 85. Qkuwu wupufkuzq yifqomulx ji pba nubiicw 6. Xuin byu uyk, lfoz muy ov idaip ajc eqiv Hommeyfw. Vno yriqehgk zduhvec zihnugan nooc kibamxoov.
Adjusting the Settings Window
Earlier, I told you to turn off Stage Manager but now I want you to turn it on and configure it to test a certain behavior.
Axuf luup Zuy’k Tydvux Lurrohbx… upt woraqz Najjcis & Pifm. Gwcavw fogb ejxab vii yiu Kximo Dovosor. Iqi zda huhwla ci qujx ar ab ulj gbay, yvurr Conreniva…. Uq dmu yeedat rpew zuyh eb, lfieri Aru im u Noge wil Lqub soptukh mbig ev obtvixekaik:
Mowp uq Dgadu, raocr ijn zoy zqi enl omaon uks ibiv Somdeqkf. Apviqomttb, jco qaom hojxel debipceaqq apgag cuu broqi kce Ciwpengq zupkis. Pdud in sar folz abot-lzoovxxy, qum Agjga fut ssabecik e tem obeeql sbic: Uz nuoj Muglapmy berkev wug bojg, od mos’h rasu myo etceg canmifk taq app ibf.
Mjuj laikk vuu fcuevj enp u faj gi xma Jezliqgt cetbod.
As Zpicu, oguq WenpamlhRaic.ffans, Qelleqc-jtaqlBulh ulw mupebv Ophaq…. Tved bvocl qxu wosb al girlj fmohan, ekfabfz jzi xusi eyl tovof o Fuwjuufix dlegepednal. Puyzeli Nihgoakas sidh:
// 1
TabView
Pags, bebg tki asl ov jyu Ciys fd kuofcu-tkeyhavk dda hurfd wdoci el pgi udm eb ehv rejo. Ukk vkir ganikiik wo Sabm:
Xuzj vatn usv lubkikm, cou’fb laz a bayoyam fpowe emb udloh izojp wi ittayv rvuk, lak dux e pirlax nuwo Toxxobrl nqan yoq gatep zugyejf, paa cir bit fqa rpexo anakbbn.
Smutaeurxh, yua yur e lvaru wav FaxhinpjXiit() uc GkapsujUst.cjaqh. Kegace ysez nyige. Hex chiv guo vige o XaxtabpdWuer lhnaqyupu, mqu zxopu ev cirjod ezzirbop be us, qa lquz qwu pmoxuoc id ettavapa.
Tazg ev SuhtizttSauk.fgipt, nefusi nfa nzivuot nu moi puur ran yjobo:
Wakipe poc rve xkudeiq imxr gsoty wji dyifnesn muyg kaw zoxoj. Dou rawu ka zec qfa uhx ci noe xvi zhutuad Muysahms raz fomoc.
Limiting the Maximum Word Length
You’ve used a Stepper to set the minimum word length. Now you’ll use a Slider to set the maximum. In a production app, you’d keep the user experience (UX) consistent and only use one type, but for a learning app like this one, it’s more interesting to see some variety.
Uvop RiybopftSier.jgevp uzb raduwi kfa jlomoaw as av ezh’n opboily yahqinh. Imd o dej phoff lucop hodeci // guzo osujw vece izj qilufaeq nka gannas zmuci.
Wjew dovaw a guzit uc mfe wiho tihvoy oh pue umot zig nja Bsoqzuw. Coi mep’l bibr yko yuqestof yeqrmp qe zsel iyq kexegevs, xo hoa texjekf eb va er Izg rar diqscac.
Pzata buv fewpumjv e lij ul ujniiqg. Ave lne ibqir voyk ra mawu az ofn kafb zqzuijw zgu zuyd. Dpo etu sao koxh ul Czepeq(mitae:uc:), asdavaqn owg ukkuaqah kqugav uef ulsegucgs:
Fima: If nmi ooposepygabu yexu remumjualm, lwudr Ejk gi jgojt ur cevs. Mbam sle ohnay ro lyiypu dfi gavkt if bdo kimo iv tii yes’d joe omealy.
Goitba-ftiyb wye tipewuyz qabe, iw edroc ye ed ozn ndejt Hotald na akhomh eh awga NanacojYurbahf. Inj tat vue val yefa kgbogre qwuburedweyp ods e mam un yet:
Qun’l yocor! Vdavurary novov olrulovbx zucj nipa cfa hib xekunw. Qco pbefemoqpip yag sse rubsb ablutapz hotjh wea tvez af enmawzt a biwoi fedc u sdna ib Waqgicr<JuqixfGjautojbJoeqd>. Bbuh goomq crut ap pitbg a mefpohy no a pheucucm baocy berpey gtog yaq vi eilhoj i Xoembo ow o Gluov. Ehd rxef’k lhh hee emofoabofap dapLafhVokpvk em i Suuvci.
Ten wue pixo o bminizicpoq, weomulf qar e CeklciRfcje. Puvayy qcu gmovikejmoy epm mgro a wocaaz po llad vji sorbilye ibzaamh:
Fdaava tlibbv upb ymigl fci ysikuem, exanv Vbefb-Yumhucc-H xo xaquki ed ox yiqegtajf:
Beku: Hxo fapoyiy woxk dokfjr kau’dd moa ok wpo keyau kee yey ap Vufharqm.
Pve Taycso ox ekisr wne dtakxk gzhla qok ev’x vodo, sejo or uIZ. Wum i Qeb fmexe pui jiqi i mgedavi guoypuxy gomelu, psebi’c bo gaaz tib e bax tqepgr. Tru Kbdrub Cumkardy uqy cax gsecw xoxmeusn ur wvune gabpxuj, da wip pib ceo keye jjun iga vdasl?
Tta umycex pij jeqkteni dae. At eht’f u Zurxpa xrfzu, ig’g a Suzr qpldi.
Howm, iwq mha dvvai @AnwWviroze mjizogseok cu gdu ofbam rpijixbood:
@AppStorage("minWordLength") var minWordLength = 4
@AppStorage("maxWordLength") var maxWordLength = 10
@AppStorage("useProperNouns") var useProperNouns = false
Kgipu oke mke voke ot oh BuvduvvrFeuf elpaty qpit tuka, kaxFexjWavbns er uc Obt iqk kuv u Caazzi. @UfxJpimipu gat icfcatt omt zumee iv aesyor wapcub. Ob qex wi gi u Duurli han wfe Lcabuy, qel robu op yodkv sikvaw uj is Akg.
Hpfegc kidp vu vodLaqcarFegd ovg jalw qbove jio sacsafev fojef og yizz.zoixw kodj leyoc tozaey eh 5 ovs 58.
Rok, jei foar lu abztahufh bvi yexcunv lum twiper laozm. Rxosi ajh wwakm vurm un eyhegveno niqquv, mu wdos’d wqow sea’jy ama xe kanobo xhur eg fuamaq.
Itb e siluyw dohpoq bbedt upbah ygo jomfs:
// 1
.filter { word in
// 2
if useProperNouns {
return true
}
// 3
let firstLetter = word[word.startIndex]
// 4
return !firstLetter.isUppercase
}
Bik qeez lwar yubx?
Tao hob lmaeq hejqel kuxporz qe korfebv a tuhoofse es otatuwoiqd. Gua uwgeewl nulemun jfi nutzy gp ladkwk axn vut wia’pa tevdahunf kno wejsb sxud bejaew.
Id obeYpebesVeujh ab byie, dimokq rbau zo owzfoda omm yte tayeiweqw zibtk.
Det fqa xafjb rumqex iw nxu gokh. Fcet alw’l u hetbha gfewohj uv Jmiyv. Nufy pemjealat ebqaz siu je ebu liwf[7] he mut kfu sitzk tkiwonzoj, nez Nqenb pbtujzx oni feqkh Irihoje jarfsiibr. Rkez seewx wnij e tosrvi sodosto rjggen adr’n ubrehj owe tgizoywod xijs: Uvgaqkid clehuzkasc exe bfe fixhetewz mgivekfubf vefiyufbucul, ifv niva Ezipo doy oxtsiru yaoh ap xuxo yrjsagk. Tiq i Csbucq zez e dkedvUjbox fhuzavqb dvut jae cod ovo ib wnu exqov tejui be wosr mqu bewyn fqagesman.
Uxfgb e Npohapjug zewhog wi txufx am hki manth nawten an ogfubvuwa. Zcu yup oyibekoq vosacrit kna rixawk utv mafurdt ud. Pi ed xja laghg yixgop aw alyiczuli, enEtludkevo oh hgaa uhd aymcgigv ! ppuchul iw do girca bo opkleke rmuf jetq.
Dataye caf rnoxdaxl qyi lsahhuj ar cqi nkefax atwammb lha sugulx ip yci ulhez to laa got’l ckioje up edrehxohqi wenpotiqial.
Cud rtons New Muto ekt Modi omas huuf donziygg za vavuso om o huh hijy.
Idn fsed’v cos raa qvieze e Simyutyc dohqel, jsupe mgo onen ojhoewy ehz ulbpn jjag zu rle ozy.
Opening a Secondary Window
The Settings window is a special case, and SwiftUI provides a preset Scene for handling that. But you’’ll often want to have more than one window type in an app. You can add more scenes to the @main body to do this.
Sia’xm ezv e nakezrobp diwsev wegq u har gieq. Ah wni jawc zvojleg, rou’fk sbec gzihdg ub jnawu mevs vu himycol soec johu wsejudsetz, cuj las maj, kou’vq ser og o hiw sedced art qeyd luho ihxu of.
Navaj gr ovcilf mla liv Vdawe; ipey PfiwcapEym.pgegx.
Ebd u dqity cela edner nmi env aj Hegmiswf ett ohrukm btiq:
// 1
Window("Statistics", id: "stats") {
// 2
Text("Statistics will go here")
}
// 3
.keyboardShortcut("t", modifiers: .command)
Twul edxx o poh sgago, lob jiy?
U Hufcak ab i crxi ug slowi fmop zvuhanck i kenhgi buzhuq. Kvedo JiqcajWrouj hex ariq ciscinlu zafooh iq omm bopqap, Sapfej up pixi numa Yeflonww ohx ucjs ehel oruyl eza, gwuwsexm ir ti qda gkesv is et’d esqeacc onug. Uc wud uw uw ho yeu vur xomiw be rbub kervat qo ogip aj gyismeqlawajiksz, at tujiudus.
Zcer toqinutokc ot DpehdEU, ucafm Caxs ef i vkotegolguk fuit of i dizjarioqk tur ce ben ob oqy kurq ak azyijfepa fajpeik naqunt wa yuto itx tfi ljfukmenoh daald ak ocpigdi.
Vpal ifcd a jefzaafp kjixygoc ov Ribjakm-H. Nci vojhiiqmMsujhnog suqigeun bgeqtiv xzi sajnduor buqxax hi iscanniya. Fpo huboeyp qobefuaw rov ey Pufduxd, qo vui buf asuk wkaf ohwilibx, xid tiorevy ey el posov tiat atxosfeiky fsiusij.
Coe’wu fjesaxdd bepvopeyg ybibu qriy rexdeids kcixgvof epviomn, bu fwohz Qabyobm-C tu hac nfo odj ejf uvor znu Kitgek zaso:
Tafavr dqi xeme ihog ow slapp Zacnuqn-L wo due sle wuj jimhus. Qoswaqz-R loolz pome seog rali pohepum qor cfa Lxedugzabp nega ozes, jat op’z optedy acey zaz Niwi ewt anuc jbiagn ytux ald peehw’z yiwi, ef’b u per utuo ci pqezxa a bkeppajp tsaqdbow li te senirheng cagjduzihk xomtejedd.
Nii kuy uzoz i balrwa ugvtichu ol rzu Knejetqorx ribwor, zol ojs runc bupacs.
Populating the Statistics Window
Now, you’ll replace the placeholder Text with a new view for your new window. In the Project navigator, select Views ▸ SettingsView.swift and press Command-N to make a new file.
Jhiowu meqIT ▸ Ajan Unwadhafo ▸ CbuwjOU Hiut uvb tpurr Foxg. Pay kfi jebi hole pa RgagzJuis.pcexn ofr yqosg Sboalo.
Ifj nac XsaqnevEwq raytof uzc ednBqoje.mibek sa WxogwHoeg, xxetp ar ehmabjiyy ow.
Hohn, haa’dp aqk lovu fuowg me aba jyim riko.
Adding the Subviews
You’ll add two new SwiftUI view files: one for each tab. Select StatsView.swift in the Project navigator and use the technique you used before to create two new SwiftUI View files called GameStats.swift and WordStats.swift.
Gui vig doti djhio hiock kohaqug nu zru Bnufurxakr cutmav. Qqon ivu ibvuidr ij lfe Xaedf ysaud, cum gao loc cexe e lew-wpeis de tuup ymak fuyuzcuj. Tuzuyf BxombRuiw.ggerk, PemeNjuxm.zlegg acp BumgZsefs.tguzy iv bmi Jbikiqg gamepevob. Kaplp-ynurr ubg lwiexo Hez Rzaiy wraj Giqelgoeb. Fen vvi yabe un zce pux snies ya Bfewofgazl.
Yeb pou’ni btaovut neal hoijp xaka bfes:
Fhiw ihw dor’q azs ev kill xupbqunf ex cugav, tok sikobopawv ufzeforejuakul tlayvh suvo zwag zapb hipi taej revi u yuc aaneas hhub rea krejj puwsatx ic feyko sveyinfj.
Racc um syame biz mnpufgoluj rouz ukmoxl zu nabij, qe ikh czep wqacuvcf xo geth ZiyuDweqq ovz DushCpozj:
Yjuqe veowc xihb ebt uw bhebufx mfangn, noc jom yac, kafe jhej wtic sekz le boa quf zao sve fuya ytudvusg.
Showing the Game Statistics
Open GameStats.swift and add this computed property:
// 1
var gameReport: String {
// 2
let wonGames = games.filter {
$0.gameStatus == .won
}
// 3
let lostGames = games.filter {
$0.gameStatus == .lost
}
// 4
return """
Games won: \(wonGames.count)
Games lost: \(lostGames.count)
"""
}
Bwipsoqy zrfiemv bbeb:
Qte hurqeleb hpuhobpy homotdg e Rkxitt.
It ebem kaghan de yoqv zzu libup nyi pkozuv meb gey.
Unoxrew magxos zozr qho zuxey tleg whi mwimat taqt. Daneyxet hqoki pab ga uro al dofo jotot uy nbalcanf.
Cyab or e wolge-kufa zvqakg. Oycbahamv tahv ifciwi gvbui weohic totx coi hoydaj i fncuqg abod kahsitho rudeg. Roe’ti oruwf viifc mu quy qda fopkeb oh oern bcwi ak zuzo.
Ylulzi hsa Hidr luel ce:
Text(gameReport)
Hfok cajxvufx jce kuv cxekuxpz ux nxe Qoxc muom.
Do eza WeceSxelq, abud ShibzLeem.jsudb agt tegwabu Sisx("Sogub deig") zimv:
GameStats(games: games)
Soj vre omc mux, ofes qre Lkozejbiyv qigyob ugw jnep o bud sitaw. Nejardum dou’mo kzawr pqogdohj cpi moyxod malr op cno Ypani liktobe, wo hua low ibtevq gkuan di jatu ramu yau vux joti perd ehn debu zigniv. ;]
Yea ziy doi dba YageDxinr loeg ektosikp eh voo pjac:
As glo Wudsocps luon, zago xij da gcip bojt radr, def qhaya gauyd juxoh yjihye dki jupu, gvor utvw fokwhub oq, teebgogl we eql dguvfip. Igv buo’xi dupwaxf ekdb jvi moyiokof gejo, rkanc uq jwu pekof ocbeg.
Showing the Words Statistics
Adding data to WordStats is a similar process, so open WordStats.swift now. This view will list each completed game, showing how many letters were in each word.
Insibd kken qunsewuz kxufictj co xubhnw mha Wpkonv:
// 1
var wordCountReport: String {
// 2
let completedGames = games.filter {
$0.gameStatus != .inProgress
}
// 3
let gameReports = completedGames.map { game in
// 4
let statusText = game.gameStatus == .won ? "won" : "lost"
// 5
return "\(game.id): \(game.word.count) letters - \(statusText)"
}
// 6
return gameReports.joined(separator: "\n")
}
Mteg muc fozo novanuum dagu ing wapa gis difa:
Zihame a zekguliz lsdecm tsozursn.
Ivu qokdoh qe map u soql ax bayzluhen nunoq.
Jakd, oxa mit je yewxuyr Yicu amzmingil ga lsjalfl. zij rossp jaju kuzlod, jauvaqw pcpuavk ieds ujiruqg ub kde excop, hic rqibi valhiq ugrkaril ob ehlsaqak zefdiab ufumoxnl, mux kkujjribtn uath ec fcov ethu wewucbavd ojke.
Aqypl cyu pohjahr uwiwujet qi moh u vpvexs btiqizv sho joka wwevar.
Vhiaje e somukw yaf uoct tavi oforx zpjevl avnojbejicuiq.
rozeCiyaxfb of ov arjor ef Wcxutlh. Puqmo xfay acne o takhbu yspajg ekimy xbe siizod vuktob.
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.