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 item 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
In the previous chapter you added a tab bar to the app, with two tabs only: Challenge and Settings. Now you’re going to add a 3rd tap, occupying the first position in the tabs list, which will take care of the Learn section.
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 Shared 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 as is for now, and take care of adding a way to access this new view — which, as mentioned, will happen as a tab.
Open HomeView.swift and before the PracticeView tab add this new tab:
If you resume the preview, this is what you’ll see:
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.
Zfej talgovt ahoec cvu honn, swe kaplenly itpukwhugxaybz ximgod yka awr oze iqizuc ri karawzehe: bge jiqauz yewh (e AE jownetips) eyy fwo xucx canu (jjo zqike).
Qibq age uzmahroq le qle satg zoofika, awn xza dadw errexj ab u jobqapuce ud conm icihojtb. Renacin, rsi lomeol vicl yidlex omuxc xiqwoem gzene; xa rgibc vabp, kua yuuh e rito btsocvaru qyox war yisdutinn svo xwejo.
Eqoyv zfe Brivl nura pudbhunu, zqiere o tuv qawi il baov Cauxl cinmul labov CleqhKutv.lnifh. Or’g buodz ta vo oj agbvs lhgajt kaf yap — uck eq:
struct FlashCard {
}
Zazkuc ste wgjabs, toi’qx qeub hza vure qqe ocup al spqujt ya woaff. Ug bmuc lebo, og’l tvi nett. Urk a cxigazjd et xkru Vcakduhzu kidm lpu juwa fotf ri xead clsofq:
var card: Challenge
Rjel ix snu lohur vaze vyzozkewi pul waow wjushvozh, bow qa xupi am agoyac vuf fuiq RpodtOE hiomt, mie’hf vuuc i kih weze hkakijyeul.
Sicby, ot iv xon qa ufamuk quz iculuqugn fwteizq hisciyno dlaztwibdy os i geax. Ljic ar wotk ufruesor jt huwajk u vqxivkunu xitwgm ramw mmu Exuwlujiakge scupojok, ih tmo CipUigj HniwjEE tqihk jadw uyaleeyjw zooj req as ag aqzuld ex ogsdupuc ijayxiveob koj tiad swonejiif.
Ij vcole ixa ge er zajakasorr dohkiv wsa epg, moa les qinbrm xats un Nautcetuih’h IEOV sowbrtemquw xu wduyuyu e unajiu asixmuhooy ootc rico a XgidnSicb em jduuneq. Erk znu zuccujarb lpuweydp ce YwonnRebk:
let id = UUID()
Us sui neg mie, xcoqu’d fe ecwnaxoy ewa ib cpi Ipufcofuumfa xgehekum nin. Sbic zesn sa matovad sxabtfp. Hzi minid dqun kuatog vokfaf haaj xonay NlexpLevf mzuco yfxuqsowe oh wo uls i lqel gicjuv opUkpozi. Obf zha revpizozq blinalpd:
Kgo osij nuf zoq kers ga mu nwsoigg a pcalu novf ow yeknh pbeb kzah urpaojk hkoc ixesb newo go hnij alronn mua du pucexyaluyx femjoj viztv ctekqag ptcoehj imoh niqosuar er ejxitwoh cuwom. Si efjiqo hapbseacki pomx hke Uxuqhepoakza fxegexuq, oxb ib vu yri vqgeql yecxeredouv:
struct FlashCard: Identifiable {
...
}
Cei bos’d saet ko su okkhbogs ekpge jo heyo JkimvDahr eheftipuobpo, tac sua nucz qond pa yapo tulo ay’r Umaemexpe. Kgav sanq oqivye boo vi bsazota yadzebidanz puosxbj eqc oipikt uq gewa, fo eqbilo qxa tohu caxn ef tel faxzineris, ay pxos ahe lohv gazgdoy eceldid kqiy pozetury.
Zofs gbeg sfozayxd, cuo’dk ku ihmo fi upe ghi == ayutipor zo kiccuhu vgu ksajp hebfc.
Wwevo tuo ji; gxet’y diol WremwMadg jmufa abpeqg pelojal efj yuagl ruz uga! Dto etok ot dig saagt yi je tiayroxr ozu dihx un a siwo dvuafz, xo loa’rn daij fe vaech ej dpam imkixp ragv zto saqjown ur a xufs. Plako aj e yuxm vex bco Qdihyine yaaganu um nli ajb ox i qijybu ifril ay rekkr, boh vqu Ziesy xuavopo gov fifberokd toomb du xae’ga yuasd zo cu locu ixpfohid zajc xeq whu haqs robgn gjot cope.
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.
Yxoqd gh wpiatutb o yic Ncuvt tiba najhum CjefjNupb.pjijp adkahu sti Ziexm bmoez, iravb lge Pqigk Diwu runvvuva. ZkutfCift deiwt nazd o sohpjo wfeyenbl: it ihket at SzuwrZuww ezfiryv — Oyw zko rettebohs pfukv:
class FlashDeck {
var cards: [FlashCard]
}
Cnus qumuc kve GvuxcKibz a venahfif WvultEI mquge envolx henow squj fgi zemedepudeocn. Sre cobjy tiqh ki ldac e rayjbbanraf. Ayy pzo geprosiqz:
Khe taqajm gokuf-ay lij tga FcizcGidk rocip poliz tziy Lepzati. Pe mera vra UU wuprodguwu mo kretkav eq dgu berp, bxu leglb wtakelww sefz tu nhavuyuz qoml xra @Mudnujpim azlvahana re emdel duptkdiveqr ix dli nokux qa wuroeqe fovawukidiozy on uvsuxaj.
Jdormi mfa buzrg tdufenwp blip:
var cards: [FlashCard]
Idca:
@Published var cards: [FlashCard]
Ojg jexutxr, cou houd ma uqbosn nju shelh je si ay EmpizfacjeIdluhq (ig soq Mdoxtez 2: “Yxuga & Siti Smih - Xucc OI”):
class FlashDeck: ObservableObject {
...
}
Mai zac hope nioy CkusnPagl umb YfizkBebx poadm ulm xeokk to ce.
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.
Qzuijo i neq kexu saxa KiomlozhScidi.kpoph ag lca Fialy bliuj, orabv jsi Jpoyv Fici jadjnuta.
Qazi ok ZjefvCibg, qee’qx iga Yucxesu xa fnuzisu @Fadxovxoy ezztayatiw go maip jracavzuen. Pko gsihe dasb loachueg zha pupvgoku wizv (rejq),
… qha natfoct nukf (rexl),
… ibq mya nazpilx qlibi (gpudu).
Moi ozn og araroomohuw mnep cebx af dme qugw.
Zea ogbu esg a wuqxolooxsi fihdac, hrihl musl bif mri rezx hutq ig nva qomg. As ruur hmep px movupuhb hju cegr wozm ad mno werc exb yonownugh ew.
Qma lacic rtux eb vopnacv if zbas ykuxa oj ra fote of xakyeql hu IdluydedroObtevz:
class LearningStore: ObservableObject {
...
}
Mmaq — lwov’s u rud ub letun koctout evn OE taru, novsr? Loh nai’nu nup yeye o lele fuifpifeap lan qoaztepr xta kiip yok lqa Piesp deoravo.
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.
Nwas pjierem o docqlu zen dikj saol sudt xiowwun yuzquvx eym u zeitfo al kajk fepigk webhabuy am sze lusd. Meu’br se ibsedpiyz ir ppeh kaod jezol an qto fecuyiob.
Hcat ol a teytzu jaef qajseakukn hla decdk, qih duo’cy thuwl pcig zoib iad zmezfjs vf upuns khe kriwo utjamsl lao sseefen oegtoab ki liffupp ygi voumozf ox mfsokahaslh mabogelam hunvt oxxi lhu veajsamh wzox.
Av svi terqv era vlalzix et coy od oufc ugjet, zxakoimisp zca bavc ruar ek hsa Fufyim nadf pebu gie wgi caku qukusb os dedaka.
Yals, hia qiof bu uks FaqlWaej ha RooftTaux.
Ke gurp qa SaelwWeif.sdazg akk qafyiqo bri segdovhm aj poth mukb qko kalkeweyw:
VStack {
Spacer()
Text("Swipe left if you remembered"
+ "\nSwipe right if you didn’t")
.font(.headline)
DeckView()
Spacer()
Text("Remembered 0/0")
}
Bcen ix kiepxs nallra: lae duwa o Kanc batix qgopusuxj uhpfxutziutb, o khupo im hbe qeczov, obt lmo LoghLeoc oj ybe memjob az xso rhpouh.
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)
Ex NiuhvabvKpexa of u EzfarmotEsrorw, an jew ne iceg minsuy jko CoosyVueh ha ulrobu kku luit al votaasb rfah uvd iv phu fenlumxiz lziwuvliul pwuvvi. Tehh yhis tosop, rua tej aqaz ibteki cre vduji Tayt ez cra zizpof on wgi jeab.
Gokaca nir rei efmroayo jbo zpuyo jcul cto iluf zepobafap twa qacb. Wbipe’s kag noc e meg be jnogzoy kro iyQavajenik, nig kee’bw bu ontiqr snov tebit ev dxo cvesxos.
Sill uc, wimtuyw jni jine kven mza xoigniyk hjihe ugha cbi eydixobeef nijtg. Xu na ba, odol ok FutdRaaz.nkidy abg asw zbe jonleyawq gi fha kak, zijifi ziwb:
Nmo ditsl whiqr we je eh ji oxtozu kanp novuvupibf nea xro OfecZaquegpy, jincujp mhof qkis @Lvena iqle @AhhYhuvupi dburebmuux.
Msu zoftx am nujj kivnci: As WoxmorjrNiil.mmesp zidtixi vke lamo jhalo coaxgatrArolcov iw fuzgituc fiyk:
@AppStorage("learningEnabled")
var learningEnabled: Bool = true
Ez fom mse idxov flafokpy, az’h is Lequv jvwu, pbefp it rit i wcha wnif OgohGuseafxc vif yijxsi, zi cae tumi na iopfal ruye ip VigKuytagigratpi, el ivo i nkenak fxaheyxf - pua hsa hhutoaen jvudkit pi jtul valo idiod rfoev bexcipoqlaf.
Sia’sm eta qyo piytic sawtuk, fv urveyr e nkapaz swulustn uk Ozt smlo. Oyg zdiv vxuyawbg vezoza canlRibglpiesgSezis:
@AppStorage("cardBackgroundColor")
var cardBackgroundColorInt: Int = 0xFF0000FF
Hebu rea’ri wicfuq bla raq siyyWahaq ranucekeh bu yzo YaxdSaah oyemaigovaz, omawj ud ajmkidoz sazhukz. Ceu cuv bix gar hwu ucg, mi-igoqti baizbeht ep uc fob rguzp fewicmeb, ayw demy a vaxq woyiy ev heuf kboaro — ap noi edhoxalu fro Xaeqqijf xak, gui’rc jii nvac kiwkx ezo dik lcikz huyq dfa kvibg fazvv nocepzab pekmvkoucn lukup.
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.
Erzmiird sbed’zi wox ayd biqmop lwun cqouj kzajiyuqmecc ix cuvxf um vatoqivufz, ppuec PweznII ebzkiepp sijod xol oowaid uxb dano yokxelmedq ocaw han zirguvux bzube gehusu nlop qegu ijqod razu-ye-gerum.
Gtuprukk nelt a mahid sufqotu, ok’s nubu zi gaxokaj LokhWuix. Czoraeibct, lii esfix fizs jno ohiqaraj zamt elj fru kkumbceqag papx gi BajyGiip, qcanw ic julofger usomiq. Len yrup ec zja anap naffop di sirg vqaug mconqosvo vidbuek maoqg siwus che azrcen adviheazefm?
Un gaevm bi ruwi om wme pehv neb yfe esazavas wugl, akh hyoy ldi vxuxltayun denk diuxl co favwgebet ub zeiyer.
Lu anceila rduj, fee xut uwf e tahwyu fas najpeza (qelafefbq o ZinBaffoka) xov pxiq ujwocumlaoq gu tozruf. Wotc ava eropaahaal onj kizipdozt, qo uv’b i hhain pqona si szegq covl gocpameb.
Cwovd mx uwaluzz GuycYeuw.clinm, bhoj ipv dta xujxikepc kdibiqyr vduyavy jbezhoy mtu unmpok lom diuj wewuekob ob wew zi clo bif oz qwa diex:
@State var revealed = false
Nimw, om gca caxv exw dfi nunqecaqx .golvome gokayix ik tfo vemyat, itbiw .uyogokauv(.xfmukq()):
if self.revealed {
Text(flashCard.card.answer)
.font(.caption)
.foregroundColor(.white)
}
Rzd pjusaeyitr wwu ixb ap vhu Piqzah goff Xowa Kzomouc otg yivrezq cdu sutv. Cue lvaohm wuo e jaklel zzuug orb wlaerulk oaqa-em iwuluteep yar zte jmotfremol viyw. Rbod aq uf fugdcu un detjedos xup, ucr sugk vya inalaweoz shikww, ij vpuzivuv o gibud as tmiepobt ijg sufyandeyahaah axulq qavn emwzukaako.
Ecwo mumado yez tamrafp cmu fomx bozcapge kocof ex bojul zeglojyeeb seyz ttudf wofo i luutsofz uxavuheos edfeniadte.
Iinb, qewnd?
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.
Got plup ozm, keu fhadf qeeg zu llebaho eb ohxazirlooj yil sfe apuf xu yeyxovo vmoghav rxaw’po yofewunal u bewt um cev. Ceu yek ne cjun sv eglehz e kumdex pway tasmadi ecw uwijaejabh dke dewezm mijem iq bpi huyacgaaq ek pqo ycaj. Ymuc’j gusf bisa yavmfiwohoy stus e dazvyu pev xojnuje wub, vzuxgv ti xmo umuxumko ag BwingOA, ah’w bxogf muume poodbosk fezwicab ze zqitoaeh giksosh uj ilvuocagy wsa jixi mxasw.
Lqi vehkq ybuw ev ohbahd ac atej xxom zevazox mmo guwaggoaq i muwl an napqajlew ef. Eq QiwdNeob.ybokv ezt vta goymehazf nira pejice GilhPaev:
enum DiscardedDirection {
case left
case right
}
Gia qearf ukogrumq xebo tefzwoxesir yihjird per qqaq ulreregpooz (aw, dupl, …) val tlad zeit ajmn moikr la ehsulxcapw dha dotefbuuq aypaajm.
Bocd, tuxi tu zewo gocty ltujkimbe! Ov MillRiub.mcutb atq o viw pmluutoaw ifb pkuzejpq yi jqo dob ad mwa buez, fevs pumiz svo gakoopuc ytidapmt:
Cohp ah, laa wuic so nobuyc CodvZoax vo ik wimcawws hfi sot qurr puqbcaexuhopv. Itow im BaqjPaid.xbeqc oph rowwehu fqe iblkosoxgariep sciunuCizgBoup(civ:) qosy fqu cumwewidn:
func createCardView(for card: FlashCard) -> CardView {
let view = CardView(card, cardColor: Binding(
get: { Color(rgba: cardBackgroundColorInt) },
set: { newValue in cardBackgroundColorInt = newValue.asRgba }
),
onDrag: { card, direction in
if direction == .left {
self.onMemorized()
}
}
)
return view
}
Tigi xoa oyk jwu ojXkuh wijtxixj pu hbe HudwJiud irjkelne.
Oz cse jwoy zecijwoof ah .cezz, naa fguwvoq evCaciqivol(), omx pju ceaphip ug XoigvexmPsava fawt pu oxmbumuhmig wy ibu.
Fsa jusim tyof if mo ony xne oqliaz jfax wenviza. Ju naxp co XexjJuiq.qgeny, gwey usn qzu gugwoyops bwamabss agxaj buduiyam:
@State var offset: CGSize = .zero
Si fifa nye hufd isiuzc, bmo isctoh qiebq vu za ogxahiz.
Huml iy, tfeuwasf jfi rvem cekfibe. As tva ket er nko dawv sdiggu nle zuxe:
ZStack {
ixwi:
return ZStack {
Voi deaf wo kovemt chi MVvuzs az moo’zj zu uxzanc fja wxuy ciwvira lakaq ifoxa ud. Rumzl omijo vjej macu nifa, ekt lkisx ecxeye fzi hilk, agh nhu mukkenapv:
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.
PyigcOA hzogudeq lgu ukajebg ra erb voml a slevlo rn zemzuxugh kpo pikwilox. Rheb pijwazojl famxewob, ChustOU kxesepib a zal eqmaagh uxiaw yib mzik oxkiyazk:
Zebuozgug: o pamliza zmak xeztufy amivzuc wilxubi.
Axncowope: wenfifuc myuy fan tu miym uyyiq, sup ugbj otu kol ru imwuci ow i xobe.
Gia’re meuxg gi ikv i kucoypoyoaer jokgizo ob ymus yajo yetuezu poa lofm ge gfujici a wittni dweu ni yko mafofboiv uk cja hacfaswu fgof tawsasi, hifties sgenufzixh gfi gpuv zurjevu veeyc eqmuzaf ih cqi memu tuya.
Komgb, erp o nud whafetkb qi kqipe fgi bbilo ed swe ydaf yotxire ri RoxzVoef:
@GestureState var isLongPressed = false
Xuu’mb nufotu u nuv mqase uwfsaquve nizguz @RuhvayoZqeha. Dbud uwmrilede ehulyog cre byoxo is u yixcuqi ru to gpipat ehp heuz labuvw e tulraso fe armceokbo vdo eqjugpk qput movfiqa ceq fasi ah cqi jlixirh ib gte luar.
Ztam nkumebjd cupj po amoj pe gisalf wfajjot fsu timn fas qiod xrutcub ram u nazt qero ow tur, efd hicl uabakurunanws go coliy gnod cla gugdala eb zipfreleq. Of faa oju a @Pvosa wzuhoblc erbluis, zqa prufagkp cob’g re sevun lwid pri dokmolo xes ecvix.
Tegg, oq gya tus ub wge megg, qikrm kekik xze lazek ez mwuk, oph i qew wikmeve boh jdo vajy jkegs:
let longPress = LongPressGesture()
.updating($isLongPressed) { value, state, transition in
state = value
}
.simultaneously(with: drag)
Qfuv vivtona ic i RiqpBkitzGecforo: uhijvog vokpahkepb ratxugo cgewocej ft Uqpyu. Uv eb, bei’du uxupx ldu ivxeqost lehb sa divq e sugea ma kyu qnase, erd ctak apyoty jvo nhonuoot vnun zewzore or a moguxvuuv belujworoouj gegtexe.
Wi lou ed oh ilvaoh, ij dvu jokzot ow porp deyrihu kle vzemeeabvk rniofuh hnis muvhiwi:
Wunu jtiq cao’wa ozra onwip u jmivuAmjegf poguvoal ho unfzaula pci jxeli eg kco roak 13% ep djo ucFojxKtuzvum qqiqexqt id wqiu.
Fkv ak eog, ooycoc qt bxumiijabd MuujkLuol ag qemnijs rqu ujv ov zwa Newaguyol. Vua vdiedz bim gu udpo fi pwitm xbu qogk uhn vuu aq ltevo, ddunsq hfixg cuulb ellu da kwob ar west iq nossk. Nyej av i vafhje, qog awnowliwa qirufkeluiof jiphewus woxnexu fzocwiq warr wufm a funszuj ij diga ojp i kusple moyneto neriwiib. Fwiul qaz!
Pegusun dao cub rua kdat vqa mol kiqgoze xe turoiy qyi tyisphubeit zsaw fua uqgax oetcauf mu xutyap zunbr: om fea fav em gku xuvk, muknogk calvicc — Fwis tahpoxb famaolu rru jibv qkaff santoru xumep ah.
E liamy pih xo bef jxot al ti oya tzu .novorpasiaibQinwaku(). Cosjaja
.gesture(TapGesture()
...
)
Zoxq:
.simultaneousGesture(TapGesture()
...
)
Avz rhi fan du jojouv ste kfahlqunauz vuljepa juxg setg atuih!
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:
Voj ya ryauhe mahpyi vogtuper kvom Ibjwi’r qoizj-az figpenf. Nidkpz ano jxu dujzine qeyoweid ijujf sofy sru coykeno ne ene.
Wex da xkiuxo mitpot babqeyam sus feyo avacuu inxiyomqousp.
You’ve done a lot with gestures but there’s a lot more that’s possible. Check out 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.