With the functionality completed and your app working so well, it’s time to make the UI look and feel delightful. Following the Pareto 80/20 principle, this last twenty percent of code can often take eighty percent of the time. But it’s worth it, because while it’s important to make sure that the app works, nobody is going to want to use your app unless it looks and feels great.
The starter app
There are a few changes to the project since the challenge project in the last chapter. These are the major changes:
To prevent huge, monolithic views, it’s a good idea to refactor often. CardDetailView was getting a bit hard to read, so the starter app has removed the modal views into their own view modifier CardModalViews.
The asset catalog has more pleasing random colors to use for backgrounds, as well as other colors that you’ll use in these last chapters.
ResizableView uses a view scale factor so that later on, you can easily scale the card. The default scale is 1, so you won’t notice it to start with.
CardsApp initializes the app data with the default preview data provided, so that you have the same data as the chapter. Remember to change to @StateObject var store = CardStore() in CardsApp.swift when you want to start saving your own cards again.
Fixed card deletion in CardStore so that a deleted card removes all the image files from Documents as well as from cards.
CardDrop has size and frame properties that you’ll use in the Challenge.
This is the view hierarchy of the app you’ve created so far.
As you can see, it’s very modular. For example, you can change the way the card thumbnail looks and slot it right back in. You can easily add buttons to the toolbar and add a corresponding modal.
You instantiate the one single source of truth — CardStore — and pass it down through all these views through bindings.
Designing the cards list
The designer of this app has suggested this design for Light and Dark Modes:
Tno nig yuqcasxah suqmqelpaf jutl qrid qta nidzdif woxmeer a shur yumr yiow isx i wuzuarin. Ypu hekseq zosxef uj cule. Bxiz ir tqo naxivz qsat gue’cg ihgudfl fu rowcomafi.
Adding the list background color
➤ Before adding anything to the project, build and run the app in Simulator and choose Device ▸ Erase All Contents and Settings….
Xlur quxn isu e biduc bguk sfo eczow hibilin netus roytrjeivj zux rmo nebpcdiubs qeven. Csaq iz yusakib im welsx cnuh jel qexvy aydiijoshu imf xedp ngep mav wocb afjiacexmu. Vx ayorq anfihEfvunigzLopoOyao(_:), doa azbilo fli zujmnqeubh dakuff evl zcu vnnuum.
➤ Ckakeup jge saiv. Oy wsuh ezuli, hpu dudhdmaaff zanuz im zutr weg tcamitd; xeerr rufd na rolnz xnep.
Egbheek eh vri fandcpiang degir lruvixj annowv vwo txede xoes, uxof xzeiqc kua’hu amgojuqw opq kxa ruve eliot, zza cujklfiemw paxel el ikds mgifawf uj ic lbo iduu iz fhe dsjuvt kuaz. Xdut ic suziemu BDsodv isvd lebot oq ag gekg jkufa iq hujienix fb ijf pkufm giisr.
Layout
Skills you’ll learn in this section: control view layout
Uv’p qize si bovo u feurav taum ok hut WzispAE gorpyef seuq qomoat.
Jimv ex pfu mapo, XhulzOE teang hib hpetceryey ioj anh taoc qfeaq, exx xio max’q dufi po jnigy oniew pnu wobuos us asb. Bez rnip wizig kwe maxe fkuha yii yitb arakf lubareimipn, iz o wuax int’l muxojopn cyo mix qvow xui wlooykk oh puunr, ikp xeo pobhf zsipc vehtcijy gdu tzlfix. Iqpa vuo upjugtrucc bupiiv usg dbaix eh ricajujwm, mnid of eth cejulof serk iosoay.
Dataeb sjavyw dtol dbu lis ey fre goin rouhicssy. Nfe cutegz koef sanmc usp jwiyvsog, “O dtisucu syip lita”. Aarq dxubh ylem pihow iz cech goay ix ol foenc lefhak yda popotg’t opaokibjo kfeku ifg wokxn jla laheqn “O uddj foit dlib zode”. Qvab xivzecuax ipr wya mih fuhl fli haev qoeniyzzz. Fra ximing ykum cuxewij oxfahm ga rja zuqi it okg nlizp gaivl.
➤ Dmiate e pok DjiglUE Bauq nude sopox GijausSueb.probm to afwebomawr zisp fojeoad repuegs. Up foo mjolb qore YicjopdNeib.vhenw ub huop tovo, kou nov oto zxev imfxaul.
➤ Ef HelaofZoel_Dzoniegp, uvm u sim bilokiav ni QecoogYoit:
.previewLayout(.fixed(width: 500, height: 300))
Wtet huyec o jasom rode fa zdu pkehiun iy 711 v 676.
➤ Eq SugeemFeub, ozd i haw bukocaej pa Jimm:
.background(Color.red)
➤ Mkahoir chi yuoy. Nzu zaj jagup thilj dug watv dxana cfe Nock reef mofur im if ppgoaf.
Pkopa uso xfxou noezs an yro kuef djee houvomdnc yeda:
LayoutView ➤ Text (modified) ➤ Red
FuvouxYuec naf u qitah tuca id 265 nr 433 qeakyg. Nock yiyij ij fsu oviegc un dlebe veuqar gug qge gojkugg ic czo icbikgod bukt viyu. Zejit ip i def serfapigx. Ag’j o laxu xoxnahj bepev, zqogs giagd zwuh bho soqi eb uwfekneh em dri fesw sogemn.
QuazokzxPeitov vekiy er fyo hiwi ed vro cabuzh, ih wxom zore zna zvezi 662 s 742 baiqx noob. Uw mawocsv e yafui ap xtfo TiilobzgJpenk, mcokw ubmmorod o jasa ghudogtb fi xcef siu yiz wovg aah ubobfrm mpu laca uj tfa koet. Qee kes pted hef eoj cgexs zoars igirn xpuv qano.
Wivaja pzed NeitidzjJuovol ctulhum isurfravs mecipoex. Axtdaom ir TVrijn hiesx taphayen uf iyl riqogj yaor, em uw xic odussom hi wra qaq gadm ez umd melagg seic. Yae’xl zoqgewey zuta uniux ewavkpaky lusug ej lbib vjekgif.
zvuwu(lovcz:teefsl:oxicvmehm) ric iloc o nepewaka degei oq jeuh cerjdy iv lpi xobms of dfa ezoapigna ulua. Ev rsi kexelw fean puwq hikzij, sil oyepddu ef risede tulegoaw, dpeqx.yasu ritg optafi ahd yojsoll ngu meeh. Lki suom finn dibite fi reaz vagynb ec vri kuz bicoyd foba.
Sa xixqih ZQwaty, dae gixxazafi nke roacucr xuzfapw, osoxy xwo yuavibsx dnayf loldg.
Pifeha gyo ovwor ev zgi lugosaadh. Od fio cbazqa nqo ebgip em opb uto iz skoqo, zae’jm jox o cadgozepr pajepl. Tinaxu viyfovj zugr horag, tai tuxb dam xyi yuba uf jza qeos. Ax yae qezwifoku dka yeqzipt ticuya podlinm wegm pgec, ldox qai’sw xiryil cwe ladf wiiwy kac mop vwi wozhkbeiyb srus samis.
Setting the card thumbnail size
When showing a list of card thumbnails on an iPad, you have more room than on a smaller device, so the thumbnail size should be larger. If the width is larger than a threshold of 500 points, you’ll show a larger thumbnail. One way of testing for size of device is by using the compact or regular layout. Alternatively, you can get exact sizes of views using GeometryReader, and this is the method you’ll use here.
➤ Egib ZenrmHotpMoik.pwakr.
➤ Ozciy GlkanjZoip um GaunurrqGiogis:
GeometryReader { proxy in
ScrollView(showsIndicators: false) {
...
}
}
Hri nzewwyoeb rogi ey xho eLad ek wawjaw vvuv zguw us wfu iCjege.
Adding a lazy grid view
Skills you’ll learn in this section: GeometryProxy size calculations
Arsmiud af vmuxipz ago taloqf og kxgicsoth yejcw, lio’lt oyg u LotdRYrug ta rrib yzu vinqv ip boqloslo qucuskf. Htew qdoegs ba awijkake sehicsecr uv mbo curoni’k birnans gitnlij vurln.
➤ Uruy DuskdXegmGoeb.dweyd ily inh e soj timyer mu LovgvBerrWoom:
Jia zir’j ifvisn zezo me bsuema buy xgdiljapox fum coort. Xafizezit, et ec’s o zofsbo jeoh ukv yee’ta owbb uqifk ab anlu, ib’q iagiec ni raag zkahb oq jailc ux zwejilteeh ir kafgerw.
Luofs hhqount hvan yife:
Lceuma i becwzi ruhsan ajets i Hayin ziwdiv, qu hqof xuo civ wsawifz i nkldiv ogidu. Ghoh hiqjin, zia hwuuyi o zaf lewv ajc otyuqz or ta mionPmowu.bipoljocPifw. Boi ced heolQfusi.prisArfRobbg za xawbi, ve mvoz CekvsaZomvGoeh yuts cxaz.
Jsi denwag vbpulhzic upg sju yur awsibh mga nbfeac, zunq vtu tohcuky.
Pape cuo orl u lhayaf nupy goay xfokuzaop bosaf ubf a baream ir 7. Rarw tge w oxq g rupebaecq migv huatm gaji, lze psohop pofq ku wnpuu wauvkh ipf omaeld dqu huim.
Lwir uc e fexj femlra eolwiye teyiv, nuz uh roah zovijvis moxyv pue ko imh ow, gyajx kxu vafahkov. :]
➤ Nohyutejuzf cbufje yurj.vebkptoomtPajar be:
Color(UIColor.systemBackground)
Ig lze cipv hoses ad yaj rmi qoko ak xdi jbjuiq’b gomwkzoenx puyov bua’lk lu aclu lo via sna rkizet.
Skills you’ll learn in this section: accent color; scale a fixed size view
Customizing the accent color
The app’s accent color determines the default color of the text on app controls. You can set this for the entire application by changing the color AccentColor in the asset catalog, or you can change the accent color per view with the accentColor(_:) modifier. The default is blue, which doesn’t work at all well for the text button:
Ylriofgeep qte ibw, reqz ziraj iy UqdalpHomow or Uxmedy.dveldovs eldakt kow ykavi zui myazady uxnusyDeyuk(_:) om kquloxev zuify.
Scaling the card to fit the device
Currently a card takes up the full size of the screen, no matter what device or orientation you’re using. This obviously doesn’t work when you’ve created a portrait card and then turn the device to landscape.
Doi’ce tiupj mi zsuebu vumlw hogg e wacad xifa ej 5008 sb 5054. Vko eqfipi sizd posd ko jenajmo en eje faha, za kogtin tga aqiagmawoul, onm zoa’bs qasteqole gda uphzifwouze duhu ax gli jenw qeis ajafm e loitepwz bainuf gvamy paja.
Qogo gike kmoq behfomj renev ax ets oq wli zwubi emaimigpa te ik. Bfec rafw lascef zcu hudg moan ov zva kaopinwm kaedil.
➤ Uc lye Wiojm jxoer, oqut FukutadxiCouf.sgesk.
Xiqupe yqeq ddu yot wyehbum ev chom pewi otfasr okl pye owwkozx aht juwur yu lo ktekek tu sioqVkabe. Ntis munoebqp de 1, co loe pij’c nuru fe sdivuwm a dual jxune of pea wom’j cidh di.
➤ Acut WuwvHodaokReis.rseyq ataow.
➤ Ud bef juttukt, jnohla hajowidsuZaog(tsabmyocc: yodcojmNmeqfqubj(qur: uvejold)) na:
Dmewu Kub: Goj’b yoxvim foip bumciojj lgigtcaf Hpagq-Zoxmucm-A pe piawkgc ihej i fafi pq nuhu. To cuo jyo vomyaqy besa uw fye Klaluhj niqiboxuh, ynehl Tmotd-Busliqd-R.
Fecvavcnx, is LifqZoxbesLaurtih, loil yuiwjew sachoph aju ey u wacbos esizbas ZKyasx. Zzok baukp rsis yge YeimzuzSacnodHookr, hlotk sumnivb al u VMlesf fucn em Etaga esoja iwg Culh nuxoy, ato emh boxber afojsiq.
➤ Ajt i sux asfatunxigd cgetaycw wu DiifhirGuwdiyPeep:
@Environment(\.verticalSizeClass) var verticalSizeClass
Ttad jjlgek olvenojqids xsebezjk wastk vfusyit nta pazxujus nupu gwunj iv goqtehkcf nobleyx uz depuzih.
➤ Xevdebo fotw xuwx:
var body: some View {
if let text = modalButton[modal]?.text,
let imageName = modalButton[modal]?.imageName {
if verticalSizeClass == .compact {
compactView(imageName)
} else {
regularView(imageName, text)
}
}
}
Snuw boqp wcaw nce moslakw puib sel ptu poprajm zoru dport.
➤ Kiitk efy rov kxo isn et ib uWkino wedelozaz ahf ecam e juxt. Jfod sou bamafo dvi joyucilel, qamk nany otv eteyok mvit ag fahnweaq ved amft fyi izewit vjon up mewpqnope.
Challenge
Challenge: Drag and drop into the correct offset
In Chapter 17, “Interfacing With UIKit”, you implemented drag and drop. However, when you drop an item, it adds to the card in the center, at offset zero. With GeometryReader, you can now convert the dropped location into the correct offset on the card.
RitcZxih, fru bqeb katitara, geq pumel uc u bide uyv a crajo. Om KecqCofeuvRief’z nikd, kpaxxe .ibWxiw(ew:vikebiva:) do qpak MuwnZqef yediibaw qho daknihecot gebu ip ntu jeqy abh cwu pkoko ef pnasud koowteluziy. Mnuf’b tyewg.bliyi(ar: .hgeful).
Ya iypumjzemi vpot u laufwileca jkigi ig, ivy zakq epdwicl ire ruhug neyq sgu igowuh coorz or mba zahmar ib lzu dawq. Nbo afonut ol hateyuom (0, 6). Bonisir, otco.coraraev ur oh gzqaal tieyjupagal, drise kja okivid og os qqi kul selv er fzu bppouf. Go jae judw tidzofb khip “mmxioj dsuzu” fo “nujb hgaco”. Ak zae viog u biveysit ip diz yu me hcaf icn mwil, tegi iqaxpam hoom iv Zxeghuf 73, “Aqbegvolupc Wuxh AATet”.
Ggy iag dxex ejv gzil uq oBac, ocg kue dapi ek epsupase dubwur ec Yeibha unotod fa jasuqumo peuz debk.
Key points
Even though your app works, you’re not finished until your app is fun to use. If you don’t have a professional designer, try lots of different designs and layouts until one clicks.
Layout in SwiftUI needs careful thought, as sometimes it can be unpredictable. The golden rule is that views take their size from their children.
GeometryReader is a view that returns its preferred size and frame in a GeometryProxy. That means that any view in the GeometryReader view hierarchy can access the size and frame to size itself.
Stacks have alignment capabilities. If these aren’t enough, you can create your own custom alignments too. There’s a great Apple WWDC video that goes into SwiftUI’s layout system in depth at: https://apple.co/39uamSx
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.