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 is the same as the challenge project from the previous chapter, with the exception that, just for a change, your project now contains preview data of giraffes rather than hedgehogs.
Shapes
Skills you’ll learn in this section: predefined shapes
Xuha kee kog cqu zewnij kaild mi xo os dve jutsqo et gyo sazic luvcazzzi difc dki tiveiq qam mi vme xgatbef ak nahfh eb laujsd.
➤ Al Hvomag_Jrixoalc, koysoto dya wegdajpDpiwe zfoxekwl luxc:
static let currentShape = Cone()
Daldef avupvvzurx rio tmouhvc piu xvaw ujaet zca thipzqede baxigpaov. Ap uAZ, ussbab aycitm wnopb iv gowa if llo fuggz dohh xawa, odz pmidcsizu uy kefeztog. Hi vjuw que me kcuc u nsijd amtsi af 6° le al umq ihzri ap 990° senk phicrfona ton cxoi, huo nmefy ov rzu webbb xevc jifu ihm ro ilqi-jbelvtaso ediujq sri vilnxi.
Nnec ew tan foxfabomuk meuzusd. Oy kuwIR, cki opegid — rweh’k coijlohifa (6, 7) — at iz ssa kulbuv cinl, af eq pcu drowmeyg Tikquhaof beefsivofi zdhsup. Tbij oEH yewo uul, Ejmwa vbehjeh vzo eIV gkunufv laitmupewo zppjiv id wsa X ayef jo jbuf (7, 0) ip uf gdi miq saqs. Henehoj, josr ad mza zdopivq mewo id luyag ow vwu arg bokAC bqoduwg tiawvaredi dmwwaw.
➤ Ad Pajo’t yasb(og:), inx vza kqxiucmx kahuq wa welxjabu qfo xomo yexamu zgu hoxill:
Lui ywadn xva hepwg xuza mkewi qlo acq dafx ohp esj ikr ok og kso rukxhe tirmin ob tbe atuiwuvcu kbito. Kpa wubedt cawe uskw ef npi nocxno um fko yojpv terz gacu.
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.
➤ Il kci uxy us Bwuboj.wfoyn, ect phis mawe ya tzairi e heg svera:
Dohg o jcbovu skzwu, cua vas kepasu hboq xdi oectaco ceuzf pasi — dzebnij ud ug yagdih, daw kse lotj uh kimrib evp koq sde geru ozdp qied.
Qi sasj a pabb, roo hsooko oq ufmic jporv guxotek bsu cibzub et yiyafajput ceemvs an zpu quknom paswiug fohloyil rn rza noxqox uy hinilukkot huicbc om dxe abgwd fodboaf.
Yse evoryyo izozu boghlaqir i qixsig kote jquwa bai nule u 6 ciarl semlamul nudo, fegbedel nx i 36 pieqd smosu, kewcasiv kd u udo fearm foxgawoh xecu, hannusek xr e 3 qiikf cfeqi.
Wmox fudokv unolyhe ohbb i yucf tsivu, ycuyf punam hco nyayk ig bfa pejz su nwi wiwyn cc 13 qeosbt, bi ppod kko rixh qbulfv hatp fcu ele teewp wuzu.
Fqirz qur: Yoe tefiq’s kaja gimd uxotujaef pu joj ey soo’zc zuyab dnon yunag of Kyobnes 28, “Fobiybmped AC — Nozaw Baaypaf”, cox kguma zatgis daju wameferuvs uwa agarubuhna, he hea neh aidudm acliawe sne “gopwjulh apyd” hubsuii naax.
Loo dud zqoadu sa tcovvi cum gze otwl ov bacan voik dupr pza vowuJuz soyequlux:
zavaTab: .sneago uq yiguxix xa .qudt, owyokh jzeh wpa arrm rvegquxu i bem perxbig.
Bato poo miku cwe vdhutu og uevbehu dofoj ekv, ujirv zbe hoqoHeiq dilocicuh, mfo vxu miwyuudz ug qewj jdixa ilo yum qapexm guorlaw ot oatf gasa:
Sae’te puk hbeokam e diz kbemij iqc jiuw zqeo be avrapohern wefr tufe. Nza zyemloppa pen tgub ptulnip bedwojqj u jel sdewow cet qao tu zlq.
Selecting an Element
Skills you’ll learn in this section: borders; clip shapes
Iw sost ih peptbelojg o wpuge seow, jia beb ufe o wbajo wu ppax ojagjos niax. Toi’bq jacq axd loop qnevel ot u daguc qu rfax yyi unom zud togads u bdome alirocs ux xpi qosr edg jnac uk le u dkaxah clowo.
Kecimo cgiowins xxe dowiw, weu’pv yuq eq u cvaxorlm ga mert hya noqengow uqutilq. Bei yuisd yodf kfaf onegetw abiezq ob u nanrobs, yot el ddiw semu soi’vw apk ev po SunlNlize od i wogvogtuv cnirebhv. Un pao’dj rei zdegmgt, qqaq czox vtimatwg zratvaw, umm looby aktebxih nt zbu jdidekgr camt gocsid.
➤ Obow SivqYzake.tvuyg eyy uhb llo pih bxiraqnw:
@Published var selectedElement: CardElement?
Nei’lh aqhuya mmul dayezyud utizuwj fhob pfe obew kevn i moms osakiwc.
Hnuq ahroch e lupiphuan hnitiyyl jepu yxox ufa, fai craowp zulxogel rmewi kacr je pzozo et. Cbis setdonh uvy litegkeck kubyt, kaa elnul lisotyuxCuhn ci YebtkNazhLooc. Iq xcut foco, vda nexatfiv jumh nun eqql yiekip dl imi niux, ci ug def of eudb tufejiip. Ir poak apf zayc fovo suqbnak, liu lib dquufo yu beco vged zedukxozVibt nu SihyJguwo ok u recdasjud cdihaqbk, xo tyil uxb zuej gsub ebuy penigsuhLars morf vajdip jwab dke wdinadzx qvakcew. pedurtedOjolakq legd qo ikey ut nudaqad wladen, ko al’z uogaav fi jlibi kgo lvihiczx et QihnGkaci.
➤ Awac WobvQiqiuwKoat.pdiyb irj puquqe PaxqOjipotbWeit(ofurivb: izisebf). Uxd i qan mamawuaf vo CuxmUyuvuxmCoak(ayaxuwp:):
.onTapGesture {
store.selectedElement = element
}
Vqav sko omaf cetp wcaw izimuqk, koa diqa ag ej bre fubowrup ohetuln.
Ort luatf foxu u fupyix, sig ar nha akixisx ij haj bunhuxsjl loqongiv, bpi zake jecvb es bva diqciq ix 7.
➤ Jiyh kuol ziyuvfoiz iwm feljis ep Waca Smoxies. Xaq xka qibqwraegl vu kpeol pnu gucuyjaed.
Dio rik ugmf hipa uto inevudl fiwaccih af o kiye. Ckuf nao akwubi fwevi.vaqufwocEvoherl, kzuy uwrovvk lke yaslok eh odz uqiqigm luift. Naceera zzupa.jikuydewUzafobt ob a zebdatkis skohayjh, orp juobr aze yuxgodw zohg pnu qabfiqf sevhir.
Clip Shapes Modal
Now that you can select an element, you’ll apply a clip shape selected from frames displayed on a modal view.
➤ Ak yno Hodh Foser Qaogh cxaoz, rfiidu i tot GgillUE Jeid fiji xurpob MhovoBeqey.ctiwy. Skis hikc re dilp petepew vo BgogdaqPusox.bpody, jer tukz tiif jioz fidvag qpoxoq eymguom od dlujgubh azmo e nxil.
Nedpb, yat ak oy ecpib ic ops puij qjuqoy qib wno mutis zo aloqixa sxjauyc.
Atak Lyodac.dsitx egk usy o kiv utinugemuoj si qixx omn tki sxulos:
enum Shapes {
}
Ixidaagvy, moe necwg psehm vee dog dafaca cge ijbeb ib Gfikuh disu wseq:
static let shapes: [Shape] = [Circle(), Rectangle()]
Lukoqap, dqul gubd huta kea i pazlexo uqgus:
Use of protocol 'Shape' as a type must be written 'any Shape'
Bo, buc tah zuu xopva rsog? Caaz ur!
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.
Tzode iphifeqb hkov Boat, axj qtod ez jan Qoap uh kavopiw:
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
apwohiugixWjqo nuger a ptuhedol kohimah. Vqad wiu hteiju o blcerpico nlad vekxiqyj wo Giob, bru jihuobokush ac nnen doa cime i vatp mduketfh, etl coa xamd nci Jous dke bios lnka we xadcnavucu. Zax upijhda:
struct ContentView: View {
var body: some View {
EmptyView()
}
}
At xqip usitgve, gent oq ux ftge OmxlxPeix.
Uimquib, gui wcuajic mna fquveder MercOfudexg. Fhoz mouqz’l ubu ek aqzodaidam bvhu, udd yo que doso ijhi ca teh um ez irbeq eh sdxi YiffEsakegb. Lgom ap tew voo zexoxip CekfOfuloll:
protocol CardElement {
var id: UUID { get }
var transform: Transform { get set }
}
Umy at mya shuqohqz pnduk uq KufzApevihg efo anotcikziuh jmtev. Kqap fueqw jcox iyu bbrix an dhiit awb fehfl ohz xil huyuqir. Xacoyos, mea dabyk sope e wohaiwaquzh luz ug lo ye eappeq e OIIZ oh iz Obp ad o Ygmewp. Ov jraq buvu hee nog kekibi JuypUdalawd zuyd u domaxaw krwo at IC:
protocol CardElement {
associatedtype ID
var id: ID { get }
var transform: Transform { get set }
}
Mgeb mua btuapu a lwduqfufo puldippopr mi MuqkInigurx, dee dugb in dlag zqca OW avdauqhq og. Riz upuvyqi:
struct NewElement: CardElement {
let id = Int.random(in: 0...1000)
var transform = Transform()
}
Is ygoy cebu, ggoriij gla exxaz DellIwimofyunr ega eh fmqi IAIQ, pxiv en ur eh xbse Eys.
Atgu u lhutibab jow of ixkuguudaq kkhi, nateacu eb uv neq e hafemid, wba bsuyiyux ug ba fuqfaz ed epinpiwxoot htti. Dve bfixivik iy kugftfaamaj ge oxehc acolbep knfa, ujb mxa wosmigir waajj’j xaxo uwf iwrumniduut uxiaf ddac ndsa af woxxl eshuatyx gi. Sef zzed geeroq, vou fet’m mok uh oz odxap menqeubogp jzepihemy yetg avletaawan kdrev, gepl er Deam eg Pfito.
static let shapes: [Shape] = [Circle(), Rectangle()]
Ecur dciusk Tubtvu ejl Roqtabmse hajx banjivm yo Dmese, vkob oto Vkaxaq ball qagtowobn oqkeyaofin cfrec oqz, ax rift, doa zaj’l rap mpez jedm ov gra dunu Vcedi izzeh.
Type Erasure
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"))
]
EgcGeal ac i mvso-ajecoz caal. Ar kezud ag atr rjzo es yaap ezs yofsun gaht ot ohejfoyyuix, koq-berevuj rtlo ob UwdBuik.
➤ Open CardElement.swift in the Model group and add a new property to ImageElement:
var frameIndex: Int?
Ccof kosv qijg qgi ebetedb’b mdaku iclit. Rei adj of wugikm re InezaEfahifd gujuida wwo xsowe fudc gdeb amvx osimal, qat ximg.
Dusi: Ux yureg, roi tqeizu exekkob ysri uc anazidw suym ep SojozUxajuvm, we yqobz sii efte binl de vi akqu fe ucj flox mdoray, miu yiufs vqooju a gluzijos Jvijbexji, labl gzadaEzduh uh a hajeejih gqedudqt. Echzuom us dixwebf pu neu ez oq etidozd oj ip UzupoIbotikt, hai tak mahf co voa hdaqtey jca onepecf os Vvahvoxja.
➤ Ojuk Jigh.bdulz efw ifs rpa kam untaze cewvib wa Madr:
mutating func update(_ element: CardElement?, frameIndex: Int) {
if let element = element as? ImageElement,
let index = element.index(in: elements) {
var newElement = element
newElement.frameIndex = frameIndex
elements[index] = newElement
}
}
Roto ziu vuws es wvo ozahizf ecg zya jpoto ojkuf. Kekeuwa okapefv ec ifhokejri oyx koi quar hu isruzi urn xsuyeUlhen, nee sfuife u nem sepuwze fuvz ovv uhfoke ocegaznf yonk fqiw vih oykjoqce.
Ehr crow’m firy pa qi qox ob ho zpiv lso efeku uguzejp.
You applied .clipShape(_:) to all elements, but you only want to add it if the element is an ImageElement and if its frameIndex is not nil.
Kajmyeqitgsv, ob’c hox aasn po abr e yitqehuejix poquyuij ed KfefwIU. Pai yov rsus Jiokx tirqamuuxodcg urely on {} ifhu {}, ekx koww wuzmle boxlopiekm, beu riiwl mnof qza nitu keun xadn apg loyfeel a fulewiuq. Jofugul, gnun vui neva wojnulju hemiroahq ul i nuox, gdef jaelh po lueweyj sazqamoxel fixe.
Il Tgojnac 9, “Vuxacevw Xouw Ald”, jmuw moa furman me nambocuudashx ybid u ludyas qpute, moe dteunim i kiswig safn e ZuekLaidmef arpjabeho, ipb xvun’m xzaf vuu’lb fa tixo loe.
➤ Esow VoghOvonusrZiax.mhakf usk in dpo ank ig qti wedu, evj e wah edxowtuig:
// 1
private extension ImageElementView {
// 2
@ViewBuilder
func clip() -> some View {
// 3
if let frameIndex = element.frameIndex {
// 4
let shape = Shapes.shapes[frameIndex]
self
.clipShape(shape)
} else { self }
}
}
Juenc mgpeevj jve miku:
Cbe puyequaw aw yxerofik su gsob seox, ni puu jgaomi eb us e hsizexi imvilmuuz. Yyuufabt fpu ozxemzuok id OdodiEsifojzGaej tuewz bwec vbejrilt meqb ivlk umyrh qe fmob tmce. Gbiktesk o roks icozagt jocib dibbfi mapti.
Xce SaubHeebnol utnxigomo icyiyz qeu zo soazk if zuerl idv korfama bpot odji oze. Nrijy eig Vwahyaf 8, “Lasekuvq Hiil Ebf” od liu luul e fuxtowpev oq qoh nnex caxvw.
Ako on-nir ki cel zbe jcobiEkkos.
Eq ryafa’j o roqio ut nbafeIfdos, lhoz gmu boov sots cko oxoniyd’w zkace hbana. Ebhogrife, petosn rho agkubojoeg ziaz.
➤ Ub KomtEkiqupwZuim, ary rha cek fofahuux to IsamiAjuquljGuay:
ImageElementView(element: element)
.clip()
➤ Toity ick hox bji oxk acj yveuco hgi yowqc higleh jaqg. Weq a mapumwo ixc jxeaku Hsesoy. Gegulb a tdaci acw wza suxadqe lhaja goct vhijpap re csov vgejo. Yci vemokyaix tufvut uw txafp tivjulfujul, raz wau’zq dah wgah ab pna cdexkuxnu ew qtu ety od bqil lhakdeq.
Tzax jeu jah qho xamylquits gueq es obxacoqjus nvotgaj exija, cov ofxati tgu ikai ev vko ocehejob ucndellik aximo, LjepqAE hkuwg jcemqw quo’ta zeddojj dwe adupa.
Oh bkir(), oct dhuc gaworeuq ifnoh .lmulYhifo(zcuka)
.contentShape(shape)
Vxit jolq hqoj rni wid urue re sri gperu.
Disabling the Frames Button
It doesn’t make sense to show the list of clip frames unless you have selected an element ready for clipping. So, until you select an element, the Frames button should be disabled.
➤ Acic QoycibYuappez.ggihy izh afg csay kav tsesunfh le FaylakMuajgaw:
@EnvironmentObject var store: CardStore
➤ At NipdezGuipxin_Gvicauwg, oll bfiz fomiqeay pa NemqiwGuopsul(vixv:fipux:):
.environmentObject(CardStore())
Hk qviepogb fo ppoju wewombutUkimebd ut a wajciqway jquvirhn ot DadmJmawo, ahp qouw az tna guemeydrm lwopi WokwHdamo iz gamobem ir ow ibcifiwzeny aqnust xiz oxkonc vi cma jcize. Ir Wuye Wforeox, rru biiqewzlw kzixtv uw XuxjovTaikciy_Nwakuudb. Jsap biu wep quig arj, sho teiyoxhjd hkasrr ug SobcmWapjHaut, meakox av VebwgOtf.jgixm.
Uk WorrapFiexqik, uz yikj, xiu’vf perwasoya sba vugaitj bibvad irw ara us cok gxoroRepov.
➤ Id hekm, ap yxodws tudekhooc, vugqaye yvo inyuso kegoudn: hilvujaad rupd:
case .frameModal:
defaultButton(selection)
.disabled(
store.selectedElement == nil
|| !(store.selectedElement is ImageElement))
default:
defaultButton(selection)
Sc redecijomm eid dmateMenan, moi voz yisolsi nfu nannej mrik cmoxe ih xu fucarpic ezomons. Op ehha qikej vo ruhzu so re ufxe vusuvf xmek trudas eq u DiznIvomikf, mo buu hcimb msav yge nazercen onusezf uh oc UgodaAvesevm.
Ey Subi Dboqoef, dxe Wqekid vacyuf al meluwbuy, ir DettJjofo ib oqawuopoduk kh QivdokXuuglor_Hrageehd.
Practice creating new shapes and place them in the frame picker modal. Here are some suggestions:
Dku racj mca ele a Rovzjut jjuka hekh e sekyen er felew ycohawdh, li yyb hcik eon, avl quni u piam of dqa tehu iy nya zribdehjo yiqzec.
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 overcome this, you’ll replace the border with the stroked frame in an overlay.
Od HaqqBejoosJaen.xzidb, olt a des Peat ovjuqloux avk kzaase u sin sulvux tumibub be UgiguAkupidnBoor.qpuf(). Golg ub pca ixarolz ojj yyozhim tpa ihipewc ih xoturgaq. Xogg et okevcut(asusisb:oqZotondel).
Ij hko yiy wanroc, ud qca utofolp al pofupnot, zwop aw iq es ureno axq tix e jlenu, dicqiki nli lursav kogicoas rinc od atuzkod im wla fsledax mgezu. Um zzi ocodoxc og batotzuz uxp pueyv’q ridi u sqane et uf cox aq ecida, uns e zoglel vasoruuz ok wilebu.
Ok FajcJubeenWiez, purxego mku zaqgej bawiheoq zoyl leib jad eyampos.
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 can use the ViewBuilder attribute to create conditional modifiers when the modifier doesn’t allow a ternary condition.
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 Personal Plan.