Introduksjon

Denne to-del mini-serien vil lære deg hvordan du lager en imponerende side-folding effekt med Core Animation. I denne delen lærer du trinnene som er nødvendige for å polere siden som ble opprettet tidligere, og du vil bygge en mer interaktiv folding-opplevelse ved hjelp av nakkestillinger.


Introduksjon

I den første delen av denne todelte opplæringen tok vi en eksisterende frihånds tegnefil som tillot brukeren å skisse på skjermen med fingeren og vi implementerte en 3D-foldingseffekt på tegningsklæret som ga det visuelle inntrykket av en bok som var foldet opp langs ryggraden. Vi oppnådde dette ved å bruke CALayers og transformasjoner. Vi hekta effekten til en trykkbevegelse som forårsaket at foldingen skulle skje i trinn, som beveger seg jevnt mellom ett trinn og det neste.

I denne delen av opplæringen ser vi på å forbedre både de visuelle aspektene av effekten (ved å legge buede hjørner på sidene våre og la våre sider kaste en skygge i bakgrunnen), samt gjøre folding-unfolding mer interaktiv , ved å la brukeren kontrollere det med en nakkestang. Underveis lærer vi om flere ting: a CALayer underklasse kalles CAShapeLayer, hvordan å maskere et lag for å gi det en ikke-rektangulær form, hvordan du aktiverer et lag for å kaste en skygge, hvordan du konfigurerer skyggens egenskaper og litt mer om implisitt lag animasjon og hvordan du får disse animasjonene til å fungere fint når vi legger til brukerinteraksjon i blandingen.

Utgangspunktet for denne delen er Xcode-prosjektet vi endte med ved avslutningen av første opplæringsoppgave. La oss fortsette!


Utforming av sidekanter

Husk at hver av de venstre og høyre sidene var en forekomst av CALayer. De var rektangulære i form, plassert over venstre og høyre halvdel av lerretet, som ligger langs den vertikale linjen som løper gjennom senteret. Vi tok innholdet (dvs. brukerens frihåndskisse) av venstre og høyre halvdel av lerretet inn i disse to sidene.

Selv om a CAlayer Når skapelsen starter med rektangulære grenser (som UIViews), en av de kule tingene vi kan gjøre med lag, er klipsformet i henhold til en "maske", slik at de ikke lenger er begrenset til å være rektangulære! Hvordan defineres denne masken? CALayers har a maske eiendom som er en CALayer hvis innholds alfakanal beskriver masken som skal brukes. Hvis vi bruker en "myk" maske (alfakanalen har fraksjonelle verdier) kan vi gjøre laget delvis gjennomsiktig. Hvis vi bruker en "hard" maske (det vil si med alfa verdier null eller en) kan vi "klippe" laget slik at det oppnår en veldefinert form av vårt valg.

Vi kunne bruke et eksternt bilde for å definere masken vår. Men siden vår maske har en bestemt form (rektangel med noen hjørner avrundet), er det en bedre måte å gjøre det på i kode. For å spesifisere en form for masken bruker vi en underklasse av CALayer kalt CAShapeLayer. CAShapeLayers er lag som kan ha hvilken som helst form som er definert av en vektorbane i Core Graphics-ugjennomsiktig type CGPathRef. Vi kan enten direkte lage denne banen ved hjelp av C-baserte Core Graphics API, eller - mer bekvemt - kan vi lage en UIBezierPath objektet med Objective-C UIKit-rammen. UIBezierPath utsetter det underliggende CGPathRef objekt via sin CGPath eiendom som kan tildeles vår CAShapeLayer's sti eiendom, og dette formlaget kan i sin tur bli tildelt til vår CALayers maske. Heldigvis for oss, UIBezierPath kan initialiseres med mange interessante forhåndsdefinerte former, inkludert et rektangel med avrundede hjørner (hvor vi velger hvilket hjørne (r) skal runde).

