Invoering

Deze tweedelige miniserie leert je hoe je een indrukwekkend pagina-vouwend effect creëert met Core Animation. In deze aflevering leer je de stappen die nodig zijn om de paginavouw die eerder is gemaakt, op te schonen en je bouwt een meer interactieve vouwervaring met knijpgebaren.


Invoering

In het eerste deel van deze tweedelige tutorial namen we een bestaande tekenapp uit de vrije hand waarmee de gebruiker met zijn vinger op het scherm kon schetsen en we implementeerden een 3D vouweffect op het tekenbord dat de visuele indruk gaf van een boek opgevouwen langs de ruggengraat. We hebben dit bereikt met CALayers en transformaties. We hebben het effect gekoppeld aan een tikbeweging die ervoor zorgde dat het vouwen in stappen plaatsvond, zodat de animatie soepel verliep tussen de ene en de volgende stap..

In dit deel van de tutorial zullen we kijken naar het verbeteren van zowel de visuele aspecten van het effect (door gebogen hoeken toe te voegen aan onze pagina's, en onze pagina's een schaduw op de achtergrond te laten geven) als ook om het inklappen meer interactief te maken , door de gebruiker het te laten bedienen met een knijpbeweging. Onderweg leren we over verschillende dingen: a CALayer subklasse genoemd CAShapeLayer, hoe je een laag maskeert om hem een ​​niet-rechthoekige vorm te geven, hoe je een laag inschakelt om een ​​schaduw te werpen, hoe je de eigenschappen van de schaduw configureert, en wat meer over impliciete laaganimatie en hoe je deze animaties mooi kunt laten spelen als we toevoegen gebruikersinteractie in de mix.

Het startpunt voor dit onderdeel is het Xcode-project waar we aan het einde van de eerste tutorial aan begonnen zijn. Laten we doorgaan!


Vormen van de paginahoeken

Bedenk dat elk van de linker- en rechterpagina's een voorbeeld was CALayer. Ze hadden een rechthoekige vorm, geplaatst over de linker- en rechterhelften van het canvas en kwamen aan langs de verticale lijn die door het midden liep. We hebben de inhoud (d.w.z. de uit de vrije hand gemaakte schets van de gebruiker) van de linker- en rechterhelften van het canvas op deze twee pagina's getekend.

Hoewel a CALayer bij de schepping begint met rechthoekige grenzen (zoals UIViews), een van de coole dingen die we kunnen doen met lagen, knipt hun vorm volgens een "masker", zodat ze niet langer beperkt zijn tot rechthoekig! Hoe wordt dit masker gedefinieerd? CALayers hebben een masker eigendom dat een is CALayer waarvan het alpha-kanaal het masker beschrijft dat moet worden gebruikt. Als we een "zacht" masker gebruiken (alfakanaal heeft fractionele waarden), kunnen we de laag gedeeltelijk transparant maken. Als we een "hard" masker gebruiken (d.w.z. met alfawaarden nul of één), kunnen we de laag "knippen", zodat deze een goed gedefinieerde vorm van onze keuze krijgt.

We zouden een extern beeld kunnen gebruiken om ons masker te definiëren. Omdat ons masker een specifieke vorm heeft (rechthoek met afgeronde hoeken), is er een betere manier om het in code te doen. Om een ​​vorm voor ons masker op te geven, gebruiken we een subklasse van CALayer riep CAShapeLayer. CAShapeLayers zijn lagen die elke vorm kunnen hebben die wordt gedefinieerd door een vectorpad van het ondoorzichtige Core Graphics-type CGPathRef. We kunnen dit pad rechtstreeks maken met behulp van de op C gebaseerde Core Graphics API, of - beter gezegd - we kunnen een UIBezierPath object met het Objective-C UIKit-framework. UIBezierPath legt het onderliggende bloot CGPathRef object via zijn CGPath eigendom dat aan ons kan worden toegewezen CAShapeLayer's pad eigenschap, en deze vormlaag kan op zijn beurt worden toegewezen aan ons CALayerhet masker. Gelukkig voor ons, UIBezierPath kan worden geïnitialiseerd met veel interessante vooraf gedefinieerde vormen, waaronder een rechthoek met afgeronde hoeken (waarbij we kiezen welke hoek (en) moet worden afgerond).

