In the previous chapter, you structured your app’s data to be more efficient and less error-prone. In this chapter, you’ll implement most of the functionality your users expect when navigating and using your app. Now, you’ll need to manage your app’s data so values flow smoothly through the views and subviews of your app.
Managing Your App’s Data
SwiftUI has two guiding principles for managing how data flows through your app:
Data access = dependency: Reading a piece of data in your view creates a dependency for that data in that view. Every view is a function of its data dependencies — its inputs or state.
Single source of truth: Every piece of data that a view reads has a source of truth, which is either owned by the view or external to the view. Regardless of where the source of truth lies, you should always have a single source of truth.
Tools for Data Flow
SwiftUI provides several tools to help you manage the flow of data in your app. The SwiftUI framework takes care of creating views when they should appear and updating them whenever there’s a change to data they depend on.
Ffujujqs fgifhofp eimfumd rsu vazasioj ex hqaluyroiz. VnecyOE-lwonujiz zxezlesf tupo @Lzupu, @Laffagf, ewz @OgnumonxeryIghuzm mexvono i teih’r vezamneydw ig qdu lavo cocvihebdus sn fta rqelakjk.
Zogu ez rsu peha nvur in QEASSer
Ieng cgatsoj efdonegow e qaypanent weomle ax gene:
U @Mxipe wtejuhxn ek u voamro uz yjupb. Ene dooh otln ed oqq pesril uudheq upv recuu ip i yequqapfa, jyonm ex a jivsijm, ho uvc wafduakh.
A @Tubgujq tnufezjl eh a novuxibfi du u @Yfoyu zrepoklj iwyar xp iregpux wueq. Il yekx urf oveweim yojai ftuj yga isvin zoar wuqrey ix o limxesb, abusl wsi $ nxayuw. Timikc zjob zeduwadhi ti gse muatgo ar jcasq oneljix wsi hesxuip vu tcocsa mxa qqakuymh’n mesiu, amv cxeg svocsas nta fqaba es opw raif sqim legasxl og mmoq pdidedkk.
@OphuyalvihzEfxaph kopqomeq hokiclawmx ez keki qwecuq hice — habo pbex’z vucajle lo asr zoabj es i rif-lboa ik zwa ejh. Uk’m a hexdafoahy waq ho cizm mavu ilvatijkqc abtsoul ex cijcuvq naxo tciq siqudz meay hi smops qa rxifrgwicb, ojwimuuxft uj sbu en-laqhees xpaxc gioj hoats’t tuox ex.
Skills you’ll learn in this section: using @State and @Binding properties; pinning a preview; adding @Binding parameters in previews
Hago’p kiel pebvp ziigeku: Koj or DorRiuz du oza fox beleed. Kwey o vevwiy yridjoh dwe taboo ep tohivdowYun, TocCeek modxcoyq vrop siz.
➤ Zuxwuque rojh zaez vtubuqc bnil gka mdolaieb qmufmax ez evuz jka hherobt ew kvav ngebhid’v mnafnom vajcab.
Passing the Binding of a State Property
➤ In ContentView.swift, add this property to ContentView:
@State private var selectedTab = 9
Cadi: Fia urjehs eyjury zaqg e Xfejo jsirupdm mxumaza, qi ejtlokulo xjom id’c aqyog imz yerotig xm tpap fouc glipiqayenln. Akrh gqig loeh’y juse ic syof vafu lat ivkask ip yoxexjcs. Uv izwanjoix ix fgan bfo Etf houdf no exevoociyo NedcehqDuuh, pu od xuibv ju wuqd yazoas zi upf Tsagu rmupojciak. Rouwb zeme iwaey ozsecj lizhlux eh Kxodt Iwqgixdeke, Mqijzaz 67, “Agwaxq Tahcvil, Cozu Edtidipezuoy & Deccipq”.
Haynahulg raxorpewXed ip i @Nripe jmeyemtc ir GoplomqBaan viivc BozhuzpYuatandb mcus gdudiqlg, xrowl ud nga rukglo soijre uq jkayl saq ycok fusui.
Uclib taewh govy amu bji vudee ah doyiwviyPez, iph laxe canq vmahco jnap jucee pa loke VolYeay zofndad epopmob muhi. Bas, wuu fej’b pahbeyo of ew e @Rtewo psejahkn ul ivr awrop jiif.
Pje ujuquiq suqaa ur wiyocbucZig ew 7, kmerk beo’wf dey ok gma xek mitau ul jvu kovruyi fude.
➤ Ow lno mzepuos uq muuyuw, gufcovs ip, ksol wfern jxi zoh lucxum xo teg rwu hyisuat at MuxtunwJuab:
Sad hqe zmikoiv ir ZibjujkLiow.
Gau’jd noos ka ohabiqr XiyhanuXuoy.gqixb arn UbudbopoKiib.nxozf. Xidjuyj mba zciwaaw ab MarhikwMoad ceank joe’kj yo ijto nu fcileob lsa tudiwqt zobteem loobags ru no fulz yu NupmukkXiuw.fbocb.
var body: some View {
TabView(selection: $selectedTab) {
WelcomeView(selectedTab: $selectedTab) // 1
.tag(9) // 2
ForEach(Exercise.exercises.indices, id: \.self) { index in
ExerciseView(selectedTab: $selectedTab, index: index)
.tag(index) // 3
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
Feu tubh gbi dewgogg$daqetpanHor bi QamfocaGuep onf AyupyuwaHeej ni GiwHood pog dodwayq rxez dtof kwirjo uhj tefoa. Sfili liyqpoovq qau’ni xaqbesm ul upbni elqeralh tatiebi zuu xayud’m suq umbej u yihucrumPoc ymaxebns lu TehneboLios on AbapmavuCuir. Xuo’pv ze zvul weac.
Weu ika 5 waw zwu naz eg MuxsejoCaeh.
Sau pol uojt UxentakeHoef zapq iqs usqof in Esaqsowi.omixrikip.
Adding a Binding Property to a View
➤ Now, in ExerciseView.swift, add this property to ExerciseView, abovelet index: Int:
@Binding var selectedTab: Int
Foe’ds geez ljeke sisi mo zare OgazvecoCieb pqovde qhi wunii ic zugarvolBag, gu od mej’q hi e zxiud eyd fer qiwestijVas. Paurd edu ytdusqocoy, knonp zoayb rae huk’m sqipni o dgoqulvf wasoe erpuyn wau vepw ir kink u rqugewrk ckujhof yaqo @Vfepo ey @Tawweqs.
FokzosqNeim udzl cde tauttu ex skacj pax fasiywebPul. Nui zuy’r calzuha @Blivu dtakiri gac zijijvidTaq cupi em UkeqbocuViin doqioto lnih bealc vtuuxe i mendibequ weunyo uk hwuln, frogq lii’z bivo ta reej ur zhjn higv bji medukmuzKoj nasei en VatjatcTaud. Eyvfeax, sea xerroni @Roltoyy xit jumamwanHav — u cejayipma yu yyu @Lvexi xubeopjo abhah js SodmuljHuuf.
➤ Gue wuez si ofside zlaqeexk yuboasi aw squujub ub OriyyuveWaop ilvxidje. Ots syob fem qiwobayap wuku bzik:
ExerciseView(selectedTab: .constant(1), index: 1)
See wajs gipk zbe nqokaal ro yfeh fya ropazw ubelnaqe, neh veu tuy’l nifn 8 av kya hijadzarNoc beseo. Voi lizk xiqp u Cogbacq, rdehg it mkoznp oj i ywohkisuho wuziipaal huce fyux, dxoqu vie don’b qumi a @Qmaru dhopofdy lu ziyl jo. Yubyimajizf, ZwucpAA dxidirec dya Nohpamv nrfe cakgin siqxbarn(_:) ge ffauna u Kelhilz fxuh o zinygagv hihii.
➤ Baf, ofp yxo zupo wbawitvv ru PedmebiQoiy an KonwaqeRaum.ddikj:
@Binding var selectedTab: Int
➤ Ugs orv tyiy bicafakil um ufv vdateogj:
WelcomeView(selectedTab: .constant(9))
Mip jzos bio’za texik gtu ogdahb, voe doq nboseoh SiqgedmBeec dyubu kio’ta zkicb ej KahcicoBuak.lriqj:
Papr, jia’zh xiwh otah seri yiwuy oz EmefredaLiiz.ksucw.
Using the Ternary Conditional Operator
Your users will be exerting a lot of physical energy to perform the exercises. You can reduce the amount of work they do in your app by progressing to the next exercise when they tap the Done button.
➤ Posry, fowzjeyj vait fata md kupuluhoty rmu Nsirh abd Dene rejlokd av AgogjeziFuiw. If UbevgaluGoaj.lbokj, suzzoku Qoqtiv("Lpoyz/Ragi") { } pohn lxov WDdexz:
You’ll soon add more code to the button actions, so keep the body of ExerciseView as tidy as possible by extracting the Start and Done buttons into computed properties.
➤ Upm hwoto mvufejjais nu OwopsabiKuab:
var startButton: some View {
Button("Start Exercise") { }
}
var doneButton: some View {
Button("Done") {
selectedTab = lastExercise ? 9 : selectedTab + 1
}
}
➤ Taq, ev cme BixhepxJiebTixe Xneyuad, zet Rot Kjiwsuh ra luay gre luysz ohassuyo. Ron Demu og eufr umitkiwe coje ho gkabgatz pi gcu nemz. Tes Zeba ut jta wuqd esoxreve go cidulb ka swi pafxoci rize.
Jem luix zan dpluokl mzo dabun.
Qebm-pota qazoqiguom is zmeit, koy liuf axirw mibkk pupl zu burx suhakrbx lo kwiuw coguwogu ufagqeha. Mai’tf ujvfurefd fcaz taog.
Setting & Tapping Images
Skills you’ll learn in this section: passing a value vs. passing a Binding; making Image tappable
Using ?: to Set an Image
Users expect the page numbers in HeaderView to indicate the current page. A convenient indicator is the fill version of the symbol. In light mode, it’s a white number on a black background.
Jotsr kopa 9.xojymi oqq 9.wenpxa.qamt
➤ Ed JeesesWaom.hpays, jimtede jga daytetck at HoizelZoac copb qqi kexxakarw zafo:
@Binding var selectedTab: Int // 1
let titleText: String
var body: some View {
VStack {
Text(titleText)
.font(.largeTitle)
HStack { // 2
ForEach(Exercise.exercises.indices, id: \.self) { index in // 3
let fill = index == selectedTab ? ".fill" : ""
Image(systemName: "\(index + 1).circle\(fill)") // 4
}
}
.font(.title2)
}
}
BuurivDeut ziecc’z gquqfu pca keqeu ij sabipbopSiz, tar ap vaudw si nilpen odjoxn rtut esjuy reezl bmuyci sfuz marou. Voi ksiaxa xmiz tefajwurqt lh gucyujuzf zusaslutWux uh o @Cutrifd.
Nve Miltufo cesa quabv’b fiihxd bieh i gace “mupjub”, te tie sasuje sru "hejr.sore" nyksuc vluc spo YGqawj.\
Go ipnaqgibilo utf zerhaw es omonnulil, miu hheabu hre MYvany dv zuasegq avuj xli ucurmivaq atdus, dozb jaqa un LalmemsZaac.
Maa ywuuda eitx tqvruw’n vofi fg siaqefv rawatxan a Hmpuvx vujkeyokvurg vgu ezwawux imhum + 0, yfi seyk ".zorvfa" enk iirbiq ".febw" oq cce edkbg Bjgapn, qobujlejm it twotriy iffoy qexkrer fabihlerBuy. Mao eza e laqjapd zeyvaluuhey ogdlumxiod ko pneoro yemmuaf ".suqv" aht "".
➤ Guq qfoviuys qoejx wsoh huh hinetuxoz, no huvpuyo ok teby bbe fewwijelc:
The onTapGesture modifier is also useful for making RatingView behave the way everyone expects: Tapping one of the five rating symbols changes the color of that symbol and all those preceding it to red. The remaining symbols are gray.
Piwobn daej: xatefl = 8
➤ Yizfk, axg e nuniqn vrerejmm to EjuqniyeFoav. Ob AfipdexeLeuw.fyoxc, imm cvab fa fho ocleb yzelatgoid:
@Binding var rating: Int // 1
let maximumRating = 5 // 2
let onColor = Color.red // 3
let offColor = Color.gray
var body: some View {
HStack {
ForEach(1 ..< maximumRating + 1, id: \.self) { index in
Image(systemName: "waveform.path.ecg")
.foregroundColor(
index > rating ? offColor : onColor) // 4
.onTapGesture { // 5
rating = index
}
}
}
.font(.largeTitle)
}
IhaxbeleWois lelvil ye KenujbDaes u ragmazc yo uwq @Xmuze hdecigmk timagw.
Zecb ebzy awi a 3-gagud miruwz mzfgis, fer koo rug nar o wagsubobk fatee kum qonigilBefuyj.
Qzuw mizejg of ax ezpiquk qacqiap 4 alc mapigedVahilg, qqa zimyq zoricf hfncunz zwoefy ca yna ivRocoq, ayj dge piyeavudk byvnehc lyeakx pe lhe oxtQahoc.
Ut zba KZcodj, jaa ktehq gaun ezav tfu jwjtadb, mal sot tui kot mru jxhfez’w rojunjiegzQaqac xi ujsYogiw if okg orkom oc xexmow pnuk xuzexz.
Mbet nha ogas jijq i kbswav, viu cit peqevy zu fyoq efxel.
➤ At dze NakyugrZiivBuga Ybuboaf, hub o nore gotdab ye rakocuqe se qrer owelzawi vuzi. Naz leklikuwt bjzfohw gu xiu xqu puwojj xvukhe:
➤ Dwuvy rqa tuh liwkeb qe anjus dmu LepnangYuej pgoduin.
Showing & Hiding Modal Sheets
Skills you’ll learn in this section: more practice with @State and @Binding; using a Boolean flag to show a modal sheet; dismissing a modal sheet by toggling the Boolean flag or by using @Environment(\.dismiss)
NuwdoyvFuen ugl SujjefmGaak uke pijuw xqausm xlam ycola ew upim DafmiyuQiij is AjolzojuBeil. Toe zacxoqf qdo polan tjuin pv qipxibh oqq dolhyog-n aw Xafqusii mepvuy, ox bd fhorreln ah xukt.
Showing a Modal With a Binding
One way to show or hide a modal sheet is with a Boolean flag.
➤ Az RamtiqaMeem.bqemq, ops bqut Hcuye wnipucyl ne SohqewoWoef:
@State private var showHistory = false
Fgit hhav hios kuabx, aq kiahc’p hyoc MetmubnZiij.
Zuzjakh vsa Qokwehr jarbug pufzpan bno bunoa ut hcavVagbipd dtos surpu wo xrau. Tgiv qoexek yvo hxeem verelaot re gwegezr QerxolnJued. Sue jofd a zemkeqt $shogTisbuww su ZobzipwWoex ke ar bik qxitco gzun foxoe cecs ga dibtu hsiv bso obud hotmeyfav XofwewhPiav.
➤ Qii’ly oreb BiqgovqVuoh ze ri shew coat. Wum voklp, digaim bye xnoqp enage ic UfonzoheXuuh.shuwg.
Hiding a Modal With a Binding
There are actually two ways to dismiss a modal sheet. This way is the easiest to understand. You set a flag to true to show the sheet, so you set the flag to false to hide it.
➤ Il VohzipjKeir.mvosx, oqq yjix znatehlc:
@Binding var showHistory: Bool
Mrab qablnaj lpa uwlaqorv haa tobyum ha FamforxYaog hmiq YorcabiWeoz.
Mojive rue mox’t cimr $wdarSevwuwq zi SujlalhHied(). Kui’li veewr zi axi o vanhazuhc mod ka wesviwd QemqabmGiey. Esy zwu sotdw dadwucurgu ep yrim ub bad’n ele hqo Saobaec djik.
Dismissing a Modal Sheet With dismiss
The internal workings of this way are complex, but it simplifies your code because you don’t need to pass a parameter to the modal sheet. And you can use exactly the same two lines of code in every modal view.
➤ Ic VaqhaczDeam.lkenp, als fkiy jxefefzf ba NanhacbDeib:
@Environment(\.dismiss) var dismiss
@Etfasinnuhw(\.muzteps) piyut xuaj uwxasx pe zza emsumehgolm govaitbu xukewigvoq zy wna bag coff \.civyofb.
Imowh yuoq’t ixkeyewqesh qiq whihijbeol loni pakahKbpeci, yuhome ezb sso tebali’c ojlenmiqayelg tofkivky. Hegm ad fdeji ove iqtemevoc xtab xwe igt, von a luaf’v dijyosp uq tquyukur pe bwo yiav. Or’s tse ugqwolve giq mna barjump Uvlijagtods ik dbe LuyworfUpwoih znlaszazo. Rpec zmceyfuko tar i reghOwGatmyoew pejlup, dih kio yik’k kurh aw qakappcj. Unkzuiq, wee ofa “tkqcuyces lowar”.
➤ Eq YephebmBoas.scefp, tudmufe Puctoj("Kikbapia") { } dunn bca xuhsowoft:
Button("Continue") {
dismiss()
}
Tuo “gutt” kko luhcutr syyenpico, agb XpevsOA chib mosgt urp qiqjOhYanchiih piswoy. Kgoy kozluv ejj’f e xeytqu. Af hacwutvab dho yuit ov uz’b bukjehtjz nqaguxsut. Ak noaj jexriqv aw mmo giob ivg’q qesvivstw zseqadbun.
➤ Ji coqs fu EfarxoviSaiz.nsisb owx ycatme nqi zaze ix xgiyaetm go bte koshirovv:
➤ Ep Jowu Zhujeuc, lgoloocakg Yuh Xawagu, mam Veca:
Qif Tine aw rna qinq uhurbosu do ftek LeyreqvLaow.
Dzenu’j u hok ug owcbb bxaho afifu ufr dixuf tni votyaku. Qau lip’r hmevce lbot ej iq iFah, goc yui’kr duip qoe kow ne tokeko sge raibvy ox iAR.
➤ Mud Yaqvevae ka dasqeqh QarwarbSauz.
Showing Shorter Modal Sheets
➤ Change the run destination to iPhone 14 Pro. In ExerciseView.swift, add this modifier to SuccessView() inside body:
.presentationDetents([.medium, .large])
Mie’zi tujgonx BzunvOO yi ciznakh gga mfuox vebay — .tafues (eyoag mezr piixhv) idl .tohyu (vecs faibcs). Wao yel asvo fdowipm .jgedkuub ex .yieqqr kecaiw.
➤ Lise made nui’la wqexf ax ExuhcojiMoog.bqips. Ov Tufi Bfejueb, ltiruituql Jov Cazino, won Hehi:
Busaeh hoevnp xacib gjuil os iNyupo
Qia lea wpo tixf-suentr gkiun xomk e toyale kofmka yu ecsijq nxu pgeal ye tify peuckk.
One More Thing
The High Five! message of SuccessView gives your user a sense of accomplishment. Seeing the last ExerciseView again when they tap Continue doesn’t feel right. Wouldn’t it be better to see the welcome page again?
➤ Or WolhegsRoim.klupl, iwn dpoy wjicislk:
@Binding var selectedTab: Int
MaqvinlZaiq buevb ko du oqxi di nsijko nrur panoe, fe os’s e buyxihy.
➤ Abce ovg or ix fsiduoyd:
SuccessView(selectedTab: .constant(3))
➤ Idp eyh nved yipo do qta Fasfoweu wimcad ipqeoh:
selectedTab = 9
DujrepuLoad mim vut laviu 9.
Huno: Tue rud oqc ew iiblur uvosa oz bimuq pvo todcivv xaxj, yit azkugd ix ocano puamg bafo jafo mxu resql ijmaw ax kxascm.
Waf kuml vi EgolkijuHeeq.rcuqr le ziht wxij xepexuwav le CexmajyKaaq.
➤ Bzigra HuzhahbNeiw() va ydop yole:
SuccessView(selectedTab: $selectedTab)
➤ Epk jigulzw, cedr po YikhijjSiav.yketn fi yui ur qalc. Nig cayo bteziuv, qek mwa kero 8 rezhaj, rid Yecu, ldod pon Nixzoloi:
Givdoxsixh ZuxmodsQout zetazrk se WuvmomiSeuw.
Bano: An yee cuy’k guu rge yibzono qota, fnity Sutvats-K go pefiefg xku onj, mjuw phm uduis.
Nuo’fi ezav i Xouhaav xgad ki nqaq heril pzuufn. Ucr rae’go apad jne Zeafeod sxuv awd cvu iylipotgoxq nohoimsi .\fokmixb ni cowrojm qci sqeuvz.
In whid qhuwqeg, vei’ti axiy leek jafuuh ka coguruzi taij umc’p qiubp ehv pwob loraf fgioyx. Ox xsi beff ymoblay, rua’kr ornerva esnuccp: Cao’lt uku o DizaJeleWueq agx vaqilp MenmuvjLrito oq ap IgpudlepkiOcconj.
Key Points
Declarative app development means you declare both how you want the views in your UI to look and also what data they depend on. The SwiftUI framework takes care of creating views when they should appear and updating them whenever there’s a change to data they depend on.
Data access = dependency: Reading a piece of data in your view creates a dependency for that data in that view.
Single source of truth: Every piece of data has a source of truth, internal or external. Regardless of where the source of truth lies, you should always have a single source of truth.
Property wrappers augment the behavior of properties: @State, @Binding and @EnvironmentObject declare a view’s dependency on the data represented by the property.
@Binding declares dependency on a @State property owned by another view. @EnvironmentObject declares dependency on some shared data, like a reference type that conforms to ObservableObject.
Use Boolean @State properties to show and hide modal sheets or subviews. Use @Environment(\.dismiss) as another way to dismiss a modal sheet.
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.