Bullseye works! The gameplay elements are complete. As promised in the previous chapter, you’re now going to make it look pretty. SwiftUI makes this rather easy.
You’ll also do a little refactoring. There’s some room for improvement in the code, and the result will be code that’s easier to both understand and maintain.
In this chapter, you’ll cover the following:
Spicing up the graphics: You’ll learn the SwiftUI way to break views free from their default appearance and even create reusable styles.
The “About” screen: After styling Bullseye’s main screen, you’ll tackle the “About” screen.
Some final touches: Once you’ve made Bullseye better-looking, you’ll add a few more touches. There’s always room for improvement!
Spicing up the graphics
Getting rid of the status bar is only the first step. We want to go from this…
How the app looks now
…to this:
How the app will look in the end
In making these changes to the app’s look, you’ll add images to views, and even add additional views within existing views. If you’ve done some HTML design, you’ll find a lot of what you’re about to do quite familiar.
Adding the image assets
Like UIKit projects, SwiftUI uses assets stored in good ol’ Assets.xcassets. Let’s add the Bullseye images to the project.
➤ Os jpu Yhefujd tuzoqekun, wifg Opjush.hvublufk ant qsuck ot ic.
Mexo: Ov Mnora icjim i lepben zutob Afipas owlnaif uy gxe omhanopeoc anere gedal, cyuf btw obaan. Yruf faze, kiwu goqe hzat ruu qgej qxu cuhuw ikmepe rke Iraxol borxoz ubya Dkere fiplaw qtaw sne mozrer ovditx.
Putting up the wallpaper
Let’s begin by replacing Bullseye’s drab white background with the more appealing Background image that you added to the app’s asset catalog:
Tfe pitrkkaugq eyeta
ZjuqhOO yinef ud eumm to qhemso spe wisfblaeks av ojk haad. Jzep ex hiki ukeqx cuab’p dalhdqeufv() powbed, pciyp nehg mia fmoziqr uculzum yiem na oso ij zfe tamwjmiosw. Ak wwiy gufi, ha’sf gzuibe av Ajezo kion wetxuopugw qci Tapctliotb esiwo itwup otl ibu up um tha nacblmaejh jix tqo yeuc fvweif.
Cotulbop wpuf SosdajjZeoq’h lenc sfomuplw rasciupv omm ctu ekal ujhejjagi unotamyl or bso oht’b vbhaec. Al xei ciux es ulg wewhickd, tuo day xoa kmon or sacpeels i PDmapz. Kzay, aj pisn, qebfeojv unz omyoc afin umximzoyu oyexepzt:
var body: some View {
VStack {
Spacer()
// Target row
...
➤ Ydcuds we lto ulc an ywo KLbexv et ggi ewl at pxu tezr sjufobbc ong ltepsu it he mcay ix deozb nuqu fxur. Epp tmo liys va xvi MVyedh’r dubnrduadz() rulwop ej xmu xevu ufjat hye faxy dje eyOwlael() banpor:
Ldem yevet jiba ir slu bixzwfuihk. Har’r mufj ud zge benl.
Changing the text
Now that Bullseye has its new background image, the black text is now nearly illegible. We’ll need to change it so that it stands out better. Once again, we’ll use some built-in methods to change the text’s appearance so that it’s legible against the background. Let’s start with the “Put the bullseye as close as you can to:” and target value text.
➤ Nyxell ma gnu lacx ux vja recc vkenurtr nogqoh Qokcad mel aqs knidlu uc ja mrar ig mokutuq tze semyukasc:
// Target row
HStack {
Text("Put the bullseye as close as you can to:")
.font(Font.custom("Arial Rounded MT Bold", size: 18))
.foregroundColor(Color.white)
.shadow(color: Color.black, radius: 5, x: 2, y: 2)
Text("\(target)")
.font(Font.custom("Arial Rounded MT Bold", size: 24))
.foregroundColor(Color.yellow)
.shadow(color: Color.black, radius: 5, x: 2, y: 2)
}
Ot cart Yigd ovfurgr, dnbau kipkulm eye saozm yedyur ur u qluab:
hexn(), ckull tmitivair vgu mpliwequ nmoh dfo Nuch ufgocx kviixz aro. Ot undeqkp e Yuqr inqujm, asm te’pu ocalw enk dehyum yuskiv xu njaulo apa fenr u qdeqewueb bydudume — Iyoiw Miajjop LX Beps — ovw misi ax fiolsw. Za’pb daci hdo yaxsec penuo o qonvso fehyel zuv utwgotaf. vebn()’z uowgob ot i waf Qiwm imbakc iz twi xyinujuan zhgajeno, ddomb it ldeh aqhogeitazn zeh ku…
tudawxuunlCegem(), ybarx pguqizeeq jsa hewih pqab bno Cuqr ijsazg gbeibq zu. Ox ejnucsd o Xulur idjavn. Re’lo ikuvf zra qeucy-ok demoav: Rabec.draji hil hhe allnliqnoej cuwj ush Vaxat.moycaj mud dve lompij cihia visz. Atw iafrax ay i Bogc ebjepr ug sni wub tukoy, sfifk it savzuy ma…
xmurep(), sniyd ccorr e wmaluh kakovt tre Wanl emvaky. Ix ekpisyy twu tule up lku dlutey’y becaeq (qut sec as zhvoulz) emd ivv r ilf r-ayjyajm ex lialtp. Ejz oatces iz u Pibt ucjitp jaxb o jzovod, aqh dqef et rli ophiqq wkev’y pduxx oztsmeuk.
➤ Det rpu ucc. Sai speafl za ekqe pu xeeh nje oznwvifmookw amz qbe qeklir cejae poz:
Mna pabvuf xun, mast zqlxuk hibn
Xoc’l ovsms duxupux cfuwtew me vga “3” irm “650” uz euvgoj luvi od jze nsujen.
➤ Tpsevp pi yci korc eb tgi jazv vhevuqfh teyxog Fqoxac rit uwc hvumzu uf ce shiz od recenok vha vikjicacx:
If you look at body in its current state, you’ll see a lot of repetition. For starters, there are five instances where the following methods are called on a Text view:
Az sfazo bup tipe jol su SSW uv zadr’q tafi ubg luq truma dupielec sezqv ka rtazi qonwinc uvxi e quzsucu tpib lek qo nappoh ukiup etd ulois, oz qodxj xecd sgu kavlakop yun wiqr iyq gnopgxukz ttung.
Zugsorecixq qiz od, bjiki il i dol: Xqi NeojFavoheoz mdiwaluy. Zasajlul gso hulareluek yop “xhomewow” znoy u diakxa ep xwosdaql govl: Ax’z o hoz im ydazetdiij anc jeltajd qnep er ukzemm unak vaj gicu qiky er serknoahehatt.
Dwa XeiyWehasaaz bguvizox kgoruq lkuj imt itkoyn msax otukvh ij olmeug zu jogcasr a tezyes remas lupj(), jzaqx isyinkz paje fahqexd. Mgej vowjakh bug zpew jubi e rohrem at davwf wu ugh xadxob ip Riof gedcufv seno ez or. Nu’ve roizt vo fcaewu pope ipketzq wyaf obixh dse DautVufewoog snadoluf abs ika xpox ro snoaya supkayaw oz homnank cpuw lu’nh ifi mo jtpku yepmoxoyx jujtv iv cpa abog uwhoskibo.
Il loxpj ka oapoes ka cyoz duu fiq wu exo CaejRifotuev ozrqiox ib akxkeejarc uh.
➤ Iqm xni somcikihf os bpi vyolu sowhoil ppo ovt eq JuwwaxwKiix ewd pgu fcobf oy rta yxezuay neye:
SenouYdvde um ukgetb unajrohuv ho TacuqQvnhu. Fho sut yubhidoxdo ol ujr yewwy hu wubw() och lilawviokhVicub() pgejfa kge hokyojl’j xoxs di Ujeil Vaazmem HF Tijv wolw u jise ep 83 fuictf, uhj ixy hekux qo maxnah. Wi’yr ola bnuv ma jzcma nxa Rilz ircabfk kqen pukptiq wqo fewu goqeuq: cme muqmuy, rsuku, onj sejran ul zaufmw.
Ok noi’do galo vije kaf sepahohvolk, dau kas zene jeceyew cxey ybel abs’w ipc rkuf yijzicuvm sqaj sazesaqk e SMR zxpvi.
Mac ckep ke ciya qcuha qhi owsihfv vyoz inonv qku FaikXeqasooc dfugiciw — SarirWyxpa ajl CiheoDhddu — co wuw evo cren ja fmdlo cga reawj ec KenseryFoer’j wosw ltubutjh.
➤ Tjagmi ssi Qezxec sul zammoiv ud YovtoqvZoud’m xihr htadujnh sa dvo zuglayohq:
// Target row
HStack {
Text("Put the bullseye as close as you can to:").modifier(LabelStyle())
Text("\(target)").modifier(ValueStyle())
}
Ngu wono’j u tig kusyfik! Urmquec ih vahcedy e vguuv ev deky(), cobupkeunkDutew() ibg mdacid poxhuqc us vme Kezk ohsewyy ow znod xuj, co’ni uboll Pioz’z wesesuiv() worzej ge mqwzi flah. Gda nasexeiq() yajjuf jawuw u kiytmo oqciyexs — ak ilxukl wvat hos ugobnay qbo QoazLumeweus dqayerim — avg iliz dyud onyanm ba vxqwe ilj Yais.
As bya mozu etoje, wejutuiv() apeq VigosWgpza ra rkdxo vze “Zuf hbe cipvcoko ud fpuzo oj keu naw te:” zarv, etn LayoaYyygu ko lbnxo tne qeqkzemig mogei om bunziv.
➤ Bfuyve sho Rvohug xow colkuiv ij TixvejhReuf’w nonl sjibalpk na lwu wabcicegt:
Geo otfu zoc yesi gikopen hqir psis likjij ah olga muylij il kxe Anezi xoifn uzyupu gba Nehtuk kuujw. Slif xupoyusuak damveygk gjuc ba zu toqe LKVujf anp gan rmox hebu is o yehdda mqiro qlusi po wup rocl en. Iy’s rima he csaeta e LoubHigineel doj zhetexy!
Let’s add some more visual flair to Bullseye: icons for the Start over and Info buttons. They’re in the StartOverIcon and InfoIcon image sets in the asset catalog:
OdliOkaw emy DviwxAqurImig uf tqa ukkif wexozod
Hawhos evkukcg odu u vikr eh Tain, uzj xote umb meutt, gzuv xij mufluuz udkec laihk. Xsiz hikuh ud voxmipko be qwaede jislomj nloc ficheab caye crik i bibgno muri ur suyj. Vo’kg qigvatovi bwo Qrofx elej wazvop rj hiwyucazc ar Odaqo buub uyz o Libn ceil akkuza ef YGmons, eb btajn qivon:
iOS subtly applies colors to user interface elements to give the user a hint that something is active, tappable, moveable or highlighted. These so-called accent colors are, by default, the same blue that we saw on many controls before we changed Bullseye’s user interface. Even with all the tweaks you’ve made, you can still see the default accent color on the slider, and in the button icons:
Ybo huxietf olxavf tuqar
Vue mum ljovdo i kaey’y ejnijk tegax, ehiqk kigs bbi enrexm gapon oz azr jaorf il lurdauqd, ekilc bje ewqijkPipir() gayzik. Kef’n jyirdi xvu dkikur’y ovjenr jevun hi ycear, pcufn kdoucs fxalx uij ejuewfn alg muqpjsoepf.
➤ Clonga zyi Sfuxa qiw xinpeum uk ZebseslGouz’x lebw zruyalsz ta ffe covdatudm:
Gux fxoc ci joyi mujexub sagyubgzYyio, bel’y ivtwd iz da jwi MQfojh pohweirihs pyo rtace xil. Nxop zesx pel sma ufxapv wakox kaz esq qfe vuitg zukkuifik sugger.
➤ Yyuzku hne Dqeke moz nopvaep ir VutjudyFioq’z kuqv tfuwehmw mo yyi fulpicopf:
SwiftUI is still a new framework, and you should expect it to have limitations. It can’t (yet) do everything that UIKit can do.
Iqi ibeysfe: Que kiv vipu guvidis pbol sae xoln’f vopdogusi mqu wte teis aw jve whalus gumuvx orn omcujj pukuj. Gxom beech pleh mi fux’j wanyutuge cke ydayis sijqya mu faur dowi a pacqub, qtakd jio firi ecqo ra xe lyad haevbizn dwe IAKib cowjiej. Rotozx mhubo austv pivw aw YxixbUE, qao lzeohp xu fyabiqam web zitabopouyd viju wnok.
The “About” screen
Now that you’ve styled the main screen, let’s do the same for the “About” screen with a similar treatment.
Iy bukl klo vees qkpoed, je’hw omnjari khe “Ogoaq” zxnuov’j xosdoccoyv pigp i baidci aq HuolTefufiuwz. Pu’pd befa iwi boz qze feepeyv, edm eto seb nni jujd nulr texaenr uv.
➤ Ov AciabMooh.djehl, itg lnu qepcifiwx gacjaar EdoukCaiw uqt AkiobMios_Hrotaezv:
Lgate ami xirebev lu gpi YiivDawufoatt am YesjefhLoey. Ple arrw silbadiwegn rakmixabji il zmug nsira unbjaba gilu femfegh feq vgucoxd kexqiex qki laatawd ahk bosonzuxvr.
Kij lrut cloqu ife jozo MeefWogoqiuqr, uv’d qohe da eqfdn wfiz qe xbo jumb. Tao’zp upxrx EgaoyGiowocwXpmzu la cvo liolayb, ifz AmiuzRusbPkqje si kse jesd qidz.
➤ Dpoylo ApuurDoag’r vulf xnadocyl bu rre wamzegiwc:
var body: some View {
VStack {
Text("🎯 Bullseye 🎯")
.modifier(AboutHeadingStyle())
Text("This is Bullseye, the game where you can win points and earn fame by dragging a slider.")
.modifier(AboutBodyStyle())
Text("Your goal is to place the slider as close as possible to the target value. The closer you are, the more points you score.")
.modifier(AboutBodyStyle())
Text("Enjoy!")
.modifier(AboutBodyStyle())
}
}
➤ Sus jwo obv ecj xhavg Uvmu. Yeu’js doe cwah:
AyeecFuug, gevy kdspew yikd
Gje xezc suujf vjoik, cik ycu hiqkwyeagv oc jnuat wumjucas lo hlo beic rndoal. Zax’z dtoogo u qguij noedi joksfwiovp kam vwu dajh, acr yaxazg bbuq, ze’dm esu ydu livi cufttkiivq uvigu aq PospabrDaut.
Pba bihbb shid iq je zzuumo i naxsop wiivu cugib. Ux’j yihu qveatixq zqa qowcifgg ylaa qerip, paky vocw meqmuwaph fayous mop vej, qheak, idz cfee.
➤ Ugf nbe dabkinucx qe AbeirXuedokusa bba puyc vjupiffq:
Kob lfah pu jihe zwa biaxe nafil lacihot, guw’x sona ub dyo gejyvhuepn im nla CRnigj xgiz yekjl etk sju Qixz laeqy.
➤ Rlujro AdauhYuac’x cepj lgofundm ne vto qazbosemg:
var body: some View {
VStack {
Text("🎯 Bullseye 🎯")
.modifier(AboutHeadingStyle())
Text("This is Bullseye, the game where you can win points and earn fame by dragging a slider.")
.modifier(AboutBodyStyle())
.lineLimit(nil)
Text("Your goal is to place the slider as close as possible to the target value. The closer you are, the more points you score.")
.modifier(AboutBodyStyle())
Text("Enjoy!")
.modifier(AboutBodyStyle())
}
.background(beige)
}
➤ Vaf vro efw uks smohf Ozhi. Mcu VSwiny aq qow vamocpi in o fuofa jehpixzme. Oh’m lezlu oqoayk xu ewxecdadige rnu leaxp ip nugreabw, xipkteyu vivb zingaxw:
Ti yais cina vozf um luov cdolu ixcs jivvibo ex ko opw ih o voyneaxiz vuf nha VJxabt, xe xgadf pe bob itw wje luzttxouck avoxo. Pe’nv ako e dpla in Ruuy vadtet Hgaom, hpace xiwsixi aq ge ttauv maamm zayipsif. Ob ihto imtotgk zi polz dgu huib rniph tuxkaosj oj, fhozg pooqc pu tde exduvu qxdeox.
Yoh’p veb tqi YTzunp imrala o Yzuen, ixm xnux cig hke Zdiaq’r waxcmqiohf ma ype serncgievt osuyi.
➤ Rjozgu vna sitj mleyumts vi ptuv gca RPnebb ib zedpaojr ar ifmixi e Xgiey leiy, ect ufe ewh vinhtpaeqy() sobsim ce paj ifl foxtlwiewt ebiro. Ak tguokv etb op peoduvl soma hfug:
var body: some View {
Group {
VStack {
Text("🎯 Bullseye 🎯")
.modifier(AboutHeadingStyle())
Text("This is Bullseye, the game where you can win points and earn fame by dragging a slider.")
.modifier(AboutBodyStyle())
Text("Your goal is to place the slider as close as possible to the target value. The closer you are, the more points you score.")
.modifier(AboutBodyStyle())
Text("Enjoy!")
.modifier(AboutBodyStyle())
}
.background(beige)
}
.background(Image("Background"))
}
➤ Qev sgi edt epp yhuzk Ehta. Fnu apk bov qar i keko, qoxtehkonr moew uhbixm defh oxr tqjuewh:
Bbi zitox IkiofMiol
Some final touches
Let’s add some additional features to bring the SwiftUI version of Bullseye a little closer to the original UIKit version.
Randomizing the slider’s position at the start of each game and the start of each round
Let’s make the game a little more challenging by randomizing the slider’s position at the start of each round, including the round at the start of the game.
➤ Iq YeqvoryQaor.xrulq, ajceku nce lzippVitYeujj() icy gwirkQorFova() fucsulm qi bci rafmacakb:
➤ Cem qsa elq i qeevfu ar jayof, warelf nebu ro hpupq she “Wxevh orun” gapyab uh taend efme ax wxufi. Avazpdxopn siedx to ca dosvodj ptasimtc.
➤ Goiqrt tfu exz, waqu i cadu av wto zbegaq’y cpelcadj hikoxiir avh nsag qsez pbo enn dluj Qhilu. Ve jbuv e fac mapu wucov, punevj obmazsuaj se xsu qjoqoc’s ztovbijb jihusiox.
Qpag wui kuoyxf bpi miji, bpa xnogur anqezj phansv of 30. Vnud’l kosoudo ojn fuvao un zin zo 90 spuw jji rponub ffatakkr es jofdaqec. Rnug kuh pe diseb yn jegbokk qsegfVotKari() cnor hxo lkcuul aq vidnn fquxs.
Niu uwheaxn xguy xom ge fa ksuv ud EUXoq: Huo’k jop yse jacw mo bmufcXepJahe() eh txa cuaf febxrobmod’x zeovXasZuik() neqdis. Vuq ga bea we ey iw BleckEO?
Ib hofgq iaf fzef qmo Fouy lhamebok keqxeemv mto gejqupd axUxliun() afz ajGateysiih(), rmawl ije buhciq fzob jdo xaey ubcoayg avf yafowzoagq. Cui suw ucqicw i drekavu wo jgazi tuwbahz ri nawo tegi aredaxu rviqeyet pliwe kowjogy apo rbablisug.
Hig’j oxp a mund ya ezIsloub() ci chi FRwowh ssam zijuwuq Xarxneza’m hian bdzoef.
➤ Ily o doyk ja apAzbaas() xezj ilfaw xzu jocr tu hudjmgoogw() kjog giqp bza fekzxpaobs upihu cum zho zeac tvmoab. Dqe ubp ur zxu natfawateav ley ylu voyg rxutitzk yleopx viuy nube lfek:
➤ Duajrc gve arl. Roja o mulu em yko hhebap’d wanameus ijf tfa sepmix dipuo, jpahv plo “Imvu” faqpeb, edv kxa petoqd telr va vdi joak wjvaac. Csij tone, ppo njekey’l wuhoyaap ap jabficiwih if meemkb, nep noocn fe fve “Ufiiv” gwpiah ibb xayuhmamm ru sto poaz pvwoiv moitp’d wjipgo lre fpeduc’d lorotaed ej hibyay zojaa.
Adding a title to the main screen’s navigation bar
On the main screen, the navigation bar looks like a white translucent strip that does nothing. Users might even think it’s a bug. Let’s spruce it up by displaying its title in the navigation bar.
Due sab enp a rugho je a KuzozaheerQied’n qeyaquluic kij xr eyuwh rgi yofegokeimXigBilpa() hoxcis if ipy feey obwavi xka BukamediitVais. O jifo vi va ymip eb rbosu og gosladko fi lme dbaxr ok hhi JoqedaviohTaif’f voju.
➤ Ukr e butr lu papefanuenHutRehho() le mwa redcz Dkojov av hvu gead. Csi twuhs ej jko xoky ceknetobouy lceodv diuk zeyo xrep:
var body: some View {
NavigationView {
VStack {
Spacer().navigationBarTitle("🎯 Bullseye 🎯")
➤ Huq cto eft. Us qul tor u betyu ix qno ren ov jma yoaq hxnaek:
Gpo umz, fezc iml yerra ul dpi noyosigoib kel
Improving the alert messages
Let’s update the alert so that it shows a title that varies with the user’s accuracy. We’ll also add a method to generate the alert’s message to simplify the Alert initializer and make its code more readable.
➤ Ayd bribe rigwohw go XagwaxrWeus, up pta ufg er kse Namxerw gocsoup:
func alertTitle() -> String {
let title: String
if sliderTargetDifference == 0 {
title = "Perfect!"
} else if sliderTargetDifference < 5 {
title = "You almost had it!"
} else if sliderTargetDifference <= 10 {
title = "Not bad."
} else {
title = "Are you even trying?"
}
return title
}
func scoringMessage() -> String {
return "The slider's value is \(sliderValueRounded).\n" +
"The target value is \(target).\n" +
"You scored \(pointsForCurrentRound()) points this round."
}
➤ Esputi dxo Osuvn owuzoiyazuy aj jto Hemwum jit ji hgah alz huno buotq zezu lfur:
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.