Denna mini-serie med två delar kommer att lära dig hur man skapar en imponerande sidviktseffekt med Core Animation. I den här avdelningen lär du dig de steg som behövs för att polera den sida som skapades tidigare och du kommer att bygga en mer interaktiv vikningsupplevelse med nypa-gester.
I den första delen av denna tvådelade handledning tog vi en befintlig frihandritningsapp som gjorde det möjligt för användaren att skissa på skärmen med fingret och vi implementerade en 3D-vikningseffekt på ritningsduken som gav det visuella intrycket av en bok som vikas upp längs ryggraden. Vi uppnådde detta med hjälp av CALayer
s och transformationer. Vi kopplade upp effekten till en kretsbehållning som orsakade att vikningen skedde i steg, animerade smidigt mellan ett steg och nästa.
I den här delen av handledningen ser vi på att förbättra både de visuella aspekterna av effekten (genom att lägga böjda hörn på våra sidor och låta våra sidor kasta en skugga på bakgrunden) samt göra vikningsveckningen mer interaktiv , genom att låta användaren styra den med en nypa gest. Längs vägen lär vi oss om flera saker: a CALayer
underklass kallad CAShapeLayer
, hur man maskerar ett lager för att ge det en icke-rektangulär form, hur man aktiverar ett lager för att kasta en skugga, hur man konfigurerar skuggans egenskaper och lite mer om implicit laganimering och hur man gör dessa animeringar bra när vi lägger till användarinteraktion i mixen.
Utgångspunkten för denna del kommer att vara det Xcode-projektet vi slutade med vid avslutningen av den första handledningen. Låt oss fortsätta!
Minns att var och en av de vänstra och högra sidorna var en förekomst av CALayer
. De var rektangulära i form, placerade över vänster och höger halvdel av duken, angränsande längs den vertikala linjen som löper genom mitten. Vi ritade innehållet (det vill säga användarens frihandskiss) av vänster och höger halvdel av duken i dessa två sidor.
Även om a CAlayer
vid skapandet börjar med rektangulära gränser (som UIView
s), en av de coola sakerna vi kan göra för lager är klämmformen i form av en "mask", så att de inte längre är begränsade till att vara rektangulära! Hur definieras denna mask? CALayer
s har a mask
egendom som är en CALayer
vars innehålls alfakanal beskriver masken som ska användas. Om vi använder en "mjuk" mask (alfakanalen har fraktionsvärden) kan vi göra lagret delvis transparent. Om vi använder en "hård" mask (dvs med alfa värden noll eller en) kan vi "klippa" lagret så att det uppnår en väldefinierad form av vårt val.
Vi kan använda en extern bild för att definiera vår mask. Men eftersom vår mask har en viss form (rektangel med några hörn avrundade) finns det ett bättre sätt att göra det i kod. För att ange en form för vår mask använder vi en underklass av CALayer
kallad CAShapeLayer
. CAShapeLayer
s är skikt som kan ha vilken form som helst som definieras av en vektorväg i den opakta typen Core Graphics CGPathRef
. Vi kan antingen direkt skapa den här sökvägen med C-baserade Core Graphics API, eller - mer bekvämt - kan vi skapa en UIBezierPath
objekt med mål-C UIKit-ramen. UIBezierPath
exponerar den underliggande CGPathRef
objekt via dess CGPath
egendom som kan tilldelas vår CAShapeLayer
's väg
egendom, och detta formlag kan i sin tur tilldelas vara vår CALayer
s mask. Lyckligtvis för oss, UIBezierPath
kan initieras med många intressanta fördefinierade former, inklusive en rektangel med avrundade hörn (där vi väljer vilka hörn som ska runda).
Lägg till följande kod, efter att säga linjen rightPage.transform = makePerspectiveTransform ();
i ViewController.m viewDidAppear:
metod:
// avrundnings hörn UIBezierPath * leftPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: leftPage.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii: CGSizeMake (25., 25.0)]; UIBezierPath * rightPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: rightPage.bounds byRoundingCorners: UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii: CGSizeMake (25.0, 25.0)]; CAShapeLayer * leftPageRoundedCornersMask = [CAShapeLayer-lagret]; CAShapeLayer * rightPageRoundedCornersMask = [CAShapeLayer-lagret]; leftPageRoundedCornersMask.frame = leftPage.bounds; rightPageRoundedCornersMask.frame = rightPage.bounds; leftPageRoundedCornersMask.path = leftPageRoundedCornersPath.CGPath; rightPageRoundedCornersMask.path = rightPageRoundedCornersPath.CGPath; leftPage.mask = leftPageRoundedCornersMask; rightPage.mask = rightPageRoundedCornersMask;
Koden ska vara självförklarande. Den snyggare vägen har formen av en rektangel med samma storlek som skiktet är maskerat och har lämpliga hörn avrundade (längst upp till vänster och längst ner till vänster sida, höger och nederst till höger på höger sida).
Bygg projektet och springa. Sidans hörn bör avrundas nu ... coolt!
Att applicera en skugga är också lätt, men det är en hitch när vi vill ha en skugga när vi applicerar en mask (som vi just gjorde). Vi kommer att köra in på detta med tiden!
En CALayer har a shadowPath
vilket är en (du gissade det) CGPathRef
och definierar skuggans form. En skugga har flera egenskaper som vi kan ställa in: dess färg, dess förskjutning (i huvudsak vilken väg och hur långt den faller från skiktet), dess radie (specificerar dess omfattning och blurriness) och dess opacitet.
shadowPath
eftersom ritningssystemet kommer att träna ut skuggan från lagrets sammansatta alfakanal. Detta är dock mindre effektivt och leder vanligen till att prestanda lider, så det rekommenderas alltid att ställa in shadowPath
egendom när det är möjligt. Sätt in följande kodblock direkt efter det vi just lagt till:
leftPage.shadowPath = [UIBezierPath bezierPathWithRect: leftPage.bounds] .CGPath; rightPage.shadowPath = [UIBezierPath bezierPathWithRect: rightPage.bounds] .CGPath; leftPage.shadowRadius = 100.0; leftPage.shadowColor = [UIColor blackColor] .CGColor; leftPage.shadowOpacity = 0.9; rightPage.shadowRadius = 100.0; rightPage.shadowColor = [UIColor blackColor] .CGColor; rightPage.shadowOpacity = 0,9;
Bygg och kör koden. Tyvärr kommer ingen skugga att bli gjuten och utsignalen kommer att se exakt som tidigare. Vad är det med det?!
För att förstå vad som händer, kommentera det första blocket av kod som vi skrev i den här handledningen, men lämna skuggkoden på plats. Bygg och kör nu. Okej, vi har förlorat de avrundade hörnen, men nu lagar våra lager en skarp skugga på utsikten bakom dem, vilket förbättrar djupförmågan.
Vad händer är det när vi sätter en CALayer
s maskeegenskap klipps lagrets renderegion till maskområdet. Därför blir inte skuggor (som naturligt kastas bort från skiktet) gjorda och visas därför inte.
Innan vi försöker lösa detta problem, notera att skuggan för den högra sidan har gjutits på toppen av vänster sida. Detta beror på att leftPage
har lagts till i vyn tidigare rightPage
, Därför är den förra effektivt "bakom" den senare i ritningsordningen (även om de båda är syskonskikt). Förutom att byta ordning där de två skikten lagts till superskiktet, kunde vi ändra zPosition
egenskapen hos lagren för att uttryckligen ange ritningsordningen, tilldela ett mindre floatvärde till det lager vi ville dra ut först. Vi skulle vara med på en mer komplext genomförande om vi ville undvika denna effekt helt och hållet, men eftersom det (fortunately) låter en fin skuggad effekt på vår sida, är vi nöjda med saker som de är!
För att lösa detta problem använder vi två lager för att representera varje sida, en för att generera skuggor och den andra för att visa det tecknade innehållet i en formad region. När det gäller heirarkin lägger vi skugglagren som ett direktunderlag till bakgrundsskiktets lager. Sedan lägger vi till innehållsskikten i skugglagren. Skugglagren kommer därför att dubbla som "behållare" för innehållsskiktet. Alla våra geometriska omvandlingar (som gör att sidvända effekten) kommer att appliceras på skugglagren. Eftersom innehållsskikten kommer att göras i förhållande till behållarna behöver vi inte göra några omvandlingar till dem.
När du förstår det här är det enklare att skriva koden. Men på grund av alla de förändringar vi gör kommer det att vara rörigt att ändra den tidigare koden, därför föreslår jag att du byter ut Allt koden i viewDidAppear:
med följande:
- (void) viewDidAppear: (BOOL) animerad [super viewDidAppear: animated]; self.view.backgroundColor = [UIColor whiteColor]; leftPageShadowLayer = [CAShapeLayer-lagret]; rightPageShadowLayer = [CAShapeLayer-lagret]; leftPageShadowLayer.anchorPoint = CGPointMake (1.0, 0.5); rightPageShadowLayer.anchorPoint = CGPointMake (0,0, 0,5); leftPageShadowLayer.position = CGPointMake (self.view.bounds.size.width / 2, self.view.bounds.size.height / 2); rightPageShadowLayer.position = CGPointMake (self.view.bounds.size.width / 2, self.view.bounds.size.height / 2); leftPageShadowLayer.bounds = CGRectMake (0, 0, self.view.bounds.size.width / 2, self.view.bounds.size.height); rightPageShadowLayer.bounds = CGRectMake (0, 0, self.view.bounds.size.width / 2, self.view.bounds.size.height); UIBezierPath * leftPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: leftPageShadowLayer.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii: CGSizeMake (25., 25.0)]; UIBezierPath * rightPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: rightPageShadowLayer.bounds byRoundingCorners: UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii: CGSizeMake (25.0, 25.0)]; leftPageShadowLayer.shadowPath = leftPageRoundedCornersPath.CGPath; rightPageShadowLayer.shadowPath = rightPageRoundedCornersPath.CGPath; leftPageShadowLayer.shadowColor = [UIColor blackColor] .CGColor; leftPageShadowLayer.shadowRadius = 100.0; leftPageShadowLayer.shadowOpacity = 0.9; rightPageShadowLayer.shadowColor = [UIColor blackColor] .CGColor; rightPageShadowLayer.shadowRadius = 100; rightPageShadowLayer.shadowOpacity = 0.9; leftPage = [CALayer lager]; rightPage = [CALayer lager]; leftPage.frame = leftPageShadowLayer.bounds; rightPage.frame = rightPageShadowLayer.bounds; leftPage.backgroundColor = [UIColor whiteColor] .CGColor; rightPage.backgroundColor = [UIColor whiteColor] .CGColor; leftPage.borderColor = [UIColor darkGrayColor] .CGColor; rightPage.borderColor = [UIColor darkGrayColor] .CGColor; leftPage.transform = makePerspectiveTransform (); rightPage.transform = makePerspectiveTransform (); CAShapeLayer * leftPageRoundedCornersMask = [CAShapeLayer-lagret]; CAShapeLayer * rightPageRoundedCornersMask = [CAShapeLayer-lagret]; leftPageRoundedCornersMask.frame = leftPage.bounds; rightPageRoundedCornersMask.frame = rightPage.bounds; leftPageRoundedCornersMask.path = leftPageRoundedCornersPath.CGPath; rightPageRoundedCornersMask.path = rightPageRoundedCornersPath.CGPath; leftPage.mask = leftPageRoundedCornersMask; rightPage.mask = rightPageRoundedCornersMask; leftPageShadowLayer.transform = makePerspectiveTransform (); rightPageShadowLayer.transform = makePerspectiveTransform (); curtainView = [[UIView alloc] initWithFrame: self.view.bounds]; curtainView.backgroundColor = [UIColor scrollViewTexturedBackgroundColor]; [curtainView.layer addSublayer: leftPageShadowLayer]; [curtainView.layer addSublayer: rightPageShadowLayer]; [leftPageShadowLayer addSublayer: leftPage]; [rightPageShadowLayer addSublayer: rightPage]; UITapGestureRecognizer * foldTap = [[UITapGestureRecognizer alloc] initWithTarget: självåtgärd: @selector (fold :)]; [self.view addGestureRecognizer: foldTap]; UITapGestureRecognizer * unfoldTap = [[UITapGestureRecognizer alloc] initWithTarget: självåtgärd: @selector (unfold :)]; unfoldTap.numberOfTouchesRequired = 2; [self.view addGestureRecognizer: unfoldTap];
Du måste också lägga till de två instansvariablerna som motsvarar de två skugglagren. Ändra koden i början av @genomförande
avsnitt att läsa:
@implementation ViewController CALayer * leftPage; CALayer * rightPage; UIView * curtainView; CAShapeLayer * leftPageShadowLayer; CAShapeLayer * rightPageShadowLayer;
Baserat på vår tidigare diskussion borde du hitta koden enkelt att följa.
Minns att jag tidigare nämnde att skikten som innehåller det dras innehållet skulle innehålla som underlag till skugggenererande skiktet. Därför behöver vi ändra vår vika ihop:
och veckla ut:
metoder för att utföra den nödvändiga omvandlingen på skugglagren.
- (void) fold: (UITapGestureRecognizer *) gr // tecknar "incrementalImage" bitmappen i våra lager CGImageRef imgRef = ((CanvasView *) self.view) .incrementalImage.CGImage; leftPage.contents = (__bridge id) imgRef; rightPage.contents = (__bridge id) imgRef; leftPage.contentsRect = CGRectMake (0,0, 0,0, 0,5, 1,0); // denna rektangel representerar den vänstra halvan av bilden rightPage.contentsRect = CGRectMake (0.5, 0.0, 0.5, 1.0); // denna rektangel representerar den högra halvan av bilden leftPageShadowLayer.transform = CATransform3DScale (leftPageShadowLayer.transform, 0.95, 0.95, 0.95); rightPageShadowLayer.transform = CATransform3DScale (rightPageShadowLayer.transform, 0,95, 0,95, 0,95); leftPageShadowLayer.transform = CATransform3DRotate (leftPageShadowLayer.transform, D2R (7.5), 0.0, 1.0, 0.0); rightPageShadowLayer.transform = CATransform3DRotate (rightPageShadowLayer.transform, D2R (-7.5), 0.0, 1.0, 0.0); [self.view addSubview: curtainView]; - (void) unfold: (UITapGestureRecognizer *) gr leftPageShadowLayer.transform = CATransform3Didentity; rightPageShadowLayer.transform = CATransform3DIdentity; leftPageShadowLayer.transform = makePerspectiveTransform (); // inte kommentera senare rightPageShadowLayer.transform = makePerspectiveTransform (); // saknas senare [curtainView removeFromSuperview];
Bygg och kör appen för att kolla våra sidor med både rundade hörn och skugga!
Som tidigare gör att enfångiga kranar gör att boken kan vikas upp i steg, medan en tvåfingerspetsåterställning tar bort effekten och återställer appen till sitt normala ritläge.
Saker ser bra ut, visuellt sett, men kranen är inte riktigt en realistisk gest för en bokfällande metafor. Om vi tänker på iPad-appar som Papper, vikningen och utfällningen drivs av en nypa gest. Låt oss genomföra det nu!
Implementera följande metod i ViewController.m:
- (void) foldWithPinch: (UIPinchGestureRecognizer *) p om (p.state == UIGestureRecognizerStateBegan) // ... (A) self.view.userInteractionEnabled = NO; CGImageRef imgRef = ((CanvasView *) self.view) .incrementalImage.CGImage; leftPage.contents = (__bridge id) imgRef; rightPage.contents = (__bridge id) imgRef; leftPage.contentsRect = CGRectMake (0,0, 0,0, 0,5, 1,0); rightPage.contentsRect = CGRectMake (0,5, 0,0, 0,5, 1,0); leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; leftPageShadowLayer.transform = makePerspectiveTransform (); rightPageShadowLayer.transform = makePerspectiveTransform (); [self.view addSubview: curtainView]; flytskala = p.scale> 0,48? p.scale: 0,48; // ... (B) skala = skala < 1.0 ? scale : 1.0; // SOME CODE WILL GO HERE (1) leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; leftPageShadowLayer.transform = makePerspectiveTransform(); rightPageShadowLayer.transform = makePerspectiveTransform(); leftPageShadowLayer.transform = CATransform3DScale(leftPageShadowLayer.transform, scale, scale, scale); // (C) rightPageShadowLayer.transform = CATransform3DScale(rightPageShadowLayer.transform, scale, scale, scale); leftPageShadowLayer.transform = CATransform3DRotate(leftPageShadowLayer.transform, (1.0 - scale) * M_PI, 0.0, 1.0, 0.0); rightPageShadowLayer.transform = CATransform3DRotate(rightPageShadowLayer.transform, -(1.0 - scale) * M_PI, 0.0, 1.0, 0.0); // SOME CODE WILL GO HERE (2) if (p.state == UIGestureRecognizerStateEnded) //… (C) // SOME CODE CHANGES HERE LATER (3) self.view.userInteractionEnabled = YES; [curtainView removeFromSuperview];
En kort förklaring om koden, med avseende på etiketterna A, B och C som nämns i koden:
stat
egendom som tar värdet UIGestureRecognizerStateBegan
) Vi börjar förbereda oss för veckans animering. Påståendet self.view.userInteractionEnabled = NO;
säkerställer att eventuella ytterligare rör som sker under nypa-gesten inte medför att teckning ska ske på kanfasvyn. Återstående kod ska vara bekant för dig. Vi återställer bara lagtransformerna.skala
egenskapen av nypen bestämmer förhållandet mellan avståndet mellan fingrarna i förhållande till början av nypen. Jag bestämde mig för att klämma fast det värde som vi ska använda för att beräkna våra sidors skalnings- och rotationsomvandlingar mellan 0,48. Skicket skalan är så att en "omvänd nypa" (användaren flyttar fingrarna längre ifrån varandra än början av nypen, motsvarande p.scale> 1.0
) har ingen effekt. Skicket p.scale> 0,48
är så att när mellanfingeravståndet blir ungefär hälften av vad det var vid början av nypet, är vår vikningsanimering klar och ytterligare nypa har ingen effekt. Jag väljer 0,48 istället för 0,50 på grund av hur jag beräknar vridningsvinkeln för lagrets rotationsomvandling. Med ett värde på 0,48 blir rotationsvinkeln något mindre än 90 grader, så boken kommer inte att helt vikas och blir följaktligen inte osynlig.
Lägg till koden för att lägga till en nypa igenkännare i slutet av viewDidAppear:
UIPinchGestureRecognizer * pinch = [[UIPinchGestureRecognizer alloc] initWithTarget: självåtgärd: @selector (foldWithPinch :)]; [self.view addGestureRecognizer: nypa];
Du kan bli av med all kod som är relaterad till de två knackkännare från ViewController.m för att vi inte behöver dem längre.
Bygg och kör. Om du testar på simulatorn istället för enheten, kom ihåg att du måste hålla ner alternativknappen för att simulera tvåfingers gester som att klämma fast.
I princip fungerar vår klämbaserade vikningsveckning, men du måste märka (speciellt om du testar på en fysisk enhet) att animeringen är långsam och ligger bakom nypen, vilket försvagar illusionen att nypet driver den vikningsveckande animationen. Så vad händer?
Kom ihåg den implicita animationen av transformen som vi njöt av "gratis" när animationen styrdes av kranen? Tja, det visar sig att samma underförstådda animering har blivit ett hinder nu! I allmänhet vill vi, om vi vill styra en animation med en gest, till exempel att en vy släcks över skärmen med en dragning (panning) gest (eller fallet med vår app här), vill vi avbryta implicita animeringar. Låt oss se till att vi förstår varför, med tanke på att en användare drar en vy över skärmen med fingret:
se
är på plats p0
till att börja med (dvs.. view.position = p0
där p0 = (x0, y0) och är a CGPoint
)p1
.se
från p0
till p1
med den "avslappnade" varaktigheten av 0,25 sekunder. Animationen har dock knappt börjat, och användaren har redan dragit fingret till en ny position, p2
. Animationen måste avbrytas och en ny påbörjas mot position p2.Eftersom användarens panningbehållning (i vårt hypotetiska exempel) är effektivt en kontinuerlig, behöver vi bara ändra visningen i takt med användarens gest för att behålla illusionen av en responsanimation! Exakt samma argument gäller för vår sida vikar med nypa situation här. Jag nämnde bara dragande exempel eftersom det verkade enklare att förklara vad som händer.
Det finns olika sätt att inaktivera implicita animeringar. Vi väljer den enklaste, vilket innebär att vi sätter in vår kod i en CATransaction
blockera och anropa klassmetoden [CATransaction setDisableActions: YES]
. Så vad är det? CATransaction
i alla fall? I enklaste termer, CATransaction
"buntar upp" egenskapsändringar som måste animeras och successivt uppdaterar sina värden på skärmen och hanterar alla tidsaspekter. I själva verket gör det allt det hårda arbetet med att göra våra animationer på skärmen för oss! Även om vi inte uttryckligen har använt en animationstransaktion än, är en implicit en alltid närvarande när någon animationsrelaterad kod körs. Vad vi behöver göra nu är att paketera upp animationen med ett eget CATransaction
blockera.
I pinchToFold:
metod, lägg till följande rader:
[CATransaction börjar]; [CATransaction setDisableActions: YES];
På platsen för kommentaren // SOM KOD KOMMER GÅ HÄR (1)
,
lägg till raden:
[CATransaction commit];
Om du bygger och kör appen nu märker du att vikningsveckningen är mycket mer flytande och lyhörd!
En annan animationsrelaterad fråga som vi behöver ta itu med är att vårt utspelas:
Metoden anpassar inte återställandet av boken till dess utplattade tillstånd när knäppningsbehållningen slutar. Du kan klaga på att i vår kod har vi inte faktiskt stört att återgå omvandla
i "då" blocket av vår om (p.state == UIGestureRecognizerStateEnded)
påstående. Men du är välkommen att försöka infoga de uttalanden som återställer lagrets transformation före uttalandet [canvasView removeFromSuperview];
för att se om lagren animerar denna egenskapsändring (spoiler: de kommer inte!).
Anledningen till att skikten inte kommer att animera egenskapen förändringen är att någon egenskap förändring som vi skulle kunna utföra i det kvarteret skulle klumpas i samma (implicit) CATransaction
som koden för att ta bort lagervisningsvisningen (canvasView
) från skärmen. Visningsavlägsningen skulle hända omedelbart - det är inte en animerad förändring, trots allt - och ingen animering i några undervyer (eller underlag läggs till dess lager) skulle inträffa.
Återigen, en uttrycklig CATransaction
block kommer till vår räddning! en CATransaction
har ett färdigställningsblock som endast exekveras efter att någon egenskapsändring som visas efter att den har slutat animera.
Ändra koden efter om (p.state == UIGestureRecognizerStateEnded)
klausul, så att if-uttalandet lyder som följer:
om (p.state == UIGestureRecognizerStateEnded) [CATransaction börjar]; [CATransaction setCompletionBlock: ^ self.view.userInteractionEnabled = YES; [curtainView removeFromSuperview]; ]; [CATransaction setAnimationDuration: 0.5 / scale]; leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; [CATransaction commit];
Observera att jag bestämde mig för att ändra animationsvaraktigheten omvänd med avseende på skala
så att ju större graden av viken då gesten slutar desto mer tid ska återvändande animering ta.
Det viktigaste att förstå här är det först efter omvandla
ändringar har animerat gör koden i completionBlock
Kör. De CATransaction
klassen har andra egenskaper du kan använda konfigurerar en animation exakt hur du vill ha den. Jag föreslår att du tittar på dokumentationen för mer.
Bygg och kör. Slutligen ser vår animation inte bara bra ut, men svarar ordentligt på användarens interaktion!
Jag hoppas att denna handledning har övertygat dig om att Core Animation Layers är ett realistiskt val för att uppnå ganska sofistikerade 3D-effekter och animeringar med liten ansträngning. Med viss optimering borde du kunna animera några hundra lager på skärmen samtidigt om du behöver. Ett bra användarfall för Core Animation är att integrera en cool 3D-övergång när du växlar från en vy till en annan i din app. Jag tycker också att Core Animation kan vara ett lönsamt alternativ för att bygga enkla ordbaserade eller kortbaserade spel.
Även om vår handledning sträckte sig över två delar, har vi knappt repat ytan av lager och animeringar (men förhoppningsvis är det bra!). Det finns andra typer av intressanta CALayer
underklasser som vi inte fick chansen att titta på. Animering är ett stort ämne i sig. Jag rekommenderar att du tittar på WWDC-samtalen om dessa ämnen, som "Core Animation in Practice" (Sessions 424 och 424, WWDC 2010), "Core Animation Essentials" (Session 421, WWDC 2011), "IOS App Performance - Graphics and Animations" (Session 238, WWDC 2012) och "Optimera 2D-grafik och animationsprestanda" (Session 506, WWDC 2012) och sedan gräva in i dokumentationen. Gott lärande och appskrivning!