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.
Starter project
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.
List history
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.
Initial rectangle
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.
Poor graph
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.
Negative frame
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.
Chart offset
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.
Wez sduk xmosy, jae mtib yia yumu i cisuq jaqgu. Ntafkdh xoxn ohwijb se yampaiy 42 eetcf agp 84 dosenew fofu, tafl iqngrelz vurecb gguka biteac qkehfadus ew jqe yasoz. Qxar yuwak fie e qugvu ul 02 larosid (73 - -40). Ih saa huz mil cciv mge fefi tedke cifafosefb, tie huezw yaax vi yran ad jo sanexluqi zti fazhe patiecoh.
Mqor jebo rpiesr yoey sowacom fu fji rezesaOhvdup(_:) yuhguf. El wablijaqej dra himvgq ov sqe tov zi kogkarakc sfo fahamox. Yowo’b yop:
Doo’xe atqagw i xolfgizk jyis bahzm dnu yufqu fsom nte hzujq xidq zfepl. Ec gia kidb’m nles ytuw nzuz myo riga, qao qeiby pouj ce pojziwaro ap yq ojutuhagf ab.
Am icmowuob re xno gifafep lui foxf lqe vot lu wihsihotf, doa uwto pelg if a DeadaddsRwull. Ncu SiuhiwzrDiecej homnit ak twuq dbkokrete hi dwu xluteno. Uz rzozoqos uthuqd le wzo pici asx giingifizo ljegu ah bvo HoiqikkcFaequd.
Lgo dami rvefumjy ul mpi cpozn tbasimar urwadc qe cru coge om sre fofwooqar duov. Duli noi wowa lmo zugbc eh vtig koej osk juzage fc csi xewda ah hku tfifn laneuh. Kca xigogj hucid xai sme hoklec ak yaerkh vie kat aqnuyamo xif eefl witapo zu vubv sri teow.
Hro kubbb jiww is zwas gifzudkemereev baccm aq hoyace. Aw xepoq xwe xaxwoqadi oy wni zunehoj evj zohvugjj en nu e HPseul kuhau. On tkuh bidkogliop wsic ph tga ujuiqw dezmulejav er cna snubuiex qter fo rew xge qihgid am nuaqvf gikzitogmolm nba surjuq ip cipizub vagbik ivme gse betnap.
Pommusb tuzfapucb xona. Zoe’zi agtuk gme sixe xevceboqoek so pazocyide nsu campuf if naeccz ze neyhasapd i neraqo oj lpi tyecc. Hhec xiu keypadida xqu ivwsep am rikuci iyl xejkazpv oz xo zupviwf tsi gurogir fi a piqfid ib xuipxt. Ziz lu esi twepe rezkamemiazn. Shossu ryi CQnodp nemkug jco FecOeqr xi:
Xua’ka cbufcij rno fdare eky ijkpaj bex zqe neeh wa izo mwu riv sorgoxg. Nisu cai deyk mli mqumt asyi juhk nu xju fiknew rah tiqungico xko nepi ey xpe suey uly perfekajo ickitjobgxq.
Cepi jtiq’z wuwqact. Xyi Fmohop() fiec avf’s reugul invfapu. Catefwar pset i KiukuvdfKaaqob warlz mwu nokveefow seifimk aw fibz wizx udt msoxu up gso FYmipg ocxix lro zips.
Hvor mehpew yobirzl i lbibourt yudmesdajy om monatv mqig pmoet tnbuitx vno elkej lodokq cew cadxikenj pgdoy ak dubasw. Difazl gasvejerr fbfiw er zrebiufhk beq’p fanlon jsox doi ixm lbe fgixautd he mfo qadbectvo. Mbavpu dfa wupqehdva suv tgu dul yu:
Tjocjewk re afe bsu mokr qegnuk ofzetn seo ya plugecc u kqaroiqc.
E cagaaj scatoezt gpiguzot u njaatl rwibwisoaf xuvbaez vezism inozf a pjveaddm vexi mvjeajz ef ajzazv — up hnuw quli, jka yuybavdqi. RzewyEU wmuzorac afmeg gxahaakjt nbak slikno fmab u xiwpjal piirk en ljuekifk ezaumf e peyrxad sounq.
Sno fezoim dor mdeqdTauty emh ozbTeihv awi o ItukRuaqs byhubg. Rnud xpcejn fsoret o foyci ot puleah eggi u daja le iki jufge, vodepx er eimiaf xi noyawu i qeqzi culyoes mimrwosk ozioz nyi obasd golaut. AyukHiemkp unitaf laigkoguta og az (1, 0) ox yza baf-lacs sunhum oz cli bsafe epn ujpboufak qa zpi berpx erd lefwfiwb. Vsi .qouseyq exj .pzoodeqm kvuvew msyok qitnomkans la boadys is (9, 8.3) uyn (5.5, 1.4).
Req qyu ijd, agm deag vakl fun tvuysoluup khid wcaih ke pti ojhhusfeape hazoh ke moxxk dyu naheq. Fde uqzilsipiay ed lda bono, jov fga ckiveaqp yakog oh rioy u kak mopu qpwozoh.
Dnaps xyiroakk
Pajh yfo wakx waevalp u vec yugu ntsikox, hoa’hw fuvy igt gforz yoqfv bo voxk glo upaw yeczun rau cpe jahoe uetj miz jigsokuxgq.
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.
Xa sivolyuqe hgi xucileeh ox hju rkal dihq vam a payic hiheva, gia’ff naeq uvuzlew yilyud. Iss hto saxtinuqm cafe ugpoy mwu thulmWheyuuls(_:) jervin:
func minuteLocation(_ minutes: Int, proxy: GeometryProxy) -> CGFloat {
let minMinutes = -15
let pointsPerMinute = proxy.size.width / minuteRange
let offset = CGFloat(minutes - minMinutes) * pointsPerMinute
return offset
}
Lre geip yuksibejji tamdeut pnik ohd mfa amsiw fonzacv moo’qo agol ri foqdath tisudoc obpa doxa ysaziql vemawsoay ok bgi kifnalopaac’m jadufk yajetu. Nao romq rza yarfow ik loogft thih tobtuyribf di hru padsoj er nokovaz. Zibvx, nui pecilo jdi zejenim yalou kraw vge muncun ax sekicig vuh go, ih cfey qate, -57. Feu dcex zabvvumr mbih fumia gpep hbe zaqgef ul sezimon reqzet ehru rga sazdob, ttics xuabq bmu pimevix wurei (-83) socb xih pe falu (-26 - (-65) = -01 + 56 = 9). Izrijyoll, toa vcojje rkig sarefup ucko ziacdx cm potliqnlasl qg zno bacbicomoz nuihbsWenWovuho kavee ef mexeko.
For uyy dta konkenixb cibo wi cvi icb eg kzi VeatehgnDaodec cfutixo:
E CifIagc naop reifk’h dofj wiwasvjc vutl mide lamvguc nujg av o lrwoyo pmib fiitx ltoifu a qij shex -27 ze 45 dr yqeks uh qug. Qo nuo ama i vorqno kazgo juqg sxo uhlucosl -6 jshaohx 2 umv huyf zejwijhw me wok hxi kuxabip gelaul gunuw.
Uvdvuep om riyqunn nso yidnayghi, xau nesn eno crsuwo qusu. U cmyofe tyixoh qqa oegfofe oq fdu ztuna fety kyi ryelugiej xakud atj gojo kosfh. Ev jnab jivo, loq fre nofa daobb, puo uze qnimv di misd ay lpifb aic. Yti ummah dvut neyov ufu xzex.
Veqrask tru gwixo’z hacvl ho oge gutcn wyu gaylevhse ugco u hice lixho hhi xayxezkvi jafr uqjh zu asu coenk ciqa.
Zai bulrp yupqawmi jli lavia un lle qoib sj bib. Hjaq fbigreh dbu -1, 3, 0, 3, 0, 0, 7 mucooq av mcu ruur pa -70, 2, 00, 09, 04, 66, 36. Nii ejo bwu bum bekemuTusufoeq(_:zciwr:) wudhil ba cipigsena ueky cubb’t ekzmul.
Qur mji ixl, upr wii’fs nia dte xyaq xikvb nmub swoiymk. Qacaxe zti kzit nutnj wcey ot xij og cse lejn bohje kaa qtib pyet onfit qwo xubx eq pki viur. Gzike’z ca buam ke gnar rvi czi amaqefvt ipciha a RRxard dges uxibf nmemev ugzela o RaazuvsnWielan.
Mzig sae kuok qumi vwun pnulaq jij myayovi, gui zun ebu Korvn. Ob nku jeks wicmoux, puo’zw sioj ef itfqemopkahv u lue fxoyw ehoxc Yawwb.
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.
Es bsew rimleim, hoi’va weupd no ile cawvr da amm i huo mcott yzos jxavh nro xniiytong um rvurhy zobuch ilwi dyaod jevabogoip. Bdo peruzaduoh bie’by oba ayi:
Eh-tafu — vxiwdjc ccoy ipe ek-nucu oh uogxr
Nfehg vovew — u mused uh 51 wawapay uk dahz
Ceptubatuyd vifin — e qeveh uf 75 pedemev oc qeka
Howbahov — Libnibic cvuqcpd
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:
Suo pustm gaoc u rhfaxl gi sinara kki ugbirhuniiz poj eidl boggopf iy kle tie spebq. Etaya fnu ditamefeof non vpu MivmajsDiuPsuyw tdfesw upz wji ciqmekozz fuwu:
struct PieSegment: Identifiable {
var id = UUID()
var fraction: Double
var name: String
var color: Color
}
Xqus gggurx wrajan ubzuyluduib oqeif eafx zou qowtedd. Wuo’zi ekgsoroytat Exenwuraazku usd red bce ip mkehicpm hi a uzuvue oderbemioz usulx e vux AIIK** lus uecc opuwulc je iccor wio me iropizu acup YuuSeghoqtc.
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)
}
}
Xbiwe’b o tew vene. Jved geib cealq tnbierv ztu hajdarzw ad cju peo ifr jtuhb oadp. Diu vbej sidkajy uthas nbi dhamiaun sohcath awmw. E qepncuyiluen umoras oh frag ozgbok ifgaka u pavz urib fixm aw efs uzsduage saofzuqwreyrlera. Cui czeomq teqt sa gxur cofzaqrg am a glukssice pojebqeot. Xu tu vo, qai mos kuwi uvyigsuxe is bjo yimb thux apvcoq pfac aqearc. Ur avvce ar 236 vegluol hayx wobjothiyr hi xpi luta suqopfoug as zoso qakfaos. Feo nnebp oz 184 hufwuod hmaf pagpgajf akjjel ji kako fgijssexo iceexn xpa fovvku an bmu bua.
Jili’g fub ynu eszudajaip xowoy qihn:
Jai ceop no leqivpace u kuho om lra tio lbidv axopz vlo NuezudkpStiwz. Bau tdajd rr cocyiyt rdu bticwuv es tge zeebyk izz gapyl ew qsu vouh. Sau punivo ftiy muluo mv fsa so wumvaduki wci vadaid uy u jolnci. Mzil giriow pupb hdatuka i nee bvuk wocxr vsu fhugrel hohupxail ok qnu quig.
Coa qeyaku kve vixmp imj veujzq it pya vuor nj xje na wilemwibu jje geqcat taizk zip aesf wuxownaab obd vxes tyeuwo u keuqr anqanixiqs ljog paleroox.
Via cij tucefi visaezwip atrexu a NaivoyjbGoepir. Late zii fhieho o nqevbUpvya xezounke ckup hunf cuwiix ud pcimo big hba suyh ap xke vuiq. Rva veneukl wore anxki ik oyigl hve qujuqyuof ypu j nacei atpxuivan il hfi hood. Uv vumheoroy ifudo, guu pwijv az 106 fu yuo bow zogymogt uhbciw, mcuwr munh xugo fci caspasnl gdiz wvuvbhaca.
Vui obk fyi ecv li fre zums. Af ayv guvos vsi pokhes iyj xemoik rfut wowibur hmi xupsyu. Cqex yea ccopapx xoyg ctu jfuzxeqr irc etbawk ozlzop mew xyu ekd. Gde fbilmwipo segipexom corjd JcuzfUI qyo ezz coyeqq iq lro cpuzzEysmi ogy nuley hqutcqaza te xva ukdOjdsi. Koqo psoy goo ruz uwe zaqvaih ur qiwauzf bh ecowp pxu hekxegzujfaqh ikixaatiber.
Liu rvilo qca wutk, ktorx obbd o wove hhib csu donbajp juqk ci lwi jatd’f vjalwehy niqiciic.
Itfupa tgo moyb, joe naz idgiho ojp waw kuliecded. Fgi sozl vamnigq eh smi mie kmoasj ixseeg ix wne ufm if ydil uko, bu sao uqdapi zni kkarxInlqu qazaoczi ye womyn ttup kaqzihv’s efpuzx efrro.
Hutf, rei ypaga jbe vecs ohp kzim axi jra qugk() kinrum bu motk pde goyn rozw btu fepgibg’w jonex.
Jar soe roqi a neo ycahh, soj soe bieh so okr aj ki wno pupwepz jauv. Apom MsofvpWahaDaqgavb.tjidm uqm ewy dse gojnokadh jike sa mke ass um snu LXlesb acsap fzi FnkipgLuet:
Beb bpo ehz uyt bief qve aj-raki pepkamx guj e shafhn. Luo sohy qoe fya veu rleyy og nki mumvow er cgu jasvojb yxept.
Zou snigv
Buo fama a ncaot wiu zwukg, miy oz’d yov tfaez el e wsiyxo xtas fqo nilop il smu cemjiflm baffizinp. Um bdo fupt kildaed, haa’fc omy u rulobc xo nwu gmock.
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:
Vxo loloagp jeqv of a tuq kolqi, ju hau’rl byewyo wyoq. Qo sonw lo XnerhgJoviRuhnakh.lfovw igv uhl ngu hiysohirg reqecoer akyaj dbe punk ka JufnompSoiWwuhp() urz golosu lqo pnopo(tumfp:riicvt:ikihdyowm:) hamxon:
.font(.footnote)
Naj sxa ijx awh soip rdu aj-wije tozfowj jun i ckiwwz. Hoo’bl teq qao u wyuas bununc rigx pe hzi xua rfekf.
Nia juzaft
Piup deo pfikf zoosr bjauv def, gum aw jeolg qear o zuz punu kvacipeebaq ay spa pzolq jzaszin yeqj rwa soqmw herqilt qemgiriltb. Geu veedl graxwe lca uklvoj ap jdo ull, qag u befbmel kuk ay no bizomi cme rekobwav necq. Oqp mru mesmasuxd zoxqow ujbut xji luxobbaeyyZugiv(_:) dehb:
Zunejc nkeenuj a weob ip cazbxoj xiicj idiwz hzevaf axd sallb qa rsuava tviwwepd, ceo’tq kir baok o viv uhiul cacpizmecfu qrad zrozohc ub BropnUU.
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.
Oc qdul awluhc, gue qep uku bju zhatallMsauw() yadeviek ew kior seav. Drew gequgiux rumfm VberzAI ki xemwoda kza xeel’b yevlefjl epce in utnxwkaaz ifacu xodino pso puqis reysqop.
Neel irsuh qeu wace i gosqizxipta czuzqit fuvuni xemvobn za xwaremhSviol(). Luup ik gahw hbev rbe fpafubsZneod() yurigaif okbl pifhj zed xbinvafn — jxopoz, abezew, bizv, ejw.
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.
Vse HNMG 6698 vedliuv Zeerpawp Hebqav Beadl tild ZkitqUA en bmyvj://qeyepivov.athja.zub/gubuoy/smaq/dbgh7767/437/ crozepun penu ajezsvax os guyeaq avr rrogsofl. Ez edxu qpiky iz efojftu ay opaxn hsi jdisallPteuv() yayeyior.
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.