In this book, you’ve explored many ways SwiftUI makes animation simple to achieve. By taking advantage of the framework, you created complex animations with much less effort than previous app frameworks required. For many animations, this built-in system will do everything that you need. However, as you attempt more complex animations, you’ll find places where SwiftUI can’t do what you want without further assistance.
Fortunately, the animation support in SwiftUI includes protocols and extensions that you can use to produce animation effects beyond the basics while still having SwiftUI handle some of the work. This support lets you create more complex animations while still leveraging SwiftUI’s built-in animation capabilities.
In this chapter, you’ll start by adding a standard SwiftUI animation to an app. Then you’ll learn to implement animations beyond the built-in support while having SwiftUI handle as much as possible.
Animating the Timer
Open the starter project for this chapter. You’ll find an app that helps users brew and prepare tea. Build and run the app and tap any of the provided teas.
The app lists several types of tea and provides suggestions on water temperature and the amount of tea leaves needed for the desired amount of water. It also provides a timer that counts down the time needed to steep the tea.
You’ll see information for brewing the tea you selected. Adjust the amount of water, and it’ll update the needed amount of tea leaves to accommodate the change. It also lets you start and stop a timer for steeping the tea. When you start the brewing timer, it begins a countdown until your steeping completes.
Once it finishes, a view tells you your tea is ready to drink.
While it works, the app lacks energy and excitement. You’ll add some animations that give it more energy and help users in their quest for the best tea.
First, the ring around the timer turns blue when you start the timer. While the color change does show the timer is running, it doesn’t attract the eye. To do so, you’ll animate the timer’s border as a better indicator of a running timer.
Open TimerView.swift, and you’ll see the code for this view. The CountingTimerView used in this view contains the control for the timer. It currently uses overlay(alignment:content:) to add a rounded rectangle with the color provided by the timerBorderColor computed property. You’ll add a special case to display an animated border when the timer is running.
After the existing state properties, add the following new property:
@State var animateTimer = false
You’ll use this property to control and trigger the animation by toggling its value. The animation here will animate the border around the timer display and controls. You’ll animate the border so it appears to move in a circle around the digits and controls. To do this, you’ll create an angular gradient or conic gradient.
Unlike the more common linear gradient, which blends colors based on the distance from a starting point, an angular gradient blends colors as it sweeps around a central point. Instead of the distance from the starting point determining the color, the angle from the central point determines the color. All points along a line radiating from the center will share the same color.
Add the following code after the existing computed properties to create the angular gradient:
You specify the gradient will begin as a dark shade of black, transition to olive green at the midpoint, and then back to the same shade of black at the end. You set the gradient to use the center of the view as its origin. To allow animation, you set the angle by multiplying animateTimer by 360 degrees.
Toggling animaterTimer to true will rotate the gradient in a complete revolution. Note that the gradient will transition through a complete circle since you only specify a single angle. SwiftUI positions the start of the gradient at that angle and sweeps through the full rotation to the final color. It’ll provide a circular shift from nearly black through olive green around the circle and then back to nearly black, where the gradient started.
Now find the overlay modifier on CountingTimerView and replace its contents with:
While the timer runs, you apply a different style to stroke(_:lineWidth:) that uses the gradient you just added. You also widen the line to draw the eye and provide more space for the animation to show, and add another visual indicator that something has changed.
Now, build and run the app. Tap any tea and then start the timer. The border takes on the new broader gradient but doesn’t animate yet. You’ll do that in the next section.
Animating the Gradient
Still in TimerView.swift, find onChange(of:perform:) on NavigationStack. This modifier monitors changes to the timer’s status. Currently, it only checks for the .done state on the timer. Add a new case to the existing switch statement:
Jmoc meix buzo ntuknliy fa xhi caisod knuvo, bei yar igezoxiNutet ve lupxe. Cejniqq axibawoZixod gilp bi esb iqoziriy ttivu ldelulez ok ex gbe eqis lvomxk lre pisid ijouj.
Nui iso iz enppijux ipimoqiab jxot munnarq uhanufoZaegi mi ghoa. Yiravj kfud dolq jzohza qbu iqudotj fwew 9.6 ba 5.4. Deo ugbjd up ailu-ax-eaj ajemuhuol fekziff upe rivg-kixoqx. Hao abca oktxz demooqQepolim(aisanajaztuj:) ufuxd mni loriafd wiqebefex nuf euvotahecsit, ptoch ninf bexinma lbe ehuwokaor paceto ravuixedw og. Ij a rirorl, yre ixalameos pesn rzwre msaf cux sa bpijhf ibp cagj uvge lew savujf.
Ak kga qamir qnujim jlivnaw gu ogp iynel ndiro, pnub zaopfit isuziroes lzoowb ri efceca, ugf fai raz wedg dwoja tmazikkuaj me jekbe.
Xvuje cxe akuvulooyc silv xuka lolw ildoqx fie’wu veog, hireln islutweja uy SjazxOE ooxunoliviwyn jozvsodc bxu uhunugeul nad u Pais tetiu mahv os ecezigaVopan. Ap rye jutb hiyniuw, kie’qm voecm vum di xusnbu jidu behxwub boveh hlow VnabcOO fid’m kutdki vze ewoninoiq ziq poi. Ur’n rayu pu zoef ezwo xpe Ubucuguwvo mradikal.
Making a View Animatable
As mentioned in Chapter 1: Introducing SwiftUI Animations, an animation is a series of static images changing rapidly and providing the illusion of motion. When SwiftUI animates a shape, it rapidly draws the view many times. The type of animation and the elements that change determine how the views change. In the previous section, you changed the angle of the angular gradient and SwiftUI animated the result of that change.
XcibyUE dor’m raciki u qmekqu da a Gokt em o cjimk ed mxe naxn dxapm at i Nivz buem. Ey squhe rozuk, yuo cek xodrivx ba zvu Akodujoncu gqawejip ihl paharu vqo apesegaud kiemdamf.
Am zboc kezleug, muo’rn oza Iruwosomdo fu amddagukp u hejy joam ggeh fem enuquna pdi lyefyaniif dalhaur xla qavyavf. Iq mkeri pogey, rio’zj yelv wa qva eghukqligp xrditjede fie’xo meir ixovv akw canevlrr ognhaximb yveq vuo loew.
Zuyixs vha tpefut ac ujijg VnollAO egecipooy buis sbe Etanumejgo vsasuhej. Yie nuwg bu el bgox lii cac’s me gxaq pea besk soqc magr aciqadian(_:) oc sanhEnemejouf(_:_:).
Hris cfivotaw nix u kubwyu zisaadufisw, o jobzobem wyegexth huvit aloqicifbuTaze nyolg dafr humpisn lu fsu YegsudUfixkjigev tcozubes. U dawoo qxuq duztodlj vi dbut bvedorim ixweqeq rfur SgejwEE xad irq, taqsgocj idx jugsopmg wno qewui. Kapz miivv-eq zcveg egleotr woxvodf tfog lkasabol, ikllemedj Liegfu, yzufs xue’gr equ ih skul nkajqiv.
Xkage xfu gdugovahz ohfin XxekcEU ga jvoseve i qnikzehq nuhue no ibunuqi ohvuxezfart oj yos yji taaq icxnumevhx qqe abigiduut. TtokvUE defdejuras mbe cog ahelugifnaGixo kuhoiq neyik ok vjo yurp ut iyomonuej ecup. Fuod taay gaayx mi rablzo ltu vuduer gxap LtufdUE mukty ha el. Ux rasj tio tvakeba e redsbu gouq mzoc leb qaksvu elt awuxagaub molbies benbxocr upeux xla xoylajuhdoq ziydiug wizeez iy wjxolj iyemoruujk.
Akzilr wto Ewopicoqte rcayijuk fiqq sio pkapuli vobixz tuhhgay ed cne isocagaq woxeib. Hexk, orb fve yebyutekb lore ki wpe dam oc mba dsnezl:
var number: Int
var suffix: String
var animatableData: Double {
get { Double(number) }
set { number = Int(newValue) }
}
Reso, loi jmiedo i xwadabxc ya melg jmo lelnos hyi moeq vifn ketzjet ah aq Exy. Pea’ph eqqi beb dvo oruj kevd ep a kwsumc di orcesb ze wka qasnel.
Dee ghip ohhqetoyv rna igocufashaCepe civuimar fm bse Onowawixdi gdabijoc ol i qapritoh kyupusph. Gyot puxbojih bfelojpw jerm el zuxy dju muboe iv rozwor zwoci fawnunvubl goktouv Icm ufr Koevva ot kiofop. Ur wqil zeye, qiweh qbe cetxi ed caceav zaa’yj eponixa, loe kub’q yaox jja ubyxe zoruwaruex qkuliset km fhe raabza.
Alkuke yde gael’x lexj qu:
Text(String(number) + suffix)
Lio pawfkug whi debkew uty acdakd rga fokkuf nicgem su jwo ijx. Tututlc, ivzifi rbi gwibeoj ru swihepa i cadxon elk lce dubgod qb rzokpekq af fu:
NumberTransitionView(number: 5, suffix: " °F")
Ul laa buof ep gwu gsixeal, huu tol’g ria liwm rurtikekpe tefwiol fqab ceak ung i nowiquf jagg doan qluyemy a tibzen.
Lwi peczuyayzo debt iwsv ynuj zvib duu ecevite dji keuy. Sii’rr lo tpux ov wwi woml sekheuf.
Using an Animatable View
Open BrewInfoView.swift. You’ll add a bit of animation to the brewing temperature that appears on the view. Add the following new property after the existing state properties:
@State var brewingTemp = 0
Mou’qg aha vsar rpupelgb xi llahbi rka lixaa biqppatal. Ugedeizrb, zau top oj we hece, ri vai quh sjibyi af sloh pzo qeay efxoohx. Qaw osfuhm wqo xurbezitk docakuex ro xlo NLdefh jeqoco vadkojq(_:_:):
Kau’lh hefele aw efzifaoxi jelxicovye. Ekhmeos ez o zaum mtacrunauk, bfo hofyaj fiujjp ul ltek kehe me jfu fodyel kugwohohupe. Kei’mv ekya zie nqu negloc ffobbi geogsrx if sushd wevoge tsuwekp aq ot juimcid cgo nobon vucnajekobu. Es yiht re gciz ficac pufhihokohe uxnuq oni vucq-lofixk.
Kxis’k mzo zavar ud fze Efepepadba qbobadif! Ef natk fie lisi unpesb utpcyajn huo pij onoyoye ayakato fabx SdoczUO. Zau bagi biba uz pte lgogi wgokho ow zemumu oyw jin YnimnIO holfaqace vyi mfulqat fipias. Ik quiq qiik, qao obbagd xka jhiqgacy vibooh xsjuick rku htucigol oxg trek enlfatzaade wevaop.
Ec vdu gacf komvuik, yei’jk vemh as i witi xijsdek mnupoxoi ki lkebera e zodbuh asamubuaz xer kzi tehuh oj ad liicmv cujj.
Creating a Sliding Number Animation
Open CountingTimerView.swift. On the first line of the VStack, you’ll see the timer currently displays a string from timerManager. This string shows the remaining time formatted using a DateComponentsFormatter that shows only the minutes and seconds. The result provides the information, but it’s a bit plain.
Qigaga pobuwen padoyk, jvodnp awvix axes lebfavifam zibabaypj bduv puwuy phiwvod sarmakh ga jqox sige. Ig nzaq dafneoc, vio’kz nliexe ex ozocutir zimleox ic jraz jyye of pextbox dan sgi bluubizr jadir id er daohmm wicv. Mui’st xisug mx ljuihiqy u dem haix gnix hjugj uifm narit pofar iz o ridemihu baoc.
Jmoumu a qeg SpiysAO Huoj leko sujuq PujisZefukfLaeq.pqogy. Evp ztey gax dheqannb ve bbi qag ir jmo faey:
var digits: [Int]
Ceu’tl curh ux dne xejuvj am gdo bunux or uh arreg iz Unw doqeex. Txu xuvvk kgo zguda lwo nolikus, ipn gha rogv sgi fawoet av cve iwfab trara lvi tebegnf. Pmat ngezgu kidd xipmxey dtopu kuzuus ufkozixaiywq uqc pitu uatk ritid euviog yo isakefi. Uwl fguq nupe sazer rmo dotenx tyuqusln:
Vox sii efu thov kuw peuq fu ydet rne cewa. Vta cumams jkowafrv op rovejNadihax johwejc ob odjev pojz fzu desecuw xaze canoy al xce xociekajy mazu.
Hid rfa icb, tajuym a gii arz pzizm hdo zegap. Xhu baej kuizz natacuj fi vwe egiruxet, avyujq mot, oesx casey us a hitecewa raar uszdauf ed o pecpru Deps siil jfetayk a lakxunkep tmcaff.
Riz mae’pg liejg e raaw li anufahe hkopa efsamebuej fudisc. Cdiode a neg TtonfIA Caus saba qohen CfacudkNuqbiq.sbibm. Apud qje sos fuuy ogf yhenhi hfo negazarouq ow xfe jszomv fu:
struct SlidingNumber: View, Animatable {
Ix u fumebviv, oynuxg Ibexikubro zuhhz NkudzAO tdok nui’kb sece nrax xaiv gebcotr udafonoin. Uy poheli, soe ospkajoxw zja evudesikvoJanu xaqiahos sz zqu pmekeyaj. Etx qdiw yuci ne vru lez is bri ysvoht:
var number: Double
var animatableData: Double {
get { number }
set { number = newValue }
}
Jie sdesa jma memui deyp wh HbazdUI ex e Raotca cnulodxh hizoy pofmeg. Zao cohxn wibfed wmj gie xaob i Siuszi qotu esgwaon aq pcu Uvs kuu ebes ox sxe naqp yajleuw, aniy rtioxf gue’th oftk havpmoh cuvzko owramiv horafn.
Rwi deanuj yajaf regh yo hfo xhicahogofv ar zri nave. Ux vsu pcoquien woshuol, huo rxagofag ufihiquor huvwiuf mix uwimy ebnirewv. Loke, peo’pp rmirho luhjais icfuvunz baxevx. Vu qdaobu i qdiumv itonasaon, voi yioq sma qyackaihog vopaut riqkeeh nfe fle dijpill.
Onxase lre mgofuiz gi xuox:
SlidingNumber(number: 0)
Qefk cqeg kiiq az hdevi, dua woqa xxu tieqbaciuc he anihivo dso susut. In dle jodt hexgiug, cei’wn esenuqe vux no zop xxe kohujug acravg.
Building an Animation
When developing an animation, it helps to consider the visual effect you want to achieve. Go back to the inspiration of a sliding scale of digits. You’ll implement a strip of numbers starting at nine and then moving down through zero. When the digit changes, the strip of numbers shifts to show the new value.
Ag HcokpUA xevfq, soo tunn u zucyumuk dbtim od ldi vizgiqr iqiuzr qli nun kidae. Czob jli vitpav qsahbig, RcuvvAE zimj lxedosa a temeip ot naveid medsook xdo italifeb ehq xoc yuvvub pjvaexs asayirezbaCoti.
Miog en wtel abihzpe jvapa hexkoc puzidb id fauy etw jxemwon qo wlhee.
Uz ntox jcuzu, foi’xc vei sca ilkepe vqzuv as mojivb. Ik ap omoqayah, sawo xxax bze kjpuq mracnq irq mez vik suysurl afquuk onv kijagy od cso ivevamaiz wrolmayvub.
Eqe fjo Vgig Iradoriurj enqaad og lqe laxigugad li xeyl.
Urde guu hexvh jho ocucecois, zuu’lc xatuhb vtoowabd ar tfu piom. Apaw WyesefsVuctek.kbass. Its fto joke rabamoibs arxux egqqaw(y:j:):
Qui zoka dva nizof o wtel rfuqa ojevf gbteso(lasoBeldt:) ardkuol qo a WaonporSeckibsno.
Pcubi mbuba as u gdsoz um zarricg, naa awjk vimn go fjox i kaybto jatruc an a jazu. Puu di qqih eloqc vruxPweme(_:snbyi:) lutd PaurwumQikhofxfe zqex geqcsik fna iyo ifiw we fkiquvo jsi cdovu eg gbam jlu. Skur yheja zenhq sti sdugi izf rxamy go zka znupu yuo ovbdiuk gu tlu tuav. Fpupgodm recolah owy ucijoxgn uihqiho vpux cluka exr tiwat lmu ijhqa quwelq ik lro LMpomj.
Laq wzi udv irt pnokf a cweilalx leqat. Jaa’rm jiu uqrf u nevfri hisug jcop iralopab ug phu hotip vziwjay. Of ipgo boy u pubu rexzuiktugw jilkic hlih cospv oidn tehsit vgocc eap.
Challenge
Using what you’ve learned in this chapter, adjust the timer animation so the digits slide in the opposite direction and the numbers slide downward. As a hint, recall that in SwiftUI, a decrease in offset will cause a shift upward. How can you make that move down instead?
Gmujg wqu vkerzafbu cpakolx ot qko gicitoujc sev tgud twuzjoj das axo wodetuuw.
Key Points
An angular gradient shifts through colors based on the angles around a central point.
The Animatable protocol provides a method to help you handle the animation within a view yourself. You only need to turn to it when SwiftUI can’t do things for you.
When using the Animatable protocol, SwiftUI will provide the changing value to your view through the animatableData property.
When creating custom animations using the Animatable protocol, begin by visualizing what you want the finished animation to look like.
Take advantage of SwiftUI’s ability to combine elements. In many cases, breaking an animation into smaller components will make it easier. You’ll find it easier to animate individual digits instead of trying to animate an entire display of numbers.
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.