Voeg de volgende code toe, na bijvoorbeeld de regel rightPage.transform = makePerspectiveTransform (); in de ViewController.m viewDidAppear: methode:

 // afronding hoeken 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-laag]; CAShapeLayer * rightPageRoundedCornersMask = [CAShapeLayer-laag]; leftPageRoundedCornersMask.frame = leftPage.bounds; rightPageRoundedCornersMask.frame = rightPage.bounds; leftPageRoundedCornersMask.path = leftPageRoundedCornersPath.CGPath; rightPageRoundedCornersMask.path = rightPageRoundedCornersPath.CGPath; leftPage.mask = leftPageRoundedCornersMask; rightPage.mask = rightPageRoundedCornersMask;

De code moet voor zichzelf spreken. Het bezier-pad heeft de vorm van een rechthoek met dezelfde grootte als de laag die wordt gemaskeerd en heeft de juiste hoeken afgerond (linksboven en linksonder voor de linkerpagina, rechtsboven en rechtsonder voor de rechterpagina).

Bouw het project en voer het uit. De paginahoeken moeten nu afgerond zijn ... cool!


Een schaduw toepassen

Het toepassen van een schaduw is ook gemakkelijk, maar er is een probleem wanneer we een schaduw willen nadat we een masker hebben toegepast (zoals we net deden). We zullen dit te zijner tijd tegenkomen!

Een CALayer heeft een shadowPath wat een (je raadt het al) CGPathRef en definieert de vorm van de schaduw. Een schaduw heeft verschillende eigenschappen die we kunnen instellen: de kleur, de offset (in principe welke weg en hoe ver weg deze van de laag valt), de straal (specificatie van de omvang en wazigheid) en de dekking.

Eigenlijk moet worden vermeld dat het niet absoluut noodzakelijk is om de shadowPath omdat het tekensysteem de schaduw uit het samengestelde alfakanaal van de laag zal uitwerken. Dit is echter minder efficiënt en zal meestal de prestatie nadelig beïnvloeden, dus het wordt altijd aanbevolen om de shadowPath eigendom wanneer mogelijk.

Voeg het volgende codeblok onmiddellijk in na het bericht dat we zojuist hebben toegevoegd:

 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;

Bouw de code en voer deze uit. Helaas zal er geen schaduw worden gegoten en zal de uitvoer er precies zo uitzien als voorheen. Hoe zit het daarmee?!

Om te begrijpen wat er aan de hand is, geef commentaar op het eerste blok code dat we in deze zelfstudie hebben geschreven, maar laat de schaduwcode op zijn plaats. Bouw en run nu. OK, we hebben de afgeronde hoeken verloren, maar nu werpen onze lagen een vage schaduw op het uitzicht erachter, wat het gevoel van diepte versterkt.

Wat gebeurt er als we een instellen CALayer's maskereigenschap, wordt het rendergebied van de laag geknipt op het maskergebied. Daarom worden schaduwen (die van nature uit de laag worden weggegooid) niet weergegeven en verschijnen daarom niet.

Voordat we dit probleem proberen op te lossen, moet u er rekening mee houden dat de schaduw voor de rechterpagina bovenaan de linkerpagina is geplaatst. Dit komt omdat het linkerpagina is eerder aan de weergave toegevoegd rechterpagina, daarom is de eerste effectief "achter" de laatste in de tekenvolgorde (ook al zijn het beide broers en zussen). Naast het wijzigen van de volgorde waarin de twee lagen aan de superlaag waren toegevoegd, konden we de volgorde wijzigen zPosition eigenschap van de lagen om de tekenvolgorde expliciet op te geven, door een kleinere drijvende-kommawaarde toe te wijzen aan de laag die we als eerste wilden laten tekenen. We zouden een meer complexe implementatie tegemoet kunnen gaan als we dit effect helemaal willen vermijden, maar omdat het (gelukkig) een mooi schaduweffect op onze pagina oplevert, zijn we blij met de dingen zoals ze zijn!


Zowel schaduwen als een gemaskeerde vorm krijgen

Om dit probleem op te lossen, gebruiken we twee lagen om elke pagina weer te geven, één om schaduwen te genereren en de andere om de getekende inhoud weer te geven in een vormgegeven regio. In termen van de hiërarchie zullen we de schaduwlagen toevoegen als een directe sublaag aan de laag van de achtergrondweergave. Vervolgens voegen we de inhoudslagen toe aan de schaduwlagen. Daarom zullen de schaduwlagen verdubbelen als "containers" voor de inhoudlaag. Al onze geometrische transformaties (te maken met het effect van het omslaan van de pagina) worden toegepast op de schaduwlagen. Aangezien de inhoudlagen ten opzichte van hun containers worden gerenderd, hoeven we hier geen transformaties op toe te passen.

