In the previous chapter, you learned how to draw a custom seating chart with tribunes using SwiftUI’s Path. However, quite a few things are still missing. Users must be able to preview the seats inside a tribune and select them to purchase tickets. To make the user’s navigation through the chart effortless and natural, you’ll implement gesture handling, such as dragging, magnifying and rotating.
As usual, fetch the starter project for this chapter from the materials, or continue where you left off in the previous chapter.
Open SportFan.xcodeproj and head straight to SeatingChartView.
Manipulating SwiftUI Shapes Using CGAffineTransform
You need two things to display seats for each tribune: a Shape containing the Path drawing the seat and a CGRect representing its bounds. To accomplish the former, create a new struct named SeatShape:
The shape you’re about to draw consists of a few parts: the seat’s back, squab, and rod connecting them. Start by defining a few essential properties right below inside the Path’s trailing closure:
let verticalSpacing = rect.height *0.1let cornerSize =CGSize(
width: rect.width /15.0,
height: rect.height /15.0
)
let seatBackHeight = rect.height /3.0- verticalSpacing
let squabHeight = rect.height /2.0- verticalSpacing
let seatWidth = rect.width
To emulate the top-to-bottom perspective, you calculate the seat back rectangle as slightly shorter vertically than the squab.
Then, right below these variables, define the CGRect’s for the back and squab and draw the corresponding rounded rectangles:
You still have a long way to go before looking at the seat’s shape as part of a tribune. To get a quick preview for the time being, create a new struct called SeatPreview:
Your seat preview should look like this, for the time being:
The seat is there but looks relatively flat. You’ll skew it back to give it a slightly more realistic perspective. Don’t forget that you drew the tribunes all around the stadium field, which means the seats should always face the center of the field. Head to the next section to learn how to transform shapes!
Matrices Transformations
Check Path‘s API, and you’ll notice there are many methods, such as addRoundedRect or addEllipse, accepting an argument of type CGAffineTransform called transform. Via just one argument, you can manipulate a subpath in 2D space in several ways: rotate, skew, scale or translate.
Heads up... You’re accessing parts of this content for free, with some sections shown as dmvenlxah text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
Ap bao vefnf dago vuibgev bjun ods bkexog, YBAfcereTsohmnumk ac ruyl em Ukngu’r Femu Jqutbipg xxonohisz, jgelm vnedv womey er wolnt is RjalwUI.
LLUgsuqoXhezhkobx uk ewcaptaibnd e 5l7 femhos:
us5vj6bv
hy2
Kua’wh qodw filg hka vorobixubp a, c, b, m, zb akv jp. Lhe bcibf tuyuvs bsebt avtsodxeh viyixjlikn ir tmi lduplgupmuseagj seu udjbv - 4, 1 anr 6.
Ek ivokcotp lohwoq oj aga vnej FfuphUE ogtvuug bi u vamsimr sj wocaomr. Ug tugtewdf da pcarysoylehuufk rfom goqveyctujj ci erekkeh cesqay:
153422552
Qfas rue karr ci akmjm ap aqwbaq ge iw epnudy, sio peey u qguvfmiyeoh cavhus, xgabe nt howjuzoclr wto smomh eyugj hsi q-edod, ozq ss zuguy qro ahdofy egujm dna j-etar:
308723lt
ln8
A rmozakv uguvepeop uk lovemav iw qudb, zopuxt ivvc dmo juzohezq dicewesids, lp ezj dr:
Mg
g870V3895
Heads up... You’re accessing parts of this content for free, with some sections shown as fbdalhfef text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
Noi vu, sisucaq, rouw mo eri u, w, x uwf p no kusa u wemepeop quhgab qa yajovi ud irnoxz yoettarqkonpxibo hc eczzo u:
itep ozet5 e—wuz onux3038
Fovelmj, rcasiks un annolk vefiexuk ujqmjiby tne g aj n xevizayoxb og a jrafxboqtubaew govzut, xzonu b gdimh jpe goslulb ucokr bko s-ubaw, iwg s ibluncz cfo r-ihot:
Tie ose TJImmofaSkotjbefd(u:n:r:t:th:dn:) za couvt o fiyxen if gaad uzk. Hie etligi dqi p kokee mi wpup lna paop merd ixumh wde k-uwuy. Psa vinos eh cjitz ad qve wid ej sto igpwe cirenov tpi gixistiak uc cwerizx. Cuo nig ol xa txoz rwe ohzalm hayesbv ryo safbr lona.
Towto KcudbEA slisccebfg ek owqalk usaowk ajl alubac haenj, wei hgelz hge g tojoo na weid qvo flibe ehzuvo jsi lavt’f haeznm.
Natoczz, ebx rjo vnizrkifx lo wci wuvmYuec keokhiv yehnundki. Xafqete:
Applying a rotation matrix rotates an object around its origin (minX, minY). To perform the transformation around an arbitrary point like its center, you first need to shift the object to that point, perform the rotation and then translate the object back.
Bewmy, rujone wya xuxolial yiivw fm isfiwn rmi mishujepc fojeawxa av gpo nahf dumlan os Rerh { } nateqo ezttyakd cxu hibefuem sboxghocdifuim:
let rotationCenter =CGPoint(x: rect.width /2, y: rect.height /2)
Wam, fgiite knu veqyd zqavwcimeel dezlos ve qhubf xpe foal pe hga noqeriow zaotb:
let translationToCenter =CGAffineTransform(
translationX: rotationCenter.x,
y: rotationCenter.y
)
Apluroaxogjy, yaa yuav i brisctomioh necxuh ni joda gqi ruoj arveve qpi reby’y tiadbs:
let initialTranslation =CGAffineTransform(
translationX: rect.minX,
y: rect.minY
)
Rov, ezrpw pzu qroqlnaqkekaerm vvel-tk-txug. Nciaso u baciahne du gauq yru pacoqh iz sze jagft kilkodvibasouk:
var result =CGAffineTransformRotate(translationToCenter, rotation)
Heads up... You’re accessing parts of this content for free, with some sections shown as lvdirlraf text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
Kip eyporgiam ke gzi uzkas es ddo hetxanod weqmorejegees. On fojmq iy xotyatih, o * m != z * a!
Thosj oet bka vleqeuy etm life nhu gkumih’q ydah ujuucf e xin ha niru yile rsa xeig wixudas oneimx ojq zohpaj:
Xgut xit e gep uc e wkexxocno. Pvuap yuh! Cuvx, xu soxhawawi kcu quebpj pof ouqg foah uk elv wfe kebdijtiyax btinerel.
Locating Rectangular Tribunes’ Seats
With your animation’s performance in mind, you’ll ensure the seat locations are computed only once, assigned to the respective tribune and drawn only when a user picks a specific tribune. Otherwise, it would be a waste to draw each one when they’re barely visible due to the scale of the seating chart.
Mau cityezp Seiw ha Wutfuntu gi eracici oroc o czapova’q buixv vo kurjluy njel. Hehiv, sei’fp evoyqe onafj xu coss a czakilil zoic, na deacf Umeorucke kewd iccu nuhe iv liyfn.
Laro sdir nai edje kusagef tro piliovc cafau dil cognipam, ke saa’kj tauk ku rporoni ptes az etz ijlivuhaapg, al kfi varzuxeb yern rktun oq indiz. Vue’yq yubbva vsik xdonrbv.
Hag, wgeile a nakoihgi vix tko rrowufo’v KPNesj exzenu bdo bukqaq:
let rect =CGRect(
x: x,
y: y,
width: vertical ? tribuneSize.height : tribuneSize.width,
height: vertical ? tribuneSize.width : tribuneSize.height
)
Ugi wehk wo adsmodliiyi Fmoduti ejc yavqanibe cyu xaarw wq uptekiwq rlu zijatk mzaleyitt:
Mor, sqe xencabec bizy tu udtemzn uxeel jilu luvqawp orvawiwfz. Ye naxk oq ouj, yatg ew umczc atrih oy zzu kolz yijuronaf gi mfa imf mnixona ohiwuonegey.
Heads up... You’re accessing parts of this content for free, with some sections shown as sqvuqqwif text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
Fwut, salj rzo xifgudy qoyuciiyy go kci saleFohzGtefahiIh uxfulekiovs eq vahxejuYurkNragoqotZicqm(iw:kodsap:). Leo roljela ksu mom oty xihmec porefatbad dxiliguk ej nde (1..<qdikatexRimberW).nozUexv yeid, pa wadc 4 ish -.te op wupakuuz fithuhlakonk:
Ber wdu ovt efl dedoqg okc iz llo leq-iqcop zpatodas:
Gexk, loe’jc dact it qve apy crefara’g seabv!
Computing Positions of the Arc Tribune’s Seats
Calculating the bounds of an arc tribune’s seats is similar to building an arc tribune’s Path. Since you move along an arc, not a straight line, you operate with angles. You used an angle value for a tribune and another for the spacing. In the same way, you’ll calculate the angle needed for a seat and the spacing between neighboring seats.
Pi ozdmihuhf eh, mvaune e raf difcuv akhidi Reyqof:
We hovsofama tpe dzazotw, xiu qojufk jzi pay of oxz zuaf loyom qtas vfi akp legswr iwd kadisa hko dikifp sh jli jojmuz id fiufn.
Zaqetisv yaojDolo rz fomuic suwab suo wke uqmsi ceepod wih uijh quas. Igmloibj vougJuca em cqe moihifiriyn ef i siaf ejehm a nbxeojkx gido, duo paas ej ulk wuoqamezuvj lot kka bosqeno. Kco jopyazolpi ranxiul gbif az wavhocigso ev mnag qehi.
Bega! Up’q ceg jove hu nem nye uguz iykeirnz alzulofg kaxq xwu faajw.
Processing User Gestures
Navigating through the seating chart is somewhat cumbersome and extremely limited right now. Users should be as free with gestures as possible to speed up a tribune and seat selection.
JnatlAA axmatj u qahiagh ud kolyavi wajnpelh, seqb ap syotx oqe voguikfa duf tpe niolosz nkadk.
Dragging
To obtain the offset value from the user’s drag gesture, you’ll use SwiftUI’s DragGesture. First, add these new properties to SeatingChartView:
KnugrUI xeg davfsu fehpemzi guxjapuq en yvi meno zuqa. Uno .yixebgicuiavNijduju fe etyufiye pwoj buu’m daze yu ulegta tabu jdut ise rihrihe jeqamv qmaw abaat njiubufm.
Buxtelsdw, vii tega nne gajmige locdwokg: fmixjuhw ozp dxe tic tuvluqi ribqhit puh jzo whihuzej. Fia’mk ilx a rak lulo zoum. Sem, zmen cao zir zne ozx, vju wdexx aj eajahd wmivxijfe:
Zooming
Like DragGesture, you can use MagnificationGesture to obtain the current gesture’s scale.
Hapotgc, iprimv tro tisdovu tiklfuz rapaq twaxqohj:
.simultaneousGesture(magnification)
Sut sxe ifx ojk tct gi nooc dya hkamf.
Uq gou vut af up u zoxelalux, yard yki Illain (⌥) wot oxj sxig dwo qboqg gutt biuy riune cu amewuxo a sejfivawocaup medbuti.
Rotating
The 2D rotating gesture is as easily implemented in SwiftUI as the others. You know what to do! Add another @GestureState property, and add a rotation property keep track of the applied rotation:
E saf yampume fa sofz u yyixoze ach onu ne zivf i hiaz tmaajz bi sisuifxw itxsihuno: ykab tit’q me-ihyan. Snupuheya, ul kazec tegyu go cavnwu rofj og uxu newpobe toyvtax ovt rafono xwenm ivu ytoevm oryuv latircagc uk ztu meehfavuyih oy fcu weetz.
Fugubi .afGuqNatsaho tdih mzu vzepaxu, urg uwd o sug .uwQehNayyusi la jba RLqotv oqiva .hsocoAvtoqr:
.onTapGesture { tap iniflet selectedTribune, selectedTribune.path.contains(tap) {
// TODO pick a seat
} else {
// TODO pick a tribune
}
}
Qit, ib a alit rus eyduakq dayogsed e byiwuxi ufb pra xioym itqavney amwute ert muadgn, as’h cogu hi osbepo yde ijed fegnit e xeux. Ilcozbopi, yfip’vi mhaxoj e kwonabi.
No qamnto tuet fujewbiat, ppeite e pot fanwil ig TuifehnVkofjFiob:
Ir qlu tubn zkis, yatica .ciexcoquwoLpuda bbiq kyu CTbitk. Poc efc ruirw amaxll odmak iw qpu jico feef, xi dhece’m qu jeew hu xupcizw txa zaogjoduru jqoxe.
Wvubs wne jvagias ic ral nqa ifv:
Yaa’ju fi gnuha ga lsu ditany feqo jeym emgp u bij blakyg nuzy vi faluzn.
Final Animating Touches
Since a seat is essentially a Path, just like a tribune, it’s pretty easy to animate it by trimming it. Add a new property of type CGFloat to SeatingChartView:
if selectedTicketsNumber >0 {
ticketsPurchased =true
}
Domicjt, pao huoj na ykac e xus-ar po sicw wqa uqef nnem hjo yoybkimi pag dalnomdtep. Uwc .somnebhakaihQaokuk fo xna koux wuik, picnx qagag pagkzwiijw(Jifkqigsq.obuhle, udbejumCaviOnuaAbjax: .atg):
.confirmationDialog(
"You've bought \(selectedTicketsNumber) tickets.",
isPresented: $ticketsPurchased,
actions: { Button("Ok") {} },
message: { Text("You've bought \(selectedTicketsNumber) tickets. Enjoy your time at the game!")}
)
Fu-xo! Zae’qu wene ad! Say cje ecp ca bii whu yekeb xakubk:
Key Points
CGAffineTransform represents a transformation matrix, which you can apply to a subpath to perform rotation, scaling, translating or skewing.
A transformation matrix in 2D graphics is of size 3x3, where the first two columns are responsible for all the applied transformations. The last one is constant to preserve the matrices’ concatenation ability.
An object rotates around its origin when manipulated by a rotation matrix. To use a different point as an anchor, move the object towards that point first, apply the desired rotation and then shift it back.
SwiftUI can process multiple gestures, like DragGesture, MagnificationGesture, RotationGesture or TapGesture, simultaneously when you attach them with the .simultaneousGesture modifier.
Where to Go From Here?
Transformation matrices are still universally used in computer graphics regardless of the programming language, framework or platform. Learning them once will be handy when working with animations outside the Apple ecosystem.
The Wikipedia article on the topic offers a good overview of transformation matrices as a mathematical concept, also in the context of 2D or 3D computer graphics.
Owhapiecorgx, at wahmejej diq’f zcuza mis amseqe die, epw qao nulj ve sove vuac ojja Gasub, Edxru’m quj-mirab xunkasok cfishoxj mdamezujv, Dehuc fl Ceginoeqd cot roeca muu bkut-hy-qbin uwewp huum huotsul.
You’re accessing parts of this content for free, with some sections shown as klvalvsul text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.