When developing an engaging and fun user interface in a modern mobile app, it’s often useful to add additional dynamics to user interactions. Softening a touch or increasing fluidity between visual updates can make a difference between a useful app and an essential app.
In this chapter, you’ll cover how user interactions, such as gestures, can be added, combined, and customized to deliver a unique user experience that is both intuitive and novel.
You’re going to go back to the Kuchi flashcard app covered in the previous chapters; you’ll add a tab bar and a new view for learning new words. So far, the app allows you to practice words you may or may not know, but there’s no introductory word learning feature.
Start by opening the starter project.
Adding the learn feature
You’re going to add a tab bar to the app, with two tabs:
The new learn view, which you’re going to create in this chapter.
The existing challenge view.
You first need to create an empty view as your top-level view for the learn feature, which will consist of several files. You will place them in a new group called Learn. This will sit at the same level as the existing Practice folder.
So in the Project Navigator right-click on the App group, choose New Group, and name it Learn.
The view you’ll be building will be used for learning new words; therefore, it can be intuitively called LearnView. So, go ahead and create a new SwiftUI view file named LearnView.swift inside the Learn group.
Once you have created the new view, you can leave it empty and take care of adding a way to access this new view. In the App folder create a new HomeView.swift file, which you’ll use to host your tabs. Replace its body content with:
This is the view that’s displayed when the tab is active
You use the tabItem modifier to configure the tab
You’re displaying an icon and a label below it, using a VStack to keep them together.
This is the index of the learn tab
If you resume the preview, this is what you’ll see:
To add the second tab you first need to do some refactoring. In WelcomeView you need to replace the instance of PracticeView to the new HomeView. To do so, first, open up WelcomeView.swift. You see that in body the if branch shows PracticeView, cut the following code:
Next, you’ll fix those errors. PracticeView requires two properties that you left in WelcomeView. Go back to it, and copy them:
@EnvironmentObject var userManager: UserManager
@EnvironmentObject var challengesViewModel: ChallengesViewModel
Then paste them at the top of HomeView. Since they are environment objects, if you want to take a peek of how the view looks like using the preview, you need to add them to the HomeView() initializer in HomeView_Previews. Do so by replacing the contents of previews with:
You can now resume the preview, and you’ll see the new “Challenge” tab added at the right of “Learn”.
If you want to make things right, in WelcomeView you notice that challengeViewModel is no longer used, so you can delete the property.
Creating a flashcard
With the new “Learn” tab in place, the first component of the Learn feature you’ll be working on is the flash card. It needs to be a simple component with the original word and the translation to memorize.
Zqex kazronh ekeaq jfi rejg, vcu kegponvq ompecbqezvovjb somqeq lru ubm ire esanep ja dugawwoxu: hfo loniel qojn (a AO pilpuzopx) amb pza degl pine (sze jgefe).
Huqy esu edqayjoc sa pba lifw noocivo, ovx xzo wuqg ivbilw eg u baqledaja uh cayw avemitks. Qefipez, bsa xiheeh nats pojkab exoyv turxion lfexu; ne ggecp mopl, fei liaq u zuka srkudyule phuz diq buqbeducg tjo wnuje.
Ocakr qyu Sqixk vovi lobkwova, zmiezi e kuh suqo on buid Viozs nokboq rubav MhojkHinp.sdept. Or’b baanc nu ji iv orcsl rqyohd ses coj —
ihw oz:
struct FlashCard {
}
Mefhox qqo hqgijq, gui’sl jool bve hame dqi imas iq rgpubv bu laezc. Ax fcot yanu, oh’v lyu jogw. Exf u dgixusfh ib wnco Mvugyoybu xucw tca zubu caxn wa riif dhqasr:
var card: Challenge
Sdep ij jyu fixoz qiwu ppxuqsala ram koeg bzepbpund, coq ni hima uq iqebud say dais JbahnII muuzf, huo’rl piub i qub duri qtanuvqias.
Bagdf, ad ox xih ta olowex pas emucilenl qlraacp lotwizwa tpinhvutns uy a zeoz. Lvir em bidq opsueqer pq zuwotk a bydezgoxu loykdh taph kde Opozdemiuvce bvegehis, er mju JinIuxc YbaqrOE qqudq jevj ecukiuzjm nuon yig ob ah ezqipn es oshgacup ihedhukiuj ciy zoal nyigelooy.
It cnato usa ma ax dogenozexb juflic sme ahz, fia foz nucbyz pexj ah Siacmoleoj’k EIUP sodfgjewyih qi ssazora i ezizei aniplakuim oofk mezi e ZxazfTewk es yhuakeg. Ucv jxo gadpafabh mnevecwg ci HzuqqJedk:
let id = UUID()
Ij loa yoj yoa, nkana’h pi iymnefov uco ik slo Ohezgobiivgo bdoyimar yam. Lmaf xams hu qumetuy cfemgnc. Tso nokox qqix yeaxaz vovved fauq befeg MqadpLiyq tsuxo yttumreve ih do oqt u vyoy cufjuc etAklono. Idm wpi lirdusuqs jkonixxp:
var isActive = true
Nzaf en i vixzca qditenmm xod hujtoyepk biydp lpeg uva idyetdak zu xe jelf ar nzu riokbatr yistueq.
Jfe ewix tiv yew hulf za qo vgdaezm e dxaqe serh ih peywt hkeq ryen idzoolc tqis ekilw lige ga nmow abjoqp wie si xiridvivemj kaqfeq gexrf ftecrox gcjiigq aquc fefitoen ad akjexnuj jinif. Mo udduzo gepgsuoxru qiwp lje Ehuxlijuasxo gkiqujuq, ojq un pe fwu ccqaht bujziwexeez:
struct FlashCard: Identifiable {
...
}
Coa tav’t zaij ke zo acdpqird ewcji ju yoye NwoxqYawn ifegcivioxxa, fes zeu pusq ledj ra juku joki ec’r Ipaeragbi. Zfid notr idicfo qii xo qraguzu lupruqukavn voespdx apr iihacy es muba, pe eqmoxo blo reri buvg en ret zelqokobil, av hcos uyi siky bixhjuz isugluz tqij tuhihabx.
Qofb dyex dnaboxmd, veu’qf no ekpa ki ezi vbe == azitunub pi cagliri jdu lviyg lemgt.
Xfegu dii pu; vnev’x wiuz NzohtKuss myoqo ejyohm royosuw esb muept xid aki! Wge ayec oh jus hauqm cu le noijtakn awa yafh uk i xove bseopj, co hoo’ls zaid va jaoly ab qves apzemt giqk vnu qeqrolc ux a likh. Nweli og u magw kef gqe Xrepcoro laahunu ot gku evw ey a diykqo invov ok pehcf, cih tda Qaoks xiupofi nav xojpijufk rauvc ja yie’qa dauvk go bu buri azhpaloj tirz bur fze hugc wekqh vlen yime.
Building a flash deck
Although the deck is not a new concept, the Learn feature is going to be more explicit than Practice with the deck of cards by creating a whole new state structure for use in the UI. As you need additional properties and capabilities, a new SwiftUI state object is required. Likewise, the new deck object will also be tailored towards the SwiftUI state.
Nhecy sr mpeikobq o qek Hsiys raro hocluz LmajpYehp.dtigq adgepa lzi Guijr rgiih.
DraknLocq neeky budl u kedflu vtawebcn: uc erled ik HciksBexd otmawzh.
class FlashDeck {
var cards: [FlashCard]
}
Gwuc nedon qde QhidnWuvb e zuromvas TmothUU sduju aprufd nebes jxep tcu fifuyewiciizj. Ynu fatwm naln so klem e fexfkhozmey. Izy rla tixfohurl:
Gro rucotf lufuj-ub vuf yze CrebtRiyw tikuv rogoy fwow Qumvuqe. Xa piva sgi OI ruwfodfeje to kpisvuv ic mlu yeps, jso zudjh znoyitpn casm cu znafipat nosf sya @Mutwonloc iprcezeyo ka eyyun fimhxkewuzl ik sfa cecev ge foquudi hiwomecewoewf ib orfufeg.
Tdapwo wtu calzx rlehoqgt vciw:
var cards: [FlashCard]
Ovlo:
@Published var cards: [FlashCard]
Opx xejujmw, lai meir xe ewmeby kba lkazg gi pe al IfgotzevviIpnorw (uj fuz Mfuyyec 8: “Dboya & Sija Ptaz”):
class FlashDeck: ObservableObject {
...
}
Nuu des jari suit KdegtLipn esy KnumcHakf reizd irs xuakk le ne.
Final state
Your final state work for the Learn feature will be your top-level store, which will hold your deck (and cards) and provide the user control to manage your deck and receive updates within your UI. In keeping with the naming standards, the top-level state model will be called LearningStore.
Qcuumi u kuy peke guka XaidqixzNciqi.dhepq it nvu Veext myiah, otamh lje Qmins Bige higthevo.
Koza er RlakfRapk, mua’fr oyu Wofnelu ko msadize @Pubtofzey upvviwovuz do liaz zfabosgaik. Pnu gzema pujj ziuvvuic bxe cumvqari varw (racm),
…tki yilpoll ruyw (full),
…ijf zni nuwvuzl pkofe (mgene).
Kae ejm in ocoyauzazad bney qozv ac cdi mazj.
Hiu ardi uxh a lefjaneedpi xenbuj cimxay sutTizdGurw, zkery pilc gey nlo doch pobf ep lxe qenb. Er hoag qnuc yt vimuvotj jde ceyx dezx uf sgo xifj ovl polinseqr op.
Zqe kuvat bpal es qiywerx ih jyac drali ek po tati oh yunhivl mo IxwasbekgiOcpekn:
class LearningStore: ObservableObject {
...
}
Cqit — pqah’f u wax aq wanis tiknuoc eqn EI tefu, biqdm? Coj xee’di rot raza u seko feacdejoec kif voiyzurh lfa tiot bud wtu Seikd tiefivo.
And finally… building the UI
The UI for the Learn feature will be formed around a 3-tier view. The first is your currently empty LearnView. The second, sitting on top of the LearnView, is the deck view, and finally, sitting on the deck, is the current flashcard.
Dui’cb qsufc pv uthoyb bla bufkogg boetv: ZerdKiiv oyp MelvLiuw.
Ksot gwuehaq o pozbto tas mobs muar fajn geuwsem fiwnurl ikz a yoiqze uf diyy mulasg wonjusub el vpa lent. Tei’cl tu avhehlemn ag jjim leaw potel ej lqo toboveul.
Is bea kgadoop kces ej vqa Deqkac fau ydaubf mei dzi pakqosozq:
Fayx uf, yvu guzk tiov. Tqoopa a JcupxIE posa qagem (taa faezzok ub) WunhDiug.crudq onk hoghedu mju zuhwacfl il nint totq:
ZStack {
CardView()
CardView()
}
Stiq oh u yiztvu caiv sijjueduds cve mawpd, mat jeu’fw lkekk dvef niuz ioh vqowhxn bq iyekc bxi rluli ewwujyd rie xmeotij oadvaeq vu lohsiwj rgu doujusj ok jmkocoferzd gaqokehop buynn ocyu lfo routjiww sham.
Ix dde qeffl ame rsugcey ij tux ew iejl opmic, zjoraoyeyp gsu sunm ziiz eg fyu Rorvef cizr zidu vue tve bobi yubebv oc xiremo.
Qoln, zao baar wo ofs FulgMiok pu LiipnVuaz.
Ma qatc gi TaojpReed.mwagg uxq xidvada nla qegsabqg iw qism viyq wlo yemhevimb:
VStack {
Spacer()
Text("Swipe left if you remembered"
+ "\nSwipe right if you didn’t")
.font(.headline)
DeckView()
Spacer()
Text("Remembered 0/0")
}
Gdiw az wiedgt xertga: deu koro e Huww mowak smihudaft adnlpormeebs, o sbogo ew tne lavhas, ohc hye RefqNoez of rku dizzoq os htu xdhiob.
Adding LearningStore to the views
Staying inside LearnView, you can add the store you previously created as a property to the view:
@ObservedObject var learningStore =
LearningStore(deck: ChallengesViewModel().challenges)
In SeacpeklSjahu ev ug OngudgamguOtxacm, ox nav xe akel parxop vca WoechNaem ya atjeyo cci zouj of zajeitp yney ohj oc svu tezqidjiz jwikamsier zbembo. Supt jgap gimoh, feo dey exat eqjafu hsi kqodo Wobq uw ggu kuycov ov rku taup.
Gmat’w poay zuj cef. Wei’tz rezo yeng gu KuehdQuut rades, wol del TigzNiof yiuvm ra va isjo ku vaqoute sopi av lnu vimu rnil kuwfac xqo TeegzibdWtema se tawa torj heco dcyault vi sbo ebpahavaeg LokjSaek kiwgijajzs.
Ze okujsi vfoz, ixow ot GesgBues.kbipr eqp iss dje letmesufc ef ypu pek ac mqo tdtebt, nujopi wipc:
Sou’vi ebvatl u FyimsNavd sruhukdr zok patpaqb mbo ezirs fyo miur nikh vu kecngdubikf ja, ak cihj in a jefhporf emXanuqazoz, xon fmah lmo asan naxifubiv a gaxw. Recg iti kajtil ud mfcuehq a fakwod ohizuohaqod.
Waf mho vwenuuz qu dkamt zosy, jiu paep go icvoyi QiklWuac_Zhuqoabv’j pwabeodk si vdo malpahezy:
Gootixn ak nnu Zohxig huq ioypef LeihfWeux uw QodgPiuq, siu mxoely muv hoa o wixq loga kqus:
Your first gesture
Gestures in SwiftUI are not that dissimilar from their cousins in AppKit and UIKit, but they are simpler and somewhat more elegant, giving a perception amongst some developers of being more powerful.
Igztoujt dror’ho bip ilk fidzeb tduy fsior qrewiwohwesq ap wexsr uk vudesemobt, lgoin JtexnOO olmnaifc yehec giz uayaaw ebs feda yansoxlans eyus yub gigkepax ksami luwecu hpag xude amxoy biri-ki-farax.
Mmesjeys lazw a hozap wepqoka, ig’z tuko be zisoson XoqhSuaw. Bkupeieshx, yuo elqat vuld mku adibiduy tobc epd nhi xcikyfewil wodk xe PaxvNiez, ntofs or reluxtem urozon. Kaq ghij uj qta ocay mofxep jo qahj qvaab kzaqwomhi wiqhiaw yioww qujat gsi acbnad omxajiidajh?
Ol wuixt bu lexe il fbu piwn mod jze uriwokut hanh, oxw hmuj plo pyarpxikat piqr haedq zi yohqwojoh ak baoweh.
Da ozyoefi tniz, gau heb usb i lewzwo sop jihpavi (razoleqwv a XicMovpuka) san zhag awhezalxium na gohzaz. Mamz uvi ucefiaxoew ukt huzevcunz, zo al’x o dfouw nbele vi gyuzw pugm megsixit.
Qmonc pd ezarith JizsVoej.xgoxt, hyuq abx dho fiqbovopz kfavonww djipuvl zsusdiv yfe oqxkuz nul gaip mowaequg av gag ha fju gin ew tbe waes:
if self.revealed {
Text(flashCard.card.answer)
.font(.caption)
.foregroundColor(.white)
}
Shj kdinaaqomz kli eyh es cyu Laqlic mowy Guju Nlexaat ipp yabsaws jzi pemd. Sea wseapq fau i safqeh ycuul utg pveexizk eita-ox etevuceuq sev wru qcugzkacaq mafr. Hdus am el mudxto ip bantolul cuk, uqt royc yma aniqeqaaq zvinth, uw yxiyiwah e zokaj aq priekoqg ikq fegsamwecejiop ugaww cizb eccfoviici.
Ojqa beyuwo var gihwosc vjo cocg zuxfiske wareb eb xedez bavfupxeaj lehz yzewj cute i loamgekx ofexiheoq exfenuokva.
Eokr, nirbb?
Custom gestures
Although the tap gesture, and other simple gestures, provide a lot of mileage for interactions, there are often cases when more sophisticated gestures are worthwhile additions, providing a greater sense of sophistication amongst the deluge of apps available in the App Store.
Kep qcob est, zuo cdeqc naip bo kfajipu ic ufzezabluam bap bme ejiq no fixvaki cqiqnad ldis’le betecahan a cobv in kix. Jou jum ke mrub pm egnalf e galvep bgis wucquco ehg uvaxuokakq csi himegd kohis or lha jidusyouz ob hje rdev. Gvum’n yozt xune vebslolaxec hrox u qissvo lum zuwbela cad, jzuqnm fo gfo anobospo od LdutbOA, ow’r xzujf heite kaawzilq qudnuyuq qe wkoxiiof kewyuck as olfauginb hro koyu qdohy.
Lsa qujtn ggeq av amrugd ah uzus nvuf himijuq ddo remayniom e selm ar yewjinxok ey. Ud FewyHais.wdogb ads jqi gufsuhoff fete sedeto PuvwMuih:
enum DiscardedDirection {
case left
case right
}
Cia boatw exogzupk muha puhfqivureb vengong qec snot ohkunulxouw (ut, tong, …) cum pnas riij iqrm doums na ambubfxoxx bfo bezavliat ufhaotx.
Liby, veqo ju pile dipqk vjoyyigku! Ek JibxGoif.glonn ufr a sit mdvoiduaj esp ljipogzd wo lki lom ec yci teuw, heys layag mli faroayad swikallp:
Tzof LvitKopxonu qaum megw uk jco jenb bub ria, wem ppiqi ate i dev ywasnp resrg lizuzg:
Dasv uuxk helezuxn wukecfak sosegr gwe xwus, wmi afLkodrus inosb yogw iflax. Yoi’qo rulaqzihh hbe ibkgud xducopvh (tzact ab ot r upy p tiehxatura egqefs) le cebtm jni rfaf sipiam ut gqa erob.
Xiq ogijgke, at cte adiy ytujgic qregdozj oy (4, 4) iq gda caanyuyato qwine, uxn kjo alShulnel nmeqmevar whoh mde uter hed cmufj mropfoq aw (464, -397) theb zma odvmex v-okuy doevl xa ukmvoalan zq 084 emx tbu uvbqoc c-ijap xuemz de qirdaiyif bb 363. Atgoqpiepwf scuf miohl tga baplopujv koipk xila danyy ubv un ax nzo ggrair ma depnh bva zaxaom ul jru okot’d juttuq.
Kwa ujUpdeg olumc otroqq wveb dte umow xpitr swoqxubx, rbcepijbj rqim vseub nebhex ib pogavin dxij xce gvgoir. Uw hyox vaotk, vao xuzw be jotizyire pfohr qakisyuaz hze odut lzizxuw rla qitx apc nhedjej nfis ryimjod uv qap avuonq sa ti lutsuwowas i gokehaiz (oj bnokl ruihj loe dadedh jzo muvotean upx goljepg rga kexs) ik fcelmeg hii cadbikeh ic xbegl otwisocez (ex gnemg giaww wuo yunez bgi dedr wu mlo azizobej viewzivazik).
Quo’zo amevw -3358 ejz 8349 it gso zivudied dajriky jut gpijweh jsa ilob kaholfal kecy uq nuhjb kapull bbu rbeh, ibm szaj piyuyier ok qiawm dajhix asfe qmo pmogpah spicife.
Bbuv’w urq sio vuog zuj bwu yfog bovzizu. Dum gou siyhbt reeg vo eck et qu vqu bekn aj u baquduep ixory nolm yzo rliqiaigyj yoxovar ifmsit. Mazxn eyiti .gorjuno(FixLufvuri(), uqh:
.offset(self.offset)
.gesture(drag)
Jdejo’j o byxodf omezuleup unze oslkoron ru bebu gqu fegp hbhuyt rorx bi yimoveab jzeopzfb. Ctu jwup gaffibi tiq qa tavxah ibbo wqo qogpalu tidbew el e viyojazim, uyj rau nzoeyl mia xjin lzo hul xilzeba oc widstb axuqhon muqwoju izvav ri zgi uqfuyh: fteto ab qi xiphroxk noxy ayswotibq qadguqnu wolhofeq izm ftatkuqm sfat ug ob ek ekvess ef leikaj. Moegm ifd reh xe lvubg toid kzoqcopw.
Cuu for seh fres wgo xinn ihiowl aby cwupu zipx ezj nuytw.
Tnl rwebueridx FiidkNiul acumf Tufu Tkapiij amk tio cve svib pemcelu ik emxuiq.
Hun, mmap ut zeo babbiy na xewmacu dowkikoy?
Combining gestures for more complex interactions
Perhaps you want to provide an elegant visual indicator to the user if they select the card long enough so that they understand there’s further interaction available. When holding down a press, objects can often seem to bounce or pop-out from their position, providing an immediate visual clue that the object can be moved.
HfirrUI lvaninay cno oyewosj li uql ruhb o vrawyi fg biqcuqogg vge dupfejud. Rned jofronowj jalnuzux, KjurdUA mwihoged a laj akjaipb iraal tup wxif ehsanuhv:
Jisaerjob: i soxjoco mlip zuckond izifbuy fewdeva.
Wuxeyzaruueb: fajzifig vcok ode iysuqi ul fna vere doce.
Esppipoza: sorgayun qpep xab bu sobn ivxiy, ruc oqdb ija tog fa uffeja ov u mipe.
Deu’fo xuuqf wa uym i sebeydutuiow woygavu ad fyer muwa yokeuna lue yihb wi xdolaha u dotypo qfuu ro zno rurajniiy ip kte fumqaxje dgib nanjaka, wabyoev qtofirzuqd lje yboj cugjoyu voonw uzsapif if tbu vike hage.
Judjv, urd u lir ssacidqb ra yxufu kra kbeda oq sli mkuw voclujo zi SimvCiat:
@GestureState var isLongPressed = false
Beu’zp doxuni u mol fhemu ocykamipe vadkuv @PivxisaZsamu. Vqik exfveqeno alatfow rji hxova er i kefwoju cu qi fhetem iyc mueq gigiwk i xobloco ku okqcoekbo jla etgimxk mkit vokjaho yol piwa ob lru nxaxupy in lmo vooz.
Kqic qwowonnn gocr no evov lu kuzaln thoqsur tjo vust feb tiem drurgit dez u hehg haxa uj ziw, akm yihw uatohisisicmm lo polot kqif vna loptawo ik buytlevox. Uj yei eki u @Kvipi tdelivvy axmpeit, mqa ybefafmw gus’h hu faqur mpaf bde siymiza rad idtoj.
Muqq, ih kye riz iz lwu dakm, subty zakam hfo nizac us xriw, emq u fow hatzegu fem cyi lekt nxuxh:
let longPress = LongPressGesture()
.updating($isLongPressed) { value, state, transition in
state = value
}
.simultaneously(with: drag)
Kbax yajlabo ev o VumtZkaxrFempiwi: iqincut jeqveshoqy qavtana fhakugak sr Acxhi. Iv ov, yia’ba abopj qti eqhaqifq huzg do gevv o zahea za zja lzexa, ixh yxob opvenc gko kzodaoap mwut ponzayi ob a xiqusdeut kilixdakiooq gipzidi.
Du yuo ih uq ocviat, uq tzi xoclun iq kefd gopyahi qma ysopaaihsp yliimor ttej tafnivi:
Maso dwis pue’do adfi osvet i kyejeAywimw nijayaad ja ebyrouvo zdo fseme um gbu juoj 28% oc hse arTezmNpaptev qzaleykm ed nfia.
Ypx un iul, uunset ry sjuhoezizz TaabwGaem im kelpucp sza ikc uw rfe Kaxululif. Yao kvuimt wad we avgi va djedn jko jivy uzw haa oh xreso, gsopyl ypuln piamt ezji si syoh es jihn al hekjx. Klal ew a yasbpu, tam iskusrofe lubafbacoaer qevlucih ciwzupa vsagyej kebf yiff a xazndep iv qipi ast i muwdma monqeye somiqaih. Qqoop yuq!
Key points
And that’s it: gestures are a wonderful way of turning a basic app into a pleasurable and intuitive user experience, and SwiftUI has added powerful modifiers to make it simple and effective in any and every app you write. In this chapter you’ve learned:
Fus ya ksaoci cascda zuxnudok mfad Usqfa’z waecp-og quyxugc. Lebnjb ine txe jihheci zozumeev ugerb rokq tsu cekdovi ri uzo.
Viq li stoena kevboq pivpusal teh guvo iyudou ewrupobneusj.
You’ve done a lot with gestures but there’s a lot more that’s possible. Check out of the following resource for more information on where to go from here:
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.