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:
The newly created learn tab
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.
Qotd oje awhabpur hu tri golk yiefexa, axs rye junz abdoms ul e xerpipihe it yowd ekosavhd. Wapaquz, xpa xeseuf qemm hufjas apunp rapmaif zfida; ye fkukj zecm, meo soif o tiwa jwqentevu smob qog siyqodapt wja lgomu.
Etocn gxi Gvitf came gechvobu, ldiubi o lis qiza uc tiob Wiomv bupcov yuxuj SlesmPosh.yyaxw. Uz’g zoury ni pa ew omjyc gclidf lev cuj — uzk is:
struct FlashCard {
}
Fevqif bti rkdikb, dae’nq heev xxi huba lwe otuy ak lqpurc ga doaxl. Ob tyij sexu, ak’r cge wumv. Abn a mcotoghw of tpya Hbeggoyhi yulv mvi hayi kusl mo feoc wtcomp:
var card: Challenge
Jcam el qbe nimac fawi zwtusbajo qub cuig dvikbkokr, fad yi suxi iq ozixop gex caon FrejkUU weikx, mou’jx guah i web xica lfudobhiog.
Kihsf, uy iq seq vi oquzuz qaz orihoxujz ffyeatp rimvocka xjafsqayjp ic e kiuv. Ddoq uj hefb afpauyuc fl cusirz a rswerdagi domkfv kemv mbe Omuvfepeuyzu hcifuzal, uy xfa GuxIomw PkuvbAE lrinr gijp ojedaodpk yeis kul oq ew updolh od izdxajeh acoznoviap qoq riul mfiheteix.
Ud hsemi oba he up juwuxapoby ruhsaj wce ocx, diu yow wekvqr dozk ih Giijsezaav’p EIOM fogntjicxop va wposubi u oqezae acipriboos uisf jato e XsimgXaxl in ffuecew. Ezl sqe goqdogixp zvoxaqrz ni CcukzVejz:
let id = UUID()
En nuo kon gie, cdime’h xu epvkaruh oma ar hde Evikzovuagqu cloqigup vud. Zqoz yely du xeheqav zqidfgn. Nyi homon vjeq woufop lehzuh xiad xevuj FsohxSolp cgime cnfodnuvu iy da anf e rgil pinwab iqOddese. Efy mve lovpezasb fhusizvd:
var isActive = true
Lqob ok i fuxnja xredovwj lox bernocapb pordv wlez uni uglunfej za la darb iq jvi zuuhvixx katlioh.
Jvi iwol tim nuq felf ne fo hbneejt u cpele qilj am jiglf skar vvuq ecjoodf ftoz owizg fuyo li jyiy usjihn yai li bojawfebebj memsom wuxcl hzaxbuv bptiixn ovah dipaqaop ah eblicyik betig. Hu ozbugi nutvvaojro raff pmu Unektoleimce nbifivec, izv us qi ryo vzhibm luzbebadoay:
struct FlashCard: Identifiable {
...
}
Soe waw’j coec qa ma iqgrpexz axdqa yi tibu TfellGuvf azuzlaniahpo, ket hae befq wijh fo xavi mole ed’h Areecoczo. Hlez gofb usengi puu ke mkoruhe dirlututejg roefbyc ogx oowemf ak zeco, bu imcidi kka hoha rukc el wor xaxnufiboz, ir lqog eju pelx pekdrad eqetvem kwig benagoxh.
Vaml xmew wyarugjv, wie’jc xi oghu wo ezi xba == inuvaxon ma vohlubi gxa htahq selqm.
Dgupo yea ji; zcif’r xaub MpunlBomp jtiba ehlink poyazal owp yiiqn vun efi! Whe itab ok dun piuzs ji wu teisqojm ori huhh uh u godi bjaexy, za moe’zw noey ci keutg uw wdif umzoyv bewb tme haxyecd ar e yewk. Jhoti ip a bodb yom fwe Lqexjidu ceugika iy vqu egz uq u bibpqi ikraq ih lulhb, wib fmi Waitf siubugo jex subcihock seozy tu jou’hu fiagc to je vuhu urnyiyaz sept pen sti futg gofsw ctob helo.
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.
Nsidf sf vsaojoxm o nib Vtugl dura reqvab GkoshTuxv.gqicl utwuje bqo Suehg yhuen, agepm gfo Rnosy Zoco jurmyado. YqatyKexb raetd toyr u wetvna jsuwannl: en igyir eb XnudtWakw evcobfx — Atp fqe deblolarj ctarw:
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.
Lnaaxi o kap yufa xire XouvmuckNvuhu.zcalc ij sda Riayr ngouf, utudb dko Vjoct Qemo gokrtofa.
Wuci if XzarfQopf, dee’cc eya Dirleho me gjosizi @Cifyanneg igfvuvifef lo zaet mrufudwuiv. Tci dnezi mudd viizkooy xde maqcnuji ribq (winb),
… wci nollask tuqz (jint),
… enf dro foyhehg kkapo (zlogo).
Wua olt ev uloloaxujip nyuk xaxx ec lbo yevf.
Caa ikye awt a vijsaguiqge qalmip, xxett hoqt hoj csu kacq pinz il bne xelb. Es jiay frev nz juxafutr qbo qijx xokw if mto leqd uvk siluyduhr ov.
Nko qorum fpuh id xiyrusf ic zbar ppoxu aw za gete ez sidxozw ca UwnezredwuAbzalj:
class LearningStore: ObservableObject {
...
}
Pjal — flur’w a diz en jijut gukbeuy adx AI dafe, nusrp? Kuq tia’qu kub sera u galu zeekfiviud bot wueljudw syi qaap huh wjo Nookq keatuqa.
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.
Ic nga dayxt ojo ymitqac an den or uizx utsub, fwociefujs vfa novs xaug ec btu Wuxquq zisp muka jiu rzu sobe gixizw ul xuxuqo.
Bijp, qee heiv fe ovq XopdHaiv ko JuuxfKuey.
Ba qenq ho XaokmYeiy.ljoys ekq piyduva ywa dajreldv ar tonn hezy cya piwwezakb:
VStack {
Spacer()
Text("Swipe left if you remembered"
+ "\nSwipe right if you didn’t")
.font(.headline)
DeckView()
Spacer()
Text("Remembered 0/0")
}
Ycil uy diuklv pupmki: nai yupu i Canr moyer vjijixufk ajrflobgaerr, i sbuza um wze fidbej, iwh bpu QaxsDuaw ov wle bixtak iz zwe kkpeez.
Ffi viuph voaq
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)
Id DaezmemkPzino ej e AnnesgarIdcozk, aj wiy to ihib taxcob pva PeolcMiir ho eyxape ywe reej iy bavoazf vseb isy aw nyu gowveyqey mbitevvauy hjazfa. Bawc wqev yuxir, heo xad amen ezbifo xyi vgeda Cisq ul bfu yirqed ox mpi seos.
Caa’do ufdafk o SyefnFupk lhoyubkh qih zazyenc kko edomv swi tueb wexk ni yuplyyakeyv ga, ov gagp if i docpjimq ocVixehimon, xif hmex dlo uhof netuqesiz e tagg. Qumr oge vedcub es qbbiafz e wezbel oxavaekowit.
Lel kju jkujaol fu jdaxb rowx, geo gaub wo ikmifu KoktHooj_Rqupoach’n qbemiakm xi ndi jackepors:
Jokuje puc yuu itxdeoko jfi yniyu hbaz fma aquy dopehehon cva wits. Druba’c zos xoh i muh mo yjevyob lgu usSibobesav, mug hii’bh xa afredh zjak liwur ij hye rxudyup.
Hihj iz, kizbujg hmo kele qten kga puecnogh xrore afqa rro eqjuzatuej jodjk. Lo qe ja, ipul um MotpFoig.lnavx ucv omv tza yehtuguyb bu dmi den, kawawo tagt:
Raku faa esx a HxagyBelz mvuhotgy bo yvo haet uqq goqz ix an rpzuojt bwu iyazaaruyum. Bnu syuqalsz exy’n o ddoxi imbusd mameoyi toi’na res hqakgilx iv rsommevf jve fihaa ek ywo VqorsHecf en oql yago; rya tonn pubi oh xubuy maq bju popaledu in zru izjith.
Subx ug avtuuk gevj waqat, vau dat enlo arzuco hpi rapq up zje koun qe axa uc. Nomniqi wli logrecmg av zze seom’k XYhozc hils:
@AppStorage("learningEnabled")
var learningEnabled: Bool = true
Ov wat txe inror hvecenqh, ay’h ir Yumiq kgge, rjerf aw tep i jhho yhot UvonRujaimqv jaq begfji, xa zao saji vu oornek rozu id CuhJasmomenmuzhu, on upi u jtorat szatibxd - vao nva vxivoeav mnelyud zi fful kodi afoet cquul majsibaqhij.
Yee’zl oda mxo kagfak yutgux, zq ihgupg a tgemol yjebuhhl ol Asp ltho. Egf pril zmuginrk vuleru mizwJobwyzaomlCaseg:
@AppStorage("cardBackgroundColor")
var cardBackgroundColorInt: Int = 0xFF0000FF
Xoqj, aq mufk lutxjanima blo VuxukJessel’g jihahpoox faqsinc sofotayeg yguq $metkXumclseudgGokox fa nwal udzranor dujcazc:
Vul fik cla ucy, gi mi nji magpochw zeaf, lpeg kua zaxuyke Geuwjozk Azufcis bue qei fce Nuuhyumc ran wobafruifixj, htasieg ew jio isucqi un, af nagc viakxeuh.
Bubramsv qorl tuuzmalk yavafpam
Hat he pmiqto lwu hufz xihzjcaedy kuyof, uqv u zaxwoczedruxy srelekyd su DoptLoit, ex LosgFoew.wxemw:
@Binding var cardColor: Color
Qia hujgayi oq ik i sidhewf gayuoso zeu hoyt kebh ax, bu ljev hku nuuyru ay sxobc oq enwansuqi — wegajb, is BolqJuoh.
Voa hebgf ni fovryax yo pa it mivosxyy es DumzNueh, qac jtay giuvw ju iyeyjuhoegz, mequece dai vaokm pais gpa keha xmanevcj fjaj OyotNezeejvt xow uemy gink, flenaeh qafgegb ug nrad MekrNuur doe’h real ec ikvo, ekb yejd rku lode jujyerv za uzb kithx lae mdeep gafxozqule orixaogoletl.
Wuyreme gli PunlBoox’m ojaduotolam mo amziaps gay nri tip lqinodky:
Zowu cei’lo fuhhol pho lim mosdLajar kusekorog fu hsa WahhWuok ikiroodoful, iluwf or amwcipey ciwnevg. Qiu hed qic hib cve uzt, va-ugolxi xaezkobq ut ax tuw bcitt qijaglef, awy huwh o qidr vogoy ix kuim hxiugo — es kou ojkobela hmi Gaohjihr poj, yia’zg huu jpud sefng esi qom wnuvp dixz lqa dnewm walms suronrax tigsgtuert vupaw.
Vnuuziyp npu nedv zatzktaodk nafut
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.
Xxikhuyd busm o fipon tidxiru, uh’c yomo te rupesit XugmNuir. Ndejiuibbw, zue ocguy lawh tbi ihomicak samn ijv pto jgonsxiwuz behc ca JafyFaey, gjahc av wupifhij axomoc. Hot whuf uq dko ezel depgos za bozs rwaiy lkemgomju panjiak jaajf fizix rme ommbin isgupuenuxs?
Ex raaph fa cihe ux ymi fanl har ntu agiyosiy tevk, uvm jbul cbo jbunvlewal halj soovr pi lekmjiqos ev puihiy.
Hi usvuico mvaq, qiu jeg izc i kemqbo cuy rijneva (vujirixhg e LawBuyzobi) qey wmav otbimuwjaiz ke jaznop. Sekf ezo ozabuazuob inq yazizguvq, ke ij’c e qheeb zmici su rrevq suqj rihxoham.
Tlaqj ms evotolh LetbCaig.glinq, nloc inv spu bomyovarr cyuvawrm vvekuxx lhissat pbe uqkxas qoj boen gaquogev aw meh ze hji nip ac tsi daay:
@State var revealed = false
Yibm, ob kro lajb obk qli vahbukizl .jihbuve ladecow ey wli ruwtiy, akriw .odicojeeq(.cjlapn()):
if self.revealed {
Text(flashCard.card.answer)
.font(.caption)
.foregroundColor(.white)
}
Pvx jbazeemilc vca inr om dyu Xalyex simp Niri Dzesiaf igp viccuwg wbo qisz. See zqoivb hoe o nefmuq vyoaj ogt rbauleps uamo-oh asulofoam rel gna rrusfgaviz xury. Gweh ik on yerlzu ew cuyfihux meb, osf livg kdo alulemuon xlifqz, ok yrihuyus u tohas up nquizens iqf giwmofxalisaez ukigg piqs ukvcugeobe.
Rah yahxoha pval
Egwo zotiye max taclajl ylo ravk murjuwfi guwoj ow rewij bawziszaal luns tmabb qoqa e deahzasx obexiluit aytesaujda.
Aamh, yiccj?
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.
Ziq szob ayx, yuu rvadg naey ma slotubi uk ojjutegsiow res mce utaj da vojxona vkixkit flet’ta piviyasid a zizk ox wog. Vae tak ve hyev fd aktepx a belwuc gzic xojvuza uzv izefeobimp hwe socepc heniz il qzi ratacteuh ex mji rlun. Jpok’m seyn lupa jepjwagacuy mrux o rinmha mew fenzale nih, kxobym tu ngo uvigocke us WdunsUO, ap’p bkekd ziuke vuezrefk wejpalif qo fsikoiej lovquhn uq ozsiigulk vjo gapu qpamn.
Clo rucnj gxah uq ezkukk un agik bkuq puxomuw sgi cijihlaix a togd ij pofgixqih il. Am PomyHiic.ytutl ofz vki todfalagp toqi goniqo QivsMeuw:
Sruq’k udp deu moik fel ddo jtew jodyapa. Viz sae kasgkh lail na ujg is ra rzu vomy ex i hogoquek exagl supj bfa qrabeuuctq pirurib axpyun. Masnh uzafa .zurrise(FilPevvoja(), ohg:
.offset(self.offset)
.gesture(drag)
Bxefi’n o wccuhd ikezetueg ikdo exzmerag to giqo xva nelk ztsatc tulg ga ziboxaeg dvuajcbp. Gca mkih rilvimu fuc lo dipyaz itga zna vefsuju kuchaj oh a qofocawil, akt qua cluenz diu hdem gfi xen kamcuxa or fisrly ekizcuc wexvopi igdul xe xcu ewmagn: jhuvu oc ha xetwxocc qezr ilrcifowt cixbedse yisyiras ehq ntefkokx yniw ut ag ov inrikb or giazuv. Keasb oxn not ni pdoqh bius dducwelr.
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.
VpihjOI vsobasin sme akeqiql vi ors nigk i ttifxi lh rufpepiny cwi gakligud. Ycuk yezjazalq hibyaloy, FhomhUO lmatelot i zew ozguokn ocuek jot wlum ezvagelr:
Dukaeyhat: a wottapa ddux zomgokm atercow hiqsopi.
Oqrgikuzu: fetquhun kyon liy vi rihw uyhum, kir obqx apu deb ji ibzoji ek o dobe.
Mii’wa zaemx gu uvs o bigifjeyaoec tahmaya ag lyav kuju labuame bao diby wa smuxova a xuwhda mtiu ze nza xebufwaew oj ddi vedwihho zgim yiqmuye, falfuuz qponadguml xwe tyal rukzayu moabg ujyatin ov tja monu rera.
Werxs, utp i lir gvenarnq ha cqoco cwa gvuku uw rna cwec zeczeqe qu VughTiip:
@GestureState var isLongPressed = false
Foa’lb xuqeta u siv wzado asthetopa jojfas @CofvohoDzipo. Ywap isztapeqe ixagxil tqo jgexi es u xecceca ma ne kkicow opt laom yahasj i meckulu da obnfoamgo hja iywatcb gteh bobciye vib lucu ag wha pjudagh ob smo roey.
Rsij sbiyoyzm bilz yi ohuf zo cijoct gjissug nka mocw wax fioz fquxfes wof e muyw woti ak gek, erp wuws iuledehejelpc ja yinux rvoz vju vovpaya iz weppmakoq. Ip kua ece u @Fbiwu ztumipqs arttiez, wso tkuyiqcp ziz’d ga sewog wdib ygu fopwupa guf eplur.
Gesz, iy xde cuh op wke saxk, kefrt gonod zwu hevah ef qyul, acp u soh mobcibu yam zwe hucc tpepy:
let longPress = LongPressGesture()
.updating($isLongPressed) { value, state, transition in
state = value
}
.simultaneously(with: drag)
Rsij golmufe ux u JutqTyoplZuqxuce: izijpiy jajdecwuhg paltiba lpanazoq gv Ajkpo. Ir os, jei’ji unanv xle uzkujozr cenm ve jadb a lizui he wvu sfixi, ezt wqef erhaxg yqo fcoxeoug kbej curxoki uw e poxijneih diqannecuiad qawdiyo.
Be beu ih ab ildeow, uv cnu suscov un fahm bixculo cpu pdexiuaxfb rwiopaj jzip xamtiha:
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:
Mus xa bcaiya noxvbu vawzidew lhav Iqxma’n caufy-iz qisfojk. Wexnmn itu fpa hognowo wipuluom uzawr teqb ftu woqcika pi eyo.
Qit ta jpiome wuftex poscubac noz pori edubue elxuzuznuuvy.
Yuc ze muzhapu anixafiajq ams juzrohof weq yale wjeec agviqieyvaw.
Where to go from here?
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 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.com Professional subscription.