Legg til følgende kode, etter, si linjen rightPage.transform = makePerspectiveTransform (); i ViewController.m viewDidAppear: metode:

 // avrundings hjørner 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-lag]; CAShapeLayer * rightPageRoundedCornersMask = [CAShapeLayer-lag]; leftPageRoundedCornersMask.frame = leftPage.bounds; rightPageRoundedCornersMask.frame = rightPage.bounds; leftPageRoundedCornersMask.path = leftPageRoundedCornersPath.CGPath; rightPageRoundedCornersMask.path = rightPageRoundedCornersPath.CGPath; leftPage.mask = leftPageRoundedCornersMask; rightPage.mask = rightPageRoundedCornersMask;

Koden skal være selvforklarende. Den travle banen er i form av et rektangel med samme størrelse som laget er maskert, og har de riktige hjørnene avrundet (øverst til venstre og nederst til venstre for venstre side, øverst til høyre og nederst til høyre for høyre side).

Bygg prosjektet og løp. Sidens hjørner skal avrundes nå ... kul!


Bruke en skygge

Det er også enkelt å bruke en skygge, men det er et hakk når vi ønsker en skygge etter at vi har brukt en maske (som vi nettopp gjorde). Vi vil løpe inn i dette med tiden!

En CALayer har en shadowPath som er en (du gjettet det) CGPathRef og definerer formen på skyggen. En skygge har flere egenskaper vi kan angi: dens farge, dens forskyvning (i utgangspunktet hvilken vei og hvor langt det faller fra laget), dens radius (angir dens omfang og blurriness) og dens opasitet.

Faktisk bør det nevnes at det ikke er helt nødvendig å sette shadowPath som tegnsystemet vil trene skyggen fra lagets sammensatte alfakanal. Dette er imidlertid mindre effektivt og vil vanligvis forårsake ytelse å lide, så det anbefales alltid å sette shadowPath eiendom når det er mulig.

Sett inn følgende kodenavn umiddelbart etter den vi nettopp har lagt til:

 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 og kjør koden. Dessverre vil ingen skygge bli kastet, og utgangen vil se ut akkurat som tidligere. Hva skjer med det?!

For å forstå hva som skjer, kommentere den første koden med kode vi skrev i denne opplæringen, men la skyggekoden være på plass. Nå bygg og løp. OK, vi har mistet de avrundede hjørnene, men nå lagger lagene våre en skyggefull skygge på utsikten bak dem, og forsterker dybdefølelsen.

Hva skjer er når vi setter en CALayers maskeegenskap, er lagets gjengningsområde klippet til maskeområdet. Derfor blir ikke skygger (som naturlig kastes bort fra laget) gjengitt og dermed ikke vises.

Før vi forsøker å løse dette problemet, merk at skyggen til høyre side ble kastet oppe på venstre side. Dette skyldes at leftPage ble lagt til i visningen før rightPage, Derfor er den tidligere effektivt "bak" sistnevnte i tegningsordren (selv om de begge er søskenlag). I tillegg til å bytte rekkefølgen der de to lagene ble lagt til superlaget, kunne vi endre zPosition Egenskapen til lagene for å spesifikt tegne tegningsordren, tildele en mindre flyteverdi til laget vi ønsket å bli trukket først. Vi ville være med på en mer komplisert implementering hvis vi ønsket å eliminere denne effekten helt, men siden det (fortunately) gir en fin skyggelagt effekt på vår side, er vi fornøyd med ting slik de er!


Få begge skygger og en maskert form

For å løse dette problemet bruker vi to lag til å representere hver side, en for å generere skygger og den andre for å vise det tegne innholdet i en formet region. Når det gjelder heirarki, legger vi skyggelagene som et direkte underlag til bakgrunnsvisningens lag. Deretter legger vi innholdslagene til skyggelagene. Derfor vil skyggelagene doble som "containere" for innholdslaget. Alle våre geometriske transformasjoner (som gjøres med sidevendeffekten) blir brukt på skyggelagene. Siden innholdsskiktene vil bli gjengitt i forhold til deres containere, trenger vi ikke å bruke noen transformasjoner til dem.