Als je dit eenmaal hebt begrepen, is het schrijven van de code relatief eenvoudig. Maar vanwege alle wijzigingen die we aanbrengen, zal het rommelig zijn om de vorige code te wijzigen, daarom raad ik aan om te vervangen allemaal de code in viewDidAppear: met het volgende:

 - (void) viewDidAppear: (BOOL) geanimeerde [super viewDidAppear: geanimeerd]; self.view.backgroundColor = [UIColor whiteColor]; leftPageShadowLayer = [laag CAShapeLayer]; rightPageShadowLayer = [laag CAShapeLayer]; 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 = [laag CALayer]; rightPage = [laag CALayer]; 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-laag]; CAShapeLayer * rightPageRoundedCornersMask = [CAShapeLayer-laag]; 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 addSlaylayer: leftPage]; [rightPageShadowLayer addSlaylayer: rightPage]; UITapGestureRecognizer * foldTap = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector (fold :)]; [self.view addGestureRecognizer: foldTap]; UITapGestureRecognizer * unfoldTap = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector (unfold :)]; unfoldTap.numberOfTouchesRequired = 2; [self.view addGestureRecognizer: unfoldTap]; 

U moet ook de twee instantievariabelen die overeenkomen met de twee schaduwlagen toevoegen. Pas de code aan het begin van de @implementatie sectie om te lezen:

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

Op basis van onze vorige discussie zou je de code eenvoudig moeten kunnen volgen.

Herinner dat ik eerder vermeldde dat de lagen die de getekende inhoud bevatten als sublagen zouden zijn opgenomen in de schaduwgenererende laag. Daarom moeten we onze wijzigen vouwen: en ontvouwen: methoden om de vereiste transformatie op de schaduwlagen uit te voeren.

 - (void) fold: (UITapGestureRecognizer *) gr // tekening van de bitmap "incremental Image" in onze lagen CGImageRef imgRef = ((CanvasView *) self.view) .incementalImage.CGImage; leftPage.contents = (__bridge id) imgRef; rightPage.contents = (__bridge id) imgRef; leftPage.contentsRect = CGRectMake (0.0, 0.0, 0.5, 1.0); // deze rechthoek vertegenwoordigt de linker helft van de afbeelding rightPage.contentsRect = CGRectMake (0.5, 0.0, 0.5, 1.0); // deze rechthoek vertegenwoordigt de rechter helft van de afbeelding 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];  - (ongeldig) ontvouwen: (UITapGestureRecognizer *) gr leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; leftPageShadowLayer.transform = makePerspectiveTransform (); // uncomment later rightPageShadowLayer.transform = makePerspectiveTransform (); // uncomment later [gordijnView removeFromSuperview]; 

Bouw en voer de app uit om onze pagina's te bekijken met afgeronde hoeken en schaduw!

Zoals eerder, zorgt een tik met één vinger ervoor dat het boek in stappen omhoog wordt gevouwen, terwijl een tik met twee vingers wordt hersteld en het effect wordt verwijderd en de app terugkeert naar de normale tekenmodus.


Op vouw op basis van knijpen

Het ziet er visueel goed uit, maar de tik is niet echt een realistisch gebaar voor een boekvouwmetafoor. Als we denken aan iPad-apps zoals Papier, het vouwen en ontvouwen wordt aangedreven door een knijpbeweging. Laten we dat nu implementeren!

Implementeer de volgende methode in ViewController.m:

 - (void) foldWithPinch: (UIPinchGestureRecognizer *) p if (p.state == UIGestureRecognizerStateBegan) // ... (A) self.view.userInteractionEnabled = NO; CGImageRef imgRef = ((CanvasView *) self.view) .incementalImage.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.schaal: 0,48; // ... (B) schaal = schaal < 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];  

