As you begin to develop more complex apps, you’ll find that you need more flexibility or flash than the built-in controls of SwiftUI offer. Fortunately, SwiftUI provides a rich library to assist in the creation of graphics within your app.
Graphics convey information to the user efficiently and understandably; for instance, you can augment text that takes time to read and understand with graphics that summarize the same information.
In this chapter, you’ll explore the graphics in SwiftUI by creating charts to display how well a flight has been on time in the past.
Using shapes
Open the starter project for this chapter; run the project, and you’ll see the in-progress app for a small airport continued from Chapter 16.
Tap Search Flights then tap on the name of any flight. From the flight summary, tap on the On-Time History button. You’ll see a list showing the recent history of how well the flight has been on time for the last ten days.
Note: The first flight — US 810 to Denver — will provide a suitable range of delays for this chapter.
Looking at a few data points can be enlightening, but staring at a long list of numbers isn’t the best way to gain insight. A list of numbers doesn’t make it easier to get a sense of how warm a particular month was or determine the driest months.
Most people have an easier time grasping information presented graphically. A chart can provide a graphic representation of data designed to inform the viewer.
You’ll first look at creating a bar chart. A bar chart provides a bar for each data point. Each bar’s length represents the numerical value and can run horizontally or vertically to suit your needs.
One of the basic drawing structures in SwiftUI is the Shape, which is a set of simple primitives you use to build up more complex drawings. In this section, you’ll use them to create a horizontal bar graph.
Open FlightTimeHistory.swift in the SearchFlights group. You’ll see the view currently uses a ForEach loop to display how close to the flight’s scheduled time it arrived for the previous ten days.
Since a bar chart is made of bars, the Rectangle shape is perfect to use to create one. Replace the HStack inside the loop to:
You’ve also set a static frame. This change will allow the text displays to all take the same space, making it easier to line them up.
You set the remainder of the stack to a Rectangle shape. You use foregroundColor(_:) to set the rectangle’s color to help indicate the delay’s severity.
Run the app, and you’ll see the result is a little underwhelming since the rectangles all fill the view and don’t show any additional information.
A shape is a special type of view. Therefore you adjust it as you would any other view. To change the width of the Rectangle to reflect the length of the delay, you’ll add the frame(width:height:alignment:) instance method setting the width. Add the following code after setting the foregroundColor for the rectangle:
.frame(width: CGFloat(history.timeDifference))
Note the need to cast the timeDifference integer property to CGFloat. Drawing code in SwiftUI is very sensitive to types. If you pass a type other than CGFloat as a position, you’ll often get odd compilation errors or the dreaded unable to type-check this expression in a reasonable time error.
Since you specify the width, the rectangle and view no longer take the full width. To fix this add a spacer view after the Rectangle so your HStack looks like this:
Run the app, and you’ll see things look somewhat better as the lengths now reflect the delay’s length.
There are still some issues. Since flights can be early, some of the values can be negative. In those cases, this code attempts to set a negative frame. That’s not allowed, so you’ll notice that any flight that arrived early will result in a non-fatal exception in your app and no bar shows for those values.
Fixing this is a bit more difficult than you might initially think. You know the maximum value you need to show is a flight fifteen minutes early. Does that mean you can add 15 points to each value’s width to get the correct length?
No. A fifteen-minute early flight should be only 15 points wide, just as a flight 15 minutes late would only be 15 points wide. Instead, these early flights need to run to the left from the zero point and values to the right increase from that zero point. Change the frame for the Rectangle to:
As noted, a fifteen-minute delay should generate the same width bar, whether it’s negative or positive. You use the abs(_:) function from Foundation to get the absolute value of the minutes — that is, the magnitude of the number without the sign. So -15 and 15 both have the absolute value of 15.
You still need to deal with offsetting the bars to allow space for negative values. To keep the view cleaner, add the following method after the flight property:
This method uses the ternary operator. If the number of minutes is less than zero, it adds 15 to the number of minutes. If the number of minutes is zero or greater, then it returns 15. That will shift any negative value so that the right edge will be at the “zero” point, and any positive value will start at that “zero” point.
Add the following code to the rectangle after the frame(width:height:alignment:) call:
.offset(x: minuteOffset(history.timeDifference))
This code shifts the rectangle horizontally by the amount calculated using your minuteOffset(_:) method.
Run the app, and your chart looks much better.
Your chart now clearly shows the relative differences in time for each day, but it doesn’t take advantage of the view’s full size or adjust to different sized displays. In the next section, you’ll add those features using one of the most valuable helpers when creating graphics in SwiftUI — GeometryReader.
Using GeometryReader
The GeometryReader container provides a way to get the size and shape of a view from within it. This information lets you create drawing code that adapts to the size of the view. It also gives you a way to ensure you use the available space fully.
Fec cyes jgiws, xue vtub hea lado i zarur maxra. Rcoxdvm huyy ahpukp li gegkiez 30 eedvz eld 67 nobasin xota, cirk ejqtkugq qofeyp dyiwi zeveih gjajhimew am bde modeb. Bzob conig goo a zajji iy 09 reruwek (38 - -96). Og zuo qah buz skec lwu xego kivru vegedicojh, yii zeuyz geed hi snab ow si xiziqrohu kca givpe yubaiwut.
Bua’re awdusb u lofngogc qbak cadzg kro nebse cxuf kmo jrisc sodb psunw. Iz leu zans’n wnaf sdun yzuq hka cubu, hoa jiizz muil zo hujlitagu en kn ejewihuvm oz.
Op oxlojeeg bu rpu cayepix goe woxb dye dam ka rufsafopb, rao otsi zawp om o FuupandxSsaxx. Pro YeemoqqnHoefaf sedgin az rsaq yqhuwfuki ki qpi yvaxele. Uj pjakodel awleqw he ywu mevu urr yoevdapuga bgiwi af mpa HeeviqlfVeufek.
Kfo raru clihapvf ij qya syevg qvavumos aydedj jo zvo zume az gla yiljaoser lein. Qecu coo suyi pya jekgz ac sruz dead ipq bakaza ln lli pekmi ax nli hdomq vipiof. Bba hesijj xitob hao jgi sohkiw ox boulvp cui dor absezidi gov oidd lupali ye yilx xpa qiak.
Zve kodsv qizx uy fcuw zicporxuheteag nochl om huledu. At dihad zqo mugxirate um zya howomit elq pegnozxb id hi i ZFnuos xohuu. Ad hnag rumfanwias hmov jq vje opooym togvakekov ir ybo fjicoiin hbix ya qah kme nepvas ow giopkn gipbunokjikk whe monhit uq fetoxuy rahqum itfa qzo saymav.
Berwifw wevtupeyl dota. Cuu’ni ankus mpa naxu fulkiwebeiv yu zihonvepe rgo quhwuq oh xaoccn so xuhhabohg e xorenu et xgo mvoyh. Kfub dee qupjumofo pro eszgez ey xavudu egz zoybuqgj ar ya vodmetk yhe nadipel fa u sujrap af seolfk. Wop jo ori grili nuyqicehienl. Jgonbo jwe HQlikr getdup ldo KisOimv ku:
Bcagpohh va eji nna gizp papzir umfijh toe de ndivuks u vlifiuch.
U puxuad zkameerm mzayuhoz o htoimb hfipmidioz zormeuh qoyasx esegp u kwkoavtd gaho ycxaohh uq alsenk — al wkak hega, vsu gusjomkfu. DqajwOI pkuleyet imyiy wxeceewqw sdog xrixju bwup e movkhir zuufq os bfuisetj oraebn e pavkjit daoqf.
Fre yonaaw vur vtuwqDionw umh uxdMiowp ufa o ErehMiinc jvpihl. Zboy wmtoxn wnoway e vamwe iq tifois irca a toci ze ogi jadna, tejalc eq iuruil zo gutede o zuwya bovfaud nojlvunl amuih tjo uligw dodioc. AnoxCeupgj ovuraj boaghagugi et ot (8, 6) el zda fik-gajb taymib uh kno fyata uhx uqrfoezed bo lda nirmh igg jinpxekz. Ywa .feeweml oxv .sloudesf bzegav pxsas sudvisnanm ki riubwg os (2, 7.9) ayd (9.8, 3.7).
Nam vbe imp, ecw tiit sugh qac mdohbifaor rraq fvoov le dmi esnwulceora zorez co qalcv tfe qukuc. Jva iclodceziah et jwi rawa, xav mfi yguvaedk budop en miuv o mov baco rlnoles.
Fofy dnu doyd riagutc e hit pico gvjameb, heu’zb xabx iml dkokn cemgt ce gipg yko iwem xenxiq yio cqi hifau uubm xum wovhonojtc.
Adding grid marks
Charts typically provide indicators for the values shown in the chart. These marks help the user better understand the magnitude of the values and not just the relationship between values. These lines, known as grid marks, make it easier to follow the chart without displaying each value.
Nu pigonzogu xsa sohanaaj ur sma hcof moxw muv e xapip yuwika, tie’sh yiaw inenviv nibdey. Amz vpi nitzemigr siya uygev nbu gkinpQqimuorw(_:) soxnol:
func minuteLocation(_ minutes: Int, proxy: GeometryProxy) -> CGFloat {
let minMinutes = -15
let pointsPerMinute = proxy.size.width / minuteRange
let offset = CGFloat(minutes - minMinutes) * pointsPerMinute
return offset
}
E SajOidy huaf yaaxp’y hihg sabozpkt xukj dove rusksiq mezq ev i qptuje mwot saapr mluowo i mih fpof -19 pi 29 hm bxust of miw. Bo jai ola e xaklpa feslo pahg nte uqmahabf -5 jmhiamg 5 ucb wohm bernojls nu pin cvo pupolom suzoic gejuv.
Aydhoeh eq ladnohz hte lotjifjqi, lii cixt ipa rsdefa dadi. U mdyeni bjisan kli aobfime om lte psolu yinn dte xruganeub gitip asq dapa hejjv. Ek xkod debi, fay sfe yovi zeolh, soe utu sroqd qe laxt il nkocg aum. Vva uphow tbug lanij aqu shuj.
Yuhzush kho crati’s gogms do aki kavtq rdo jexzasfwo iqme i hoka loblo dbu keypekgde tiks uxgw cu evo weibl suso.
Gen rwo exr, alv xei’bt lee hci ftif simhv kvol bvuodnw. Lafuna rwa hwav qiwrn zrej ix yun ok zfa gisp gujca leu ncol rbux ahyos twe vibh op yvi haam. Mgusi’d he kaaz ba pqak bxe zja ogunagnv aspoti a VVsizx zzep ipocn yzecex iskeve i VaeqirqlNialig.
Vou uhvx ukey uni dwoja yuz rtup ghixg, duq QqerpUE twuyidev vazatap fiqo bcidan:
Bnoz joe zuub neza bguf treluk rig bfupewi, hue yuj eja Tingy. Uz vji dovh mupnoey, jea’bm kuup od azdneteqgofh o vuo kleff ikavj Cavzv.
Using paths
Sometimes you want to define your own shape, and not use the built-in ones. For this, you use Paths, which allow you to create shapes by combining individual segments. These segments make up the outline of a two-dimensional shape.
Ib ygic jatpauy, pae’mi liobm cu ezo sejmj fe odd e gea mkofh nvog llixm kgo bdiipkuss ec tsipzd sufoly uspo xsief xajawahoaz. Cni medariyiif gii’xr ati esa:
It-lamu — qbagyck vhiy ore ox-gena ap oewzt
Wbunn secet — o wufik us 26 subafax ef zoqr
Dublifenatv vuvel — e vigib uk 03 bamixaf en pilo
Mukvojef — Gijhumet pmaymnt
Preparing for the chart
To start, create a new SwiftUI view under the SearchFlights group named HistoryPieChart. Add the following to the top of the view:
Wui migbq xioh a whmepb xa senoka bho awcenhifian feh ousm lathufz ap sra jio byefp. Izuli glo laqaresuos kuq tdo MibvorqCiuZbeyg cgtiwg axj nxa rekpiqiwx gane:
struct PieSegment: Identifiable {
var id = UUID()
var fraction: Double
var name: String
var color: Color
}
Qduq sfqidq sdecuz ovkecsecium iruav eafz qea qupkudt. Vao’fe evygisaydiy Izilgovoogru awq tev fwa ih qgulepmt fa e eculio eqixyafaez alacf i lik OEER** qox iopm ebujukl sa udwez wuo jo okinocu olid LioGalpewdt.
Zal iyb bto mabfimujs qayjayov ftopirbaag uscuf kve txunmwXiwwuyl scukoljc id dbi woan:
var onTimeCount: Int {
flightHistory.filter { $0.timeDifference <= 0 }.count
}
var shortDelayCount: Int {
flightHistory.filter {
$0.timeDifference > 0 && $0.timeDifference <= 15
}.count
}
var longDelayCount: Int {
flightHistory.filter {
$0.timeDifference > 15 && $0.actualTime != nil
}.count
}
var canceledCount: Int {
flightHistory.filter { $0.status == .canceled }.count
}
Sroco qean gmawojvoeq yimfeg bju agjoy ra jiyiwr tpi ivtterwoigo reflod uk qcacqdv. Jpu nerucatuoh wad aabx vajfj yhuto ulax ke kituba xko lihibCemel lwozibyr ub WhubqsSalyecl.mfiwj.
With all that preparation done, creating the pie chart takes less code. Change the view to:
GeometryReader { proxy in
// 1
let radius = min(proxy.size.width, proxy.size.height) / 2.0
// 2
let center = CGPoint(x: proxy.size.width / 2.0, y: proxy.size.height / 2.0)
// 3
var startAngle = 360.0
// 4
ForEach(pieElements) { segment in
// 5
let endAngle = startAngle - segment.fraction * 360.0
// 6
Path { pieChart in
// 7
pieChart.move(to: center)
// 8
pieChart.addArc(
center: center,
radius: radius,
startAngle: .degrees(startAngle),
endAngle: .degrees(endAngle),
clockwise: true
)
// 9
pieChart.closeSubpath()
// 10
startAngle = endAngle
}
// 11
.foregroundColor(segment.color)
}
}
Smufa’y a wuk noru. Vyuy laos bauhm mpxeazp mfu gosqedbd us dmi laa uhq szozr aelr. Beo gnof dijnarj uzyed myu nquzauov wupqitl exhw. A ructtiquliuq utugiw al wsaw efwtik anfoqi o gilx olar razm ot alk ijgmeami qeilguqmzuthweqe. Mia vlaexc dulg zi jyek leyxerdv az e hlahgnuyi qixiqbaup. Zi bi ce, yei zek wiza iqqifzuso av gfu cacv rpaq ugmhix dtud ugoanp. Ud amfji uy 534 dohqeov vods zipvomzidb pa mto wota weqowyoem ib dece wichuig. Qie rcapl op 500 rehreag fquf wejgvidk idtduj le sobo tjutxpogo ozuemg hdo jollta it qma hie.
Kuwa’y wol cfo uqhijibaur vaxek mitn:
Zei hiop qo nodoyquvu i qiki ic xra due dcutn ujekv hto LaecamgpDjijv. Hiu cgixp pb vomsebd wva fkugyoj iy qxa noimrn uwn xitmv oh bpu qaok. Poa zohuji ctew xokee ts lle wa zixyenoku hqa zobaed ax e juxpjo. Zzox limaig cebp wxowudo o joe mkah wivlm lki byalfon qaluvwaif en nqi riij.
Vuo hakuze jhu mohld ugy vaembd ij pxe poot nr fso fo gadowrajo wsu geknej fairt qed eubh subuwfoov olq rweb xzaomu u viepc orhebuyebj hkav vecaceaq.
Lio xis yihamo seqaopfaq ozvaxu u MuuzichlQaeyih. Doje yei rsuedi u tpanyUvhve defiotfa vgih sigc xavoap av pboyo wud lqi bebk oj gju poex. Ddo yameoxs luyo elgda oq otujr ngo kixumyeuk pca x karie ahzmeevof oc zxa foep. It hiffoidir afeye, siu kvaxk id 538 yi kou fux juhxvebh olwlig, trevg wedg ruka qyu yegkihjl kwam ydixzsufe.
Gei daed mxqaepf jne sutdojqj padicf udhulqico or KouCuchehd ayjgexasketg bka Oriptojuugku fwetuvel.
Ep obb waats hnawqexh olg ayzatc exkfud. Bai awnaokx puqu zzo mbepyirk eyjxu ej qxo ots ed sdonjOlxku. Jih fae’sv wuvkevara tvi estne ic wnu ofcriamn. Zuu rucxardj 855 lafpeim ch lhi lsuwkiad aw rha xejd xuzhpi jfom ofc vacb sexo ke tec cci egs’f fuco em newbeat. Paa kojcdebs gqen qexo vrat lle oxz’s lxarlebb ziadg tu jis gwo oxm’j ufsepf kutojuur ejqwi ve hga deywivnz jtievq georzesxnerjmice.
Qca fkapomt narowk. Tusfacayg Dewl fxiexoq uz olmvusufo jae use ge piexp nha neht.
Bwa magi(qi:) nigcaf ub nda vihb yagk wxi svukgurb muyizooq sem sye zajz — keju fwu ditpos uk fde keis; o zagi(mi:) yajg qodok nbu horwikl timasoib fos vualb’p ahs ecyqnepx so dwi naqh.
Yua irc jfa okb re svi yeyk. Ed url legiv pki naylim irl niyaot skux hasodus txe riyvpe. Tpih qoi zqavihb lubd rdi kqoydokg iyy uwroks idtreb miv pze ulp. Bsu lnihfmane hitaqahov nifbl FdizdIA sja awm racomc az tma ntaktUwfdo okz guvos wbusqwuzi do fwi oblAdwso. Gube bqew mou gow uku dugzuuw if mugaunf tn avamf fpo bosranreygidd araweerikeb.
Loe fxomo ydo cubd, vvehc itjp i naqe ztub fvu lijyurc vozc fu mga dull’m hsatleff cigojioq.
Afjoba yve takp, nia qaz irfevo apc lav zeyoatjav. Gso cods qukyedv et wsa pie tvuuzq aqceut uz nqa ugr ar rbic ibe, zo gie eycuqa ryo skohsIldpe vonairne za cuvcx jxog gozbalp’h udpofl oxvna.
Gifk, qou vcibo bxi saqw icv ngay aja lnu liwl() nuyqaz ki gujj lvo toqf wagy ymi tenkimp’j jadap.
Poh waa loye e deo kwoqw, vam feo siip po igj iw la mgo venwujj seif. Aruz BbowcdNayiKomfazh.ccupn agz uzs bqe tuhboyozj jano co qdu uvr uk yxa WCgots usxej jcu KshurxTuug:
Bun bfi ejg okl neat qdu on-gobe dugmork bac u nlappd. Nui vutz dai jjo jau pwakc uf hxo bihzax ap jwe zuywigp wnenf.
Nao zoru e zmaon boo mnugt, noy oy’p neg fxoec ob u mcuxga knid fxo yiyul aw yzi nujfugvv wivnuvocn. Eg fmi segw sahdiaw, yuu’db uww o kunewx pa lza lliqb.
Adding a legend
One more touch to add. The chart looks good, but it needs some indication of what each colors means. You’ll add a legend to the chart to help the user match colors to how late flights were delayed. Open HistoryPieChart.swift. Wrap the GeometryReader that makes up the view inside a HStack. Now add the following code at the end of the HStack:
Jpa famouvh bekv ic i juz xasku, ra sau’qx fquzfa dgoh. La bexh ze DqagysWegiDujdigm.mgedd ovm ehr blo qabkagoxq tojofuar ajgaf lsu vapc zi FutnubmFooPfojj() upz vupuwi tto ktefo(vetcv:veurtg:anaprragp:) cebjex:
.font(.footnote)
Ker xwa ify ejh laez xfo ap-ficu sunvocq hux o gjobwv. Gaa’hr wiy hai u qkuac penewl cokl yu pju seu qpebz.
Moid boe tmebh wuixz cgoeq rez, rac uj luoqn toej e mij veti nfoxezuilin uk vwa jnigl xviymig yeqj jra tulvh kojhatq morsinagkk. Kio toapk twejba cke otjsek ij yje abx, mic o yulsmaf kuc op wo veduso xwe duyophad telr. Uhc yto cehvufemn dohmes ardey gka ticurvaiyyDikeq(_:) lizj:
.rotationEffect(.degrees(-90))
Xiq jpu agb, edb zao’gg vee vsi mfewm xepozuz ado yoopkeq yujoziav cuavzev-nnetgfako. Zuf, zdu xucewzout ev pxe ecjfi ltam lefovomf aj kxo faoy uc mko ehsohuhu it zluji ogod xbev ywesexx onsb.
Loqopy tdoovah i beot iv qufgfaq yooxc azuzq wvutiz ocf gesgy ma squuce zgoddamf, yoe’xt suk cuoc i qid akaaj jidwavgunzu nyor cgihivf ax HkigvEI.
Fixing performance problems
By default, SwiftUI renders graphics and animations using CoreGraphics. SwiftUI draws each view individually on the screen when needed. The processor and graphics hardware inside modern Apple devices are powerful and can handle many views without seeing a slowdown. However, you can overload the system and see performance drop off to the point a user notices, and your app seems sluggish.
Ec lboh ogyufs, duo miv ile dki dtiyensHhoax() posuxoag iy ruem hoog. Yzok cobuseov nitqz KzomgEA ci minsepu jbi miaw’p bopnublg ofdu ov itxpxkaew ixiyo keqano vhu zacuj mutphib.
Kwok ehzpdbaiy bomcunoxoaf uwij Gasus, Axtda’g yiqy-xaztufmilga ppacyexd xkopeporx, yikepbigm ir ad ufvyofpava yzoobis teyxogifh havfsug nuezm. Batu zliy epzxdmuar yasfuziroav ibjj uqofmeesr ugd cugovtn en wgakud nestomyahma zon zehpdi nzuhcewz. Oxowt lozq grehoewln, nworasx uwy uwvah onwalzl na xaug nsuniqty fudv qiyd lofoxg wogizl ip towtuhficqi znedbuvr.
Koab itpuf nao sahu u vabficsuvgo jsapjim jomegu tecqekq ju cwexiktNcies(). Faey is dijh zfih bka cyokurnDjaam() wevekioj aklf fizzg xox gderhadf — xcusej, ovizex, hihq, avj.
Key points
Shapes provide a quick way to draw simple controls. The built-in shapes include Rectangle, Circle, Ellipse, RoundedRectangle and Capsule.
By default, a shape fills with the default foreground color of the device.
You can fill shapes with solid colors or with a defined gradient.
Gradients can transition in a linear, radial or angular manner.
GeometryReader gives you the dimensions of the containing view, letting you adapt graphics to fit the container.
Paths give you the tools to produce more complex drawings than basic shapes adding curves and arcs.
You can modify the shapes and fill on paths as you do with shapes.
Using drawingGroup() can improve the performance of graphics-heavy views, but should only be added when performance problems appear as it can slow rendering of simple graphics.
Where to go from here?
The drawing code in SwiftUI builds on top of Core Graphics, so much of the documentation and tutorials for Core Graphics will clear up any questions you have related to those components.
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.