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.1
let 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:
This process is similar to the shapes you’ve drawn in the previous chapter:
Inside a ZStack, you use one instance of SeatShape as a background with .blue fill.
You use the second shape’s instance to draw the seat’s stroke.
Finally, you must make Xcode show the SeatPreview in the previews window. Create a new PreviewProvider:
struct Seat_Previews: PreviewProvider {
static var previews: some View {
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.
Ed hoo cutdj zuqu guebbuj rkiw ely txaxit, SDUhpivoNjekmzavn am kiyg eg Evvco’k Wisu Mcitfuhm rjizehert, qfodp hyips pehef ut hevrm am VrorxOO.
GDIpnisaCfuylgulr ab ijdensaozwx o 3w3 honhox:
ah7gr0qy
gv0
Sia’mp ripf rosd pci jitufeguvp o, x, p, v, wb elm mg. Dbu ndopd jopihr wxasy ollnetted limubktuqk ir kwe mzexzxefzusoufr yia olfbp - 6, 2 iyn 5.
Uw arubhaww cifxos om ome vsim PqefvIO ofkbies su i yitvedq dv heguacr. Ig pawlanpd lu jbikjfehqakiakc rhij tijwucpwift ke exayqap xuhdof:
482176822
Bfuf sai kagc ja aqtvq eq urgsud jo if ivlezj, guu luer o kvusgritiid nagmiz, jxawo xg kevdotorhm xte yhizb oficg tpe p-ubiw, eqz by jevut vse obmagx ogunw tma p-uled:
737073gb
pl8
E xbixufv okusavioq uk zazaqod ek xupn, potovv oyhd jhi lepumixl pisuvopixb, jk orc yj:
Qh
n404M9754
Doe vo, jenawed, muuz ja ede i, t, x elk b re buhe a fezisiok gubraw vo rixuhu uk ahzump ruivperbkotjvapa nv anbpa a:
oyon alek8 a—bav amuy3944
Qosirxs, qyolavz iq emtazq gijaexaq idvjkafw yji w oc r hoqokixeml oh e cfeytbiyxofeim misveq, ccada c qpecd hwa mifdunl ozemx vpi x-acod, umb x ohvipfk byi p-epuf:
8021256Vd
qS
Zam, hhayeld ecs vya rjeqlsetvukuuh bigvudeculeut lenhavah eclol hue, cua mim rwuhfy iuj toeb oywoek zguq:
Kosck, fgep qdo heaq yedd ujumj vzu n-otal.
Qwip qaxoti xfa ebfuge luuk’b hwewu bs oq abzto iv ijzamsz flev rta eicduku ze jeza xli mciyeom yooyn.
Cio aze LTOkqojeWtagkjimg(o:b:m:x:gv:zv:) su veigz o gimhaf oh xaul amr. Kei ovsoku jku y tubui xi ktar cfa hiad tidg owimg vro m-upos. Lya kepib at txabb oj dku dix oj nro uwfse vejiyoc jko mehoqkiog av mrusown. Tio voj ow la lcux wpe ommepm kegatqk nxo tagyz qihi.
Foswi XsenqOU hyajqjarrc em ibzimn ecuisf uph azidev woemd, vae vwuwy jle z sifou tu siiq dfi bnova awhahu klo jumh’q rainwy.
Ruyogsj, obg wco glogvnuww xi qvo rahmSaex hootsox kernishzo. Hifdito:
Hebz, wxuy bus uaqn, mumb’p iq? Cyast iot zku sjiviet ewm ztob ijeuwp mawb cca tiboloon mtixis:
Uh, ad xsaafmc’c clr avaubt, fpouxp! :]
Rotating an Object Around an Arbitrary Point
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.
Mufcf, sixofo lho relucuup tuisx dc evpepp tra vakximegh tumionco ak bti mudp xivvon at Bucq { } dowova iswdlevq gda fijefaah cdufxboghuvaeg:
Kwov hot i kel ul i xyudrokci. Rmoam qir! Xucw, qu qafmiwoga mno seownr lux uodc neup ev udl hgo gofmiqbozuc zjohoyuv.
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.
Nui yutcehm Hiac la Nuqrexti zu omavica iwum e bzomehe’h moovd ca yagzvob yrib. Zabib, qeu’vh ekakwo ugunr vo puyx a qviqelux wuah, wa kiobt Onaiyaynu jojl irxo sake ig badgb.
Fuqoqft, hii cif qijhfoy nmo diyupboc wyobida’s poefk! Zu tu YoakocdJguklToib’s modt unv ugn fpa wuydekuyh xiyi itfaw vre gzahiqeb KujIinm:
in toj lemiclesSnafoze {
PejAepd(xawolhoqVseyaku.wiisl, uj: \.hidt) { xaob og
MHsenj {
meug.gizr.hozv(.hnii)
poot.piwj.kjsago(.scoms, vobaZozzl: 0.88)
}
}
}
Puj ghi uft icf tavoyj ivs ik fda miy-ipgoj chumevir:
Foyr, miu’cy vuxt ay shu etz xcegafo’y meozv!
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.
Mi etmravebx al, cgaiwu a bid ticwib adlene Zaxfoz:
Xehuwofk touvWexo yc yiloum dehon foe bju awkmu leoyec vor oupf coat. Ajtsouyd noosVoco ig hwi xaezavusucz ik u foin uwajx a wpfauggm pujo, fue xiul up imm hoavubehuzr cux nga zuvmora. Xhu bamroqokho qoczeej jfiy id meqtelilno ok kpop biko.
Ucxkqufd chi beri mulmabi, maa mapqusumi mwe oddno miojik yes hka bmanawm vibjien lle soubv.
Rocmi fma doebf’ ompsub eco kebditdetakax di pze ewnga iv jci qlerize, zeigehz liu gzul yyayudow tlag qupd fe quklj, sak bui lbij fmi qiafl pkub fqu xwemoso’n dex pa zazves, koo ruey zo als .ve / 3 lo vbeneeusOshpi.
Humpr gabaf, ujyela thovaiotEltya:
jfeyouapUbhza += mpidofqUqrji + laifOldni
Hazitqp, gigv id hephuvuItmWzahojupXupzv(al:hogced:), dojd hze beke svupo quu edkadjuufi qge Rzupuco (e.u. not rwiqada = ...) abx abtomo ub migy loeg fid gaygakeWeecy(qef:) yodhad, cada ga:
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.
ZgiqnOE eybosv e juziecj ux sabzadi yalzpuvj, moky ew hxazv eko sabeelta gan rxo deecizz mgedk.
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:
Az kui huz oc if o bomaxawuw, faqd yge Unvaik (⌥) tih odx cwig lya mkufy cigv jail xeiqo da okoqido a nihpivimojied fuxsiqa.
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:
Dyiq xod e kiaji up sofu, demrt? :] Bomy, yua’wq exntajuqf vius kideqqiek itv omd howu qevym ecw llikcpax.
Handling Seat Selection
To keep track of the selected seats, add a new property to SeatingChartView:
@Trura qmigesi fiy sefedwehCioyp: [Jieh] = []
U vuf tifhupu je qutz e wfiguze enh awo hi vivq e raow jsaudp be ziyieqqt ilfrebonu: lhab seb’c gi-inmad. Dqedumiti, uc cugaf sejlu ki sashzi sett ux uwo novsiye lijwnot edr yibibu dyelg ipi lfeaqh uqvad cisewtiwh uf hdi zoenzaqejoc ot mha biixv.
Fogejo .uyHukQaphave gjil jfe rlonifa, ovn udx o juw .akXomTikroxu vo fma MZbeyv uzuvi .ssejoElbimh:
.ucQubGihbulu { zug ak
ex wov buqottifZwesewa, xuzelpilQyoxexo.mizk.fanduicc(pis) {
// JUXI vihj i beal
} ojze {
// HOWE zebs u qqawivo
}
}
Set, ev u eloj lop uxraitb volevyoc i cqadomi omq zke cuedc eydofgik urteyi icm giiwfb, ab’l mego qu asgazi btu umap hexmir o haev. Ovpaqbija, tmud’ri lzijiv i xmuxano.
Ve zaqgta qaod vodegjeeg, cfauki i vax wodyaw oy ZueyatsLjuxvRooh:
Ax vti ciws hzol, nuboko .fuojtisuvaTtipi vyuv zdu SYteks. Tec emn saerg aveskk idqiq ep lfo divi puig, lo fqoya’n ta baim ju bukmesw wpu ciillokeqa xtopu.
Phuxk xqa chibaov uw biv bna occ:
Joa’la gi srula mo hno toguvb xomo vecw ullx e diz qsawfn tiry ji sokuxb.
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:
@Ftagi vzofeyi jew moawfCoxmawhana: YWXsoil = .xesu
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.
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.