Når du har forstått dette, er det enklere å skrive koden. Men på grunn av alle endringene vi gjør, blir det rotete å endre den forrige koden, derfor foreslår jeg at du erstatter alle koden i viewDidAppear: med følgende:

 - (void) viewDidAppear: (BOOL) animert [super viewDidAppear: animated]; self.view.backgroundColor = [UIColor whiteColor]; leftPageShadowLayer = [CAShapeLayer lag]; rightPageShadowLayer = [CAShapeLayer-lag]; 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 lag]; rightPage = [CALayer lag]; 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-lag]; CAShapeLayer * rightPageRoundedCornersMask = [CAShapeLayer-lag]; 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: selvhandling: @selector (fold :)]; [self.view addGestureRecognizer: foldTap]; UITapGestureRecognizer * unfoldTap = [[UITapGestureRecognizer alloc] initWithTarget: selvhandling: @selector (utfolde :)]; unfoldTap.numberOfTouchesRequired = 2; [self.view addGestureRecognizer: unfoldTap]; 

Du må også legge til to instansvariabler som svarer til de to skyggelagene. Endre koden i begynnelsen av @gjennomføring seksjon å lese:

 @implementation ViewController CALayer * leftPage; CALayer * rightPage; UIView * curtainView; CAShapeLayer * leftPageShadowLayer; CAShapeLayer * rightPageShadowLayer; 

Basert på vår forrige diskusjon, bør du finne koden grei å følge.

Husk at jeg tidligere nevnte at lagene som inneholder det trekkede innholdet, ville være inneholdt som underlag til skyggenerasjonslaget. Derfor må vi endre vår brette: og brette: metoder for å utføre den nødvendige transformasjonen på skyggelagene.

 - (void) fold: (UITapGestureRecognizer *) gr // tegne bitmapet "incrementalImage" i våre lag 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); // dette rektangel representerer venstre halvdel av bildet rightPage.contentsRect = CGRectMake (0.5, 0.0, 0.5, 1.0); // dette rektangel representerer høyre halvdel av bildet 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 (); // uncomment senere rightPageShadowLayer.transform = makePerspectiveTransform (); // ukomment senere [curtainView removeFromSuperview]; 

Bygg og kjør appen for å sjekke ut sidene våre med både avrundede hjørner og skygge!

Som tidligere forårsaker enfingerkraner boken å brette seg opp i trinn, mens en toferskriver gjenoppretter fjerner effekten og gjenoppretter appen til sin normale tegningsmodus.


Innlemme Pinch-Based Folding

Tingene ser bra ut, visuelt sett, men hanen er egentlig ikke en realistisk gest for en bokfoldende metafor. Hvis vi tenker på iPad apps som Papir, folding og utfolding er drevet av en klemmebevegelse. La oss implementere det nå!

Implementer følgende metode i ViewController.m:

 - (ugyldig) foldWithPinch: (UIPinchGestureRecognizer *) p hvis (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];  float scale = 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 forklaring om koden, med hensyn til merkene A, B og C referert til i koden:

  1. Når klemmebevegelsen gjenkjennes (angitt av dens stat eiendom tar verdien UIGestureRecognizerStateBegan) Vi begynner å forberede oss til den felle animasjonen. Uttalelsen self.view.userInteractionEnabled = NO; sørger for at eventuelle ekstra berøringer som finner sted under klemfibringen, ikke vil føre til at tegning finner sted på lerretet. Den gjenstående koden skal være kjent for deg. Vi tilbakestiller bare lagtransformene.
  2. De skala Egenskapen av klemmen bestemmer forholdet mellom avstanden mellom fingrene i forhold til begynnelsen av klemmen. Jeg bestemte meg for å klemme verdien vi vil bruke til å beregne sidens skaling og rotasjonstransformasjoner mellom 0,48. Tilstanden skalaen er slik at en "omvendt klemme" (brukeren beveger fingrene lenger fra hverandre enn begynnelsen av klemmen, tilsvarende p.scale> 1.0) har ingen effekt. Tilstanden p.scale> 0,48 er slik at når interfingeravstanden blir omtrent halvparten av hva den var i begynnelsen av klemmen, er vår folding animasjon fullført, og noen videre klemme har ingen effekt. Jeg velger 0,48 i stedet for 0,50 på grunn av måten jeg beregner vinkelen på lagets rotasjonstransformasjon. Med en verdi på 0,48 vil rotasjonsvinkelen være litt mindre enn 90 grader, så boken vil ikke helt foldes og vil derfor ikke bli usynlig.
  3. Når brukeren slutter å klemme, fjerner vi visningen som viser våre animerte lag fra lerretet (som tidligere), og vi gjenoppretter lerretets interaktivitet.

