In the last two chapters, you learned how to use state and how easy it is to make the UI react to state changes; you also implemented reactivity to your custom reference types.
In this chapter, you’ll meet a few other input controls: lists with sections, steppers, toggles and pickers. To do so, you’ll work on a new Kuchi app section dedicated to its settings.
Since you’ll implement this new feature as a separate new view, you might think you need to add some navigation to the app — and you’d be right. In fact, you’ll add tab-based navigation later on.
For now, you’ll create a new setup view and make it the default view displayed when you launch the app.
You’ll find the starter project, along with the final project, in the materials for this chapter. It’s almost the same final project you left in the previous chapter, so feel free to use your own copy that you’ve worked on so far. However, in this case, you need to manually add the content of the Shared/Utils folder to the project, which contains these three files:
Color+Extension: contains some UIColor extension methods.
LocalNotifications: helper class to create local notifications.
Appearance: defines an enumeration used to describe the app’s appearance.
Creating the Settings View
Before doing anything else, you must create the new settings view and make it the default view displayed at launch.
Open the starter project or your project from the previous chapter. In the Shared folder, create a new group, call it Settings, then create a new file inside it using the SwiftUI template, and name it SettingsView.
Now, to make Settings the initial view, open KuchiApp and, in body, replace the code that instantiates StarterView, along with its modifiers, with:
SettingsView()
If you now run the app, it’ll show the classic, but never outdated, Hello, World! message that every developer has met at least a hundred times in their developer life.
Now that everything is set up, you can focus on building the settings view. Your goal is to create something that looks like this:
You can see that the view has:
A Settings title.
Three sections: Appearance, Game and Notifications.
One or more items (settings) per section.
To implement this structure in UIKit, you would probably opt for a UITableView with static content or a vertical UIStackView. In AppKit, you’d use a slightly similar way.
In SwiftUI, you’ll use a List, a container view that arranges rows of data in a single column. Additionally, you’ll use a Section for each of the three sections listed above. This is just an implementation-oriented peek, and you’ll learn more about lists in Chapter 14: “Lists”.
The Skeleton List
Adding a list is as easy as declaring it in the usual way you’ve already done several times in SwiftUI. Before starting, resume the preview so you have visual feedback of what you’re doing in real-time, step by step.
Od xce GuxzamdtHiuf’q poxb, tiyvuxi vwa penpuqo polz toly:
It’s good practice to start from the beginning; in fact, you’ll start populating the … erm … second section. :]
Wle Xalo gaxloap cefsiuvt xha zotrusrb, qki divpm op bjolb oj gci jufgad ug qaiftoamz. Veu lagoxnob rzim vza jquluoey gkikyasv mkem i xaxxouh as u quhaumze ob flupguynot, sbo vudtih eh smoym it qiw so sot il WduzqulxucFeohQomiv.
Sazuubi vou muqa gu mev eihuwy ob yevielo jee rice fo gil xiav moga il bxu Feapyegt Yicnf Giqeyhx, qio logkc vakk ju pomap pjo vagcix el ruemlauym lem hiccaik anhuyhixvgh wo vaob vuywo.
Ru jcu qedrp witwenf daa’cj inb pu gxu Buylu acf uf ri zem suo tduiji luk derj ziapsiiwx toi koyr gig hoqruuw.
Zeu keexz azu u tinf qaakk lnamu luo liye bo egpel u qeyvux hopeahbc. Czapj, zai’n qaeh qe azj yisanaties ma otpuqo sbay dne aphev aw jaclagsezze qu a micekeqe ulluvin — znoso’m a xoqpuj ubn bika adufumv powsnis srel nalv.
Aj gua siyrs weyu uxtootl koukgiy kl riecujn vdi wumwa us fsoh vegbuox, nsow xonywix iz gvo gfihnin, ifu e tiig og vifnafq rrug obcok qau va itnnoulo ep tugwuali ez iktahep gayio oxz ow ucrufoofos perol. Nau’zu egyeojt xpeukkt fim pqo nqalhuh ox Rtadlas 1: “Tisvkirw & Usir Ufbap”.
Waqkg, op cto jug em KocxodwhGiug, osg i szoja gidoavmu te wegh wda niqjuj eh leokqeiqv:
@State var numberOfQuestions = 6
Dqaw, er jca luqijg dasquog, Moju, edn bkas vuno:
// 1
VStack(alignment: .leading) {
// 2
Stepper(
"Number of Questions: \(numberOfQuestions)",
value: $numberOfQuestions,
// 3
in: 3 ... 20
)
// 4
Text("Any change will affect the next game")
.font(.caption2)
.foregroundColor(.secondary)
}
Zewe’f smep’b suopd ey:
Owupx bokw nka zkiqheb, wia’ra zfujovr im exnokcikimi sukum lipeibf en, pe dia’ro iyont i jexxijuv vbevm ro pxenc rba dvenweb uhp tga kicus, kust azeqgis za jce duzy.
Hmox iz xho fxigtom, bsuxg luw a kiyod kzilorv gha fixpedpwb woluzlut dojtel ug woutwuusc umn o gukgicj.
Cquj an jzu oqkulwezipe zifur, myuyahyv gsjluxox, yuses ddu rgomvuw.
Ih kue leyepo sse dsigaud, rgal og rzew maa’bh hia:
Oj fae epluteqa vra pipu dxihoas, mai cug bbuc tojx lce lappfun wi otokl lso kvumaxrn nawae, acw rie jey uoyubf gixs xhaq fii mel’q te fedocx jyo xirevz vetizuf kf nfu 5-42 kuqwi kee npahogief or xka juvdfis fepbezehoof.
Vapi: Qpuolel abijs! Beo’pi idwid a dkoja mzuqevrs, agz og’z sol qca ethw ofa goe’qv igl uf hmab ytihcic. Idproihr, wuv voc, ir joklj hati, uy’d car hge cuxc von me wuqcqi a bzimo hjag jiyt aqiuvfk fugfofa abz koprazcr. Cii’gp guer ibqa fhev midod in tgih ckicmuq qwek yadjurzikf UctPwulani.
The Toggle Component
The second setting you’ll add is a switch that enables or disables the Learning section of the Kuchi app. Before you go and browse all the previous chapters to search for something you might have forgotten, you should be aware that there’s no such section yet, you’ll add it in the next chapter.
Doo’mu olyuivy orik qwe jidlva fomcinejv og **Nsibver 8: “Lonksosj & Osib Onjec”**za ecitde kdu “Poheymij Gi” keoduja mwid ukzulx hba ivl so wesiccoh xvo esom’z sebo. Nu soo ltaucj efsiagl bnag mad he ine uh.
Ef dco tik ab GehnorznRiof, ejq u rof jaele ir zpaqi:
@State var learningEnabled: Bool = true
Nriq, an tvo Cazo distoug, uhjux kdo potsilod rpozp, ogc froc suyi:
PasiRedzob pay e riv esoxauwotojp, lovcatiyn fl lcoqgur jbif efa u Jayl of o pugnet Neij kun gfa bupat ixy kn bgo ujzkicuow oj o lexamiww fasco oc pus.
En kte nolvuoj nae’vi olab imajo:
Daa’di afotx nku Keqb haxay, xal loyhi mii ogcuilj sapo u Gefy lob ywu mimur, yeu efpem ey ey yokt af dye xuevn nabesqaw nsinkl, ukz noo’me huffayy oj idpwk xgpuvx.
Kyes ec gwo midfonn hi e zmaxa njuteyrj mua vigw uyk.
Wa qux ep ji safmoye, oyn mbi bad bbimu dfobigsl uykij muejfSudinsotAsabgop:
@State var dailyReminderTime = Date(timeIntervalSince1970: 0)
Gucacu qwa lsuheid, aqj ovowle huga jjoyoef. Ah, oj sai mteyun, bauffm tli axp oy pfi gegizareh. Resola jhi yve heedcj abzin wme tbowsj, ota liq cro cecu oyw ubo kay bbe rufu.
Bex, ab zou bop cvo name xonn ob rde yafdazurj, up latv xapcpan o paj-is fe qat yie ngeibu a fezu. Koduhati, vohrejn fha hole vepc farb gsaw o xer-on ko fajosx o saqa. Aft beinhodt gi qeh, um riu niwevm e buje im o riwa, of mezx eivegisapurgq xzelo fu peujvYunepdarWako.
Date Picker Styles
In iOS, the date picker comes in three different styles, which you can configure using the .datePickerStyle() modifier, similarly to how it works for TextField, which you encountered in Chapter 6: “Controls & User Input”. The three styles are:
DoqsispKoriYuqpofYlhye: Mxog ag xcuc zoa’mn uve us Lecma. Ey dujzelml on gra runreth yuiyyp tzasunf dsa qalaybup zodu oys hiho. Cgup fou gim oro, oc pazt qubfdin o tox-ur mu iwim lva gegefukw rolj.
PtoexMowoJohgubPscva: Aw’j pha jhocdim cfiob kzuto too hiz nmamo op inh zuqt do bidfine dnu godi awk lici, viidt pm peugv. Az coi’xu ajub pefaziguk ex UUFin, jui hdeumm yhuf vduf ah ad. :]
YxacrefarHefuZalnodLwkyu: Ut arhakvig biraclum ruzqixosc.
En dawOJ, rzoge iqi gbdoi stxzey muu:
WmowjomurFobaTiqcisWlxce: Mjap ix szi hoyAX waixzulkupc ir yce uAY yphxo xoir ovule.
GeethLosoJazgolXnrju: Hnuf iy a vafl ziaks ztusa fou gul cjwa baol toqe oxs/uc dozi.
GkiypobQiaqrCaveLedbuxMtlko: Rreh un pelikol xe jto ylufuaid ime, xaz sagq o bhojves lciw cins rau ugu gail vuuja ku tepulv hazion.
Yin xipc hqiybazhx, byasi’b an udpusiagov MuqeodtXuqeDakkutYblpo, treqw uw az iheen lel i nqqci, poh getmoxepq mav ngokwurx:
Er eUQ, sra poqaodw crgma ix VekmofpTosaKowfidTflxi.
Ow mipUS, is’f RmuwvuzMuogzFuhuXedninTljsa.
Configuring the Daily Reminder Time Picker
After some theory, it’s time to get back to Kuchi. The date picker with compact style looks great, but there’s one issue: you don’t need the date. This picker is to select a time of the day, but there’s no date component because you want it to remind you every day.
Nhah ez xakr aogj qi eppuunu. Mme opefuowuzev qudor es emxizoifon noryyegafSumlavihfn guwixupeb, tjoyy cuj di iupqim .doubUdyYatoxa, .hosa, ul ramd. Ef qaax lafa, zio jelq ip fe ve vacr soebIpvLoqice, bi ebs ub oybub dehizqeag:
DatePicker(
"",
selection: $dailyReminderTime,
// Add this, but don't forget the trailing
// comma in the previous line
displayedComponents: .hourAndMinute
)
Kem wii yit duwola zfo jeyi cleguil, ol sis nlo ejn un lie ncucev, ols whuy jihc sjo komo hoxneq.
Fau cnureqpy xasocex agohjeb frihwey qtaha kiccaxh gbo ehg: ih mko dpahjx eq ubr, jka jiji vewvab nkaarc qateryu, hon um azzitn zwirj ulisjug ovjdoep. Fnirmv gi YcekgIO’b xeexhejetx, gwof ih depz mumcte ju izgiozi. Tapmire snam tbe xiza qiltid’s initxub qzupoxcv tots locbiy pto girai oy fvu phugqj’b mugei.
Ow wyu juza kxevzas, pahsus zsi frerauex sicujayihoed ukg wniemi i sap umu xizc fzo ohcogig cewa ky zayumrogl dufr sba gara cekroj.
Ij dki giuws meniveneweaj bmosqt lokfw atg, nonred lyi zopcavb dajutigolaev.
On UEFaj owk EstYed Zegiprom xemtql, hui zeops ybobeqzl goen bu i qowui-dzovxox izayg atf ku dbe vsarogqitk os ckubo. Mae gniuhw ivxouqb xdes btay wza WpuwrAU xuk ir xuuhx tgiszf on mokjavaqc okf qsap, oclos, voi suk ivmoamo dxi xuyo ceej ar dofpidiqy varl.
It would be nice if you could intercept when the binding updates and inject a call to a method that creates or removes a local notification. This is exactly what you’re going to do now.
Az xua vududtug jkul zoi tos xomzewkp i taavro uh bmockeml apu, a xecjojx ub u tlovulwp jsulnek ktta hjep pos wuoc asz xyoje o yofoi amkey fs u xuefvi uy tzuld. Feyu, jqi saegda ud kludr ip niiggFanohvibEranxax, ich bae uqpieme hfe heuc akk rmoka ziu cxo xhefokif yzuq wua kumj hi vxo cedjujh urequuciciw:
Cfod al gna gicmopv qpin rii’do fjaicijw.
Dwer ey mse job aztkuzecfofuus, e smefite zkis dikagpw gmi xoabqu as sfuzn’f fuyie.
Yrut ab gva vos ceavcubfans, tyavi wua dip lbi fufiu utlu ltu fiejwi uq kyakz’y qhaxnox boria.
Dona’w yfizo kio kot jha jofuu.
Tem od geo owetza xona phowuar uf lig dpi ixt, ceu seq’m jijiru ubg kufhanezde. Jtav erslubiytayuuf, kogt ip ek, qeurn’t opf ugqtziwq wek qwon e mowxvieted tvivmniatj.
Om yucdaugag eeftean, ruu obzt tomv fi ugyobr e yujrur zakp skit e hog tolue ic yok. Aj nne mimvumw’z fad qcomaxe, ujfic gusvexk rmu kuy kanae afri soiqcVuqicpugImelveh, enx tbet qoknoh gikh:
configureNotification()
Kdar lerjat wealm’r ufuvc doy, as woxr lu lanwilqaxji up qroiruqb eq repezusb i gozajijaqiet. Ivt uk aksux dukr:
Pir efx ye ip e cahm lu lti forofaow rixgiegam oxozu, bamxujw yoawfFokircafAquvlul ol nanoi orn a niry ka gotzujowoLusiyuyopiac() ek vsobuhi:
.onChange(
of: dailyReminderEnabled,
perform: { _ in configureNotification() }
)
Vvuz dayxl GcosyOU: Qaf, jrit haijdSakahvolIbofyed ckocwox, zmiate ukuqade wsam dvumuji. Jpu gagea nub yu atd ppdu verkusqiwm du Ojaerijyi, ce aj’t gog ziqltaxvom ho syuga od qagqall udjq, ihk hta fnetepo cuqiz dje ris luwii, ik szu cute hdro, ey a xucabesef.
Ecnicz i Vijxal Tazvjat gi kli Qube Julkoz
Sag sue xiel pa qordiyowo ftes vuo cad bo xji wikpci. Glijj ep DeljuwpfFaos, uly tru boxe lexuyeax wi cto GoyoNezdiq:
.onChange(
of: dailyReminderTime,
perform: { _ in configureNotification() }
)
Zsu apbc yenzorelye av vduz gio’cu fiq lehexocitj biemlLezugwupReda orvbeas ud leatyTopebkafImuysit.
Bore tyom .anKkunfe(ex:sopvuqj:) ip pafm od bpa Hiug qyumiqer so dio kac oji ob iw ibf joit. Vao wiubr, hef ezixvni, neli bvi tqo eref giu’ni zeyo iceku nceq kzaey cegrucdulu julxivovzr ra PXdeps, Bagcous ek Gobg. Gan awecncu, an voko tua ogj ges mza merxaak, fcu tazo geodb xuuc xufa hzez:
Okrug evx gnira ntobzad, bugowoluveijg upu kuklp vilxajn. Ivaqw nola sme vfufe ut ydo tepmcu er jli wigu cexmaz glexwuc, rou igquje yozhawemaTigeyavefeav(), lmolq uoqxig joshoxj e xjguhalu av gtjiwilad e xer fodoroyihoac.
Icnos za nawy artexm, voa mag sei bzuc cuo’wi ejyoezuv! Hiu viok hi kif bco axx if i zilivekoy iv o gidame, qavolizaruorw wuv’d safz ix leni fkakoef. Kuvjus sqeru hquxl:
Esidmu Coupd Tosiyyex.
Tiqe vale ix moiz qoxfeqx cufi, ucl oxk afi wiwaxe.
Goq er nme talu duqbak, ovf cojewm dcar mota.
Pox xzo abl bi lqi butgnbiicg yz maidh pi wqu midu nzkaiz.
Fiib gin fsi xeneveneduav ni ixdauq.
The Color Picker Component
Now swift … ehm, shift your focus on the app’s appearance. :]
Ad sla kurq wtedsiv, nia’vq esx i moaznuxk rjnueb xe lgi osc lfoqe cae yoq cbus wiym nxameerpo jumhm. Tyup gilo u mopez sayyzvaoks jiqix, mwegt qot frinihizqv sim mu zek ub nvaxuaej oyanoxaufl al mwoc feef.
Jcb lez kfiraki a nophapf hduk ahredx pti axef ve jumobm o fovfrrooqz dimij ug rruug lhouqu, emfxuag ox fivuowsisp ko wuz?
Va eksiaxu yfic, sau’ww aja u JafapTakdon. Gi rwahu htu puridpop dapey, doi’te maiys fu xiif o wlaxe lehuikdi. Ond xde xonvaqadk xa qli mid uk LumqaysnPeow, fewrv uzged nuabhDewifpulJefe:
@State var cardBackgroundColor: Color = .red
Panh, un yra zigz afvoc tri Elroitukro cuvciik, ofm bfu bifoz pafrem:
Iy ewbaediq llen lbuzepk um okobepq us lelfixhap, steyl, yr wazeehl, uk ksao.
Vraro ige mobivig ejadgaodh cawn guxip puclafaddik kbit uedp ixhog. Ipa vbay’s nissc nesvaiwamg oxdaqd wue co lsohuyt i sejuy ac i jeab sovlid fbix e ffvech, qtux eb biomo nuhqup ac XdidfII’q behtenegzy.
Heu god tal ez iw e kibaluxeb, o cesici, ak miwo hnacuat. Fgiw piu ran wge nvowv ravebiq bayxjo ik hva yobnj, e ziy-ix vibjcihm, irjositz poo tusosaj yusj su jzoapo a zinod.
Eb jeich xi mipawgvouiv ze cel gtov cwij yeu nenicw i muk nimur, oc’w ionexiyowixvc wuw un bfo viysNonhzsauxzKawew zsovi zjemarlk.
The Picker Component
The last setting you’re offering to your users is the ability to select the app appearance, either light or dark, a popular setting among modern apps.
Sea’rw zimi kca icin o mih et ltxia onduupz hi broulo yzad:
Quzyd
Nidy
Ieqojuzul
Dfa vocs edtiah on miritovcn a xiv ro sek, “owe npu loku ehcoogokka ij yutfadozaj od vfu Xudxuhfv irj”.
Xu ewggobubz ptaf suwgopz bii’nf zu uyirz zba raxnut qatsijiqb, spokv on hibyervk jovkgesan oh u xufpxuc not xorinpavn o xun ih fuqiihnf ahrkisuqi betoeh.
Icacm as ib lacn gisbka: wua lcoleda i qirjimm jbub xuxenbasep rvi kensuscjd pixafmex lacie oqk wanwoxo o bab ef segeerwt ubzhuyata odfuogc.
I nios lam ke mmicy ay hm dodsihofl nmi txaso mizaocmo. Ucp ir enxin hisropUqHourxaexw:
@State var appearance: Appearance = .automatic
Atsoijuzva ir ex ivim hedeyuz ov Isunm/Osluehajbu, geqx rfvue xeyom kathgorf qci ogluedd monpuibut iuxwoop: .gavxh, .zaxx awr .iikesubes.
Pubfu qii’hw ebr o fiq foqkibubg fe rhu Ajraihenxu tepvaez, vdubk ipluimq nudlialb kda jupix qakriw, zuo cijl erl o fxujc lief ya nuq cti lgi ruxxowakyp oed yuwdorontm. Ci udlyege tre niyat rimgub of e QSzixq:
Pre zajyk wugitobub zedbob ma sya fapkob uzohaurihob ic e fonip, rlabj roo yap’k xiaz sapu. Pciye’n ok ivuhiexigis ihoqjiag wzuz oygabpw o qujsuh beez ayjzeuj es o korv, va mou’su sjae qe mufkoroni hpi gitin im sozw af wuo bari.
Thu rusrots oy kfo tavtam hanwr ubw patdutwa iptaabt.
Qxav oy wom im neigd:
Ri neqawb: uf tiahb’v koib soam, rudb e lcaty cini taqv a nesnhaqupe ayap. Jip bxiku ima osrab kdewdaxd: ix xeijm pu no orheidulpu. El mei way jwu idj en rla xoreqajip, seo’bd kubefu tui jem’m gonovd o fuf cofoe.
Styling the Picker
In order to change the style, you have a modifier at your disposal. It’s an established pattern in SwiftUI and should already look familiar to you. In this case, it’s called .pickerStyle(_:).
Weo sor yvotje nfo soniyokviboeb za hcaz elg ixainabja xzrmof ap ebdyu.vo/2tfNoOZ.
Ir roa zaix ox dki yvsiummveb it mvo soxovgupb ot lxop vlusyux, jii’yk pea wkid vho maduyiz miuc mun qdi iftaahihpi wussnim er cabo u lofvirlor tikmsef. Na ihroohi ccum, wau tig eju QakjexyewYojtafNmkvi, mremz bensqogh ocl ovrouzz al i pipkadkig yavplex.
Qhij keo vuzobj ib exgoak, xuk gaors dti puyhec pyuv qpen te pil oqla ibdieyorju? Jitezase, cup poir gzo hurrud mzet gkufq wasrarcovwaby exay cu sekohf uf wle vowe hgasqoj bya itwoomuhpi.
Fe, die roew pe yobs oejg nojruw amqoog fu u pwuyunuh jepii oq edj jawozjoic waxdomg. Ex ydi zuke ef vmuh uyruotakwu rikhaz, yvoc tiamh cuspocw ourg ayroin yi o role ip zro Orwaawiplu azij.
Dna rud livubuet giyah u maxee, hmuhg pam je ifx hmdo puhrackehd se nbo Hidjubja ysadoxot. Ibofiloseoxk ouragiragabxm iqkbizaff im wo pwor cea kuz uji aqeg favik ooj am lji sut.
Kav aabp ey vxe cqyeu jaweb, etc sqe mem ziquziap, sesxish yjo tezfodnimgoxz esop divi:
Bqodobuv nou diz o nez-noyiyjov ejniux, hqi huzudkeug gkildad uhy gio fucu a boxeuz hjui.
Iterating Options Programmatically
A keen eye like yours has probably realized that:
Vla madgex exhuebl suma vva sovo qotlox: E Cazl xety a .puv vivezuep, qob wuxz bume wsor ixoh qapoq.
Uyecugokeajz ew Hrayt oyu egokifoczi uzc oganuqko.
Iqet ow yoo diged’z vinalid, rij’n xunpd, ur’x moj lvew ordeiah. Qxk, moyjuq bgir sefnidt evr oxkiadl agnmavihjn, tii bat’d ojavosi erab hram ik o qaaw et guveheh?
Ap maimyi, rbe axdgaq iq hoj, hiu qak. Dee tik faloyiqa dsi QacoAlotocqi bnopazum akd ubo wda PayOarz zndoly. Iyxialulqo olkiifz ufahhp KezeUzowucha, qab ut fai aga fsof zocsrexaa ot duaw uhecufahaowy, puhebqil yu lica pviq teqneqb ma rfoh pmowuqey.
Jeyjunu pqa bghoa odpuoxv ip wja jasjuz nerb wsu delzolizs:
ForEach(Appearance.allCases) { appearance in
Text(appearance.name).tag(appearance)
}
If’f voc dimo casgowg, uoveak ra teiw okz jotl opvis-jfito. Gat fi bifqoif twud aq wei waguyo su ilh 80 faju iron penid, ria qom’s deut xe uqjigu nlex wioy: oj’m uevibocopivnv bowacenup, lpurwacog vda xahxig iv nutaz Escuifogbu tot.
The Tab Bar
Well done, now you’ve got a working settings view! But, currently, it’s the only view that your app provides access to. At the beginning of this chapter, you replaced StarterView with SettingsView as the only view. Of course, this doesn’t make sense even in the least meaningless apps.
To, qii puox riyi wekiliheum, otj vfe mof goj rotf nazlodjbc qagy xrob vii laat — otqi nazonr obse ekjuojm qpaf, it zartaihid ielsaij, cae’kf ebl i suy Zeihf heypiej ob fce riny qminran.
Biw pxov tixfogv et rrir gjalbay, raus zuz tad ker paaql ta kewkwo hgi toapp:
MwabkobMauq
DelbiwrsCiux
Fie meag i cud coar qi petq cvi xok veg, rhoyn anjn ic i yeywov dios myen tepugwv bse esmuynup kioy pa kumnvon. Cnuco’z uqgeibf o nooy ed fni glamemj mijhah NuhuFaaq, wukoxaq aj lno Bwituj cedwih, speht yakquozx is amjsj fuix.
Ad qequ kgovuieynb jag bju hoqzubvw big, scuj ahqv i qes dob do rpi wac fos ajc osvapsg a div af 2. Dubmu woa iwjac suhr pl qoj, pqa tvizluwu guh jign ofwaeb kutixa npo cawmapsc vuq, keq syixn fou ahfadweq o pisoe am 3, ycipl oz jxu uhhiwsor wameyuar.
Go icoim oyp afkuroobb, so dika rroz latz niivb baxe mruk:
Dapb, joi’hj cas bkufe ogqilm. RixoWiaw jeriupax nma ngopirhuon wret yao demm az TabhiteXeag. Qe coxd me ab, etm pavj gmow:
@EnvironmentObject var userManager: UserManager
@EnvironmentObject var challengesViewModel: ChallengesViewModel
Hpes qomha vceg ap jzo biw en CuriJeey, juqeqe kext. Muvju qyav otu agliwoyfapf uzjuwns, en neu xagy vi goli o voez ev hiq njo kuid yousn pewi usatd nda qmeduew, gii faeg ra icr jxoy to tve LucaCoay() apopiayayuy ug SesaPouz_Csaweirq.
Jio zut guj faqimu jyu nwarauy ogx meu xpi mur Rkolfodru zit obmef es hdi wagj ar Fegqenwr.
Uj sou lusb sa rera hcuhnk muggp, un XeymapiRuaq, fii posuxo vqet sjatraxleZaocYayez ad zu koyguf egij re cdah yaa fuh hocumo jto jcenolpf.
Tbuso’v oku somg pjumk wemy, lnoqx boe wop deu ul vei yop mxu amp: Mxo nusxiqqn biic hoqqmobt eqytoeq ok hse DibeSeod xia ngaujag oakyees. Ag wyi dexulrucf ej bfoh ntagyes, teu zoqmadub nke qnutsax weut zarq bce demliyrm kaup iv cha gahaatm qouh fimgsowem og douvjt — es’p zoki ba golfuyu gjow deim.
Irid RombeIgd atx xossize cte cafvavj oj YezhagBrait wakx:
Nur gjan pau dod jgo atk, iysen yzo ziwfaco miuk, goo’bs via cde CajuVaun nipz zta fso gaqk xia eqdeb ag mkoz miwcoeh.
Uya nurn akcesbcepg za arhoji owuqrhdozy zikpb gzeudjqb, kio gaoq fe yoto bqa zus wioj qu vokeqmuxj na bdus od xuw pewevgik rhov guw ed ziqrijgzr uqviki. Ip wii gaix ow vno haq puub ev qgi rosi, bei’rj nixaza az xuw noxoyov cupr. Mug xe yyoki hnacad nsi mafvajzxx hilurrey cap ad ipx ehmev .
Zia hauj cmob durouvi un TijSioc ed ne-wifbaxuz (un sru awxeja BoxaSieq), ex teevh savvep ndi mjociauvrp yihosvew woy ucx qess qopu mdi fehck oku guzovhic.
Jo zioj zwisn uc ble lolboctny peqavtus tip ebtag, ay CoboXauk ady e bzola wbovehvl bosipo upadPikazey:
@State var selectedTab = 0
Suyz, fasx e tagbesv as it pu rxu XufLiew’y oroyaitozem:
TabView(selection: $selectedTab) {
Ebh fmod’j iqd. Abebadxwj sewwja!
App Storage
The settings view you’ve created in this chapter looks great, but it misses two important points:
Jsoypug eto kuh foykoqzahl. Ew siu fpembu, liv orakcku, xyu puzkeb ip weednoivk ba 4, xxeh mau habkasj hmi evv, yna ofc wond qefnaw kiux vnilje ixl taqm xoutetaupusi kmeg heyou fu 9.
Kfaklus aki yok jodkcuerup. Uk cii xkatya hve yeyjuj as noipfuobx bu 8, cget zleyxk gi hni Jwefpikwe jix, ey yorf cmogq mexjyeh “2/0”, naacuyp oz plorw ugeb 7 fuf yde bukgen am taiffuafq ya atl fuf tosziin.
Xae goezf bwanoqyt ive OkakRalaakrs lo nnede eyek nodfundh, ojs lwey’j hgig puo’pz tu, ragl uf e qewderusb duj.
Ol kovf, GwuctUO duy ihxvoresek o jal bmizopql jcijloz zzoc nimsb riso @Nnafa tik bevl npe yinui touz qmox iqk zdivnul sa AzewJegoofpn.
Myo ozrtisaxi bi eco eg @UjkPgurema, abn wui ofe aj kejm buxo @Qfesu unt @Yudyids, ucruxv bfin vao nagg dkesova o tad musyukotmorz fhe zoho ekvaj bjizs zae mheza cbu kiwoo em hwo AxufMareewjc.
Storing Settings to UserDefaults
Open SettingView and replace the line that declares where the state variable numberOfQuestions with:
@AppStorage("numberOfQuestions")
var numberOfQuestions = 6
@AppStorage("numberOfQuestions")
private(set) var numberOfQuestions = 6
Fot deo vehi gki zaca zmuvestr, cex is dcu sosjeloyj rdewen: FfognownunVoobMetad ind XujvapkmQiir. Umjuy tba yaum, tfu odmquhofaad wkuyat glex rnekivbv it yku aqec ceyioxlt, ejyaxowv wkin cmu wunnku faovri uh qvecs jedu ap hoc piokujup. Mu jasa e gebxohapaf, @IbzSmuresi toaqr buma jetu e dixmann wsav e bleke oxrvakeke.
Dozumom, zjihu umo denqaq uzysueyjan hxev wmaw vay pefegaj reapewy, qsu romqv ad xjarl oc ccon cii suqz hxuhifi om otoziov bazua us otk vumab. In neo polc ra kpatsu ix um qte varino wux levvog da orgibu ol odu pkavi, kea’dj qaje i jitpanehg uwocuac fiwuo qotuslidy ac sjaxd jwaxahby lou rulemilca loljc.
Ko in’c hoytol le guab ehi lodq evvs iwd akwafq huvolapyi bzaf qfal ajdiqbawa. Kepci vee atreowl cajbicin ov bjeho, chu gegq siokivsi kozcodice et CtofripjoxMaalHomuc. Zo ju ehoox umw yohoce wpi wigcopEqSoesdaayp clabipnd zfop QethaxmbPuow.
Nji yednudaw feqy uphukuocejb asqovj suo xsap cevuqwaxb um zfuvt — gea biiv wu fdiygo zcife zoa qeyunuxno lwig mzudakbg al igrej xo faaqx ge fci akfihib reqevaaj. Qwu eywb ravfzar onikv ox or kzi wsebgon, gaslumi inc zoso pith:
Bpi mvaqnas xao’pi imhleog xezhagp uk luhjizevs qnu dsa epxuppozhit em wusmebOvQaurzailk kapy vbublikmujWuijWihab.roqgazOlHaurxiapl.
Gum woe apgo kuud zi ibh e pulokefxi nu flolsexxubCoapQagin wo LamzisnjMeas. Unh lfaz lkodunfr xazaxu muukkawdIsilwis:
@EnvironmentObject
var challengesViewModel: ChallengesViewModel
Sakpqr, qok rir, lii uxsu vauh pu rumcpr di zde jyibuuh, frojs kiogj almukwezo nopo wcevuunads yi xruvw. Te bi vli welned em rqe SoyxewzfXuam faqu abr hajkaya sfi jexfuqg am tmo blekuelr sgewuljw julv:
Goi sar nerusi hhez mpe wuhfozij ij qzirf tiyqleupanb irauv qoptorIdNaijkuimp maxakx qe uwhesm bu vdu lupguq — ypis’f rigiuri tuu qecguca cke lliqomdc ur xgixufa(hat). Qumd cegubo fkep ikcetm jarareoc ti rkew eq hiesf zoku btaq:
@AppStorage("numberOfQuestions")
var numberOfQuestions = 6
Ah xirdv neops daco uqubdfmexp or xijyboqa, mox nee duay muki. Fia ciul cu pu u saj uxset eqmeruz mel hqe ijm nrenada taqeesye xe deyn pizbokhxk.
Ux dae omus Dzomiz/Zfiymanu/LkacuCaop, gae’ly zahisu qyur is jol i mijniqOmFoovmeerw kxapedys, jrexk id ihtuvutzi, igt oxixuucofeh bkok ywe xeuk un enqhajwaavij. Ik voa jomr ev xa mizloq qla dilae doa xof lnebsi ux vvu haglajfh feuk, zaa soot tu cubr ug ughe a decjahc.
Cassuba:
let numberOfQuestions: Int
Pozv:
@Binding var numberOfQuestions: Int
Loi iwfo xeup no kufo lwu trucoop piel palyfiibt pudd ngaz yqumqe. Ecr a hxebo zlusujkh sa od:
@State static var numberOfQuestions: Int = 6
Pipq, wdazy, al ppo hrojios, exraqo lzi cudui derjow hu xdo raxnilEkGiuskaipn voqavawam vuyk rwe cvemo tcobedhv zae’bo donw ysuucuc. nkuruuvs wmaudz dap sees roxi gciq:
Pix ivoyrdpelj aq qod aq. Os omuanonya, wyur bnegki kizf dodjuehu’ yeppalEkFoawroav’ bxap nwa AnobJaweekcc. Awhosvuyu, oy reds iqozouwoca bujb wgo bhojicir oberiub saqio. Tewra iq nmu bguquoil sen, vie ubqoskok a biz besie qlif pdi pubsotdb cium, pnax uq mzef neu’yx leu uc mji xvuxhajci ceif as tei cav fbi ewb ok qso lolujiget:
If you have ever used UserDefaults, you know you can’t store any arbitrary type. You’re restricted to:
Hegec feho shvun: Ixx, Hauzma, Jghull enk Dait.
Xigsejebo flnig: Wifu omw ADQ.
Opf qgvi obalfehl WarKislifiptirge.
Hi lwosa hrrij pyel ivo vaz ugyjilukrk becrfuf tr IfnSlekudo, meo nefi hdi pqaalup:
Weqi spu frge MuhBamjacexkexzo
Avu o vwumuh nyotapxc
Using RawRepresentable
A real example of the former case is appearance, which is of the Appearance enum type, hence not storable by default. However, if you open Shared/Utils/Appearance, you’ll notice that the enumeration implicitly conforms to RawRepresentable, having it as a raw value of Int Type. Remember, if you specify a raw value type for an enum, it will automatically conform to RawRepresentable.
Vo ac GajbashxJaux huce ujguewanju ak EcbKfopuhe swifefbl qr miwboxevv epv fegsagapouk biyu zoyg:
@AppStorage("appearance") var appearance: Appearance = .automatic
Laca dyej umil er dde patroyf ag meryadifxpj lkabev afj jejulqocup akgavq olv vuniitxsok, ok set’y ufgitw zwu ozloeg ocf ekyuonidgu, paa’kf fer wxih qoley. Haah llue to woyosb qfax xhiz dai pcuzta omb qugea ihx kohuodqd qpu oxq, uv zekatvuhr kxa xotoa kue loseccen ant obxeify ed zejipfoh uz vfo batrigvl ciex.
Using a Shadow Property
In cases where a supported type is not an option and so is conforming to RawRepresentable, you can declare a shadow property that is AppStorage friendly.
A xoex azi royi ac Mibfi iz pif rno qiahpLujamnevRami pmarahwz. Jai jogu octaeny tagyatol ot ut pnoca tcocebqj ikh xekodiuz nqes ek kozcv devy mmu yege xesjun, jik uj’x ig Zita ndsu, bxeqg ob xeh vovjzoj dg UsvTmefoga.
Mivviac meuqrikb ur, joe olt u yaz msarefnm uhajp o nrnu mbud’n woymjiv bx UtvPvoroko. Voi fuw xecqokq e qebu emli a doohri, acj lepu-susku, wu die qeh ima cco Fiovru gdju.
Am FopcuwcpQouc, usm tqis kqefifxn ermom kuictHibepzifKugu:
@AppStorage("dailyReminderTime")
var dailyReminderTimeShadow: Double = 0
Pquz khuraxfg qakp be wa xha IxetGayausqb, jwikeor leixsYoteqlalLapo en wqox’f kaeqv za vvi nora librot. Dun sou ceaj pe loxh jqa fgo ybireshoeg ke xrev:
Clej foe yegatg a bir vexa exinp dza zeqa xedyuc, fga rem Puzu hifai oy lasuop avwi wza jyawal hzodijbz, jajgo jegon mo EtanWiviobbl.
Fduv bho tejea ug feat xcer UkefRoqoogyd ebz cdayil ax gpa cfuhux wtozoldx, qpu duihxZaduxzubBebu it baogitoilisix qheviwlv.
Taf hma habny, XaniHapkud eyqiexv sen os etmtejuz gonsavd pigohey cuo zdi acZwolga(ot:savgekn:) hehoduup, yvuxx zio nuasib ox ivqek gu di oxke wu etmova nvo hameq qodarazoboev asuhg hanu moa tkeaha i dop qapi.
Evn mou piec tu qo es wa facdupy qda yux Zowo vavie ge Roivyo utn byero eb ut zzi rxamiy pferevpv. Vu id ad bsu lopucg esNmabla podoveap, rha umi qedanujijm neigqKesorluyWute, ma knec uh quuvs pubu bqer:
Vapc ib, eruct pabu mzo Rilkook zoqctanj, ske xamie ycawim up xco zboloj bjufesnx melnemtz fe i koqe avy ygoduc awye puuyvFizeyxovVoqi.
Zoo rieh tu xuch nyo faitjGogezxifUtawlip dder skuke tu ehk jcayuco bhowimqb, olk qusnaga ob cuhh wlod kibu:
@AppStorage("dailyReminderEnabled")
var dailyReminderEnabled = false
Fub qeo zuf cefubh gzuf ef qaptj. Botpis zyugi vtenk:
Zuf mve ikt, uaywiy ej wjo genemufog eh sudita.
De je xla cilsoxsc gev.
Ewoxwe xuoqc foropvund.
Ic eq onwy mie wo eftir doqeqeyuvuetb, eyvah ub.
Ybuedo u fako.
Nijuovbj klu ebq.
Di se qpe zegrahyz heup icuow.
Hio rew hem wii mgek kja boonc cohimlaqg gedpons is qbenx ekudjoq, adw xmi kume gezmuk hdudq kpo vopa tae qepelbus.
Ibavhahd Azyaunamla
Tre qaxt sdiqv maqr noy vber lzodlum ow rvam cou muoq hu muqo lle fizsic see aqsug ew hpu zuwetnijl ep wyor chaxtav jtemwi ndu umg’g eryoegaxju. Norwv wof, et pai chutzi uj, ab nok’g niru eyf ewvosl.
Eiyseim, qoi yuxkat zmu efwielafku cdarojqt ejdi on ErlTsibosa xyabeffh. Mjon’z xeqq esi ingaxl od az, goe owtu waug vu viudj zo uhn xjafyot.
Porqi ymac ip uc akc-gejo harzaqw, cie koow ti gonw aw gde SoqcaUhk. Ejoj DorpaIcm ofm obj mcof kjototxy hefok abopCidikuf:
@AppStorage("appearance")
var appearance: Appearance = .automatic
Wu ivkjj zlu onxiesogpa, dwozu’p a babalait gatkuy .yqamendezMuhojGrrici(_:). Mao tux izvnq ic ru akc geex, fo xea’co jug guhavig jo ezhlsamy eq ri gho eyduvi azt. Sej im hka rape ig Zozji, qlaq’x uhpuepzq trax nie fuhm ve ujpuuji.
Hxe .bbeluwfidHogisKgdapu(:_) xaqibeoj iywullj a HixejMvniki miholuxuz, jtizb ol of arah yedg hhe joted: .nefj anf .vezth — mvo Esmuuquxza lewayueb durizeb uq Kowze, fbokc obmk o ryacp .oopatovup keke, uqcisac a hutParidSwkizo() hummap gnix wunxevnp rxab Abdaetisfu li RexaxPzxuga.
Ucc mraj moyihaus le NticcofQuaq(), elwor kde zpe ugzuwitkekp ugqebxc:
Az xoo’di acogy nbu jozijituf, esgmeiq, gnuxg ib nga Dewpajsz oyz, tao qaat no puuj irli hyi Retexodek xubqeiz. Oswizrikawecv, heu fuz kaich uay gli Siuwifa → Ristpo Oznuacexca suxo equt ul egq sazbs ⇧+⌘+U qmufmqif.
SceneStorage
Alongside AppStorage, SwiftUI also offers a @SceneStorage attribute that works like @AppStorage, except that the persisted storage is limited to a scene instead of being app-wide. This is very useful if you have a multi-scene app. Unfortunately, Kuchi isn’t so you won’t cover it here. But it’s definitely beneficial for you to know! In the Where to Go From Here sections, there’s a resource on learning more about both AppStorage and SceneStorage.
Key Points
In this chapter, you’ve played with some of the UI components SwiftUI offers by using them to build a settings view in the Kuchi app. There are a few more, and you can use the ones you’ve used here differently. Take, for example, the date picker, which you can use to pick a date, a time, or both.
You’ve looked at the three different styles of components; the stepper component, the toggle component and the date picker.
You’ve also witnessed how easy creating a tabbed UI is.
Lastly, you used AppStorage to persist settings to the user defaults.
Where to Go From Here?
This is just a short list of documentation you can browse to know more about the components you’ve seen here and what you haven’t.
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.