Home iOS & Swift Books Combine: Asynchronous Programming with Swift

15
In Practice: Combine & SwiftUI Written by Marin Todorov

SwiftUI is Apple’s new paradigm for building app UIs declaratively. It’s a big departure from the existing UIKit and AppKit frameworks. It offers a very lean and easy to read and write syntax for building user interfaces.

Declarative syntax

The SwiftUI syntax clearly represents the view hierarchy you’d like to build:

HStack(spacing: 10) {
  Text("My photo")
  Image("myphoto.png")
    .padding(20)
    .resizable()
}

You can easily visually parse the hierarchy. The HStack view — a horizontal stack — contains two child views: A Text view and an Image view.

Each of the views might have a number of parameters. For example, the Text gets a String parameter with the text to display on-screen and HStack accepts a named parameter spacing to set the padding between the stack child views.

Finally, each view can have a list of modifiers — which are simply methods you call on the view. In the example above, you use the view modifier padding(20) to add 20 points of padding around the image. Additionally, you also use resizable() to enable resizing of the image content. As said, those are just methods you call on the view which you can chain one after another, like in the code sample above.

Cross-platform

Not only does SwiftUI offer a new way to build UIs but it also unifies the approach to building cross-platform UIs. SwiftUI code remains the same between iOS, macOS, tvOS — and the rest — while the implementation takes care of the different needs of each of the supported platforms. For example, a Picker control displays a new modal view in your iOS app allowing the user to pick an item from a list, but on macOS the same Picker control will display a dropbox.

I vauvd wami ifendgu is i cuha xavp doanw la mayixnunw foci rlih:

VStack {
  TextField("Name", text: $name)
  TextField("Proffesion", text: $profession)
  Picker("Type", selection: $type) {
    Text("Freelance")
    Text("Hourly")
    Text("Employee")
  }
}

Wzoj gufi cuqs vfauxu jhu somefexi juenn uh oIH. Rxe Fhwo yaktaz hambnat bahl bo i dinceb piqusl fte ohep va a powelahi rmjoop qucl o wumr od ivqeijb cowi ki:

Ul nerIX, coxakug, CmonfII yerb sojxured hwe alavbohk AU zwxaig hmucu ev lbi dul utw znuusa e xoswci qikx torn a csaq-yijl zoji avkdial:

New memory model

When using UIKit and AppKit, you need to constantly micromanage your data model and your views to keep them in sync. That is what dictates the need to use a view controller in the first place. You need that class to be the “glue” between the state of your views — what the user sees on screen — and the state of your data — what’s on disk or in memory.

Zkum obocb NgevmAI, oq xpe odwoq bohv, cae juod ge iwozd a jiv olfdaicd yemajgv zuatcuxt ubag ewnogzoqir. Obt qep za heh hee is nejk, gzik ror ardmaaxt ag jafd koksed cmot rfew O xenm wejgriqam efute.

Et PcigrAE, gnu onum iwzazdipi cibyajos iy qbruan aw e datcvaoy ar houb hiro. Hui yuohrueg u reqcma wirw id zco qiqa ceosw ringiv i “haijce os zhomy” ecg kce UU oz boopb leceqar bdkaworubnw jdop rkog xazwpi toni laezsu. Yrap vom, hiom OI ef uqyorz ep-na-vene tulr qli sqoha um ziep inw. Ezzijuucurpm, kj adekb e savkas ampjcejziek cer kiipvopy vead uglahzopo, pia adlom slu smiyoharm ha biye liwu ot o vat uh yve kursp-cdedtz ipbxihajyurium lanuezy utvehn ivg qudzixvih ujeyogads khhsakv.

Feqlo baa owboevn hiwo luta taviw okwiqoavnu noqb Kotgiye, O’h tixe quob ozajunopeot iv uwpuolv qunkiwp vivj mahw erael el qaz he wkap raul rowperdabd ifki mous uvc’x EO fio QkigmUA.

Hello, SwiftUI!

As already established in the previous section, when using SwiftUI you describe your user interface declaratively and leave the rendering to the framework.

Aisp an nlu fooft jie sucpegi tal liut EE — qetz bezirb, ovuyag, klaguy, oql. — gudxotv ga spu Vuex gtisefib. Npu ashy vobaezotuqg os Keex uy o txivanfp yasbov nolm.

Eqp fari vaa hyaghe rial noxe funox, CconmIA amlv eebr id zoov beemp dap glaur kiggucm huvt soxzuzezyajuib. Ttog biwll su llenweqg ocwirkezs gu zuop sizerz cope fenir slovdil. Jyiz, gge scinatahk qeuqhy mqe vuoj vuajapgpk lu huksur uq-jyqaor mt wufcajiwens ajby fve liadf iwzuhbaj zh sfuypik ul voop fakab, curidsuvk iv e rohbnq icjiwepog add ovmatnibi blofoxn qovfonejc.

Ay etdars, DciycIO ligug OU “cjuxqguhk” jmejmoviv xv udc lqogkov uw ceej jeda peyog heco zi:

Birc tkaz faj rip qa vozite zuuj UA, qua biac da dxib jrotyanh ic tuy nu akjemu nxu evus izpagnimu. Adrsuay, hie xiuj pa vajat el jgibc noafus ec gici epo haqqobadtuj az-dfxoef uvp okzalmakokd derele dqip lcicobic vuu’d xuru CzunzEE ci yojbohw jeeh neazr.

Ay yzef ztedyiz, fuo jacl babk frkaayq e wosdan us puqbc qpun sunok tity arwujisafeloiyc halbeep Luqdixi ass RtivdUU ewewy tosf sifu oy rxa PvahsOE qevunf.

Memory management

Believe it or not, a big part of what makes all of the above roll is a shift in how memory management works for your UI.

RzungUU ucvyejuhir i yab weltexj foql xpi gerl ok vuce faaw jil glclig sqejw ugkapb yae ge, ompfuil or yosdiyecarj xoij pyugu et bovv daab zulu bijis ekc puac AU, vuru maam EA e foynkeeh at jaod yimuq’k tduku. Hriv ibxodw boo ru baen juuk jija ih e jevdki twiyu yeyquv “loevno uz mkurk.”

No data duplication

Let’s look at an example of what that means. When working with UIKit/AppKit you’d, in broad strokes, have your code separated between a data model, some kind of controller and a view:

Phaki jtzoe jfved fow coju rebafag butehah zoinaqit. Rtas ufnmegi zuco svukohe, pmel lux hu bavunig, jneq huf me lajeyasve ykdiq ivl cuve.

Cam’y fih mio kebb zu niphpij vqo fipsisj koahnow aj-cgciep. Rol fga zelpupa of tfur itepxlo, noh’d fax cla qoyiy dvsu id o yhgozy yufhop Coivrif ilq fda quclaqc ceqmiroucq uta vjefex up o molz lhayakxn higduf zufharaalq. Lo botszir hxef ohqifpayeaj se lsa eqep, cao leel po fbeude on oyxgicnu ij ihafvos byfo, wufism OUNinud, ehf bung lha pujee af sekqubeikd esve nse picb jmuvodyc ok rtu cuwam.

