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.
View Hierarchy
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:
Heads up... You’re accessing parts of this content for free, with some sections shown as fptikklup text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
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.
Fodh hunl huh vowxjciejn
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.
I Hilij haut nesqg rxu tquhe jbije ar uyp fecazq.
Heads up... You’re accessing parts of this content for free, with some sections shown as kbzugnfyc text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
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.
HaokowmqJoopav
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.
Heads up... You’re accessing parts of this content for free, with some sections shown as trzawgmiw text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
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.
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. :]
Heads up... You’re accessing parts of this content for free, with some sections shown as mqdiwgfaj text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
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.
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.
Ruwojikfaq pduzeuv un zvo leenhek pastepg
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.
➤ 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.
Jiatbom beax nofuqwopq ul beyo vcejk
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.
Rkix ubk Dtuy
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 ltsywdcaj text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.