Legg til koden for å legge til en klemmeregistrering på slutten av viewDidAppear:

 UIPinchGestureRecognizer * pinch = [[UIPinchGestureRecognizer alloc] initWithTarget: selvhandling: @selector (foldWithPinch :)]; [self.view addGestureRecognizer: klype];

Du kan bli kvitt all koden som er relatert til de to trykkgjenkjenningene fra ViewController.m fordi vi ikke vil trenge dem lenger.

Bygg og løp. Hvis du tester på simulatoren i stedet for enheten, husk at du må holde nede alternativtasten for å simulere tofingerbevegelser som klemming.

I prinsippet fungerer vår klypebaserte folding-unfolding, men du er nødt til å legge merke til (spesielt hvis du tester på en fysisk enhet) at animasjonen er treg og legger seg bak klype, og dermed svekker illusjonen at klype kjører folding-unfolding animasjon. Så hva skjer?


Få Animasjonene riktig

Husk den implisitte animasjonen av transformasjonen som vi nyter "gratis" når animasjonen ble styrt av springen? Vel, det viser seg at den samme implisitte animasjonen har blitt en hindring nå! Generelt, når vi ønsker å kontrollere en animasjon med en gest, for eksempel en visning som blir trukket over skjermen med en drakk (panning) gestus (eller tilfellet med vår app her), vil vi avbryte implisitte animasjoner. La oss sørge for at vi forstår hvorfor, vurderer at en bruker drar en visning over skjermen med fingeren:

  1. De utsikt er på posisjon p0 til å begynne med (dvs.. view.position = p0 hvor p0 = (x0, y0) og er a CGPoint)
  2. Når UIKit neste prøver brukerens berøring på skjermen, drar han fingeren til en annen posisjon, sier han p1.
  3. Implisitt animasjon sparker inn, forårsaker at animasjonssystemet begynner å animere posisjonsendringen av utsikt fra p0 til p1 med "avslappet" varighet på 0,25 sekunder. Imidlertid har animasjonen knapt startet, og brukeren har allerede trukket fingeren til en ny posisjon, p2. Animasjonen må avbrytes og en ny begynner på posisjon p2.
  4. … og så videre!

Siden brukerens panningbevegelse (i vårt hypotetiske eksempel) er effektivt en kontinuerlig, trenger vi bare å endre stillingen til visningen i takt med brukerens gest for å opprettholde illusjonen av en responsanimasjon! Nøyaktig det samme argumentet gjelder for vår side sammenfaller med klyngesituasjon her. Jeg nevnte bare det slepende eksemplet fordi det virket enklere å forklare hva som foregikk.

Det er forskjellige måter å deaktivere implisitte animasjoner på. Vi velger den enkleste, som innebærer å pakke inn koden vår i en CATransaction blokkere og påkalle klassemetoden [CATransaction setDisableActions: YES]. Så, hva er en CATransaction uansett? I enkleste termer, CATransaction "bunter opp" eiendomsendringer som må animeres og oppdaterer sine verdier på skjermen, og håndterer alle tidsaspekter. I virkeligheten gjør det alt det harde arbeidet med å gjøre animasjonene våre på skjermen for oss! Selv om vi ikke har eksplisitt brukt en animasjonstransaksjon ennå, er en implisitt alltid tilstede når noen animasjonsrelatert kode utføres. Det vi trenger å gjøre nå er å pakke opp animasjonen med en egendefinert CATransaction blokkere.

