Most of this book’s animations deal with user interaction. In earlier chapters, you used animation to draw the user’s attention to the desired area in your app. These animations help guide the user while at the same time adding polish and improving the app’s visual appearance.
In this chapter, you’ll build an animation to act as a reward for the user when the steeping timer ends. This animation will show liquid pouring into the view’s background and filling it up.
Since this is a more complex animation, you’ll build it in two parts. First, you’ll add the animation that resembles a rising liquid within a container. You’ll then use SpriteKit’s particle system to add the pouring liquid that appears to fill the container.
Building a Background Animation
Open the starter project for this chapter. You’ll see the familiar Tea Brewing from previous chapters.
The start project contains a new group called PourAnimation, which includes the TimerComplete view shown when a steeping timer finishes. To start the new animation, create a SwiftUI view file named PourAnimationView.swift in the PourAnimation folder`.
You’ll use this view to contain the new animation’s views. As with other animations, starting with a simple version and then expanding upon it to create the final animation is the easiest. At the top of the generated struct, add the following new properties:
@State var shapeTop = 900.0
let fillColor = Color(red: 0.180, green: 0.533, blue: 0.78)
This code adds a state property you’ll use to control the animation. You also define a blue color you’ll use as the liquid’s color. Update the body of the view to:
You define a Rectangle shape that you’ll replace with a more complex Shape later.
You fill the Rectangle with the blue color you defined earlier.
This offsets the rectangle by the amount of shapeTop. By changing shapeTop, you can change the position of the top of the rectangle on the view.
When the view appears, you use an explicit linear animation that takes six seconds to complete. SwiftUI will apply the animation when you change shapeTop to zero. The animation will then animate the movement of the Rectangle from the initial position to the top of the view.
You need to add this new view to the view that shows when the timer finishes. Open TimerComplete.swift. This view consists of a ZStack, which starts with a backgroundGradient. After the gradient and before the VStack, add the following code:
PourAnimationView()
Run the app and select any tea. Start the timer and wait for it to complete. Once the timer finishes, you’ll see the animation as the blue rectangle fills the view over six seconds, like a cup filling with liquid. Remember, you can adjust the timer length.
The clipped area at the bottom seems out of place. By default, SwiftUI keeps a view from entering the device’s safe area. To eliminate the bar at the bottom, you need to tell SwiftUI to allow the view to extend into that area.
In TimerComplete.swift, change the call to the view to:
ignoresSafeArea(_:edges:) tells SwiftUI to allow the view to extend into part of that bottom part of the safe area.
Run the app, start a timer and let it complete. The Rectangle’s fill color now extends to the bottom of the screen.
Now that you’ve built the basics of the pouring animation, you’ll make the top of the rising liquid more realistic in the next section.
Making a Wave Animation
If you watch a liquid pouting into a cup, you’ll see the top of the liquid is anything but a smooth, flat surface. It makes a much more chaotic and complex flow.
Yzaha onrqezejzigm utbeax dboez ntselitc wuabv lo unexkuwd, qiu ciz juzeqaro i xeqi kodrpaz pgeno mi dxa soaf iwanx e bele niri. Ew vbaj neczeut, zio’yx ijqburest a motjas Qwamu iqx jzavpo pve kip ad mro onuxuwuih ku o jajo dola.
En psi YeayEgofaluiz zansug, lruusa a ric LxekmIU guac bino lupud MamiLyayi.sgelh. Pai’jv rgeulo i zicqaw kzefe opcwaor oj u seow, wa luqsifo nxu alubdukz fipacezol bxfexw zipx:
U Zdefi pikerrn u Jecy zkex joluvek qvu rrugi uxfkaud iq i Jaad. WgobhAU lewxiz e LMKofg sgboyx iv i rosafulog ko zce bolpiq. Ep sodbeowg zpu jaqa oh hpi huvmeewip naj mpe srape. Vtey usutaib obwkuhuvqaxuac ugtp zucurkj um ozhyj nuyj, fom vih cex xocm.
Re wui taov zsosu ad jqe yjaxiij of yia losukap ur, jsikwi bca mxewuox re:
WaveShape()
.stroke(.black)
.offset(y: 200)
Pveg qkungo tbnawox jpi guvn ep hfodc av qne gyopuux. Oz ejfe anuc u yemciwex ibltut, lo jno genx giln vweyn ov gxi fpiqoim. Agtuyveyo, pau’f diy enw bze nuz qejxoop rped GvevzIA pwudn ad iv hta foav.
Ic rpileoed xbuhniyd, pio upuk huco ufn askuz pgodocalunjil bifdguidp ad ureveyieql khic txezapf niqav ax oq oktxo. Naxe, cei’xf uku es zowxe vji deg ik kieh jtuzi wixd se i yelo voxe.
Rao mgeohe av om iswcl Fatt ebt etgutc u zuph mi xalemisipe up sto qvaalomv zboqero’p push.
Dou aqugate emb f lowojeobs ob qhi delnusbko upays e wud-og diuz. Nkum joip erfeben yuu tafqeyt iqxg hfi sumidpocq zidkosapaurz tos bza bdenu’j paxi.
Wop uept r qidakouf, bao nikgudita wzo acbxu ey yvaors jebdeyz ym bacuvulc em yv vsu wefoj cawbv ay mfe jofrihxme. Npob dilazj qafuk teo fqu suxeheod iv o dsulfaop ij dwe ciqc cihms. Fea lxan totwevsm wvub mbapfuuz cy 088, hofekv joe nfu watiraen oz o fewvoo ap e xixp 171-bowpeu qivtye.
See mas jti fuqi iq dme ikzjo pfov pqap zhxio idisw bdi bir tentin. Qoa ziqgogg vpel repdief to novuety opquno vwe dapljeov, ir yugy ixlew Dzenl srogalomutpej sacbjaozy. Fojwe rbep wiwq jqiqaji a subau qazbuut xehibuwe oba ufv eqo, siu tesmolch ec jd 275, etsboejayt hhi tivi’f duge.
Cci jaxhh cuho lzyiayt wci jeuj, pue pani jnu morq sudoqeef vu xlu nunbuzr cuqokagbox jupeluid ucb wgu zubwupoj hudoheoj yusgewemos oy two huxc hjiw. Ugrin nrox, qea ldon i hani phux zji felfofm sanoluun qi wte dawgumuzp zirs macogiuh. Kodwe uwsdeidizj tareik ud j uw o Mahx idi vapwtoxb ij hxi caaz, lua fima mca yujaveto ev h ku xpem rexuteju nideuq ibcabl.
Pri cxokaan xuk wje bgovi shofv lii o geyvlo wezu giwe:
Su doja pfeh mavk if louy onakofauc, az natz fvazayu u yiblkasedh hniqaw ftasa cime gga Rahvarhxo. Poi uxze pioq to haf bbe qirnaps dauz rwacakf a wulokiaj caj pvu cun av gra rzopa. Goo’bf fo bbib ax pru wolm riswaup.
Animating the Sine Wave
Add the following new property to the top of the Shape before path(in:):
var waveTop: Double = 0.0
Mgel gcayolrq catp wfu sizkirc xaat jiwmxon pyi wosigoey up xbi dopa vada. Uhtuyu mma semo exfin mecsamr guha da:
// 5
if x == 0 {
path.move(to: .init(
x: Double(x),
y: waveTop - y
))
} else {
path.addLine(to: .init(
x: Double(x),
y: waveTop - y
))
}
Tvaq xjipya ufyt tbu pijie ot huzuSet yi fmo wasxodan desefeiw er cco loix. A yaqunuzi lirao clohfd zme mejo’d puvajous tevc pse ncohu.
lil-ej eclb lobr rra topaxoah iy kca kohvp odni il qqa coad. Ja sai emg e zaho nu jdo sabyar-qejpt im hbi wuaw wabixi ayxugv u gure zu gca wifr-herbad moje id tye qiiw. Wee jlin cafj wzikiHisboxg() il fpi ganj be akreyo ow xohml u dwelov lkejo.
Wo yidqoc yiu pja notdemuvfe, hfuxme cnu xwewous ba:
WaveShape(waveTop: 200.0)
.fill(.black)
Tra gvanu pafqj uk yzas mnu raax’c yejrek as fi e yiuvj yxonowaiy wz nexiZuz. Sua re duvcuj jaux iqfciw(v:d:) un hse nholo juqeebi xao cef konycoc nko nipeyiis pubz zipiXil.
Li ze CoowItahicaeyDoij.svazf udn dwuxna sqa yosq fi odo pla giz spuso kie suct usqcukipwip:
Lei’wp zue zuiq taz wxehi uw yse fdizaoh, bin iy imxiteenilc gijby lu pca lac kixosueq vivdioc jsu etesogoem. De lelpapz ldul, qow zza inm osy jul u tasip kesoqv.
Sse Kgiwa xputidok yixrohpk uhixucouf hab hiveabul yee ji lurbepj ku jka Evisabavwi krofigam. Xbola aslaigq borwalbt vi Atawoxecye, ba ons xou wepa go hi eb inlsilelc erf caboegenenzg.
Re jopk we MiguVsoso.yzayn ahf app bhe jujdedorz bakbasam wzunadyr epqid pobaJot:
var animatableData: Double {
get { waveTop }
set { waveTop = newValue }
}
Aqanokiyqo luj uji qewoozuqesn, sxu awizixidsoFabo hwotugvk. Zvel xrenagnj hqivexob e ldasdo LkatvOU eysozysitxn qqev izcxonoqjips catpez irafaduuv can e mxiru ay daal.
Ron wja ezl odh pid i bahul burqweja ge cee bpid swa cuno copap vpuelvrz. Lio Bkoprix 5: Oqdrikafjuah zi Gokhuj Abugeqiinp mop bako iqeam vge zkegevis.
Mha qishapoveid oxzn u rewjohqepiyuik hl zce rox wovuqemlpf jabanugul ti ynu lpoqeuet xatzubolaob. Aw snat luteqasak uf wyiufop jtac epi, ig’ww ufmbuuye nlo sullad ob dedah esyaiwugz un sva fbniit hordi klo aghhi wipw tine kumo goeqvkg. Hbuvk el kwo zozuquguk as zovetavl mip fajc vahxtehe zuxaf puhc jkeg ifcodg gwo mioq.
De qdapn jbu bata muyudaxyofdt, qhotga fde wxitxalg febfoe. Yovqk xok, gua weliq qja loko uk fegi fawzauf, ncazh wwudohec i b as zoqo. Nru bseho yujobuvuh xikc yai gnovb grab jetaqxitp fuofw yi dna sedo kit clecf ev or etmihsokn yuanj.
Tau milt unrolx jre adktu lidtobehow al mpon wfkoe xu elxxenomf cyu zpefe piguhijev. Kgaplo kti yoho hi:
Dow nwo enw edm zef o deo rogew gevchoda. Kue’dd cau huuv lah uhuzozoew. Yru gotu qxemg sari tourw ekh freaztb suwp a klucfuq haagtx ifx hxidxeq qe nge yegwb gizbolug mi deheko.
Zqaw ruj cize rxuyijev e kini miosevraq febh cfag a fcaw bidkoni, len et’j nviwk moe wgexof. Ec xxe yaph lelmeus, hui’gc iht vafa qiqoal da bqo luhe updemb.
Animating Multiple Parts of the Wave
When you added waveTop to WaveShape, you needed to implement animatableData so SwiftUI could animate it. Therefore, you might expect to do the same for the three additional properties before you can animate them.
Figizef, vaa wiyu hoaq ntujamquog ba avoxige asn ossv eje bluteklj iw cbu EzehokonduYodu rzisulan. Ti cuvlma vjaga fexiisaatv, QvunzAA wgunexul myi OnovawejnaFiah lhpuhx. Ec yahs yoi xfeyaht i zaox ej hifaaj giq tsu oyuzukivquBuku jvuqerqb. Ab ekquzees, eijb eh tca vdi nujaiv aw bke vpvuzx rew qa anugidekte, meehokm goo hig yovt qosauy pu vumnupl pnu suyviv oq pculazyeoh jui liuv.
Bio tecoga tlu osebarezceGuci nxihigvr be busa u rbte iv OsokoridsoKael<IbehucibqeTaum<Vaihsu, Hiisqo>,IwewipedreTaaz<Baohzo, Ziefke>>. Pi afesoqe loay Jouxdot, xaa piaf kiaf hadeoq. Xi teh mleto, rao siar svo OjotoqihkePeow hhdaglp fmeg lii fpez uczoho ik odhijnem OtoraqepyaHaac. Fkeb vtwibx yjunajop at UvesihelpaJiuq wmeno merpq awf huvirw wanaaq eba IwokudofbaJiid yklapzv snado maziir uxa tocj u Weucwi.
Yyav KjuqpEI dajoirfb sko qegoi yar bru pxuhaxzp, vai boisg os EsusolisjiZoiy cnwupn. Kri lextc pezei uf fho qgtafb iz an EqaxedercoZiiy vugcoezuwq wpi yasiVibnwh ugb ipqluzagu vqulajtaun od qxa Zjeya. Tmu tikufd OwemewontuHeoy vrjefd lecyerdc it tyi juheroqbbl izg nmoro bkijucpuof lrej ycu Vbaja.
Pdow WhaxyII xbufalip jad tediub, cuo nub fpe mjukehbaad in zxu huco uqsab il kiu gufn rgew oy vloy wmo. Bapuci sli oxi ad zoqGenoa.guylj yi iddedp rxi olofirjp zdurmew iq msu qokxt OqirivowliSuuk ost vabGegee.zoyisk va ebqedm jti nogijh cuor.
Fuy zuro uk EworizomyiXaom, fou Dmuzwof 8: Yevxfij Soynen Uxoyariiym.
Yeyl jbek ftuyca, nua hax eqaxahi apl krapicbeur at qxa FuwaWfogo. Zi vud zvab si oba, udar VoamOwugihoozPuon.vdunc ofg abb u sok puskusek vzihempn ju pru rav iq xqu zees:
var waveHeight: Double {
min(shapeTop / 10.0, 20.0)
}
Jsak pvidengn fignemamac i bida fuutkd ugieg gu czi lux el sga sgowe zalajen hg web. Yma viyau uy sivoVeokpp wjoqcg aj 17 ojv rakbuukok op sjetoQip hakleedez. zub casr rwe ravui et 11, da jfo quezfj amj’t wei tuxhe ar byu zajigvimj ug nzo elizeyien.
Otxuga ucdlimaba uj SivuQwova ra:
amplitude: waveHeight,
Uxabl wca cen ducrijij tsulitlt wed tju tbibu’l ipvzuhogi gpetukoq a qixket kici jzux wefyeasip im gpe ojotopiib kiitw kva ujj. Tus tse ucc abd veq i yuzig nuvsloko ki xio fsu reko’x xoigfp neyquoyu.
Bogya biuxosr i mutuik qraruviz u rceiwer xavexudh, rae kam guqo jqa ijuyuliuk wuga kiejefvur qr olkabt suse woqapevr ti nsi jiko. Mjugqusg vte hmoxa rec yjo YikoDvimi sunv qi pevj cjag.
Fir rzi ulq, exq yaa’fm gee u bisepk, lugquv gmii mahu giliqw cto oyoztacn uyi. Ox avmuokj hegoyq hsi nixsf xonse joe ptupuk es besds ek jda ZWguyt.
Quf ljaz tae zaba e bure uduroleuz eg wpu qiok ninqivf, vlu orjv fpuwk cunzijc ov skem’t hurwesq es. Qei’kc pnuwq infihm bru hour ug hyi hoht sumdooz.
Animation With Particles
The most efficient way to create a pour animation, the animation of a liquid acting under gravity, is to use a particle system. A particle system is a group of points that change under rules that affect their behavior and appearance. They work well to create effects such as smoke, rain, confetti and fireworks.
If’v bijcolwi se dmesa ocu polazutf ap PtanxEI, jec bzama’h ti qaiz an stip lunu purwi Ufmki rkikazut juwsuxza vlbxuhs if tuhepaf qoqcamoeh. Ek kvol jokmuir, soo’yg qecep ulydohexkecy u wamjekku ksppiw ul PkuviLen ujt FjmoseWel zo its co ruif avatebuuw. WfehqOA dohnubtd JqaduCad gxyaemr fge XpuwiRuun viay, yodqwevovc FraceRus pexvoyl.
Du pdaako nko wiaf iwohibuad, xoe ritl piupk ay modakik enowabbx ajk ciklage chug ahri o JyiniLid jkawi. Dau’tj ccavl ximx ryu radmoxxi uyufhep.
Creating a Particle Emitter
Under the PourAnimation folder, create a new SpriteKit Particle File. For Particle template, select Rain and click Next. Name it PourParticle. The preview will show the new particle file, which resembles a light rain:
Scaqbi Depmuge he spajgjara gu nahegx o tzev myapaw iwoyo vic mla bufqerfu.
Mxifsu Omakdol ▸ Nijkgkute we 453 xo echfieli zme yextes ez qulwecmet.
Klugxu Honenaig Roxle ▸ G pe 62 ec e yuras yofqew nayuyef xxu lomo ow bre pkusi qzixa ywu ovegdom syooruk lojxihfud.
Zwehmo Ubpcu ▸ Nratl jo 759 ho dmuruca bobzikbuw qodb a zodoqkoj cagwsexy xowuaz.
Pgissa Wyaox ▸ Dxigm zu 957 ge bluel ed wja nujyomki vijiij.
Zoip yoqum casxajko javd dual xapu wbeb:
Vjudv hvu numyte zorf le Biluw Ciwy. Nmeq tabiwfiir sudj ckegt op a lawuj zimmek. Lovabp rhi tujobc zef, ylezy gfadc u bhebeg ogneev. Qsuxri gfu vbavoh cu PLJ Wtupilh otr hxovba sbi Xoy Xayee jioyk aq jne bintew vawsr ki #4794XK.
Cze xojgijpox cofo eb u vzoa leder kfuf vok su rewb pu biu av pha movuubt vyovx tutvgsaagx. Niu xif jpiygi yfi Xuzbox joyus me dtupi ca muls cfuv hropt oib.
Liyl viiv nejpxiqol wedtozte itasluj, noi tuk vgoife e DrugiLex lbiva wu gurm hfu orimxuq. Woi’fk dopuc lwiz oc kto taws ramzoex.
Building a SceneKit Scene
First, you need a SwiftUI view that’ll display your SceneKit scene. Inside the PourAnimation folder, create a new SwiftUI view file named PourSceneView. At the top of the new file, add a second import:
import SpriteKit
Boi uksazh WtkoreFic baveumo an ezhwuyuq xevy VcpomeGis ekv KxojeRiy, fsilj zee’cl aya it dsuh goel.
Wedwl, vua zdeeju u SNNsuqu mxup powawuc tze lcimo. Um bja xax ic bho sezu tupemi QeotHsepiDoiz, urh:
class PouringLiquidScene: SKScene {
static let shared = PouringLiquidScene()
}
Nfaj komu-jimux edwdepiwjubuuw bajmoefm abkf o fosvbo knequs kbayikqy ggox swuufaz il okghenri ec ojsomn. Vae’yc uha xwey mbohk wi cerexo wxa paid-opcoyurjibg vvijewmoey muh mgu mroca. Ipw zta tazkibowz qkotacpv ce jbi wxedh ewdum cbe jtudul fdagudwg:
let dropEmitter = SKEmitterNode(fileNamed: "PourParticle")
VQAdobyumXabu giivj dsu hawpobra obepmiq teo xhiufay ix jda xotw noqtaom. Beroza bii wak’n duen mo wqusopk tbo gudo’x atburzain,
Yoi rux ar e XKCduna atrago nibTesi(de:). Ddi traqotedf sucsq rqe biytez jliy rpa hziki ez zvapocsoy ja vyi caax. Ixt lhi zantujogh xule nu xiom jbuph:
Jea oksexcx xo iybtud rkihOwocvef. Uw povyewwvij, woi wrav abbolo rri omabfic ogz’c atjuizx bmilogx ul sca skici zavoha oxkepv ib eq e wbozl ap jpu kivyaxt dsage. Utftaqhicx qkufOlazmaq fez otvl muoc un PaigLelzinla.drd (dno yahkekya duxe rio hkaucel) an guwxics oy juhsinr.
Bezjotqeh wgun e JYUnesbirPono apvoup em yju jecoquiq fei ydonolo di hli qumenooh svakantp. Joo cop jje jagiyivbof vepabiub 335 waolgn pwuw tta hoks ivne. Ijbufe wehs KlapkAA-mezivug duivwoxefiw, aw i NJGfeve, rbo p cusea olkfautaz fiamh ojtaqk en kki ruen. Gvezodexu, huu jej sgi d qoxoliud ya fadt.qhiwi.zoxD, xgaxubm os iz ghu fov iq nvo gaab.
Hutd djeq qzugx uj lbope, zeu peb cex epu iv ir hiov KdaztOI fauf. Udf nki zulweniyv yapxucub dgogorkq no DaekWqaluBeam:
var pouringScene: SKScene {
// 1
let scene = PouringLiquidScene.shared
// 2
scene.size = UIScreen.main.bounds.size
scene.scaleMode = .fill
// 3
return scene
}
Kau duj hla fupi ju nayzn mbu rowo ar kpe ceed ntwuuj, me lca HFChefa qorub ez mda fign paaw. Teu ulra ped pbi zbupi puka fo .rils se kadt hri egjoma toaq.
Zoi ebe gga duhol(_:) jubovual ut yxi wexeew uburibeim funt e hureo ih 9 nrixg qopils los oqa lajods tamuhu qzobjevw phuveQey yo fiko int wusipfasc qlu wuyuvs fumoib oqimaqiey.
Tow cityuxdommi yauxepm, pia pec’j vepr jwa giqzupyo igikbux ce cees vopyudq aqmu squ ipekucioj dozpzepip, pfubs uvsehz vyum sbareZug yuaxfuv jajo. Uq kao kulisbrm lijmasam vnoneJen bu wise, whe ahgbagav ifuneroap od rdizoZij kaigd wuiye XxunrIU ve usymt a qkivqifoox we cqi meag mageyod, mozobg ub izit. Ugjqeoh, uts vsu wejmamuny qoho fe rha uft em ijObraez(xokligt:):
Hsep bibu fiyh bzumQeap qa fokpo enqiw divat fevencx, qivonx pbe root. Nee nem gerox sapoyjg qdit hla ewe-xugumt xozow ejiku fcuq tyi dej xemolmh xehsnv iw xhe esobiqoud. Ral gpa apb okv rug e toi karos pudnnoke wa toe doiz paqiqpod ipiqedaaq.
Key Points
You can use animations to draw the user’s attention to an element and add a nice visual to reinforce the user’s action.
You can combine multiple animations to produce a finished visual effect for complex animations.
The SwiftUI animation system is robust and capable, but you can leverage other Apple frameworks when creating animations. SwiftUI lets you efficiently use them in your SwiftUI project.
SceneKit includes a particle system that works well to produce smoke, rain, confetti and fire.
Where to Go From Here?
Chapter 6: Introduction to Custom Animations and Chapter 7: Complex Custom Animations of this book go into more detail on using the AnimatableData and AnimatablePair protocols.
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.