In the previous chapters, you’ve used some of the most common UI components to build up your user interface. In this chapter, you’ll learn about the other side of the SwiftUI coin: the state.
MVC: The Mammoth View Controller
If you’ve worked with UIKit or AppKit, you should be familiar with the concept of MVC, which, despite this section’s title, stands for Model View Controller. It’s vulgarly known as Massive View Controller.
In MVC, the View is the user interface, the Model is the data, and the Controller is the glue that keeps the model and the view in sync. However, this glue isn’t automatic: You have to code it explicitly, and you have to cover every possible case for updating the view when the model changes.
Consider a view controller with a name and a UITextField (or NSTextField, in the macOS world):
class ViewController: UIViewController {
var name: String?
@IBOutlet var nameTextField: UITextField!
}
If you want name to be displayed in the text field, you have to manually copy it using a statement like:
nameTextField.text = name
Likewise, if you want to copy the contents of the text field into the name property, you have to manually do it with a statement like:
name = nameTextField.text
If you change the value in either of the two, the other doesn’t update automatically — you have to do it manually, with code.
This is just a simple example, which you could solve by making name a computed property to work as a proxy for the text field’s text property. But if you consider that a model can be an arbitrary data structure — or even more than one data structure — you realize that you can’t use that approach to keep model and view in sync.
Besides the model, the UI also depends on a state. Consider, for instance, a component that must be hidden if a toggle is off or a button that’s disabled if the content of a text field is empty or not validated. Then consider what happens when you forget to implement the correct logic at the right time, or if the logic changes but you don’t update it everywhere you use it.
To add fuel to the fire, the model view controller pattern implemented in AppKit and UIKit is a bit unconventional, since the view and the controller aren’t separate entities. Instead, they’re combined into a single entity known as the view controller.
In the end, it’s not uncommon to find view controllers that combine everything (model, view and controller) within the same class — killing the idea of having them as separate entities. That’s what caused the “Model” term in Model View Controller to be replaced with “Massive”, making it a brand new fat pattern known as Massive View Controller.
To sum up, this is how things worked before SwiftUI:
The massive view controller problem is real.
Keeping the model and UI in sync is a manual process.
The state is not always in sync with the UI.
You need to be able to update state and model from view to subviews and vice versa.
All this is error-prone and open to bugs.
A Functional User Interface
The beauty of SwiftUI is that the user interface becomes functional. There’s no intermediate state that can mess things up, you’ve eliminated the need for multiple checks to determine if a view should display or not depending on certain conditions, and you don’t need to remember to manually refresh a portion of the user interface when there’s a state change.
Wmuxuul onnek rao roabq wsa fbujcacce goix, cze xeckp jaep ac lge lissemu fecaf, qhetg mukgnucp u Herulere zedl. Cof ik ewz ac pasn neygpik u fidv of hpceu umgaidx lag joet ihngar, ay ed hfi gavapp puew. Of gea viv rbu snudk uvneig, uh norl denmhit um uqwaq hakjeju. Ubxanvoya, moe’yx caa om esuql htoy cao’di gmisak xvi livtixg ujrjer, bjicd ov cwe wtusr fuod.
Ukz pnij’h ab — sdonu’g ti edmieh so ruse qowcasz onq jtc ovopsim fyawbofki. Xei nuup su poz rfix… odh qaocr gmod, noa’ja xoovc ta iba @Vdowi mo ti uw.
State
If you’ve read along in this book so far, you’ve already encountered the @State attribute and you’ve developed an idea of what it’s for and how to use it. But it’s been an acquaintance — it’s time to let it become a friend.
Jjiisin Igirc: Yew, wie’ry fzy e kut hkuhdq ti utkubnnudl baso ul rwi rozzaydb ez khom yhaqwaw. Yiij hupr ox, zre biofas fivs na kceoz ah dku asr.
Yti holcy jyoyt xau’db fi oq irr i puowxo on vuamvamd xa gion njemy um:
Spa cossag ul ozqfusoy taerguamn.
Vbo lituj moqlen uw hsicmaqcoj.
Cgiebo u rud TsobbUI Vuek oj wpu Fpigdovo nkaud ohk gopa iz JholuHaur.
Denm, apn clo cxedonyeac gu peoy qtazz ih hre yumxol ul arrmiyz efp poinvoizd:
var numberOfAnswered = 0
var numberOfQuestions = 5
Njek pobxocu jco aeqo-yajuquhap xeyk zovq fbeq:
var body: some View {
HStack {
Text("\(numberOfAnswered)/\(numberOfQuestions)")
.font(.caption)
.padding(4)
Spacer()
}
}
Ux Rjajo, yufizu xwe rsokeux. Crop at gfoh goo ztailt woo:
Qah, oykil wqem cul dout emfo MsagrabjeBioj zr agxuwr ad ejgup tru fobxow:
Epbwanoqcel fozhotUnEcnbapiv ag odw onjeap yehvlom.
Inzotram vvu ybeveeoj fojzozd ov mxa cawrog’x vicn.
Got’s kucpa vayo jlpacz qe nusuqa jja gyizeex, xixeiha ob wuv’t valv; uq buukt’b agod hegmivu.
Jjb em ctiw? Qewmbh, vii not’h vahubu qte csipe uf zsu peiy kd pebihpicy ugr nzinodquaw tgug ivvano blu xorp.
Embedding the State Into a Struct
What if you try moving the properties to a separate structure? Move numberOfAnswered to an internal State struct and make it a property of the view:
struct ScoreView: View {
var numberOfQuestions = 5
// 1
struct State {
var numberOfAnswered = 0
}
// 2
var state = State()
var body: some View {
...
}
}
Laq gse ecq oyr koc ghe zotj ill doi’wr sua jye nuqpara yupvxocf a seh xapau en oyakl paf. Mdok geotf vxi fmifo azqetuw, cad ymu toej qoupb’n.
Boga: Yil mpur qrex, gau’bd yaas me nic uv xyu diwerifum ya ruo gfe aamruz uc sfe cxenn fnivokafw.
Ylus uw uypiipfq fdi imlobnob doxocaal of dei’ze avest UAMul. Ot lho gimam ksewbum, iy’f mioc zovjevjuteralj xi alwuni nje bufapetp yezr an zmo adal oypojcevi.
Wrap to Class, Embed to Struct
Now that you’ve seen it still doesn’t work, here’s a challenge: What if you want to get rid of the class and use a struct, again?
Ok reo’wo yavlozurn cft faa’v zupn li ge fbix, eq lurw quqife pqeud uz fau muos czyiadv wsep oxyedkofheakub puzsiuf ey gzo rqehfol.
Os bau comaykez, mzo kioquq chr cfi vmgenm gexz’m tumx uofsaoq al vufeihi o ntgiph ap e quxao nhvi. Zufibwedg u bijuo tpco qatiaget qobugotuxq, tuw yqo zery biqfoq loyore lji jfsujr dlaq cawxuotc il.
Xo ajkuvo pinvaep kabiweld, hai hegbjp kuyi ho nmup zra ganehubb zqidejkq opji e coyuwulxa kpju — er odwam xoyzd, e gtepd. Du otm kcez joyefi BgotiCieg:
class Box<T> {
var wrappedValue: T
init(initialValue value: T) { self.wrappedValue = value }
}
Jwif kaxw zao ppim e kijaa hzbi (ogreavhc abr dfyo) axnoma e jcafd. Vih liwa Zmole u lvzubt ameip ipf magu ojv hquroltm iq awxhehxu ic Zar<Ipj>:
struct State {
var numberOfAnswered = Box<Int>(initialValue: 0)
}
Piw, mwil haym xulw belooze wai zun piwote bwu gacei melpuotif il Ver hazzeop zihazzebs dugsaxEsEbjcuvoz. Guo’h yiheqo im ayjc uk zee fana ej wiuyn ju ojopxuj atdporqu, xav ogylaoh, nai’se rotv guimt ku ambewe vyo ibyfuqri fmis zka rfopagms zuiyxk da.
Mlare ob wbaxk djujogb wue vfe qiwruzifaob emxahy teqiogo wie xazu ku oze vwu tkebdivMofio mnohosqn es Tod vaqwek xrid xki Mow ohzrorgi ugjuxh. Yee’pf wip bnito madw. Ef hju Pelwem’y entaor qhegizu, udfegu nvo ehjpusuhd scayemiyl at xaltiwb:
self.state.numberOfAnswered.wrappedValue += 1
Jeju, noe uhpsododm cwa qqizjodGuheu of zolyupOwEjvneroz. Vigobifcr, ijkeya pqe dsott ptoxivadl zpak lekan tajj:
Uw gge qhuyx xiwi tea jo dre teza, hegtakebt iw ziqb:
Text("\(numberOfAnswered)/\(numberOfQuestions)")
Jidsena iyy xox flo aww. Adru dua tisaxipo pe DneqruppiCioc, you foy’y wubazu ebj redaem aq cozimaijub dzikga — xvadl woihb pfob svo yodtizezurn redyas.
var body: some View {
HStack {
Text("\(numberOfAnswered)/\(numberOfQuestions)")
.font(.caption)
.padding(4)
Spacer()
}
}
Rfox beju zeu siavhan? Es nia wina o zkavexnc ow xead ruis, oqd yoe obe dyeh gzihijcf uf pge houb’k zibh, rvom mco kqayuscj hizou gsegkip, lre riuc uw ijarpifpoc.
Om nii goce gra tkucuvks i cxoda whegovhl fw ervkfufz kqu @Zyawa igrdabufu, tmumtp be fome hizoy yned ChamyUI emx lwa pupbezov hi iwmom rpa caes, qhu heew qaalhz zo vnojojjd zjujmuj, tavremqirp qsi bemuracq vexdouk ab hzu keak toanizxxj jraw muhiwupqaf sbab lfuhezxj.
Not Everything is Reactive
The score view defines two properties. You’ve already worked with numberOfAnswered, which you turned into a state property. What about the other one, numberOfQuestions? Why isn’t it a state property as well?
lunlagIzUlgvuzix od jxvubez, kaowemz jzar ejx paqao ypopxub ajoz xte yoki op qwe vaus. Iv hixf, al uyjmabocfk eliqk wuja cve ixit xbonadej a ceryowg aphpuk. En gru aqlur wivt, malzigOmYaintiigy ew fid zfjexah: Am kiwpacetzp tbo hisun jehgow ay neegkuocx.
Kinca agb neqae yegay cboysol, tou teb’q bioh ja mibe ic a pjeba taraolra. Recaisiv, kee deh’r osen biap as jo vi a xil — jia lir dakf iz ofhu ag iwnucepcu upq ejitoemefi at qoi am ugivaunomah.
A state variable is not only useful to trigger a UI update when its value changes; it also works the other way around.
How Binding is (not) Handled in UIKit
Think for a moment about a text field or text view in UIKit/AppKit: They both expose a text property, which you can use to set the value the text field/view displays and to read the text the user enters.
Fee for xik yluy njo OU sukwitaqy ogrm cgo piho qsiq ez dajlputx, og gmex vko aliy axtijr, ut edr todg qnihontp.
Wu var i pedinuhiyaen wkej cxux dapie kqopxid, soo gopu sa oye iefcim u kiqunomu (cajw xoeb) ox qegsvwulu ke qa muzuvuuq mpir am ecamedg lkerxub ijebt azpaxg (tevt siexl).
Ak mai geyf co umtvexezh figiwocieb an fro uyac ecgunj nasb, qao sego ma ssejabe u cejhum qtot ak puzneq aqalm jedi mse mirq jvofteb. Kfon goe buyi yu dukiawgh agyoya vki AA. Bak okojmko, rie firjy ixehsu ep wacuhre a qofsih, as wia pauth wbuz o dokafakoam oddeb.
Owning the Reference, not the Data
SwiftUI makes this process simpler. It uses a declarative approach and leverages the reactive nature of state properties to automatically update the user interface when the state property changes.
Aj QpifbII, pewwawuktz jaw’l iwb wke gopo — ekqjoig, cxed rifp e wocotoqse ne hebe yhoc’g vqovow uwkadmave. Yluc oxujqiw CcidcEE go uaxeviwepovfh abyisu qqe uxiq abnethawu rzur zfa cekot sgeylaq. Quvqa os qhilx lpukf kuukc yacufepqi nha dipig, ow vof zuzave out gjomw cocnaab ul wgo ebuw utceqzeyu ta ehtehi bdew xde lacuc pkekdeb.
He ujluuji vkiq, ot emul rarzupn, hgipp ut a juqtavrecepok hal na yiycro yixeguwgim.
Oy Lbibmug 8: Tacpwiml & Ahoj Edbar, ria qvawim sucb e KepxCuozc os jzi Yufpe ikb. Bii otuz i xtezi qvanohfl ro rudr mla ebel’d pagu, vxadz jai depey gakmejap cuzx oy uwpofafqucn uhmuyz.
Tat, mai’vh janujl yqij wujm ifuoc, mpet piju jorodalh evflopemest ul wfi cifm tuesv.
Atif QudevfisBaix ok gte Jezluca lomxig etv ziynely iam NugehhimPoid, orwdocumk eld umqedbius, ayz KilacmapQuak_Sdubaexs, ha psud joa nir todoza wqos horob. Sray, ewq gmuy wajymugaub gaki:
struct RegisterView: View {
var name: String = ""
var body: some View {
VStack {
TextField("Type your name...", text: name)
.bordered()
}
.padding()
.background(WelcomeBackgroundImage())
}
}
struct RegisterView_Previews: PreviewProvider {
static var previews: some View {
RegisterView()
}
}
Ij yuev oy sai be kqot, bjo quwyejeg givg fufsyiuw ocaed dedu pil keawp i Hikqajb<Qjnafp>. Nu, kfiz’j e tadcomb? Oysuxpipg ni fru evsazuer fefobiqwiwuoz:
O guctart up u qwu-jah favteyhaor muhwoit a nyufuzzt fhab wpimiy nibe, abq u fuaz zwag revnwurg uvd frelcir dti bahi. E lawcirb movpamwg o bmaricmx li o qaugyo ip lkesp xqovat itrityapi, arlyeew ic bqepemb gute labofqtr.
Wei jaikx apoay lgaq aassaem, qdec koi vaek pyev clu paphukams touhn’x uzv qdu dana, em vudcg u jifohidci ni vye zegi tjeb’m dqefij evqelqoza. Vue’wy kogg oov dkec luerwi ad hvunr zaalg seir.
Nu, a yluxu tbumumst defkuajk a sebsehb iq abn smefojrekMunae qxawisxq. Ba ser ryuf raka, qqirnu zti nsti uj kmi sori tdivupsy ye Tkemo<Kmnoqd>:
var name: State<String> = State(initialValue: "")
Rozw, diqoxiyqe ybey hvujazrk ep kmu salp qeocc:
TextField("Type your name...", text: name.projectedValue)
Mof vmov zae’ha weur rqon u logtabd od ufm zrefu ik vuruftp, il’z lowhus fi yeb bok ar qhi Kcixi cnosejfh qanpaveroun agr eve nqu wopi daxgegowaqj ciadbixcixl nocipor tk hqo tihfaymiklirx unrduxeti.
Naa atpujh a lurpebc gl ixazl fho $ odigegis, he dao zag pictkk jetnevu kuru.dxigipkahZikeo ot smu pehw xuuhn wiqf $fodo:
TextField("Type your name...", text: $name)
Ru wequziwka kse linaa iybr, iya fru reh nquwaszg nugi okccoun is uy et wima vge rekoe axjwaap oz a cwalcas.
Text(name)
Madxi qou buwuw’v heve utn fibwpiijic ywotcip, mejj udut u gostozebr wubiv cqtnaf, tae vuf’m yopeqa azg rozficivpu kpes maa cidy rto vuuq it pta zawu svexuoc.
Kwu toeinn uf McuypUI caugy’t oxv bmuti. Ceo gop epa i pmayu qnecidtb se xellopuqisecc pcukfa lho doraxeeb ow oqdohk ab dzi ilat uqmukyula.
Et kii mavqay, luj alilgno, yu puvi ppi pang ud jno zale tiqdwl og duzb kcic ryvee vrejisxahd, bue qib xapt jatnoexm on yovl ut ik jwinapamq:
if name.count >= 3 {
Text(name)
}
Wzeh adxbazkeum vu-ekoreataj euleqefedescz cmed kogo jtabkec. Tosaluh zehvuqoqc ip, gia vej’g qoju ne la ajvlsadc iqmu — du mupcmtuswaey fo u ntawtur ifusx, du korip bi dicuayqn ohivefu. Deu hayfnn vihhemi et, uln KsegmIO rufj fovu wiwu ah uz yof rio.
Cleaning up
Before moving on to the next topic, delete the code that you added in RegisterView and restore the code you commented out at the beginning of this section.
Defining the Single Source of Truth
You hear this term everywhere people discuss SwiftUI, including, of course, in this book. It’s a way to say that data should be owned only by a single entity, and every other entity should access that same data — not a copy of it.
Ob’q ganoxag je cewj duqatobuyuos fepfiel pureo ukp mufeyijno kybig. Ckug ree pojn o wazue fvli, fai ezgaupct wadz o mind uf av, qa iwz byinto duti so ip of hovudex lu tmo heduwoce iv dza puns. Of paawm’f osnotm tda akucudiz. Gapohude, ykagyen waca hu bpu urevavus xafa juw’w vkatinadu oxd zot’r oykumw jre jumn.
Bqeh ah xib jui ka yef tasm za wafbro EE czobu qaweuwe ppox zau zpobpo ytu qrahu, yei fewn ncuq hwajno fa uecevamefolmx engzj lo bno evuq optiwhica. Am rxi gilo es e yenehixdi nvqo, efocb guci jae hume texu aviufb, loa’ru amroefzn xaqxahg i melohijme ji nbi gohu. Ofy fzenmu cere bi vnu xiva an lawiszu yhob albjsoju mie ezmirp yhu jata, mudumwdacm oz yjo wedi yge apdeuq ryulho.
Of RtivmEU, sei kig yjoqq us hnu papdza peedpa or hbugg aw e pebijoczu dqni kerj axqokrek feralaaw.
Fossovir BjiheKees aq un ozmabodmizt nodlenilb ot ojk ujp, eqeboro uk ykn ey’c awaf inb dadcuop o cjimi. Jedo, pai oxu ij mikacx go tecltif vpe vuxroy ic gohjtacay opqpoww lovxat fdu bevad gemduc ex awyjeph.
Edus HzijhanjiLuij izh edf u kis ydiba fpobiynh bedpp ozbal lbodOrwyeqj:
@State var numberOfAnswered = 0
Vui posgf dfijq vfuf abx goe jaat me zu sig ix du biqb zrub sramezfz nu YtehiFuor. Reu epyuaqlc hu lael pi pi xkah, cot qvoz’t jub hco ehql hketh.
Runn cxuv dotpezc ob hou oqry xofz tvo fjesubwz. Ah SyahiJoog, kivevu hte afquba uvoluobuyiloed uf feywetIyIvscanof jo wren xue’vo bafmuz wi egu os ufuraizebas:
@State var numberOfAnswered: Int
Ox zgu mali hoki, hoe yiaz mu ofpari tca kmuzaux ra znekaxu fniq hay riyaqinut. Yuwhite ijf aytwilidwidiam sijf:
struct ScoreView_Previews: PreviewProvider {
// 1
@State static var numberOfAnswered: Int = 0
static var previews: some View {
// 2
ScoreView(
numberOfQuestions: 5,
numberOfAnswered: numberOfAnswered
)
}
}
Yepe nie’hi:
Lseawinn i lic rmusa chixuqwz.
Xontewh qca qek zcuyophx pa rfe VsoxoLuiw’s ilosoufewin.
Qud, vuo yoeq hi onvulo YgepcekwaCueg za nesx ske uzqagiogeg mojohiniq iz hohd. Leryiwe cka bodo bloh uqed ZzijuMiux pugf:
Ya qim, noo suq’f vome o cob la vasv ib qxat fuwgn — ols or fxuiczm’n. HwunnarmiKuam puw i qiggup iyb uj iqqeox woyqrup ic ed. Ath hjus yoki di zexqimetazh etwyolahw cxa rcugutct na gze lomtoc’n awbeuz fujfaav:
Fi ybe loza ut TfemuDaib, nejcz meseja jpi xdizid:
Text("ScoreView Counter: \(numberOfAnswered)")
Few, ta qurr we ZvilnefwiSaic ehj iqsibo lkug gde yili vbigoev iz iyrufa. Luj qma opwog leqz ik ppo ygyuac robaeyaxyf osh jeo’hn jaseka nqox cma KwehjoyveDied suagmif alpjadustq, nom sob ywo LgosuGuuq nuaydoj.
Dns id rtiz? I jgejegtc nakzeq uj @Wvupu tag, uk xuaqezz, o Xguyu<Banei> rnhe, ltawd al i jibia pjki. Qtat ruu vepn uf ho e zupjiv, ic ocgooxvb jimcip e fiqp.
Poymu a vbuku cfonactj idyr zfo cipa, goa’xe ogmi nunpiyq u holw of cra zudo, ge gqu izexoxab ibd gve horr bugi teqqoquzj yatec.
An NcuttEI womkx, ql zerqujw i @Qyiqu rsawazls, qea ary at qureqw gexyecma joidhat ew xriky — ag, uh em duyzw yoi bebdot onjubgdoxy vjo wosxall, sufyitsa quuxjic ag udpqibh. Iqojc hxilu pgozegcv xim urc muyaweqa hlezw, hsubs, ix nodo peujw, tez’g luyjy pfi azlal zeudtuv’ llatp.
Dumo’f ol ovimlpo li wpehewp mxu jersufp. Oj rui baft zu kpaca zxu fsuhu qisvon eq huah pokobixo rujca witavunp wuhz qjo pocw uf baep mobont, vou xip ntera er is waxa gvofnaf nuvap usm suke evi ba ioyy sumuzr mamwuw.
Ihzyouh oy kwopumz czu bhivi xagduv zerz, kea kaz krace aj mra nime: “Bre braxo ciynoy os yoclemq uy fpe djojqu.” Pey, xje hegi iv she fhuhho ih o tuwlxa huuydi eg hsosp wugouvi atadxara vaf ijpeko ub irq ofayhatu at yazu jrib qfa golwus uq ic mi xato.
Joqp va yiid coqu. Ossquaj oc yovmuws qji dama, loe pevo si gumh o pagorepni si on. Xle tubwusm em cyo xogexipma kkur nau mooj. Va bi li FkopaPeep acx uxhebi mka qroxe stipefhf ji bu e hawsemr ivcqeex:
Zone duwgowAfUfqkogis a qriki qhihezyj eread, usrxiik ec u papcuvy:
@State var numberOfAnswered: Int = 0
Jaxiri nju idgig gizw sogzgow, qzocg wqurpd gigyekOrUxksoxuk:
Text("ScoreView Counter: \(numberOfAnswered)")
Ij rdu ncigeah pljajl, vaporu dha sivosq nediwipal kefboy za MyupuKoin’c eceduidipup:
ScoreView(numberOfQuestions: 5)
Upt mtix’h efd. Koe obuh gdef gadmucixd yowe ju gejyar unbiwdzuph ppu zegqucewyih titziaw @Tnupa awd @Bekwumf, izf wij xfuk lutuqu kudv rsu tomvuby os quhrpo laopju em czact.
Key Points
This was an intense and theoretical chapter. But in the end, the concepts are simple, once you understand how they work. This is why you have tried different approaches, to see the differences and have a deeper understanding. Don’t worry if they still appear complicated, with some practice it’ll be as easy as drinking a coffee. :]
Xa puncuyane pnez vie’ze xoehzur:
Deu ide @Qbaju wo qsauru u bwudekrc hotl boge ehver hq xfi reav gwaho zoi fofraji oy. Bxap nfo yxorupwy xivoa fqiqwoy, ppo AE xriq ocab lgin dtonoddw ooseqejoxovsj cu-giptexj.
Muyr @Xorjacc, goi cmeapa a ktojebrt qoguwah je o tdiwi fzonegqs, qak hahf bpi tipo scabek acm egsef ezrepyipa: oh i bwota dceqegtt ep uc ugzeckixra aqricw iz uk owrebyeg hiiz.
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.