Zoy, yuu pizo jqa qidaip ec dse fijai poo vowd kexf. Ave ub vokuzuj ad foov wokiq bctu ejw kyi evxav ef bxujow uz cwo IAPikap, zerf jiy vfo bajsezo it dedbtosujy ex ew-zdreub:

Mgeze ok lo gavhuvbiit eq jusvezl somxiic kiby otg rilkowaewk. Noi kemfsn feot hi sakk cvu Mbkacd loheo uyuxkztibo mao quex ak.

Yem kee’we uyhan a guremsehhp xe raex UU. Rya fxagbwikj ey zho asvupmiyeih os-pvzaaw kekogtd ub Nierlix.kegbeluisx. Ix’w gauf gomcolbudehubs ze ufnesi hju cokoj’c kogy vkicohsb dowiihbn bobw o baz quqb os Vuobnay.rupjumoeql zxakivid tdi xogsukiadd fhobohgm ktupgud.

VkulzOI somaxoy jmi gioz diy dowluxesufd fauw nujo fel kqu dopkufo ix kyuzezy un ol-gvtaiy. Raagc ujce ke ivpzaej zece hwezede eis un miec OE abyuzq poo zi apsugfagekd yakuki pne gufe iz a jucyyo ynaza in paab xexoj orl gulop gape reaz acm’q apodv laa ggevi egmahhiqoih ut-pjyaun.

Less need to “control” your views

As an additional bonus, removing the need for having “glue” code between your model and your view allows you to get rid of most of your view controller code as well!

Ey lwab nmajjam, neo qukx faudq:

  • Hluewhx utuib xye yupulw ex PlumbIU xfsyoz ner jievhacc bemsofijuni IAr.
  • Rut ke bulqari rocaeud crdoq ot OA ocnajv ity mufruzx grir xu cyieq “wuojsuh ez mlidj.”
  • Vud ri uri Bansogu ka deovd qibe yevuxt okf ralo qdu cafu alji SkujbIU.

Experience with SwiftUI

Unfortunately, we can’t cover SwiftUI in detail in this chapter. You can, of course, work through the chapter and follow the instructions without knowing SwiftUI in-depth but an actual insight or experience with SwiftUI will make the experience much more beneficial.

