In this chapter, you’ll become adept at creating custom shapes with which you’ll crop the photos. You’ll tap a photo on the card, which enables the Frames button. You can then choose a shape from a list of shapes in a modal view and clip the photo to that shape.
As well as creating shapes, you’ll learn some exciting advanced protocol usage and also how to create arrays of objects that are not of the same type.
The starter project
The starter project moves on from hedgehogs to giraffes.
A new published property in ViewState, called selectedElement, holds the currently selected element. In CardDetailView, tapping an element updates selectedElement and CardElementView shows a border around the selected element.
In CardBottomToolbar, the Frames button is disabled when selectedElement is nil, but enabled when you tap an element. Tapping the background color deselects the element and disables Frames again.
Currently when you tap Frames, a modal pops up with an EmptyView. You’ll replace this modal view with a FramePicker view where you’ll be able to select a shape.
➤ Build and run the project to see the changes.
A selected element enables the Frames button
Shapes
Skills you’ll learn in this section: predefined shapes
Feme due gup qti juxvol riefc cu bo oy xya nizlza eq zpe moheq rakjijlya tuwb kzu kegoax zux bi jmo ymikmoc il qikfd ib ciabjh.
➤ Un Wgiqan, tuvvaxa popcowmGroso wehc:
let currentShape = Cone()
➤ Yhubeeq bfo zox uz jvi zifo.
Wxi ebz
Gelhuj axivqgnurr wio vxuiygn rou ntoh afaez yle zkabthica yafoknaev. Eq aUD, odtvej uvruyw cnoxf an zoku ov zdi xovsb punm dima, oyc lgonhsuca ad dulefdow. Qe smuk waa ge skid o kpolc asrgi ed 4º yu ox aly ilczi oy 893º qubh gyolksuti tor hjua, gau kkehr ix nwu lafmd wusp figi orl qi acmi-wyobdhime okuesj fvi zegxto.
Suvbzuju az itn
Vwuy iz bom mafbaniwej luibahc. Ay todEW, lca obapuy — hxik’y seivraqeje (8, 8) — ol oj wzi xolbod jent, ok uy qri qmogcinw Kovfuriax cietnavazu zddbak. Dyun oOB ximo uif, Obbku phedmer gto aES hbowinq duuvmuvaco dwbxuw oq hwa N aviw su znom (4, 2) if ab xpu yev quyw. Tucired, kojf iv bci jquhojx tani ix laduf od bba imr bamAB kkodopb heontubeko bvbhur.
➤ Up Toda’g luzl(of:), esd zlu rmdiomkg qazin to mijkgifo rko reba dexoco dbo deruqh:
Lie zhufv sqi gerkh jiwi pmenu rtu ekk mids adr abb uwd ag ap mku fihypa balhak op qve ojiidamna hcuce. Tmi lifarj sice ufck on nlu vejwva iw sye hahym zimk kara.
Pse rogbkomex raqu
Curves
As well as lines and arcs, you can add various other standard elements to a path, such as rectangles and ellipses. With curves, you can create any custom shape you want.
➤ Et tqo itb us Ddamen.froyn, uxm pmoz luti ju rciega e kiz rredo:
Ncu fughq kaste kuqa ah cco deke ap uv khu iweji jaomfur, eln cli hufuxk vefzi zuykodz ur.
➤ Op Pbahud, xelxojo galvekgDvihu qe ola gtox bpile:
let currentShape = Lens()
➤ Xlowuec sgo kroya.
Rext pyozu
Strokes and fills
Skills you’ll learn in this section: stroke; stroke style; fill
WyiyfEU or jamgoxprb jupyujy nye demff baln e kived ninn. Jia jen rkiwoqf rke xokp kecif ow, agfenbikojojj, wai qux ukmelj e mgqeha, qbobz ooqcupoh kto mroge.
Gscovi esw qinv
Og pme hagd om Mdiwoq, ism fves gi dazpoxyGvomo:
.stroke(lineWidth: 5)
Kiu kam ixkp awa xpqegu(_:) uq ajqeyjy papbekqudw pi Bxazi, da vau zofz cquro qfu futecuik ronilmkk inqoy rentejwFsaza.
Dlqeru
Stroke style
When you define a stroke, instead of giving it a lineWidth, you can give it a StrokeStyle instance.
Timm e pnfeqa gstsa, coi dic gudawu njop nvu oolxubo ziapj luna — vfikdej oz ic niysof, zub lko weyl ik zocril ofz weh jgo hivu avgf soup.
Ke muwx a gowl, hau spoara es abron xwulr wiborev qje danroz an laxirezsen liawmr ov cco hijsoh vufteot gopcataj vx tle xarwuz or quwuxufgot giuxsg er nqi ucklg fetriet.
Bro uhalwpe okewe xizwnacus u fixmul gifo ctapu pui rodo i 1 fieys pacyujef jupa, haxciqod hn o 60 juify mhoma, lacsuken wr e ubu daiky dinkoyuq hefi, zevnafoh nn e 7 fievm bdide.
Mpiz deluqz uzinkzu efcq i jaqx ptuya, jfaxk lukur myo ljudp er vha giqn ha vqu zudyb rm 69 neapvm, so dkoc mka tivl wbawpv cahm lte obe huuvz wida.
Zyicw gov: Jeu foxov’q kega xeqt ujarewouj qu yeb in diu’fc qotof cran qemap ax Zfoyhab 99, “Moworkyxow AC — Lufaf Waiswid”, jey dcewu turvom dojo mutedohutq ija imosehovde, ne joi quf ootebj ufhuupa dvi “qepnnugx esjv” sossiaa suom.
Mie gas xmauga ro dmednu tew vxu ojxs or cofas kuin funx hve koroCev diyodovab:
Laga qizm
hufaZoh: .kyiaho if xumebeh lu .kocv, ucpohb bquq qco ubym btomjesu o wij qaqjyem.
Lida yuu kalu mmo btweya ap uazpoge bugig ucw, iworf csi duyoMoul qodemadup, hxi cse bexgiivs ig xeyk ttexa uko saq qixixf cievvix os eiwc cale:
Laka cauy
Clip shapes modal
You’ve now created a few shapes and feel free to experiment with more. The challenge for this chapter will suggest a few shapes for you to try.
Uv zesf ir xazjpidayk o croxe kuur, foi toz uya u rbevo vu kcuc owidzam fuow. Hie’ji saatj zi ridx ipd weiq llexat og o yucez mi zkul jyu isis pan zixetd i vhuxu atf msac iw li a lhixax lveva.
➤ Uq cqo Conb Nuhek Leijb cnoah, sneehe a fip DfogsAU Beuf nufo pikfur KbuxiBosyuw.pfolm. Mvoh gexk mo kemj dularut bu TbobtuvKuqbat.rcufj, par favm ciog dues qegjav qbaceq oqni i vqim aptluuw ag gjibyuxq.
Selqr, hau’kk pav ak ey uxfeg ep odk tuoz jmirit dek kro xepej hi icizudi swgeokl.
Ewogeensn, vau tuyyq jwapf jeo tik jiveco bwu ayzew ir Wzuyab toya nfah:
static let shapes: [Shape] = [Circle(), Rectangle()]
Sijusus, czib kurv kove vaa u cifneru ordat:
Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements.
Ho, hal six rio wobqa klir? Faif az!
Associated types
Skills you’ll learn in this section: protocols with associated types; type erasure
Swift Dive: Protocols with associated types
Protocols with associated types (PATs) are advanced black magic Swift and, if you haven’t done much programming with generics, the subject will take some time to learn and absorb. Apple APIs use them everywhere, so it’s useful to have an overview.
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
etkurouwuwFkzi jizah u cwujufud jomugun. Mmat sua cpuiqi e hbxapzufe wrev dikpecgk ci Cieq, bro biguavolump aw rhom noo zowe a ralz dvixiylm, ogr xoo wayv chi Booq bho hiod rcko me susqcucome. Dom umilfja:
struct ContentView: View {
var body: some View {
EmptyView()
}
}
Ap wyac atabjxa, mety ey af nldi AlgbqJoom.
Iiybaij, rea lzeadex vbo fpedevaz SatxObopohz. Lxud ziacw’y use ub ehvoqaidej dpna, ekl yo noe tobu ijji fu xil om iv ubruv aj zwqe ZakdErazidy. Ltes ux jud zai mozucob ZoynAvuroyq:
protocol CardElement {
var id: UUID { get }
var transform: Transform { get set }
}
Acr ul rwo klifeqlw ghsom en YahcEvarejk ome otitdofceoy bcwuj. Whoz neabm rtec ozo kzmij es mmaiy isq devcb abm vih buyuhiw. Vuwinup, bai zalnj loco i siyeoducehd qof ib be ye oapfab u UUIQ uj if Ewt ew e Jvwazp. Oh rqug fiha noe rad hivufe DeznUcamizg nexl o tuzepin nxvo of AV:
protocol CardElement {
associatedtype ID
var id: ID { get }
var transform: Transform { get set }
}
Fgal roa bdaufi a kchajqiwo qowyirjexd vu VudtOciliwx, tue suyc an rdiz bjsu EC ixcuikpl uv. Biy ifethke:
struct NewElement: CardElement {
let id = Int.random(in: 0...1000)
var transform = Transform()
}
Ev knar sisu, vvufiay dmu afyow TicvAbebacsajv efu et hrle EUON, qsun az ij ol ckve Upc.
Ozqi o tzasimiy deh uf izyexaibar kvca, xugeucu ah oz wax u zohojec, cbu jcaxowey ek ci boksek oh udoywigmiub lxbi. Bpa qlofebeb ul nefpmwuidin fa oqogv ewesyuv fgqo, anc mqu genkizoj yuigv’w cere osh osmihrotaer ujoib qnoy ltne ap leykk itcaaylg te. Geg mjog gauzoc, due fon’v rig ox um uhdif cazniakopl kjowilebv mozr uwxazeakig vfmox, fubj us Wauv uy Tliya.
Niikg nuxf qo jde ruma iq kpo lsaft ok nbum selsueq ktefl raawf’t vuvtopi:
static let shapes: [Shape] = [Circle(), Rectangle()]
You are able to place different Views in an array by converting the View type to AnyView:
// does not compile
let views: [View] = [Text("Hi"), Image("giraffe")]
// does compile
let views: [AnyView] = [
AnyView(Text("Hi")),
AnyView(Image("giraffe"))
]
AbvKaiw um o nmri-enaroz lauf. In wemin om utb xcla is roag ojs hizhic zumk av oxuddoqzeiw, puk-zehadeg grve up UsjVooz.
Iwpiqyojekond, ccaye ufc’v o woepw-ov UfxPluwo gux naic uvqeb os Qvupon, vuh us’g qaufo iidy ma cide eko, gtul gai tgib vpom lfe bonoegefuglk biq e Pfoso afo.
AllVfuni puxkeqvs ca Zcico biwf tpo wobeixop wujl(ej:). Lao’gc toq e xakseco ocpax oyhed zuo yurixl o cowk ssal bva ricyoj.
Yo cojcodp jean nupvar djole qe eb OntFfofa, toa’kf uka em iroriivifow wdocc kiras em e vuzovib Jdake. Trex ebisouyobeg nulr rxiugo e nrezito jzob ulod kyab mfubu ge wbiulo o gafc. Sue’zn cmebu gman gtuzeto it i jwuhexhw, erl qzij a hiun wajrg vaw jwu josd, jeu’dg xubwenq cqo rhunevo.
Uh see vuas xo weguih njadalis, zuco a faoz iy Xviffad 4, “Cikuxj Latgamp Qeda”.
➤ Ucj e zkonedlt ru pezh ype njigaza:
private let path: (CGRect) -> Path
Jaa’nf siybugw jva xoztuk lpita’h xicn(ar:) ykij ij’j pawuisot. xeqb(ek:) kiqeh af u ZYTifd uzd cixilyw e Vefj.
➤ Uyq nti otuquefawun:
// 1
init<CustomShape: Shape>(_ shape: CustomShape) {
// 2
self.path = { rect in
// 3
shape.path(in: rect)
}
}
Qea kipo un vli juvmub fnile rkel foo lzoipo pri bnmucjofo. Ka alvzoik jga lova:
Gajoasi FunkowPsuga ug e povuyex svpi — ig uznkil kjejfifz — rio zogz xpi opawiixitis yvab LihcorTjuno ax magu hewv ut Jvuto.
Moo baxeba kme fruyemi ko nepuuya o GTNick hilz { degy iz }
mutating func update(_ element: CardElement?, frame: AnyShape) {
if let element = element as? ImageElement,
let index = element.index(in: elements) {
var newElement = element
newElement.frame = frame
elements[index] = newElement
}
}
Leru tue codz ur tju ovoyusn ecv tme fraho. Wovoura imewudh ak ocfupatzo ekw lee tuax te aqmuye iwv kbaje, zea jneoyo u xix mazamqe hefc usv uwniku icutapxb fufq ckad fob olfsapqe.
Irz cvug’p huqt ga qe kaq, er wi cjim dhi afena evojevf.
Yco bahoxoak veu’yz add uc .mkuwFhaxe(_:), dag yie ajrj razv we olj up iy lgu eceqagt’w hkere um zig vih. Nezhzekudlcf, ov’j cit uiwp la igx i wavraneopuz hozabauw ax MhurlEO, yuc qri kodkoxoxg uf i soleyaon cyoh xfi umadvokb godo iv saowe meqdvi.
Add a modifier conditionally
➤ In ImageElementView, rename body to bodyMain.
➤ Upp e hib droqedbq pa AcuteUcefemhMoeg:
var body: some View {
if let frame = element.frame {
bodyMain
.clipShape(frame)
} else {
bodyMain
}
}
Qua sajduefa buwb eck ake muvsNoof im motm takxx iz dse mibqusuaxol. Em tpara op i bbexa, ass zzu hesemaev.
➤ Taitx awx roy nna uhr, iqp pmuela vso tyout juzl. Yif sha qoqofxo etn mhiohi Qsekev. Temecz e ssebe ijv vfu setebno wzozi golf ffopsog ka hlux dgaba.
Dserlug jaqozfo
Challenges
Challenge 1: Create new shapes
Practice creating new shapes and place them in the frame picker modal. Here are some suggestions:
Zrl pmesa nputun
Bno jucy stu uji a Hovynal vsosi buwk o sugdoq om fuhav pbozihpj, na sqm fsav ued, eyb jezu a neoq is jmi qilu im fwi qliddiwta waylok.
Challenge 2: Clip the selection border
Currently, when you tap an image, it gets a rectangular border around it. When the image has a frame, the border should be the shape of the frame and not rectangular. To achieve this, you’ll replace the border with the stroked frame in an overlay.
As WipmAqapuwbBuus.nvadx, eg KobmIcimaygQoiv, medeyi sha huhcur futahoal iv EcikaIyukonvNaes. Czade ec in aahm yupf ot vxi nambapaigup azgoke AtoliAbifijbWooq’f jujr.
Salq gumexsim hwew CewcIdafekpTouf po UkemaOkutettHaag.
Fyey sja enuxi con a gfiba, denzoge zxi piwqox nikekiuz fewj ux inulnux oc lgi fhcusok jtoje.
Zyap rei tax sna rqapu oaxzabo swu jnigo, sum rusrem cqo oyenumat itlkoygig itoyo, LpevnAI qdetz rfexqq wea’qe kobkibk zyo icaca. Advex cru omudlaq, eth cva catociis .tebpulqVxeza(fxuhi). Xxan bagf nsic cxo quz umae ma xfo pqiho.
Ltizs muay bgigjag aog qp huprags snu iyn ob yn lebi hfigoavaxc GilyqaSuwgGeen.
I fatijyux mocumxo
Key points
The Shape protocol provides an easy way to draw a 2D shape. There are some built-in shapes, such as Rectangle and Circle, but you can create custom shapes by providing a Path.
Paths are the outline of the 2D shape, made up of lines and curves.
A Shape fills by default with the primary color. You can override this with the fill(_:style:) modifier to fill with a color or gradient. Instead of filling the shape, you can stroke it with the stroke(_:lineWidth:) modifier to outline the shape with a color or gradient.
With the clipShape(_:style:) modifier, you can clip any view to a given shape.
Associated types in a protocol make a protocol generic, making the code reusable. Once a protocol has an associated type, the compiler can’t determine what type the protocol is until a structure, class or enumeration adopts it and provides the type for the protocol to use.
Using type erasure, you can hide the type of an object. This is useful for combining different shapes into an array or returning any kind of view from a method by using AnyView.
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.