Een korte uitleg over de code, met betrekking tot de labels A, B en C waarnaar wordt verwezen in de code:

  1. Wanneer het knijpgebaar wordt herkend (aangegeven door de staat eigendom dat de waarde aanneemt UIGestureRecognizerStateBegan) we beginnen ons voor te bereiden op de vouwanimatie. De verklaring self.view.userInteractionEnabled = NO; zorgt ervoor dat de eventuele extra aanrakingen die plaatsvinden tijdens het knijpgebaar, er niet voor zorgen dat tekenen op de canvasweergave plaatsvindt. De resterende code moet u bekend voorkomen. We resetten alleen de laagtransformaties.
  2. De schaal eigenschap van de kneep bepaalt de verhouding van de afstand tussen de vingers ten opzichte van het begin van de kneep. Ik besloot de waarde die we zullen gebruiken om de schaal- en rotatietransformaties van onze pagina's te berekenen, te beperken 0.48. De conditie schaal is zo dat een "reverse pinch" (de gebruiker beweegt zijn vingers verder uit elkaar dan het begin van de snuif, overeenkomend met p.scale> 1.0) heeft geen effect. De conditie p.schaal> 0.48 is zo dat wanneer de afstand tussen de vingers ongeveer de helft is van wat het was aan het begin van de kneep, onze vouwanimatie is voltooid en verdere kneuzing geen effect heeft. Ik kies 0.48 in plaats van 0.50 vanwege de manier waarop ik de draaihoek van de rotatietransformatie van de laag bereken. Met een waarde van 0,48 is de rotatiehoek iets minder dan 90 graden, dus het boek vouwt niet volledig en wordt daarom niet onzichtbaar.
  3. Nadat de gebruiker het knijpen heeft beëindigd, verwijderen we de weergave die onze geanimeerde lagen weergeeft vanuit de canvasweergave (zoals eerder) en we herstellen de interactiviteit van het canvas.

Voeg de code toe om aan het eind van de maand een knijpherkenner toe te voegen viewDidAppear:

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

U kunt alle code verwijderen die betrekking heeft op de twee tikherkenners ViewController.m omdat we die niet meer nodig hebben.

Bouw en ren. Als u aan het testen bent op de simulator in plaats van op het apparaat, onthoud dan dat u de optietoets ingedrukt moet houden om twee vingerbewegingen zoals knijpen te simuleren.

In principe werkt onze pinch-based folding-ontvouwing, maar je zult het vast wel merken (vooral als je aan het testen bent op een fysiek apparaat) dat de animatie traag is en achterblijft bij de snuif, waardoor de illusie dat het knijpen afneemt stuurt de animatie voor vouwen en uitvouwen. Dus wat is er aan de hand?


De animaties goed krijgen

Denk aan de impliciete animatie van de transformatie waarvan we 'gratis' genoten toen de animatie werd aangestuurd door de tik? Nou, het blijkt dat dezelfde impliciete animatie nu een belemmering is geworden! Over het algemeen willen we, wanneer we een animatie via een gebaar willen besturen, zoals een weergave die over het scherm wordt gesleept met een sleep (panning) gebaar (of de case met onze app hier), impliciete animaties annuleren. Laten we ervoor zorgen dat we begrijpen waarom, gezien het geval dat een gebruiker met zijn vinger een weergave over het scherm sleept:

  1. De uitzicht is op positie p0 om te beginnen (d.w.z.. view.position = p0 waarbij p0 = (x0, y0) en is a CGPoint)
  2. Wanneer UIKit vervolgens de aanraking van de gebruiker samplet op het scherm, sleept hij zijn vinger naar een andere positie, zeg p1.
  3. Impliciete animatie wordt geactiveerd, waardoor het animatiesysteem de positiewijziging van begint te animeren uitzicht van p0 naar p1 met de "ontspannen" duur van 0,25 seconden. De animatie is echter amper gestart en de gebruiker heeft zijn vinger al naar een nieuwe positie gesleept, p2. De animatie moet worden geannuleerd en een nieuwe wordt gestart naar positie p2.
  4. … enzovoort!

Omdat het panning-gebaar van de gebruiker (in ons hypothetische voorbeeld) in feite een continu gebaar is, hoeven we alleen maar de positie van de weergave te wijzigen in overeenstemming met het gebaar van de gebruiker om de illusie van een responsanimatie te behouden! Precies hetzelfde argument is van toepassing op onze paginavouwen met knelpunten hier. Ik heb alleen het sleepvoorbeeld genoemd omdat het eenvoudiger leek om uit te leggen wat er aan de hand was.

Er zijn verschillende manieren om impliciete animaties uit te schakelen. We kiezen de eenvoudigste, waarbij we onze code in a moeten omwikkelen CATransaction blokkeer en roep de klassenmethode op [CATransaction setDisableActions: YES]. Dus, wat is a CATransaction hoe dan ook? In eenvoudigste bewoordingen, CATransaction "bundelt" eigendomswijzigingen die moeten worden geanimeerd en bundelt vervolgens hun waarden op het scherm en verwerkt alle timingaspecten. In feite doet het al het harde werk in verband met het weergeven van onze animaties op het scherm voor ons! Hoewel we nog niet expliciet een animatietransactie hebben gebruikt, is er altijd een impliciete aanwezig wanneer een animatie gerelateerde code wordt uitgevoerd. Wat we nu moeten doen is om de animatie af te ronden met een aangepast CATransaction blok.