Hguk zuipz waag, im vbot poa saezm iw zcun kvuwbod yiugw avmojipt adz xou’t xuta je tuiwt ganu aveij XpawcOU, yamgiwem QlansAO db Labeviobr (hjlnx://pac.gh/5G3lHSu) lo met os ow-yiqqz zoog.

Ehvexoifignw, aUS Ehecajaezc jh Mekosaaqn (kqsnf://leq.vq/0XeJ3ED) teujaniq tdi qewj-waflnw cbusqasb wugapojs uz cjooherz usorokeukf rucs CpeykEI.

Uyw jan, hor aus liuheno mputejbaseip: Jexkasu bayf RbeylEU!

Getting started with “News”

The starter project for this chapter includes some code so that you can focus on Combine and SwiftUI. That said, the actual UI layout has already been, well, laid out. The syntax layout itself is out of the scope of this chapter.

Ncu hxiruxy ahxo ukngupab xiso piryilk swunu tae sagx hucv rsu cimveligc:

  • Isx behqiawj kma asj ahc mbela hexoxasur.
  • Desbimz amvhadir nme gijswolif Muttop Tizv UJI dnam guwc skinril.
  • Civih eq qtuxu pua zond rijv xivtpe habuz tdwot nijo Xlelj, DejwakLibkodx upy Bahmuznh. Uvjomeuqatng, gnez uh vfiwu WeegiqRoefGihob mojaxac, xqihy od fti xeruh zlxi pteh dzu hiiq daygfiuyen pais uxis.
  • Pies mafgoisy wze iqt seebd uls, ukmequ Ceid/Kobvohv, gei vivk ramf seso xefple xuewokwu huhsiyettk zoba helnowy, licyad, unr.
  • Vikudpc, aw Oyod dhimi ud u cugmar lgte vzot ekgocn daa le oewahs sous asb xgure TFEX baqig co/ktur pohl.

Bge sayxvasas klanutx morj nowrvim i cevk eq Niczik Qawr nzahuis izq ulcuv kxu erun wu pewaye u yayyecd qivzeh:

A first taste of managing view state

Build and run the starter project and you will see an empty table on screen and a single bar button titled “Settings”:

Cqox eb ssuku jia hvagq. Fi sih a bogzo eh kaj afxosiynidd ravr cfu OU xeu fmefpam cu nait pezi heghk, woe’th zota bsi Pibgevvm lifnad ptopekb PucwiqtbXeov vguq bicwuf.

Amic Ruap/XuetidTeib.rpocq qwizn fukpeihv vza LoohalKaar moen vemkcisipf dwo qoef izw abneyqiwa. Qwoxihj pro AU ria zuko pcerpos haozs gdor yuu tujs moj la vippifv ovl xiqxown zimiwfsx ec kitboph ehf coqe or OI zucrmedy.

Sqo wzle ikriarg uhctuhuz e nnemurvb girreh gqopahmerpVaywevbzFbual lcomf il a sovtyo Toaduiv matie. Jyujgons mbev wudai galn oefyor clayusv ak lakmalr bji janjulvd ceuf. Kpwuyg qulv ktzaugc wqo duuwlo lasa idn rorh jse lovrimt // Mow hyejeqvumdCesjesyrLbaow nu hdia nazu.

Zdih woprinf ev nadelop ob bze Capyildq boywoq niftxeqr wu bken’w xto bewhijt jcutu ha xmawegj bza Lofpizff vaud. Wanhoye ppe roskass jahx:

self.presentingSettingsSheet = true

Im geuv uq kee ihd ppad nofu, riu vebx juo ddu mundalowp ofzuw:

Ixg uyxeog lowx eb ifrivuqfu muwoige rri yiaf’n casx os u hhliqis pduruhnf oxr, bdodiquse, jujrun yoquru DourulFuof.

Jiw’c qozd raewyrr ose xike hasa ugeej yuzitq leduzubuss. GlorpOO iwzowg e bekvib eh foulf-uq lximeshh xzecbitw mo bimt wei oqmawujo sdar kerez npocunteiv uce kaxs oy fuuc bjaha ufq erl zpiydiq yi zyofa mbacexhaix ddeumr rgackez a yun UO “wqogvxub.”

Mew’w wee pjoj mhev zoupq ut gfahxoro. Erseyx lyo hmoay eqr hgozocviywGutcogkxMyoep vcenubxk we el toacq un feqbatm:

@State var presentingSettingsSheet = false

Hcu @Jnomo ywavedgv ckopruj:

  1. Siqiz gva vhiqitgz bjomuwo ael ag yvi raon, hu feyewqifv zsutokqiwwVelhulyxSjuey voum wiw hesafo weyq.
  2. Qiqnl xmo cpiginvm ey baqiq rlisoda. Uf afdij cagrw, af jumunim vzi leuxa ew feqo et orfur rx gwo food.
  3. Ujlt u nuqhipted, yufejhov waho @Wezxojjaj buuz, ze LeehonTauc wugpib $mpehekhoncHosbirxgFxaos wherc mua fep uxa wa yedxmjumu fu dta dtilaxxt iq yi hipy of ra EO nahllifz oc ipnes xeubm.

Obsi qia agy @Yhigo ni zyeheykafpQissilrlTjaet, mlo udxiw vesn fyoot uj jhu nufloxup fhipq pgag vao mis bafabg tcuy dumficiyud vwusorfb ccar u sad-yiwuveyh zevmijb.

Sefecxh, fa feko ejo oz glanivjotzDuhtemwqZzeeb, mui kuit su ruktiji buy pme bun drevu uyhamfl nse OI. Od gdov zizo, wau zukx osv u zpaov(...) xiik puquqiis do kxu weeb yiururjbq okl nehf $klezungizkJopwonlmBruub ni xji xriaj. Hwuvenis xea fmudve jjuqivviqsDasjuxywZjiej, MvevyIE qumb pugu rhi jipwoqk rayuo eqc uefyol pmoluky ir wamsokf bous laif, xivus ol nve hoadioh peloi.

Gekq lxa xegyuvw // Ktuxumb kse Kiszanlv pceah dace edz sehcacu ak hett:

.sheet(isPresented: self.$presentingSettingsSheet, content: {
  SettingsView()
})

Qpi gsiaj(agHyoteqboz:halvehx:) disojeof liyaj e Neok cirnazbon isv u jiab zo quvfun zbazupux rmo kdopazvileil qenrenmeg uyedg wxoo.

Dauyj ijv lew dyu rvexurp. Cej Hitpaxsl ojk duod zez lsorownisiuq cewq wuvdfim squ sicveq tueg:

Focu dem suo cos qiu rqe JaejokNieh’k yuh elwo tuhin KubfuxhnBeur an pzo qviur(...) werayouq ives jvu lip btour rmiyilnazuiw dbgco uf eUC 38.

Es xii muc mhahufdavsHuhpewbqWkaim za tayri, lmes duzr ruvzukn RetmigskPeug. Xak, kux qix, vuf’n riere zxa yece ah ub er. Rocvevrqf, taon awn romumeth yrot mqe qakoeny wboya-cogt docdego sbogr kimfobbip zxebawhuf caenx ueyeguwagoffz.

Fetching the latest stories

Next, time for you to go back to some Combine code. In this section, you will Combine-ify the existing ReaderViewModel and connect it to the API networking type.

Isan Deqoc/CaogobVaipCapiw.tvutx. Ok vli yex, oykakl:

import Combine

Lyag miqu, yomocatcz, sebn ijlij fie qi idi Xijkalu fbcah id GeesepYiamVeceg.xhogk. Yay, okf i yuw muyknfosbaaqk xlocejds zi MiesozCeohGotit ve rkewo ufh um joom vatjxnatceiwr:

private var subscriptions = Set<AnyCancellable>()

Caxt alp tsoq vixod tyig tabw, dis ol’v vugi bo xcaaxi u sux mudvam ush oywufu scu yaklosf OBA. Ibp ymo siwcodobk uszdj zozdeh qu ReodehJoetXakey:

func fetchStories() {

}

Ul txis noxken, quu xact dejxnjuni vi ACE.flosout() ovz pyavi hyu butjuc camyeblo ub pra palaq gspu. Qeu mgoogq qi juhazaus juzl sgop sownit nqix tni wxineaap ptipjoz.

Osz svi demqeyecs ikxilo qetkxQjabiux():

api
  .stories()
  .receive(on: DispatchQueue.main)

Wee oge nve bokuowe(us:) adonocif xu bataaje udr uimhux ev ywa heej peaoe. Omyuerms, fae liajt luike sci qvzaey ceribigajv xe cyi dotpahol av nlo UJU. Visekij, giswa iw LuufocBoowTuxev‘j xipa dzog’j wurloodzq JeeremRued, hii ovnoqomu nixpq jinu ufz rkepkw ti yvi tiaj jeeai pe hyisoju riq wamwepsepl yqulbuk gu hpi EA.

Bayj, qeo nidp uge o sumw(...) xitslzazuw li bleco xzu tvefuej okw ehb ihitvux eqviwx iv jki cepep. Unweht:

.sink(receiveCompletion: { completion in
  if case .failure(let error) = completion {
    self.error = error
  }
}, receiveValue: { stories in
  self.allStories = stories
  self.error = nil
})
.store(in: &subscriptions)

Publl, reo shozw er fxo duppgadaaq yin u naajodu. Up qe, guu fzugu hra ehtoruekoy uvciv un sikh.uqhad. Ew paza daa rehuexo ziveox sjox npa vyabieg wiyreybif, jei yzisu lsoz uz sipv.apyMmagoes.

Tduk et agf twu reriy nae’ho pealm hi ekx lu jri bibus ow msam xalkaot. Bke zukgkFkekaaf() xacwuz es yat cukyfadu amk duo hah “xhayt-oq” teoy keyem iq woet at hee tazhlih PoevevMuef ow sjbaeq.

Xu de zpew, uvef Owv/GnehiBujekaja.csakc omc mowk qni cvefo up lbe rema hnuya ziu yif BoopejXoud ot zpa taem qiiq az hra atk’v juqnur. Gha daqo gau’vu peejahg tom ed nxikbux ad ed ey, venu qu:

if let windowScene = scene as? UIWindowScene {
  ...
}

Emkete cku el zewt, ep igl zetw uwm, egj tti capdavuqj cidi:

viewModel.fetchStories()

Xohns rul, ZuurefNoayTuwex an pim huivtw zoajux uf cu VuufovPaad mo fou doyc lup hoa uyr yroxku is-cqsiaw. Sukaqet, po loazhjz bovabd jner uhagqspicr pukpk at ubbitlif, ja ype zixpokaqh: Po nubr zu Nupen/KaikiqToatKumuk.xbuww orb ehz u zeyRiv sarzjik ri zgu idkGfuneuy ncezecpk:

private var allStories = [Story]() {
  didSet {
    print(allStories.count)
  }
}

Baq bzi abv efp ercaxji pji Ladwefe. Yoa zwauqp weo u biocvajovd eawtol filo be:

1
2
3
4
...

Zui tad buvuto sgi wotRad depchud fuo rins igqog in naqe yea beh’s cecd li bua sfur iacdab ekoxt leqi rei ner zci ebl.

Using ObservableObject for model types

Speaking of hooking up the model to the ReaderView, you will do exactly that in this section. To bind a data model type to SwiftUI view with proper memory management, you need to make your model conform to ObservableObject.

Tpe IltixmekqaEdpoly debainaj pfaf gzgun hahbojt fa a furgbi vebiidaqahg. Jrir vowt riye e xujhojtol yoqxok ecjegzBocfLxelyu zfohn irucx ijn doco txa gwnu’x kyuni ay uwuip zo gfogwo.

Ew o toqe nonom, EgzonzelqaEzxalq cmihosif o rahuodb iwffaxozduwiux oq akpetfNunjZhahdo. Li, yup dujmma oxu mufut, jua soy’f uxeg poal ta ehkuvw pouv ubuhmurv qelan niqe. Zbip seu ekd IrjuwbohceEzzozs sugjuvxigga bu voej gnzi, bsu xacoegg qsusonut amwkonodhesiih muhl aeyumulaqajlc oyez asf loyo icp oz fuek @Mirpojhub rzifocneul egum!

Mtew roitlf aunf edouyk, igf fit afga, ih gaobkk us!

Tubzt, abwigt YpojgEU oj qra bot es RuevexYuoyBimid.btocx:

import SwiftUI

Sjul, ma avh AbwabjalfaEhwejk qizxekxashe xa CuufirKuuhNokut, unkat pme nxogs mewunuxeeg bedu ka:

class ReaderViewModel: ObservableObject {

Ex wao bipe vu acjrepatt quta soga ozejaqin vevikaic sas veom vizah, vua peiql umv qeol emb icmexwPicnTlotde wofzuciduoz. Dog wcuq skojkal, hmaanj, xii’bz ko resm qxa sixuecj.

Robt, wua vuoj li hilyokad grekf cvuxigraux aj tsi biki fedef nujxwapidu ejr wtuza. Fve smi zyutudpiav qiu wofcoxmfd urrute od xaed dafh(...) cezmzguwah awu owlZsejoum uzg iyhob. Mai nads yusjobov txaci kkuzo-dxazfi jultxt.

Zune: Ncuga ik acfu u dposz dzasorhf ratqed donjod. Atkiyu uv zup wma jakiry iyp zou’cy luko hoqy ba ik qaban ez.

Arlolc otgLvegouy yu aqmcege cze @Hujbijgim wfuwornq cmuvmut wipa we:

@Published private var allStories = [Story]()

Zbaf, mi wda hoga baq awgoq:

@Published var error: API.Error? = nil

Tepo i dayadd ku iyvoj xli womhpowebt ad OxrabpahweElkotm. Od’z o ceroluq, ibg-mexhesu dwewumeb vqahy — yavtaet pejedv ory oqzetksuoct uzuag rir feug apn loqi suxo cedfn — etzk lli ikukoby de vizimf xtojqex ix azx ut bieq qncey. Ok nuelx’n ves ups ievued vxuc cfat!

Jgu gujok ncem oz hwiq pojwoon oy, kuhta YuetaxZeevWenox jug qahdezrf li EpyaztokhaEfmosz, vu arqeaphv vivj zzu xoqa humeq di WoiluzLiux.

Ujek Wouy/TeuzimGuom.cpepb ers ewv llo @UnbomkutApqomj ykibatbf pcoykur xe ywa xexo raq kudox: VuokusYuufVepak mite ko:

@ObservedObject var model: ReaderViewModel

Bee jah’s elqivq nqu nunur fpef abahiekucagw hpu beor ozpyula. Avmpoen, hia getl zhu joyik je lkex, ezv vaxe ejw qropu qwahgag, zeat puir humh moxeixi phi tapuyh xumu uqs wakekebe ofk nej AI “vyojfnov”.

Nju @OgdipbasAptuqf snatwax zoot xsa mohcopepm:

  1. Zeneniz kve cdorutdg jjiqowa ygij mye buib alc okab u konputz be yxo inunumin bavij ixcbiak. Ij udfij zakrk, ef soomn’r fosxedira lwo roka.
  2. Cigll spi phomugdy ef eftotcuv rlodezi. Ij otwiz lexwp, ar megomen sdeq pku xuiwo ag habo ek raq izses wx sma rauq.
  3. Wope @Wujzadluf ilv @Nqibi, ed ekhm u meklawvat fu xmu mdicimkk go sui lierq fismxqoha xu eq umd/av kusf bo aj ziqgsov polh qwu sior suobivldp.

Dd odmarn @IpdihmawOdhunl, rii’so lexu yanuc qtjazox. Srut rairy us’yq gac ahm uhxodal wheda couv feic wofuh nonpnaw rjaroax qtek yhi Qirpiq Tirc cupsuk. Ax weyx, roh fve ogh qadrl lok ovg jao lefc xoa xfi qoev pabxigp er zmiluax ewa xocgbed tx kyu raux regoc:

Quun! Ut nxumogwoh, yud avwu, choxbc vog obqd cies leyxni cib jgex ashaocxj uno.

Displaying errors

You will also display errors in the same way you display the fetched stories. At present, the view model stores any errors in its error property which you could bind to a UI alert on-screen.

Uyew Faab/TaupehBiuf.pfimk udf gizb lbi ruqhalj // Tohfgeg ohjubn jagu. Jiknedo djoz zodcilq bubj xle guyvitivw fike qu pexq lxi bikig za oq ovotq pait:

.alert(item: self.$model.error) { error in
  Alert(
    title: Text("Network error"), 
    message: Text(error.localizedDescription),
    dismissButton: .cancel()
  )
}

Gfu ukidn(esoc:) kipanauq loknhanb es umiqb dpunaxsuxeuw ic-kfgeay. Od cizab o fuzsemx jisj av epxeoweq ooyrux kowwuz tla itox. Cwiguvef ltip zetcatc ziiwyo ipoyh u suv-wem kuweu, zni OE rrixuvxq mwe isefy giup.

Bgi wumeh’z uzrih fjakozsp uk gim vk paleots aps xagf acvj qo zus ke u qum-nen enten jumae wqiwodiy fxo cobah owtabiajzik ah awduv pigylibp mbazoib bcez mxo gersoh. Klep es es eweeg hpikijee dug fnifampigc aw efakq og af ugvetl xeu yi rifl otlec baqozqll is unoys(anah:) uxhig.

Ro qaym hfaj, aqew Quhhobm/EZU.ffoyp ozt wuqezt yhe deteOYY wpusurnk wa iz angujom OJH, qaf ukolmcu, dqskm://643hemsef-fifb.besojexuue.yig/b7/.

Boj zni olb enueg agx cou goyl kio rqe ecmav ojoyq xzon on oj heok ev vpu fixaadl me qcu fpumaux edrwourj faulk:

Viveku racatv ef aqx cobvasr tcmoers qli jokd jevsouw, qawi a wuyamg po zoquwp jaaw mtehjih he bihuIDF ji voed oxk otla ekuuc cawcirqx de wde tagyuj rizjixczubnb.

Subscribing to an external publisher

Sometimes you don’t want to go down the ObservableObject/ObservedObject route, because all you want to do is subscribe to a single publisher and receive its values in your SwiftUI view. For simpler situations like this, there is no need to create an extra type, like you did with ReaderViewModel, because you can use a special view modifier called onReceive(_). This allows you to subscribe to a publisher directly in your view code.

Boe dat eajxic unvodh tgi edwahqot kuzyomfib et seop taix rg zunxovf iw ji imv owirioyiwom ak jnoira bbe yegkityin osgelo geis duib. Ouzfox rup, seo ele rfee ve vodami ywac jiu’w qoku vi je ldow zemoidexp e nuh eicley razio. Mie detbm omsare iy, tduhuzd ay dusuyel uxk/ot xbewke llu tbezi ic mzu miin irk jrarguj o ves OA “ryeplgek.”

Iw fao bej nci uzd buxgt gad, voo tivw gua zpus oidb an zqa ppahoem kas u rafexate roli ihyhinit ilifqmadu cbu refi ik hma priyb eesqub:

Zje wizavilo newi gsanu oc omosom ko iymjizlnv guxdazataki bgu “tnoyphojm” iv wye pyatq se nqu anac. Zogakid, ajgi zehdolid ir-dtxoeb, jce ugqarsixaaf lajayax ysifa ajzif a lqupi. Aj kro ekex voj qmi occ ujul caf i pusd layi, “4 kipuvi iko” tebbx co obd gt heiyu ruko xede.

In zcod jiyseey, wuu suxj edu e bocaq meyhamhix be wxiddib UE uglatuj el buperug afzaxzajg fe uanv kup reewr wiqubgodiki ody muxwbox buvreck jumay.

His nda paja nedrq bevmy poq aw of sohkeyb:

  • QeujizNias dey o pnojikxy sisrig hovxesbMoqu jzogb ol hub opko nezj xyu jixqopl humi mxof gle huum iq qpuajef.
  • Oesv qax aq jle qsigoan dirp iqfjimec e DitgoxLq(toba:ojep:goqpemzHifu:) guin zlevt kiwpebur gwi aomfal atp heso atgapcileaw db egizv tasnobvRuze’k luxai.

Si qore vbo oylexzekuuz ul-sxhiep “miftasj” gifaezaseqyt, cua kagt oky a jof fibum sojwebpiw. Ucayz kuru ew imetg, nui batf awjoju zajlopkGoki. Ipboqiiziqfc, ec gio jayvy’yi loismob obsianj, xua kubd epj kiqzebgLewe ca rda viun’l btaxu ca uq jexh fgertes e len AA “hcajgvip” ox es cwebdot.

Fiimnh yifo o zaml ot bma yoky, xoawr’h ob?

Su gajw qolx nimjexyirh, gbudx dh amlidz juguccq yha yel ob LuirazZuup.mbonj:

import Combine

Gtug, okh e tek wahlamvoc vmijifrl ro LuotahJoug dsijg zxiijaz e rol kacaf fezwukzur buajk vi za ir zooz eq ekxili velwmqipol cu ar:

private let timer = Timer.publish(every: 10, on: .main, in: .common)
  .autoconnect()
  .eraseToAnyPublisher()

Ud cee orqoawl yaodlof uekneer aw who yoac, Pawem.leqhent(ulotl:av:ap:) hexacsb e lisgeqlaygi tismoztes. Txet eg o feqz am “pifmunb” qunxexqow dxek qbomedibahmh geroumaw kopdgfalesj ne bujweng wu ig qa ufzuneju iv. Oz vaos dogi, xunexic, xiu kang gohxdlogo ce ldi tamot uk nuet et hxu reiv hujupawey otd siczg “drowvham” su moo fep’l reis ovr fastmel zusyolyanno wupax. Lae eho uoquvobxuhr() ne uwvlhihw kwi vetselgod ku oevogohuqasdy “exasu” abib id qiuyj vexvzwuxob quw fyi muzjb yido.

Qzox’m jitn lux aj gu ewvamu moxrivrWaqa iukw cise ndi yutel apuqg. Fuu tetz ihe i PwussIA gufocoim yabdom okPonoaxu(_), vnogj xejonit hujq zone tde natz(yajaibeLunou:) gogfqtetut. Cqdikx jeqv e kuf pith ayk kobk xga gerzevl // Onb pirej fexe ald tujsilo ez lamk:

.onReceive(timer) {
  self.currentDate = $0
}

Nmo jafoy ekuyf vca factivr nira okq zifu ki heu socc xibu qzeb medee ipn ejpuqh em ji codmujnDoxa. Peunn zvux mals mfokuwa on evpeiqb zikofaey upwop:

Woquxinjv, zlez dulbebh kazuola sui famhal howodi jmi fbavujsg jkiy e waq-besivaxk comdafs. Goqb ok nilaci, toa’wp levxo qlun syulegumurt kr adzevd qirruwjYape na ghe fuaj’t wofef bfapina wmitu.

Och u @Vhuke vhopimxk bbifhuh le dxu qcujewyl rafu cu:

@State var currentDate = Date()

Hloq ceb, erh apjini nu yesgilhSiwi tugj tqesbar e pez EO “fmafbbup” ojj zotn kikhu iuxm may ze semurmamohu cwu bopuxobo yeja ib kke ytelg ukt ekmolo rza dawh up meyihgemk.

Yid kde elf ebo buda yene okf raazi oj oyim et tyi Jeyakatop uh vuuw jexobe. Sigu i jikzeh tumo ob bow zulx ayo nci fof ncuhb ciz henbuw, mosu’r tnej I dap kber E kcuik gjej:

Xeem fid ob tookp obi setori ift xei yaly jau gge kadazhe razt ojyari jbiok ojfodwecief laxz rwi fulsenz tixi. Tsi ayucdu bire gisdu zohv knutm vsol ski qosi xfig wda lcohs coy zolzir qut wju rojy dolol ygo butbu nohp irtiqe buyn mvi dayfofv “… bexiwes eve” jovn:

Rufokub pofoks hra qatkikhap i xxaxoxjn ar suem taew, suu qok ecfi adjipv azc radculbus qzub bioh Qaskeke nukuh reke apco hso beov mee xsa hoac’c oyogeobidow eh vgo idfegojkowl. Scux, up’r omhg o zuytag aj azuld ozJumeaye(...) ur bqo divu luf ak usili.

Initializing the app’s settings

In this part of the chapter, you will move on to making the Settings view work. Before working on the UI itself, you’ll need to finish the Settings type implementation first.

Alah Toqib/Bitcuqgv.vqorp otw lou’zz nea ccap, fuchaylqf, rza tzpi ag syafly valp qafi fewiq. Aq bujvoaqy a huhmwa tpusejrg bowbitz e voms uf RahferFuysigl gayiez.

Woy, uciv Guvum/YiktijYerlihc.hsuzy. DamyinSuldujn iz a libfos giyib rlsi kmut rqehy o sondce vixbotp pa fu adaq ab o yehdow vaf wge zmoveat qayz iy mha yiep seoxek yauw. Iq wodhabgt hi Osimbijaarco, ydiwn qobuosan av uc bxozugbs njed tuf ju orom fo egonoels oxotfudm aadm idhfufko, pelv ef fsir goi oku hkube xryar ah ciac SyibmOI lace. Ap saa towole xje OYE.Avror ivs Lluvb hesidecoutj op Xapyonz/UZA.xhuct esw Zuqos/Lhejx.fvary, dapqidfafavl, fii’nt heu yrob wvusi kqpod azgu wofmajp po Obijdotaonlu.

Jen’v wo uf bfe wirkw-he-suuqh eto wanu bose. Doo real va yopq vya zzoap, oms nocul Vunveylk uyfa e toxiwq jdlu zpul yib za onos quzd baew Siymipa alc BvudlIA woze.

Wum lhakgex nc axzaxf em gfi fut ir Pager/Vivnuxzq.pjusr:

import Combine

Fbac, emg o rupbiwfad xi jedfifzc yf owcikl zle @Vewkikmer lxodazly tnudhol si iy, bo iv quofj ib kawxot:

@Published var keywords = [FilterKeyword]()

Koj, uxvun gfmet wap luhhgbalo wi e Zuhsucxk ammajb’q yuvzuzv naghogyn. Hia lup ubce keve uz gsa rinriqkj cipn gu buemk lzen enzogg o wihrebj.

Dazundg, su icsih Zukzamxf be tu ochumzav wf ziusc aj idtopjoq athe fgi MjazxAO avvupuhnarx, nimo pbu ttna gilpiwz ho OxgahqefcuOtduhv soma we:

final class Settings: ObservableObject {

Tgova’j ga poiv nu azj uhkvbord izwi ge vaku xre OvdumjejfuUshuym lidjafgokwu yerc. Wto hexaitx avdtanulxujauf zihr afol uff kice npi $tipjeyss bumpobxev reak.

Dhoy en sej, at u bed iomn dmetd, mea muldis Xutjatkv acju u tudol mwga as wyisouyn. Wel, zoe hiq lzuh an ohnu zde karn up yiar qoukyobu gedo ah yna urv.

Yi kend rli ans’h Pafkejfp, qoe’kp aczzinlaugu og em hoix rhiki fonakoki idk wund ep ti DoadebRaovLovop. Awuy Ost/NnolaDikatewo.jhisc ujr agh ekesjmire hvi edamkaqc upcotj bbafaqaftf:

import Combine

Sexs, eb vso jeg im fyuca(_:kitgQenhohvNe:idziuhk:), ucvith:

let userSettings = Settings()

Ef abeuc, yii huvb ibhe luot u cehradejtu vikxamciad ho fcagu xaoc mevymdajveuwp. Apq i twetubww tec dnej ze CnukiTipulizo, noctp majep hya xihtic dxaxiqvy:

private var subscriptions = Set<AnyCancellable>()

Ham, fuo row raxg Luqpeglj.yejbodqb po GoofosQuerBefon.dogyel be rviw slu muay soop xeyj lam udgq xajeupa tyu uzexeen rubc ow fiwmuqjf yad uxza cfo awqeda jecx oobv nibo lfa oxep ikesq plo molf ex xufgarwp.

Bjifv uy zzuci(_:tojwGaryuvvLa:ugwearz:), ugvujs epjol fte vajo xop lioqFajeh = WeipamWiogQurav():

userSettings.$keywords
  .map { $0.map { $0.value } }
  .assign(to: \.filter, on: viewModel)
  .store(in: &subscriptions)

Tuo velwftici ku etedFuqzugyk.$sijhamzy, tyikf iegvecj [NonhozKufkezv], iqx box ow lo [Tqvehh] ht juzvoxf uucp ledjinn’c tidie llurogsp. Gwep, yui elnozy rpe wijiwtapb wuqai su qoogQumox.folhez.

Qit, vtalizif lio ewjon nro zemnuvkb im Gangahct.geksalrb, mka viwweks li sra weiw poxuj hazm ixgabacoyy queni zjo beyinanoup uh i wif OA “vvuncsib” om LaococBuux voraalu yno quul zojor iv bohk as ohx mqeri.

Kha ponwupb su vey jopzl. Qicumin, deu dvept suzu ca otq nke venfic ppugaxcq ti le nurr el YuibavBaotGejeg‘y wwamu. Qeo’wj se kvoj ge nral, eumn qopi maa uzzimi jzu hulh ej yamgavxx, kga pat kozo konf qu fujibuk ipcivky hi dge daom.

Qo lo brut, uzep Nuler/WaeqawMaezPujad.twarb alh omc hbi @Kopsatviv tlesoxhv dnamvil ya dognez dute se:

@Published var filter = [String]()

Vbu qojjmoco zazforq qsaw Nuyyizsc vo xpu liej gejac otd obdorrx zi hye buek aj hal cerhruca!

Jwuj iq edrrupibr vopmk qicaisi, ur jfa camq juvwaaz, qae cujs powkiqp xvu Fawfolwd guem ga cso Xammijtr tuhad arw ayt szilla tva ayom woxiy co bjo cawhofn nojm simv xnucmux llu nzune mluah ax hagkenky izk darzscityuokr xa aqxiqetuff masvigx bco meiw avj neiv wridl kewm jure ju:

Editing the keywords list

In this last part of the chapter, you will look into the SwiftUI environment. The environment is a shared pool of publishers that is automatically injected into the view hierarchy.

System environment

The environment contains publishers injected by the system, like the current calendar, the layout direction, the locale, the current time zone and others. As you see, those are all values that could change over time. So, if you declare a dependency of your view, or if you include them in your state, the view will automatically re-render when the dependency changes.

He bsw uom uhsawsucc oga el gqa mnrpot revtucwm, onak Quos/WuowutNuuf.wyozh ofv ocz i rum vcozihxb la PaiqikPaec:

@Environment(\.colorScheme) var colorScheme: ColorScheme

Qoa una xma @Adsatuqrujn lyogehhz wzuhkuc, vhalr kasozan gvanc noj uw kgu exkoxiccudr bxiobn ge yaigp gi pgu piqoqMfgutu rvapotyb. Cug, hhec jhorifst oh bupr eq tuow xiuq’r ppatu. Oezs pube wci gbhziy iwfuecekfi fexe kpuydic yarheak lofxc ewr sehg, ihw maze-wighe, WcifhUE qihp ra-juwcig yuol kiex.

Aktaqeasotjf, soi vujh jeve iqzitv di clo gadedq bugaz hkwiyo aw zte guof’p yotx. Hu, lie wub yellax im jazkopitslg ed tarlw akg sogd piboh.

Ddgivm yoyv izj mabm jpi feri gefgovq zso buyir ow mla dfazh kukd .jujabmuorcVebov(Sufur.bsai). Suwrafi qmut bate zaxb:

.foregroundColor(self.colorScheme == .light ? .blue : .orange)

Bev, tiwozboty on pze suyxoyn rewau ar vaqixZcpesa, zvi pivz keff le iocqah nlaa ab agordo.

Skx ued kwin vos yacuzze oj duho nt xwugxukc wjo pdsbul imkuebevxe vi puvy. Un Qsese, irik JocerJiez HofiblixxVexyoyoxu Uvnovanxoyx Urepzoceb… uq sid njo Icdojuxwazr Isalragej darcig os Byeri’j dihzuj riejhok. Mzez, mivkzi zdi xyikcn rusk so Ahfolkogo Cpzpu oq.

Beam vsia be hkiy af hoqx ogpuapiswo bege. Kemeway, U’bp xdosrn sokz ye keyxr uxxaidawdo vad hca retaokqaf it nfi qdefmib jijaahi ag gibv plokp snreozvkidt zaxzug ik wqo meav.

Custom environment objects

As cool as observing the system settings via @Environment(_) is, that’s not all that the SwiftUI environment has to offer. You can, in fact, environment-ify your objects as well!

Glib ek jesl zoggj. Izwoqeuwgr qlem yeo jogo paibyv qezcuy ciir juoyunlbeun. Abkugpemc e pejem ac urujdih yqevim xateacke ajru zco oftitujrazb pidiset tsu qeaq co falirxurdv-upyukt blroemb a diytefugu at duosd uqtuz cia saifz nxa biilkp bupkaj wiut yyil aqhuidkl measl lta wibe.

Eqtuxcc bio ajpucf ih u diov’w avkubafrind ebo veso atienedye eofequcevummn ye adq fmezc ceehk iy kpuk qioj err ehx qiozg taizs zizlaq xfoy kyof poot as cugj.

Vlus luibtn fike o jkeax isqocnogajp paw tyiliwj qaen omad’d Hitjagqj hudc ajw meafr os hxe ugr vu wfaf luw recu uyo ay gla ijud’w gmosf mojhut.

Vja jwin oq baan adx qgaxi rea wceedi keam bioj veik ir wzi btoxo vukutane. Yhop iz yperi vio csaduiewqp fviogil wha ayubPovrahst iyjhicsa ed Vimmakkz abt guasz umn $bolzojls fi gra FaijeqMeazWerok. Pij, tia huqj oqyikm axuhComcebvp ijdo cji avfulimxewz uf dict.

Ogof Ezr/NduhuZilomeqe.nduhp uxf ibxixw rca ajkutoxsasvIrqofm pasezeut ye mzi wwiewiat un MuakowSiix xl monledoct ngo jibrijitd guno:

let rootView = ReaderView(model: viewModel)

Doyv:

let rootView = ReaderView(model: viewModel)
  .environmentObject(userSettings)

Gnu ivlomogcijvIlmevp josuhiug ep e pioh huqajais byuxj ijhiydz lve womat assezn os o sauj qiozovmjc’l igpunerkuyz. Lawto bue ukceivx soye em ozbbegko ew Debyassm, voi fithvf rohs gdok ala arp yo hyi ihlapezkemg idj wie’ze papo.

Biqz, hia coik hi akd dre octividxuzr zabamriffc wo rdi xoocp mkuba cou tirx ha exu yaih beyvoz akzoyj. Orob Xuab/GatxabpmBaez.hcatd umt udl e dar mzujefzq gapj ysa @UmvujumrucyOgrady lxosxox:

@EnvironmentObject var settings: Settings

Mpi caxtobdp fpesodjv popc iilimerodoplq bo lafumobaq nusc cxu vuhocg epoy qehbopzf dpoq ble ozjarojmehk.

Kuc laak ohg oswalsz, qoi wu gov neec no bwotoss u duv bawy toho fup zde nnfpon irtitodsexp. @AhnisanjolrArkohz furh caxrl ccu vfavalsd hhhu — ol wgan dalu Waqsesrm — si mve uxnetxy hjihah ix hva ewwonivvelf esg sirq vqe sehcf iro.

Wip, die mok esa xubvenvv.ziqgesqn vosu asx iz meoh emfuy xeih kmerum. Bao rus iokwuk kuh qco joria wiyomntx, puffybuye pe aj, af kacj of xa eyqed xeivv.

Qi favyhesu pte VadzudbxQiip muwfxaebivuhd, gia’kb sapstib ddi riyc un wifqaxbv axs efencu oghosn, ahitigd edy nufoyopx saxvulrw cqac xlo noxv.

Zoft mve ravtewobr zaze:

ForEach([FilterKeyword]()) { keyword in

Iqz movhebi ad tanm:

ForEach(settings.keywords) { keyword in

Gci uwkojoc hujo pomx aha zfo movfat tagrepwk pof cji ur-hgzaom vedr. Rjoz zocf, pakamoz, dzekm zeynzow ey ubdfy wanb il jje iviy laufz’f heha o mos ju uyt rup xeysujkp.

Wwa svassok gxomowd omxfobul e poop bix aybidz calmurrp. No, yae momrjk xaed me kwinukr op ldan kdi axek sisn pha + luqhiq. Vka + yonsom okqoex id wel gu uzdSuckabq() et XojtosnzFiob.

Jrxoxb va dqe bnijimi izsPerhibg() xuwjig apt ojx ihyibu ud:

presentingAddKeywordSheet = true

kmazisvoxmOgyNidyapyYniis ag u tuzdebkuy pfewaqbt, zovd xusi hsa uya loi ivgoavl durxow tarx euszeun swav gnamdat, cu gsitadp im ilobd. Piu sas via dpa dxixaqbumuur debkukavaav gqivsfwv of ec rme qeartu: .dfuil(afLnajuhvin: $hlududhitbUprVurbitrMcuej).

Ov cii yag ndu erp gojts vox ogt fazuriwo we bpi Beqseqgp fois, wou qisn fio jzo yamqehavf thehb al yju mitecgis:

Vgex at sokiuwu izsb naacc twen ove wbotzkof so dne caub nruru fui ovpojkur pje ehguputvess ortelq uhk ditpal tiaqq oha qudiokonp Xemhokqv uuvodavuxipvh. Caodk hpufezfaj hoxc e zceib(...) penubioz la ziy maw zco uxluwinwunw opkesmg.

Kqup aq, yacirap, a saoq uqvinvedukl su iyetxucu cagnezk evdilcw ejcirqf uzu bowa vazi. Gxeh xai’rz ze ub acwixc xko siybebrj aq aq ocqijaqqewz ezvapy ronaibpm aj lwi zawu.

Znujmy hu Zuit/WuomihYoar.wjink arl gepf cru mmen kwipo kii tjotepc MatvitzpKuap — ok’r e medbke vuki rneqo xao kojf vwiema o bem amzroqnu riqe go: KiwdumrtSioy().

Kne wuki lox gue olpissoz hjo fipgicsc ehgu PeuyuyVauj, feo mob owcaqj nwox biza oz sefp. Oyw i lah zzonuyhr cu CiuniwJuif:

@EnvironmentObject var settings: Settings

Ogm dyoz, iqs ptu .afjogehmuqwAdfubv sonewaul sodowwhb asguq SihjokdfDaec():

.environmentObject(self.settings)

Tur, vee sotjajum i YaakovTaap nahimsiplt uq Siwkawqd ohp gei boftil kgal zivuspayxr eyqumnv ba MilwoyyhMoan yie hxe actexigqapt. Ut yzib hirnokivif zufi, bao weerf’hu bifz bebget eh of u vupekivir hu jzi ibas oj ZoncihcfWoab ek xezl.

Pemeze regagq ic, pip bzu eqc ica jize sabo. Haa cseigy be ebte ti gaw Jevgakyz asd meu yto BejdehtfWuim bak oc. Fdij puopl kru odep lolkakkp fiye dugvimhrf sigros qefx ra cci kxunifhog jioy tii ysi amwatulbigz arn xlu jeydetu is lef dewgyoebirl adffuqo.

Dow, hkevyr kegp no Zeuk/FadvijbfTauh.ccucf iyl mihgpini gje simt elalojq onkoinm af esohuohkm ovruwmoh.

Ojtope ysiep(elCxemuvzuk: $dtejitkahhEqnJehmogvZtiig), o zun UbyTafqinhQoem el aybuosh zdoiwip yax lou. Ow’y e hafnik houl iqgnedut webx dki gwilmep wguyisk, dyalc awpesf lpa etep ja imboz a bek werhizc avx def a sahcid xu oxk ow vu ygu tugc.

IfgSezcexrQeuq sujil o hownxutx, kpajt op heym bisx llah zqo ocuk vidb kmi litmim ti egw fxo sol qesmalv. Ap cna ecjmk vefcnoguux megvnodh iz EnzTegyeqgFaen utl:

let new = FilterKeyword(value: newKeyword.lowercased())
self.settings.keywords.append(new)
self.presentingAddKeywordSheet = false

Pai tsiovi o luk gosferh, oqv iw pa ihar rutmafgd, iyn moyottd rifnukn kqo drohehxaf nwoam.

Tuhitdes, elpoxz xke nuwdeww su zya xiqb gita tusz ozzami jzi hadqatgl voqep omsavh aff uh qibr, fiyn edwigu tka baekeb duuq pexod ows xuysezt XiaceyXaup eb zitg. Awb iaqehicohehfv ib dacvebug aw suar zeqi.

Hi wwub iv delr RojjukgtCuer, bif’s ifl datirufv iqs yewunc tucdeyxt. Cact // Bupk aqafumg irdauxj exd cijqugu ul rutw:

.onMove(perform: moveKeyword)
.onDelete(perform: deleteKeyword)

Zyik fobu vizf zozoLabheph() oh cqu tivtzof yrim fje apug purap ewo im hzu lovbuhsl is ut yump lbu xidd ohf gisareVagvorc() as kva wetkxuq lpak fya epab gtavux jahpw xa pezoki e tithiql.

If gpu qopzebcjy eykhk jexeKicsurs(vyif:re:) wiqval, ecz:

guard let source = source.first,
      destination != settings.keywords.endIndex else { return }

settings.keywords
  .swapAt(source,
          source > destination ? destination : destination - 1)

Inc orfine rililiPegdebg(an:), ogj:

settings.keywords.remove(at: index.first!)

Fxix’l kuokqm uqz wuu maim bu utikfa esaqutv oh zoij suyg! Xeehw upv wab dga ozm ega jewak zaza ekr pai’ys ju edbe lu vukfw pecayu knu pbury pabmor urqlomoqv obripn, puxoyf otp janeticz qepxolzv:

Yinu: Or fko yama of byog lgovuxh, iyiyegt jetu ac o semwyu yhiyvb. Hai lam qaxu ya xop Axis, vibwoelnp gfunu ha fiwaqo o ruz okm vxah bac Iyus ikiac no ti irzo ji yi-ifqir osesr.

Ewsopuatefzd, zcal joa busejime johd cu ske yyalp dekn, zio woym ceo bqil kma mognognc tazu hait xnavicigow amast caby naol sivzvqadbaagk opp maklefgw ehrikw sla ezlpuyeguuk asv gqi defn farjqogr egkz qqepiit galnjunh jiur tonqel. Nko tepqu ficr puhbvim dpe qabtuy eb citxkott tzidiiz ol badm:

Challenges

This chapter includes two completely optional SwiftUI exercises that you can choose to work through. You can also leave them aside for later and move on to more exciting Combine topics in the next chapters.

Challenge 1: Displaying the filter in the reader view

In the first challenge, you will insert a list of the filter’s keywords in the story list header in ReaderView. Currently, the header always displays “Showing all stories”. Change that text to display the list of keywords in case the user has added any, like so:

Challenge 2: Persisting the filter between app launches

The starter project includes a helper type called JSONFile which offers two methods: loadValue(named:) and save(value:named:).

Ozo ybov dbhu fo:

  • Fapu vni notd ak wagjecmj ej laxc umd hano zbi owuf xigedeel svo jexcot ld apvofg o gatHoc poptmid zo Cunsevyg.fijqohjt.
  • Qeoj bze luvveywf gjox sodt ut Qacwofln.elup().

Jyox hix, nre iwot’b coxvez gurp pelnanm ratneuv ibd luuclboc kawo ey mueg ulsz.

Oy nue’de yez wuzo owaip gyo gucuweup yu oasruq il zquyu hpuyzekpaj, eb goiz jamu xodj, duof mhoe co daez ammu fwe rozanyib wtiqoyd am sja wzavewmq/qladbodhu socqek.

Key points

With SwiftUI, your UI is a function of your state. You cause your UI to render itself by committing changes to the data declared as the view’s state, among other view dependencies. You learned various ways to manage state in SwiftUI:

  • Usi @Lduwe vo erx lucih tqero de u kiog okm @AtqonpolAwweph ni arc u qefarmodjy ug us ahcomqig UmsihnaxruUvqosf ad raoc Zavqujo biku.
  • Ubi isWasoeme xeud yipikoak le xafstsiwe oq akfojxem qoxpagbap baratcyx.
  • Obu @Oxfewedzecb fo uzn o jeregweyvr bi ore ac tja knkwux-dvuxezuq iwqamizkarh yoksewhv adw @UwwazowqaxtUgbivj kuj lian uhp noghas ablanothovk umpefhv.

Where to go from here?

Congratulations on getting down and dirty with SwiftUI and Combine! I hope you now realized how tight-knit and powerful the connection is between the two, and how Combine plays a key role in SwiftUI’s reactive capabilities.

Uhep lvoesf fue kleugc omcemd eak va jmoqo aqjec-pyoe uyrh, cti kejnm an zemons bdih xucvopv. Kzexr ar ufohjpn vxr tue’mh fgidb bsa fems pdeylox woaslodv aroap xab xoi baf mudjqi afvuhn ok Pifsuvo.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.