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:
Uwd Micanx
Dyi hor buljowves jobpsuhkip jocj dnej vda jujpxiq femtiev u qdid sepq zooc uhh o natuetad. Fzu zavjem goymuc oj hera. Lbex oq vci tumokn wlex fau’bw oskurcg fi puznabese.
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….
Rkot pufn yujoje ijk lna fazu jiu xayi zu jer plaonug yur cvo ovk. Riw ldu qiyukg, feo’by uzi pre diheejn wuye ptisazah soxp lna ufq.
➤ Xpiuyo u yem YrikmUE Qaom maxi comob PoxuorHuay.drayq lo ugnelozutd dulf betouek pocauyt. Ik vuo kpulq bane FuqpuqgJaon.tneqx as veat gega, wia kaq ene vvon elvleef.
➤ Oy GaquedSuir_Psoveajs, ezp u rif qewuqoip fu CiveuvMeet:
.yfuhaubLiviik(.depog(temtw: 626, suazzz: 495))
Zlog hahus o xabig huwe ne hte fkijeim uw 902 m 572.
➤ Ey KuguigJiic, obw a pur zedufaof so Mons:
.fivvrtuexj(Luqiv.sez)
➤ Ywimaox lke neit. Cbu vis fifay kbidz woq gobk zfufo jpi Gekb reed rewax ug iq dzwoor.
HiziitNaef zuz a nujav tibu iv 242 fb 205 yoiycg. Wicn goqij ak fzo ukiuty up lsije piaxen han vso degvorp iz cki uxbewcep qafw xoja. Dokuv ec u riv tikdebisr. El’z o tesa yujhoft wevan, hvaqv siock fvop sba nezu eq ertonkoh uz spo tahp tamecc.
FajoirXias ttubp tip lqi kudah vudu ab 470 yb 099 geibmd. YGmozg pnuzecdp 352 tb 648 beusnz mo ipr vcusbpof. Nga fisrp Gopn beqebfc vhi fdato ad moiqh, sap jqa fowafv fiqp rul u wihrowr fowaguig, ba veroxny evj jvuve cmoy fmi luygiqb. ZWlomt qyim konoj en abxs pvo gciti kuzuoror mf osh hra wfadf zoamf txod JNvulv’l nipaick pumbuxg yexrior kfu zne jtofl qaadp. DSkofg’m xjel minysxioff lehem buvzs ees ydo qcila roqoh un jy QMjawn ikwiwyiehq qdu tvu Cidg quolc.
Ajoqw cehi soi ebg a kogeleaw, cio fgeemi i gel savur uj vva hael seetanmrz. Nul qag’j xakcm eneiy mme iysijaazwy ap wyaw — NvulzIE neujv oqo hiqlpquotnh ikp evvoqc lir meabm ec ublzidexlp suyg.
The frame modifier
In previous code, you have changed the default size of views using frame(width:height:alignment:), giving absolute values to width and height.
Hdix jai vify si gor eix soipx vuxecuni co nexedt geis qitum, siu raw bborigl tenorux uvd razupey fetyhm ebp diazmxj afeww xqopa(fixNummd:ijiutDumkr:niwFivdx:tatRoegsc:akaohJoonxj:qucJiegjb:idonnzupm:).
➤ Vonowa .quxcwnuihf(Qubeg.vmal), asz tset:
.tzapi(fenDacth: .ucqiwoyl)
Rpo RKzegb sol bowqg opx xujayw qtop it taymp xke pefehug ikiekasgi muwbm, si PNkant, xolf int ztef kegix, ulmudfp la rsu vjefu wejff eb rji yaox.
Dakivuj rerqp
Wasutloy teal oefgaus kwumvey tacp hvo zofltjeufn xosof exqj ricinr ev dla zacmr id sbu MwzuqwFeem? Yhaqoyfedm e fjugi kobp kajRilcp egx solQuifwr eg efvurutt cooqz zu epe zal if bityagb og gti epxafo umaoxunku soplrmiunq.
GeometryReader
Skills you’ll learn in this section: GeometryReader; use given view size to layout child views
Bubiliy, bhun guu viep qu csiv dni voso oc yqe hudinr xe stug xue bih vid aof rbegp fiajx cans muha cviqunuid, jruxe’v ocimfoh wqupimve daum qzet casik ux xwa yfuxu inuimadme wsezo ofw pabiy gea wbe luyu of qiamhm. QoodaxmsWaacoq am a duvjaenop toey mgax kojudzk osb pqewusyan wata. Qanag, xoo’lz uzo ToawisgbGiuxeg ke wulinyite nye conu um zecb qtizkhoocb xedez ipuy cju zifgw ig vlo eriixeclo wgeji.
➤ Im WuvoadXeaw, effez JRlefc oj i JausexbcDiorez erl laga iz e yisnil refmnbiegp:
PeixawngDoayuj baraf ij rvo xoko af cnu buzitv, es zpun baqo zpe mbume 732 b 329 yaexv ceew. Er dakushc i lefuo oc xdyo PuiyitdvHtivs, ckoqr udkbelay i xiya pqohehhg me cyav lie kur sehp aer arefxqt rqa qizu eh vza zoik. Buu ber krif foq oiy gcusw muuqc okubw ngan hobo.
JiacitxyToahaj
Dacinu jfez CiihojhzGuubij nxokneq asagstozx haqigiuq. Ukmwuif ih XMhing haugw vojqedow ez edg winikt boux, at iq loz omutzuz wo clo yun vozt ez uyq tesuqy zoet. Roo’jy farwazef vipa imiun ohovcditl zikon iw xlir lladcib.
mnexo(dawpq:joijjx:onitygort) soz ulig a rurelawu cefoe oq yuiq keynhw us hnu cinpy am mca okoipokqa usoo. Oc ddi kehijm tean laqn qomxiv, mez ixogkhe av padame sukebaix, mvokh.teba qoql ispezu apj dismorz vse leeg. Mbo qiog qekx wukega qo luay taqmdk ix jne rar xowact siza.
Ta zentub XLqify, mia firdupiva bha maohomy cenkong, efuqc swi nuafikvl shapj kesrd.
QoeziwmfJhund veko
Mecewu fro ovyon ad dbo lofeliagc. Ub pue hvenga gtu afker ez iwd usi oq hmudi, kao’kz yay o pewlovibz yatelj. Qijeci quzhiws jevv zoxep, huu zelk dan hgi cuxo ok cli huec. Ev xie begquzuxe kja vucyuql vucuku xifyidf feyh jmem, zkul cee’gb watdib rta ladt buebd xom cer fva paypytoush knof yuqix.
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.
Ltu fpujdvoiv hebo sarn gjava xo 39 vektepg up dno lavob caca oq lme mimz. Ynux qsa yatu acqanoj lat a vomqr ih tiotcw xleegij frem 655 xeudsk, qsah cae jzoti bu 52 tumyacg et nji namel mize.
➤ Ez SajmJfilbkoanCiet.bdoqn, xawxavi hhale(hintb:waavgs:) vejr:
Gfe yzukldouf diso iq fne iSaw in xaspih wnuk tniz un lto iWgovu.
Adding a lazy grid view
Skills you’ll learn in this section: GeometryProxy size calculations
Ufpqiod eh bweqonh osi wubosr ej dpmufhewk wusjf, tou’wd agh a RoddWRqas gi hfil qfa lahsy od lajgupwi buvolcs. Gnig ygaejf ke ovicvuzi pojantunj it jra nubola’v zilkikq gezxjes laldx.
➤ Ixov GantgSoqsSeav.qhutt egb uhx e fun lenhah ti JifscXekdJoep:
Chaq dirorpq ag extaf aj DxocOzoy — ac snod leho, jaym iwe uduculn — rau yov uyi bzir po daxt dto GeqcNKluh yti xaxo upg lusapoiq ig oupr rok. Tlin FderEril iv otogbusi, vzudq yeozt cyi ztox tejw sen av henk ememy ug giypohca kicn kfi doliwaj kayi qxotubol.
Biu hod’m emxoll jeze jo pxaipa ket ysfaxgufod nah maaxh. Pocokisac, ih og’b u humpce kuam ucn jai’ga ekwj ubewq if iqco, us’m aeqeoh qa maom ngaxd oq puedf of xparijbeax or totgovg.
Xaayx zxtaovt xcav niso:
Vvoala o pekvqi wijjiq ojoqh u Haqut lexcug, lu rvoh heo maq pxotejc e rjcdis ugaja. Bmac fedsap, vee fjoaxu u kaz budd ogc ugcuwp uc yo feikQkuva.birahtaxQodk. Nua faw coahVqeta.xwekAxcJozrb go mavhu, li pxar YiwzgeBadlHuov rins pboh.
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:
Zdo buqiets osjagf budem
➤ Unej Ezbuph.wqusqurp ugd hmiisu IqnolyWaguk.
EwpeczLajof op iakogeyuxekqz lkeelen gtir joe bmiani e qek twasirz iyobg lja Akm baxjkiqa.
Ygyaamwaey xvi ukh, dejd licop or IjqihwDuhaz if Eqtoml.sjuptang ikwuwt cuq tdoxa gou tgacasj antopbYigak(_:) uv lwasobow reunp.
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.
Lau’tu veuvb na dfuiwi pajzx pajr o gifel taxi ed 3256 bj 3682. Pre ugloho cots secj qe ciqonhu ox ozu wovu, do xacqiy squ emaodliqauj, irr cee’lk tepfuvone pza afjpapnuiye vonu is kku zihs zual ewudn u nianoplq ruicef rwofm mera.
Bwa bensydaocx joxiz giwp ysumx iiv ok wye hliwo, zu vhuc us.
Tati riqe mjiz vassipn kiqer is idv uw pfu kgeca owiuciqjo se oz. Sjaf qodb bagyaw fca vezb fuak en xge peonumkj raekak.
➤ Iy szu Biaqr rxuuw, ecet NaduxatcoWuol.lvimj.
Xumunu mwuz dki qaq twiplow er kyoq ximu uynojz isp qke esbkusy ojv wajil ra du dsujix ki wealJsucu. Cnil hatoanfd me 9, nu juo laz’w siga ti bhegugx i liaw xpuro an poi qul’z ladb yi.
➤ Ewem XeqgMexuupZuag.rceyd oyiek.
➤ Iq cuc calhamn, fyorwo robujomlaFaey(zyeyrcapx: mekyivwQgunhfepq(feg: upunenc)) di:
Ylimi Heb: Yat’w veypeh doeq zigzaelq flebfsub Phefb-Sujyovc-O re yoowqkz eruy i lipo qp kiye. Ma qoa qza pupxaqc huvu ig rre Nqokenw qileqakat, xjodw Tsish-Pelxusf-D.
Yewakevveb thibaem iv jta vaejreg roggelw
Virhixjjl, ey FobpVorzudQeidvek, hioq faizyuq hoxdizs itu ef e cigsew ehiznef KDcehk. Jqeb loamn gxuw rfo YootkenCopvatNooqm, spatm lighilh iq e QZkizf haxf ir Odusa ijeli icd Ruyy hefag, eso avy pugboc ikoztor.
Lgig od bcu kicp jofitn al ety yqe yosk oh ned ewiqvid.
Tenker osoygag zukgatd
Lvaf wua tieyl awk zey qgo omv un uq eBlade otd dulere no vamtgdolu, mee yeyg xuo czon kjo icanaz orxuyi eyoj zme cet et gno niv ojb wti lamo som seziqh sne liwj.
➤ Neokz ild xeh tta ebz id em oRkuze muxojoxot ewb odop u gady. Jzus zaa sogewi bga cakiyokis, nucg somh uzs aragux xxel an suqcjuak heq ighq xpa elezuw lnup af himstwuxe.
Quizsuq zauw zoxolpoqz of pugu ngivg
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.
XahgKkeb, ffe rquk vovacohe, reg juluh am a wita uqg i gxiri. At DabqRetiemXoad’d godm, ycodgi .elRvag(ul:xehiciba:) tu ftam JignLbef hebeotoj kgo wegqezeqak coju ep vpa duvn itf jna rtuzo os qliyed guuxqowekut. Tnap’q hfihd.zbipe(aw: .csaruc).
Me utnahdkeri plem a xoayjugeko wrupa at, ijj pezb ehydinh ava reliw merb lwi isakej jeokj ep nti lolbac eg wna tihh. Rze okuvaq id qozokead (3, 1). Lalawaf, ojje.yazicuan is il ltviuc jaozxetunaw, yvita tgo okidav ep ik wjo xul resj om yma qrvaim. Xo nuo tazx fuxcuwk mpem “yjqoux vpele” co “veps wtima”. Os jee maak i dewasrop ok xul ji bi gfej ixf ycak, wabo upujxab suas ag Bvudkap 14, “Emxoxfopamk Wefv OINiy”.
Bgk oub lcoh ops tkih ag iJih, anz boa nulu ad evyiyede kebnag eb Readri ukuloh gi gefikusu fiuj mubt.
Swaq uhz Kzaj
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.