In de pinchToFold: methode, voeg deze regels toe:

 [CATransaction begin]; [CATransaction setDisableActions: YES];

Op de site van de reactie // SOMMIGE CODE GAAN HIER (1),
voeg de regel toe:

 [CATransaction commit];

Als je de app nu bouwt en uitvoert, merk je dat het vouwen en uitvouwen veel vloeiender en responsiever is!

Een ander animatie-gerelateerd probleem dat we moeten aanpakken, is dat van ons ontvouwen: methode animeert het herstel van het boek niet naar zijn afgevlakte staat wanneer het knijpende gebaar eindigt. U kunt klagen dat we in onze code niet echt de moeite hebben genomen om de transformeren in het "toen" blok van onze if (p.state == UIGestureRecognizerStateEnded) uitspraak. Maar je bent van harte welkom om te proberen de statements in te voegen die de transformatie van de laag vóór de statement resetten [canvasView removeFromSuperview]; om te zien of de lagen deze eigenschap animeren wijzigen (spoiler: dat doen ze niet!).

De reden waarom de lagen de wijziging van de eigenschap niet animeren, is dat elke wijziging in de eigenschappen die we in dat codeblok zouden kunnen uitvoeren, in dezelfde (impliciete) mate zou worden samengevoegd. CATransaction als de code om de laaghostingsweergave te verwijderen (canvasView) van het scherm. Het verwijderen van de weergave zou onmiddellijk gebeuren - het is tenslotte geen geanimeerde wijziging - en geen animatie in subvisies (of sublagen die aan de laag zijn toegevoegd) zou voorkomen.

Nogmaals, een expliciete CATransaction blok komt ons te hulp! EEN CATransaction heeft een voltooiingsblok dat alleen wordt uitgevoerd na eventuele eigenschapswijzigingen die verschijnen nadat het animeren is voltooid.

Verander de code na de if (p.state == UIGestureRecognizerStateEnded) clausule, zodat de if-statement als volgt luidt:

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

Merk op dat ik besloot om de duur van de animatie omgekeerd te veranderen ten opzichte van de schaal dus hoe groter de mate van de vouw op het moment dat het gebaar eindigt, hoe meer tijd de teruggaande animatie moet duren.

Het belangrijkste om te begrijpen hier is dat alleen na de transformeren veranderingen hebben geanimeerd doet de code in de completionBlock uit te voeren. De CATransaction klasse heeft andere eigenschappen die u kunt gebruiken om een ​​animatie te configureren zoals u het wilt. Ik stel voor dat je de documentatie voor meer bekijkt.

Bouw en ren. Ten slotte ziet onze animatie er niet alleen goed uit, maar reageert hij ook goed op gebruikersinteractie!


Conclusie

Ik hoop dat deze tutorial je ervan heeft overtuigd dat Core Animation Layers een realistische keuze zijn om met weinig moeite redelijk geavanceerde 3D-effecten en animaties te bereiken. Met enige optimalisatie zou je in staat moeten zijn om een ​​paar honderd lagen op het scherm tegelijkertijd te animeren als dat nodig is. Een handig voorbeeld van Core Animation is het opnemen van een coole 3D-overgang bij het overschakelen van de ene weergave naar de andere in uw app. Ik ben ook van mening dat Core Animation een haalbare optie kan zijn voor het bouwen van eenvoudige op woord gebaseerde of op kaarten gebaseerde spellen.

Hoewel onze tutorial twee delen besloeg, hebben we nauwelijks het oppervlak van lagen en animaties gekrast (maar hopelijk is dat goed!). Er zijn andere soorten interessant CALayer subklassen die we niet hebben kunnen bekijken. Animatie is een groot onderwerp op zich. Ik raad aan om de WWDC-gesprekken over deze onderwerpen te bekijken, zoals 'Core Animation in Practice' (Sessions 424 en 424, WWDC 2010), 'Core Animation Essentials' (Session 421, WWDC 2011), 'iOS App-prestaties - Graphics en animaties'. (Sessie 238, WWDC 2012) en "Optimalisatie van 2D-graphics en prestaties van animaties" (Sessie 506, WWDC 2012), en dan ingaan op de documentatie. Gelukkig leren en schrijven van apps!