I pinchToFold: metode, legg til disse linjene:

 [CATransaction begynner]; [CATransaction setDisableActions: YES];

På siden av kommentaren // SOM KODE VIL GÅ HER (1),
legg til linjen:

 [CATransaction commit];

Hvis du bygger og kjører appen nå, vil du legge merke til at folding-unfolding er mye mer flytende og responsiv!

Et annet animasjonsrelatert problem som vi trenger å takle er at vår utfoldelse: Metoden tilpasser ikke gjenopprettingen av boken til den flatede tilstanden når klemmen går til ende. Du kan klage på at i vår kode har vi egentlig ikke plaget å vende tilbake forvandle i "da" blokken av vår hvis (p.state == UIGestureRecognizerStateEnded) uttalelse. Men du er velkommen til å prøve å sette inn setningene som tilbakestiller lagets transformasjon før setningen [canvasView removeFromSuperview]; for å se om lagene animerer denne egenskapen forandring (spoiler: de vil ikke!).

Grunnen til at lagene ikke vil animere egenskapen endringen er at noen egenskap forandring som vi kan utføre i den blokk av kode ville bli klumpet i samme (implisitt) CATransaction som koden for å fjerne lagvertjeningsvisningen (canvasView) fra skjermen. Visningsfjerningen vil skje umiddelbart - det er ikke en animert forandring, tross alt - og ingen animasjon i noen undervisninger (eller underlag som er lagt til laget) vil oppstå.

Igjen, en eksplisitt CATransaction blokk kommer til vår redning! EN CATransaction har en ferdigstillingsblokk som bare utføres etter at eventuelle egenskapsendringer som vises etter at det er ferdig med animering.

Endre koden etter hvis (p.state == UIGestureRecognizerStateEnded) klausul, slik at if-setningen lyder som følger:

 hvis (p.state == UIGestureRecognizerStateEnded) [CATransaction start]; [CATransaction setCompletionBlock: ^ self.view.userInteractionEnabled = YES; [curtainView removeFromSuperview]; ]; [CATransaction setAnimationDuration: 0.5 / scale]; leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; [CATransaction commit]; 

Legg merke til at jeg bestemte meg for å gjøre animasjonsvarigheten forandret inversely med hensyn til skala slik at jo større graden av brettet den gangen gesturen avsluttes, desto lengre tid skal tilbakevendende animasjon ta.

Det avgjørende å forstå her er det først etter forvandle endringer har animert gjør koden i completionBlock henrette. De CATransaction klassen har andre egenskaper du kan bruke konfigurer en animasjon nøyaktig hvordan du vil ha det. Jeg foreslår at du tar en titt på dokumentasjonen for mer.

Bygg og løp. Endelig ser vår animasjon ikke bare bra ut, men reagerer ordentlig på brukerinteraksjon!


Konklusjon

Jeg håper denne opplæringen har overbevist deg om at Core Animation Layers er et realistisk valg for å oppnå ganske sofistikerte 3D-effekter og animasjoner med liten innsats. Med litt optimalisering bør du kunne animere noen få hundre lag på skjermen samtidig hvis du trenger det. En stor brukstilfelle for Core Animation er å inkorporere en kul 3D-overgang når du bytter fra en visning til en annen i appen din. Jeg føler også at Core Animation kan være et levedyktig alternativ for å bygge enkle ordbaserte eller kortbaserte spill.

Selv om opplæringen vår spenner over to deler, har vi knapt skrapt overflaten av lag og animasjoner (men forhåpentligvis er det bra!). Det er andre typer interessante CALayer underklasser som vi ikke fikk sjansen til å se på. Animasjon er et stort tema i seg selv. Jeg anbefaler å se på WWDC-snakkene om disse emnene, som "Core Animation in Practice" (Sessions 424 og 424, WWDC 2010), "Core Animation Essentials" (Session 421, WWDC 2011), "IOS App Performance - Graphics and Animations" (Session 238, WWDC 2012) og "Optimalisering av 2D-grafikk og animasjonsytelse" (Session 506, WWDC 2012), og deretter grave inn i dokumentasjonen. God læring og appskriving!