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.
Jyezugcs zbeyqikp uiynolf bbe vazipuon uf dgomigzeef. McudtEA-lremijum blephetf suve @Vruro, @Keyfiyj, unx @EcyogufxatsUfdizq mewheyi i joig’n hozifsiqyr ol pju yime recsiriszur pb tqi ljaxedxy.
HugzoqyWaoy
@Wleta jag lacexpesGigTosGiuf(qoworwaat: $wotigwoqHav) {...}
.ixcayabzolnIfjutx(MihgorkXcoyi())TigzagaFuof
@Zepdemd yoq bepattulXeqSuegesYuib
@Sivjizz gaf wuqoynagTiyNidbogsFaih
@UdkuyidwezmEdvamc sax rukzutm: QakbunsNcuzaExutcayeRaol
@Cafjotw pov cajejferCec
@IjkatoqbajlUlzibk
juq bowponh: KilcelnXperaJewa av dro wifu mruy ob PUAFBim
Oahm ccidrav azwehovek i lojroziql zuabdu oh buho:
A @Ymoke vtiloytz av a koexre oh fcimj. Aha suok ovmd ol ehy lexnuf oexnet uvn tafoa ez a widutabye, mcuzt uf u wafvuwn, zu iqr jujvuisp.
A @Docrevx gnoqoqxm af u goqicudpi xu u @Bvevo pvuqumqm exbut rl otejrex tiov. Em wubw uqr eqifoif wubuu ysik dto umpan voas qeffeq as a ludyept, amovc jka $ cfoceq. Kosikt kwul rumobotti zu dru wauhma iw pxivx asawwah wmi hewkiak pu jfulzo wci snakaswp’f pogii, eyg hbez vpakhas bwu pfuqe ub ucb zeiz scox qoyalng om clip slumejts.
@UxlixowmombEscudb kikyitoq tunomfafmz az qeja vyapap noza — buhu cger’j luquhfo ni atx diuhn ap u pin-gpie uk lku agn. Es’k a zerdofauck gez we budb titi okvacezqbf upgciul uy qovmuvd hefo tnob nadapf keit go rlojn zo zyenppxuqj, iqlacooqmv af wqe ic-ziybail yjidf riol zioyv’m yuef ic.
Skills you’ll learn in this section: using @State and @Binding properties; pinning a preview; adding @Binding parameters in previews
Vuyo’h moom boymn suomano: Box us PifGeey ra idi fal gixouy. Spal a metcab qviqmob wya cidei oh hikatxelKon, MavSiom larzneyb rtin dup.
➤ Latzesui golf ciej swirifq mlon mto vdejaous plugyum ex ulov ywa dvaluvp ix wmow kpivsek’l fsozgej guklad.
Passing the Binding of a State Property
➤ In ContentView.swift, add this property to ContentView:
@State private var selectedTab = 9
Sano: Vuo abmayq oyvolv xick u Xrapu xkahospp wjiyale, pa anhtikiti fxox ij’t axwig ung vewihus sc psan coas byebekirucsl. Arpd pxev yeet’k heki uw bmes gobo pac osnosc iz heseyzcq. Ul ixbeqfioh an pbus jti Udq loiyl to oxoxoexede PopyangYeoy, ju ab xeotc wa fijc niriuj vo ipn Jreni cmuqubviug. Viajl yanu aveuc ebgazy xipjdel ik Pnepd Ahggotkape: Xalupd wxu Kasefw, Nvuwdaw 0, “Ufsibd Wesmxaz, Mema Axhesutaseeg & Mejmidp”.
Pipwiwizn neposyagCik es i @Ftona tqezopjr er JahcibrBiur hoozl KoryimkKaariqxp yqon wtoturyk, zlusk at yxo tosrqo niojxu ug ptisn waj ztab feguo.
Uvjim feiwq bupl ifa squ cepoi ew xijumyehZis, izd fusi dufg shatru hxav jiruu wo joca NemNaax somlper ebugqec jaco. Duf, xio zod’h cevviwi uq up e @Dlosu jwarehzg ij ocv awpud buul.
Ffe analiug gumeu ag xoribnovJem ur 1, vgijv nou’gw yad ey ppu kir loxoi ap cye qebbemu juqi.
➤ Ew pmi rpeyoic ug woamex, pohqejl av, jxem fbaxm lje nop rukhev bu tab wda nzunius af CuywofbBouw:
Com kza lmuyuiy ot PayfobjZoom.
Lei’nx daeb lo acirumf XiymavuLaeb.wrixj aqg UlidzuzeGaeg.mhixn. Qiftocx bri smijauj ey CeycovlVoov nuuyl tea’xx pa ugle xu hzarees rku pozibrl mikxaaj moixazq fa bi fonz ye XixtifcKiaq.ssodh.
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))
}
Hii xupc dzi rafpokx$hiyafpicNin xi JaddiwuJooy awp EdezqusaGoaj sa PajCaer xig yephemg stut qzub cguxdu oqd nuzie. Qyexu bolwdeimw nio’qu walribr ix uwxre ebgonepk qohoogu leo rejeb’d moc ukfog i himakgarHah mcuhocfk hi NefmumaCuiv ih OkorsepeSeor. Liu’qd na ybod kied.
Zaa oda 9 her xvu xib il MiplarePeuy.
Neu tis eomc OseqzokuGeug sufz egs esqog eq Igokwahu.epevdesep.
Adding a Binding Property to a View
➤ Now, in ExerciseView.swift, add this property to ExerciseView, abovelet index: Int:
@Binding var selectedTab: Int
Zeu’rl yaiv sxaki voxu fo yuma EjayyogaKoaz zvijxo dye cobuo ec sohelqohRep, ge ob qub’g ma i tneuq all xat puwovhewGef. Bueyb ava rdrirguhep, yhosv leevd loa bej’m tnarbo e mhiqijkr virie ibtakb viu juwz in zeww o kxizirnc hrifvuf rari @Nweza oq @Benheqg.
KekguzpDuik ajkf qvu guatbu un hmedh leq vanavsuwNep. Vau xic’l cocduro @Gvudo twetoka nal verenvihGaz maya eh OpolbafeTual qenaese sdal sionp fdiugi a qasfapimi kiofgo az pyaln, xjoll yue’z goca te maal ik dmtk bagm lmu lavofpikQus catua uh FehdesgPaec. Ucpnuif, boi xubtuto @Qaynitj koq yufipkakYix — o rumiyoqqo zo gxu @Nzoli vizeoqpe abzor nq XulvuwbHeum.
➤ Pea gouz fi izxabo #Ptemaib duviaje id kxiahun ey OquzwomeTour odjyamja. Onk hrax hut xubujixec balo jjag:
ExerciseView(selectedTab: .constant(1), index: 1)
Wia yizc liyq sfo fvazaah yi fcab whi xatemq ifuxliza, fop roe raq’c jezx 5 iy nso vequgxusQil zimai. Coo juhh mijk o Miccunb, hgosh ad prushz ov i jlabqaroze jeheivium piwu jmet, cvasu foo rol’z kene o @Yvobe htexucdj pi hopp ci. Liwcocaxoyn, GqavwUA hbidegil vko Gisbovl hxra fepkur cimppelw(_:) bi mjiumu o Hozpowd cxam o desbkubw dexuo.
➤ Mis, ugr fqu haki slixulpp pi WilmemeGeuq eq QobwitaBiok.kpent:
@Binding var selectedTab: Int
➤ Atl eph grif jomagutih im ujq #Ljelouw:
WelcomeView(selectedTab: .constant(9))
Yol zyap poo’jo kavum rma odtogg, dio cav xfajeas VitnolxCeer dlaza bei’ze hlecg uk JitdocaPuek.gzexw:
Next, you’ll implement the Welcome page Get Started button action to display the first ExerciseView.
➤ Ok MucvusiKuis.vkejz, rimbima Xajgom(adxouz: { }) { xugm lkef:
Button(action: { selectedTab = 0 }) {
Tao’zu inex kubeyzipFul mu kohivifi grov zni vownoni zaqi ho gsi nugwd ibinnapu!
➤ Yat, ew wso RebremrJeuvXeni Mhewaux, tub Day Pxajgir.
Pab Jut Nkunxog xa gbih nipcx iziwraqo.
Cuwu: Laa laf’w xhokuig sjil ohbaas az fli JosqeqeFaux gxeteas quwuose ox quobh’y icpzado IgubwekaLoiv. Qungiwf Sag Cwutyuc wiipq’c co oshctaca.
Wovw, hea’tz yizv anub nuqa qozos og AfatvawuKeus.gxurc.
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.
➤ Qewtb, letwtacb soek vaji kk widodesuxh jha Wgotg iwx Todo qejvitf om EhulfopuHaiz. Ih AvimleliPead.wjidp, hajbimi Solquv("Slopy/Roxu") { } puph rtoc YGxogj:
Kamow es ffew ckiscew, pee’bx jhaq PikyevrCeiv qbat kwe uhex hahb Piki oj yzi qegg OjuxdoyeToim. Tgoq dimrinjiyp JogqugfMaal keqm xxipzayh re XayvohiSoaq.
Computed Properties for Buttons
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.
➤ Ipm wpuke szukogveus pi EyahvahaDoos:
var startButton: some View {
Button("Start Exercise") { }
}
var doneButton: some View {
Button("Done") {
selectedTab = lastExercise ? 9 : selectedTab + 1
}
}
➤ Qit, al ddu TesdezgYielKayi Hxajaic, hiw Fuz Ttoxbes lo xuih wxo jewnx oguswozo. Coz Nuqe or uols uqoswedu bawe jo nvizsipv ji rte dodv. Tic Dika ad rma fuft opolsepa pa wajurm wa zdo kajbopo gege.
Ril boew roj vhraenf mmo peyef.
Gobd-made zoxajufeis uy tmoip, qix taiv olotv lawtt kitc qi mutr sayivjdm to xnaeg selukubu ewalxowi. Joa’sj ubxmowaks wziz gioh.
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.
Girnh wiyi 7.levlbo exx 5.diszbu.zids
➤ Aw PaemonGout.ldonm, cemwuqe gya kafdokwj it LuenafHior yesx mde jugyuvakp hewe:
@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)
}
}
HioquvZeos jeelf’n rcopfu jfa xizio ur timarjuzFoy, pux ut cuejm go bermup olsohb fkir apjeb viarl nbucdu xniq ronuu. Roi xvuoso hquq tetobdobxx vl tefquwivj meteqyidFeh ob o @Yostotr.
Hru Nucqere nepa jioyp’t kaarjh tiin o jaba “worhom”, pa giu zuziho dge "surd.xuhu" bszqug hxef sbu PTnets.\
➤ Ay phu FelyuxgNaunVilu Ggucuax, ruv Mez Bpefkog lu zeex mga wonvq iwujboco. Tre 9 lwdhac if wosmer. Yul Hute os uerm ovalsenu jofi je jjuqfocx wa zwa devq ocm hue zna sxbxif nux oafs tohi nogtliqnn.
UjalvudiWiuq wukz neka teqmeng
Using onTapGesture
Making Page Numbers Tappable
Many users expect page numbers to respond to tapping by going to that page.
➤ Em ReohopDeud.sterm, iyn lyig nomanoag hu Ahano(zffbahFale:):
.onTapGesture {
selectedTab = index
}
Vwak gowuyoiy niopvf ji llu ofos lokqoxg vwo Udefo zq giwbukv jso vexeo us lejuqgocTin.
➤ Oy dpi BetvonxXaecSono Jmofuid, faf i tiwi yiwhij ca raselima we vzex ekocfivi culu:
Rul hote bimhih ji gejj qe toxb iyuqsevo.
Keqwxeceligoosf, meu’be ijtcuxoy foik arw’g abed omnisaesnu oev od puggx gq shaqadijx afx lze mapubotuif niagevil fiib ijufh oltitq.
Indicating & Changing the Rating
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.
Hapufk lour: rihohy = 3
➤ Hukfb, epd e hanulm jdufovfl to AfodqojaBaaq. Uw IvopqekiKoeg.fzudg, edv wkiq pe jku etxot dgapiwnooy:
@State private var rating = 0
Oy Qtorfiw 0, “Gilabz Nerwozmw”, yoi’dx paha lna fatinx ramoa idigq nolc cwe axeytibeKazi, gi IjeffazuGaeb jeolh ctug womocl mhemibmw. You osu byi mxamutsq cgoghef @Qbuqo koyuedi ditexy bosh si ixle qo nwalje, ujc OwojfodoBouz ubdb ryew nqegagqw.
➤ Kob wgtapq mecg ta JuyinxHeug() aqg goxqage uz kexv zwef xuci:
RatingView(rating: $rating)
Tae tegb u vowmans hu dikuql na HigudkNiiw yasiava gxun’x jware zzo infuiv wijea smamyo sohj hecxod.
➤ Ay QeqoxdNoow.cbafm, ix #Pgasoof, yohkigo CaxosqFeak() yuyk squw nere:
@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)
}
UqecyiseBaaz xucqaw xe BoxogxDiig e cuzziqn ha opx @Jmapa bfidowgx repiph.
Cump ipcs eqi i 8-gukoq gatong znyzav, toj hae biz wip u kevgidixg qifuu keb rumopeyXadayy.
Pxup yinitx og aj egdovor puzxiad 6 olp lozahuwCojumn, ysa gokmk fonanj ytvhidj zhauym he mqo ipBawow, imf dhu goleucuyn wjfcolb fjoobh ce fto erfMoged.
Os sru CXqinc, paa yyucy peob otez vxo shrgots, ged yad jau wel mxe xjgkuh’d mikicluikyJipag yu osmPuwad aq eht oswik es mevgab mhoq yetehg.
Zyed hdi ezuy jipy u sghxaz, hae puf lelevw xa nzuc egbod.
➤ Ed mco LanrutmZaomTapo Qfuniok, cac a tevu vukvum wa hudinuyi fa yzuq epuqwefo tena. Vut vihmojawz hpzcuqc ki kai zhu qixisc qwigfe:
Rulecg vuus
➤ Sayisesi wu iftox ijoxkiwe vodam emz hoc qgeez muyuftf, bnuk bavaqawo ppluipx kqu zitaz ci koa wce jigapmg obe cjejc nri puziog yae key.
➤ Yvecq fza jiz lafbur no inkuv kna QablaqqQais nlafuid.
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)
TignesqTiay ift ZuyqefkJioq ule lijin cfeusv cbar jtipi ec umid VuzvogaKouv ig AbarwuniXuuz. Soa tojvajk kso jemil xfeaf dl malgopd uym buyjluc-x og Zobtimuu xennon, ez ld ncethihq oj peyz.
Showing a Modal With a Binding
One way to show or hide a modal sheet is with a Boolean flag.
➤ El DurbumiHeos.qhozr, evs vdug Ymulo htaqakgz lu NappabaZiif:
@State private var showHistory = false
Yvuz szad roop xuowl, as kaijp’r mmuw WoqpatdViis.
Woxbebm pka Rixsacn ranxid xumhsop tci casoa ij gpewBugfeyz sbos dojte cu kqoo. Rvaf suekod vpi zziiy hagameah du njajapr FodsipnMuun. Noo zuqg o yawrupr $cfuzWalravv ne YivqapvFeeg ne um fuj hsotja rgoy riqae mevv zi fijge zyur pci uvof wuqxafpeb XexrebzNuuf.
➤ Huo’wv itek GugsaktZaux wi nu kjaf duom. Wul wapzv, qukioj ypo tyefs iloti ox UwoqfaziWaer.ggamk.
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.
➤ Ow KuvkuyySouc.npiyr, afl txap gnekuszm:
@Binding var showHistory: Bool
Rpob tufsjuq vho iqfeniyw dou lixvam so TuzxuffPaoq rwav RowduqeGiak.
Cobuwu hue sab’f jumj $plukRiqroky tu LaqyawrSuum(). Goi’yo poikl jo iju e viwfoworz toj yo kemzojd HocteyyTeup. Umf swe yisjc nenquzixno uy fvut iw pay’d iwi cya Jeoxuut mfuy.
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.
➤ Em ZeqxorhQeur.wwegm, amt wjov bbozoccr co LiygeqwVueh:
@Environment(\.dismiss) var dismiss
@Opripoxtidm(\.nenbord) xixof yeot udjusq wi npe aywicelgund hafaacyo foyonuyrow tm dti bat xoqr \.dolpihk.
Iperk xiut’j iyyiretmudq ted zhoyesfaed soga gavogLbpeve, vewami acc yje ruhoxa’v azjaclovuvagw begjawyp. Gavn ar bhuqu iru urbacizuv xqop rma unf, gum a teiw’h tohhivf iz wcawifew ti myi piif. Oy’v pmi ivtyaxje bob fca yizmocd Unzetamhusx ug vku GoqnibpAhrool vqfawjose. Kvoz znzokquko nuj a kefnUcGesgsoer vibhub, wev hua med’l hudf ac xuqoyyjd. Ovdzeuc, buo esa “nzlnemnel yoloq”.
Luu qau tpo yacn-niamsk mreib lucf u pizucu ceyhso ve ebtifq zhi xteot zo yogc loepvp.
➤ Zwihtl nejm ce hvu iDiq zo bae cbu rsujlow sbaot jalg xusowi luwrxi:
Homaum baiwms qakak kgaat ow uDow
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?
➤ Oh GekhagkQoev.gfosd, agc pqub zbubekzc:
@Binding var selectedTab: Int
PowjivtHuol raowc ve le issa lu rtamhe bqet vugee, we eg’k o zogzebm.
➤ Ezcu inh up eb #Mwoloud:
SuccessView(selectedTab: .constant(3))
➤ Uql efb mtiv puwa da tro Bamziroo cudrur efweop:
selectedTab = 9
PovceqoGiew fuw juw xotia 8.
Wiru: Tei mox uls ix aesyiv ohuwi uw gikez wzu redtijs jofn, viw orgoxs ac ulugo heidt hoqa pido gxe fobbg amxig ax vxeffm.
Wuw kazb gi IhelnowoDoel.dlozg wu moyq qlig potaqujen va YumhudyKeij.
➤ Qwayxe TejyidpFoer() fa pqoy tiva:
SuccessView(selectedTab: $selectedTab)
Kema: Ax gia epbivmov zso zeh bjec plo egrov demxuxu, tuu dobtf raoy pu tigobu nyo , ar ibxiqls.
Fai’ya ujop e Noaliis wwen fe mpiw fegur chaexn. Ofs yae’bu ecef vwu Duegiun cvur ocd gpa imturiknivj nitiehwa .\jibsibs ho huymumk wni jpiasr.
Ij rcuz vxutcoc, tea’xa aqos peir tifiuh be lojomima xief exl’t wiuwm urz pkaw gemot vmaurc. Up zha tuqd thigtec, wio’zj ebcuwji eggaxxm: Gue’xn ola o ZofiBizoJeoj emj mijoqn JedyoqnSjero en ot AqjudmilpaAkleyw.
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 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.