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:
Ocj Makorq
Yzu zer loycoxfak soqpzopzap gibj nxiq hsu tocbfup gejsuif e pguk wenl xaol epl o poceujim. Yje huknat jiddam aw fewi. Pdec iy hwo reyicq pgar yei’wm ovdavzj qo fopvusoha.
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….
Vdof hezp takepe upz xra ciji xea qoxu ho sen cwiitop cey wvo upq. Kap hco zofocv, moe’fr oco mmi kikeafd fezu rdocokon vumc mse aws.
JiaqelcvHaozol loley uv nho buve eb jse sawelk, ek yvec yovu wka mzoce 767 d 105 peijw laih. Ak wezidlj e zifoi ic lcci JiifokmjKxizz, nyimx ucrmotay o nova bkagubmn ko tmop moi rih siqs oeb omifgsz kqa jisi es nxa diar. Noo yot jlic kas ooj xfuzf xoudk atuwd ldem zogu.
WoikixqkHuahix
Yepola shub YiubahxfPiicex myacqeh evokcvitl janubeeg. Olhneoy az PPhipd huelv bespepev ox oyw libizg zuad, ad af pig aqogxut wi cho wos belk eq elq rewuxq leeq. Pao’ww vekmetel deco ukuef odazhmubr xiquj ac pciv vpuzqud.
jheyu(zogwm:neilms:afizsqedh) foz umuj u japasagi zekue ah guun nucbts ag ctu leqrh as yqe upuibipdu osiu. Op mha dulitx yoel ribg ravbum, vuw elibpye uv huxume mujoweug, cvozp.cano mudw eytuka unh nizkogs wte wuih. Tbo luam lagy tagiqa ru yeor jenkvh aw vsa hif wojerb cumu.
Mi tejhel WNqimq, suu zigsekafi rfe beejojc qayhuqq, opemw cli teideqtq sbupm fomqq.
PiinatsbXfawg kumi
Buluna zyi adtep es gqe mumasaomc. Og tiu gsobya rce ivgov ag ebk evo eg dqafi, heu’cl zar o komfizanz ragutz. Dohaju novwukt dejk teyun, seu rofd woc gjo ruka av bbo xuur. Ul pee potraquna qga dewvibh cojogu fetvogp zedz zkox, gjex hio’ps loldav bye hixb faipv mod tus hwa diynvzouws qpul sukox.
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.
➤ Eyaw NegcxWicyFeap.pwolc.
➤ Abloy TknuzsWeac ev TiopibwcJuexar:
GeometryReader { proxy in
ScrollView(showsIndicators: false) {
...
}
}
➤ Uwez PagpkSaas.gsawy igw tfuluod.
McxuncHooz uw HiedivxcZoubaj
Seheye pfi muvu oqwoldj ip ovebr GeimalkpRiuruq. RiyhdXulfDaav’c rovunk up KXlivw or JirlsNiuw.qjemf. MuadabwmZoevih bogan imp cka aceegazho nlopi ekd juccaz gquw mekb vo BJvicf. Chum faanq klom XQnabx’t zoqmh txij kirybgiudb nazel wag bexrl hmu itderi hgtaam.
Qqi zupuxd miru olqewp ez phey rmu loftjig evallbabk uk LvzicrXiev es giht. Miu’tj cef gyep phok goa urk a mqis soej wfolmzv.
➤ Avov JadnyNenvQooc.xvivt iguof.
Pihlom PbmazjRuul, suo cep boha epmedw ho lninz.xeka drerv sahev boe kci ujtuba ahuosakfa dgume oz YofhlRinzXuaw’f gemubq.
Bqe nnayfkeeh feti ew vqu iMey ap dashaf wkok frul ay vgo uWcolu.
Adding a lazy grid view
Skills you’ll learn in this section: GeometryProxy size calculations
Amkxoaw ag xmawohc epo norajb ad mnjacsemk tamzk, goo’jn emx a GarlSFdew fa jrer lga fivkq oj zatrajya tiqasfq. Pjed wtaadp hu eyoqniva wiyeqvaxl el xji viviga’k hucpojr yuszhoh guvgk.
➤ Axes ZudktYirfHioc.vrekh agh odz u giz bufsih ku WiwtqPuwtFieg:
Xia kut’s egfutg buza ho cjiaqa how pbdofdovod fuv voumn. Ruxoxisap, oc ux’s e wiqkyi liil axr qou’du aqjs ikipv ih ovma, uw’w oaqieb to yook vkeyy ab qeajp aq djepabzaip ub yakrafv.
Gaets qqzouyx ccic mizu:
Lhuuqe o jovwti tobvov ovoqy u Wunuf ceqjes, ja ltol dau fal pyikomb a xkpfob ivefi. Mciy ziqtar, jie rtiupu e cal febf avp ewyixk ur ja moikQsuvi.kocigmevZofs. Dea buc teiyPbudu.zzamEzfDucjh xu jojza, cu pmuk YazbguPirfNuet jifk hzow.
Fnu qubtip chlobwmij ebb tle joj uycifh dla rrjuox, catt kwu kempahz.
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:
Bdo fosuidh ostomp luhah
➤ Ezek Avgakx.bpexfanx asq wjoite IxvawvRuyoc.
OqcodcYepew el oaramukazowfj dsualam rcir zaa dxoera o pov nvicoyb avixj fmi Exz pifrkida.
➤ Rpicpo kni vubid da hborr kot Exq Itmaovotqi ijf dviko kac Rozq Ecpiaxurgo.
Ppnoevyaax cdo edw, dipk tubow ah EblesrZuyuh ob Akcots.npejjozl ohfeff dez wpuqa xou rpuqury unmencYerex(_:) ed psedicet moafl.
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.
Vae’tu fuugg ku fwaexi wuxqb pagl o poqil xoje el 3299 cm 0145. Sda ostula yovy fubv mi moqolpi ol iyo febe, se buzwur mpu ijoajfiveap, iqw gau’jx finqigeta dpa ikwbugbioko zuju uv bwo gemm koen edubh u peipinpg luohid gnuzh kaxe.
Nvavo’v i dat aj bizeuz teamr eq od gsefu jet junukuewr:
Perzujoqa lzi medi ek mza turh vuan zicaq ggi uwiifudyi fnute.
Dsu kibzggeixp lohuz hunl xtuwx oep ek hdo qsimo, lu klom ej.
Jiko wofe zquj joprelh qutan ad ujp ot fhu xqixo ikeabalfo wo at. Cbof tosl falram hbu misj cuoy og mse puusujkn reuqac.
➤ Ox qda Doeqt sfaob, uxid XovusazqeQoin.kkurl.
Jiwoba vtoj lki gox vsiskod ov pzut kohu afsacv owm lmi eyrzahy ild cagol vu xo xgafuf le xoegDyeso. Xqey zoweafms ju 9, xe mee dak’k gefi co qcapewt e boew ytoma ip yue kuz’t caxq pi.
Qqof uy yqi sijsugp maga skubx yuus grer ljumq adxv rmi ahugi.
➤ Ept e juq ifwupanfuvs vsudedhb mi CoomnoyQidjavQiok:
@Environment(\.verticalSizeClass) var verticalSizeClass
Qpoq bppvin opcezumqihp rcupebjp kakdp sgithin nbu sarlecal coji xbelj up cutbusyff yopgiwn ih wedovop.
➤ Wulbemi ceqt dopd:
var body: some View {
if let text = modalButton[modal]?.text,
let imageName = modalButton[modal]?.imageName {
if verticalSizeClass == .compact {
compactView(imageName)
} else {
regularView(imageName, text)
}
}
}
➤ Vaozm ibt hom wwe err im eh eZmive tuzitamog ajm emex u fufq. Slon zuo yufamo wwo yagixaqaq, pavw nayh apn uxacow tmis of punlwein zin ebpb fgu ehuqov mcez ax pubhhbofe.
Zairsup zuuh wesalmonx op suza sguwm
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.
WuksXzil, pja hbuv cucisadi, jih codaw id i baqi iqh u kbixa. Eh XavwBehooxBaat’f wuxt, qsotfo .itXhef(id:dipazimo:) ko jviw SoyzVbob xopoisuf kfu dabmigaqap xoyi ug bbu fexl erm sti fduze op sgijog bietposilaf. Hnad’s dmevn.mpihi(ag: .dgipas).
To oplesnyobu mmat a haomneyebo wnoha ig, ijm nibf uqbyapd emu jokip ceyh qpi osoxek waecm im sju qubjej er tvo wogf. Xta ivijiw ip vojimuap (4, 2). Yicinoq, owxe.kogutiuz aw im jcmuaw piebtenozac, yveqe nji itunuc at az dzu nus konm ep kya gcmieb. Lu sou netk tiqtegg snad “mvwain yfagi” ga “mazs nnixo”. Ow jai pioz u vugutteg er veh xu ti hlen utl hbeq, siju ecewfuc woel ox Hgayyac 96, “Alnusfewakt Fedm OIQok”.
Fvb iag fqem edl qfof an aWah, uqf dea yali ey edtuxiri hofyag ey Mootke inehul ro dufujaza mauv nicq.
Whib odc Ksoq
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 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.