Einführung

Diese zweiteilige Mini-Serie zeigt Ihnen, wie Sie mit Core Animation einen beeindruckenden Seitenfalteffekt erzeugen. In diesem Teil lernen Sie die Schritte kennen, die erforderlich sind, um die zuvor erstellte Seitenfalte zu polieren, und Sie bauen ein interaktiveres Falt-Erlebnis mit Pinch-Gesten auf.


Einführung

Im ersten Teil dieses zweiteiligen Lernprogramms haben wir eine vorhandene Freihand-Zeichnungs-App verwendet, mit der der Benutzer mit dem Finger auf dem Bildschirm skizzieren konnte. Außerdem wurde ein 3D-Falteffekt auf der Zeichenfläche implementiert, der den visuellen Eindruck eines Buches vermittelt entlang des Rückens gefaltet. Wir haben dies mit erreicht CALayers und Transformationen. Wir haben den Effekt mit einer Tap-Geste verbunden, die dazu führte, dass die Faltung in Schritten erfolgte und sich nahtlos zwischen einer Stufe und der nächsten bewegte.

In diesem Teil des Tutorials werden wir sowohl die visuellen Aspekte des Effekts verbessern (durch das Hinzufügen von gekrümmten Ecken zu unseren Seiten als auch den Schattenwurf unserer Seiten auf den Hintergrund) und die Interaktion der Faltung und Entfaltung verbessern , indem der Benutzer es mit einer Prise-Geste steuern lässt. Auf dem Weg erfahren wir einige Dinge: a CALayer Unterklasse aufgerufen CAShapeLayer, Wie wird eine Ebene mit einer nicht rechteckigen Form maskiert? Wie kann eine Ebene einen Schatten werfen? Wie werden die Eigenschaften des Schattens konfiguriert? Ein wenig mehr über implizite Layer-Animationen. Wie können diese Animationen beim Hinzufügen schön aussehen? Benutzerinteraktion in den Mix.

Ausgangspunkt für diesen Teil ist das Xcode-Projekt, mit dem wir am Ende des ersten Tutorials endeten. Lass uns fortfahren!


Seitenecken gestalten

Denken Sie daran, dass jede der linken und rechten Seiten eine Instanz von war CALayer. Sie waren rechteckig und lagen über der linken und rechten Hälfte der Leinwand. Sie stießen an der vertikalen Linie entlang, die durch die Mitte verläuft. Wir haben den Inhalt (d. H. Die Freihandskizze des Benutzers) der linken und rechten Hälfte der Leinwand in diese beiden Seiten gezeichnet.

Obwohl a CAlayer Bei der Erstellung beginnt mit rechteckigen Begrenzungen (wie UIViews), eines der coolsten Dinge, die wir für Ebenen tun können, ist das Schneiden ihrer Form gemäß einer "Maske", so dass sie nicht länger auf die Rechteckigkeit beschränkt sind! Wie wird diese Maske definiert?? CALayers haben eine Maske Eigenschaft, die ein ist CALayer dessen Alphakanal des Inhalts beschreibt die zu verwendende Maske. Wenn wir eine "weiche" Maske verwenden (Alphakanal hat gebrochene Werte), können wir die Ebene teilweise transparent machen. Wenn wir eine "harte" Maske verwenden (d. H. Mit Alpha-Werten von Null oder Eins), können wir die Ebene "abschneiden", sodass sie eine genau definierte Form unserer Wahl erhält.

Wir könnten ein externes Bild verwenden, um unsere Maske zu definieren. Da unsere Maske jedoch eine bestimmte Form hat (Rechteck mit abgerundeten Ecken), ist es besser, dies im Code zu tun. Um eine Form für unsere Maske festzulegen, verwenden wir eine Unterklasse von CALayer namens CAShapeLayer. CAShapeLayers sind Ebenen, die eine beliebige Form haben können, die durch einen Vektorpfad des undurchsichtigen Typs der Kerngrafik definiert wird CGPathRef. Wir können diesen Pfad entweder direkt mit der C-basierten Core Graphics API erstellen, oder - bequemer - einen UIBezierPath Objekt mit dem Objective-C UIKit-Framework. UIBezierPath legt den Basiswert frei CGPathRef Objekt über seine CGPath Eigentum, das unserem zugeordnet werden kann CAShapeLayer's Pfad Eigenschaft, und diese Formebene kann wiederum als unsere zugeordnet werden CALayerMaske. Zum Glück für uns, UIBezierPath kann mit vielen interessanten vordefinierten Formen initialisiert werden, einschließlich eines Rechtecks ​​mit abgerundeten Ecken (wobei wir auswählen, welche Ecke (n) abgerundet werden sollen).

Fügen Sie den folgenden Code nach der Zeile ein rightPage.transform = makePerspectiveTransform (); in dem ViewController.m viewDidAppear: Methode:

 // Ecken abrunden UIBezierPath * leftPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: leftPage.bounds byRoundingCorners: UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii: CGSize. UIBezierPath * rightPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: rightPage.bounds byRoundingCorners: UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii: CGSizeMake (25.0, 25.0)]; CAShapeLayer * leftPageRoundedCornersMask = [CAShapeLayer-Ebene]; CAShapeLayer * rightPageRoundedCornersMask = [CAShapeLayer-Ebene]; leftPageRoundedCornersMask.frame = leftPage.bounds; rightPageRoundedCornersMask.frame = rightPage.bounds; leftPageRoundedCornersMask.path = leftPageRoundedCornersPath.CGPath; rightPageRoundedCornersMask.path = rightPageRoundedCornersPath.CGPath; leftPage.mask = leftPageRoundedCornersMask; rightPage.mask = rightPageRoundedCornersMask;

Der Code sollte selbsterklärend sein. Der Bezier-Pfad hat die Form eines Rechtecks ​​mit der gleichen Größe wie die maskierte Ebene und die entsprechenden Ecken sind abgerundet (oben links und unten links für die linke Seite, oben rechts und unten rechts für die rechte Seite)..

Baue das Projekt und führe es aus. Die Seitenecken sollten jetzt gerundet sein ... cool!


Einen Schatten anwenden

Das Anwenden eines Schattens ist auch einfach, aber es gibt ein Problem, wenn wir nach dem Anwenden einer Maske einen Schatten wünschen (wie wir es gerade getan haben). Wir werden zu gegebener Zeit darauf stoßen!

Ein CALayer hat eine shadowPath was ist ein (du hast es erraten) CGPathRef und definiert die Form des Schattens. Ein Schatten hat mehrere Eigenschaften, die festgelegt werden können: Farbe, Versatz (im Wesentlichen in welche Richtung und wie weit er von der Ebene entfernt ist), Radius (Angabe von Ausdehnung und Unschärfe) und Deckkraft.

Eigentlich sollte erwähnt werden, dass es nicht zwingend notwendig ist, das zu setzen shadowPath Das Zeichnungssystem ermittelt den Schatten aus dem zusammengesetzten Alphakanal der Ebene. Dies ist jedoch weniger effizient und führt in der Regel zu einer Beeinträchtigung der Leistung. Es wird daher immer empfohlen, den Wert für die Einstellung festzulegen shadowPath Eigenschaft wenn möglich.

Fügen Sie den folgenden Code unmittelbar nach dem gerade hinzugefügten ein:

 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;

Erstellen Sie den Code und führen Sie ihn aus. Leider wird kein Schatten geworfen und die Ausgabe sieht genauso aus wie zuvor. Was ist damit?!

Um zu verstehen, was los ist, Kommentieren Sie den ersten Codeblock aus, den wir in diesem Lernprogramm geschrieben haben, aber lassen Sie den Schattencode bestehen. Jetzt bauen und ausführen. OK, wir haben die abgerundeten Ecken verloren, aber jetzt werfen unsere Ebenen einen nebligen Schatten auf die Sicht hinter ihnen und erhöhen so das Gefühl der Tiefe.

Was passiert, ist das, wenn wir ein setzen CALayerBei der Masken-Eigenschaft der Ebene wird der Render-Bereich der Ebene auf den Maskenbereich begrenzt. Daher werden Schatten (die natürlich von der Ebene weggeworfen werden) nicht gerendert und erscheinen daher nicht.

Bevor Sie versuchen, dieses Problem zu lösen, beachten Sie, dass der Schatten für die rechte Seite oben auf der linken Seite geworfen wurde. Das liegt daran, dass die leftPage wurde der Ansicht zuvor hinzugefügt rechte Seite, Daher ist der erstere effektiv hinter der letzteren in der Zeichnungsreihenfolge (obwohl sie beide Geschwisterebenen sind). Wir können nicht nur die Reihenfolge ändern, in der die beiden Schichten der Superschicht hinzugefügt wurden, sondern auch die zPosition Eigenschaft der Ebenen, die Zeichnungsreihenfolge explizit anzugeben, indem der Ebene, die wir zuerst zeichnen wollten, ein kleinerer Gleitkommawert zugewiesen wird. Wir würden uns für eine komplexere Implementierung interessieren, wenn wir diesen Effekt insgesamt vermeiden möchten. Da diese Seite jedoch (zufällig) einen schönen Schatteneffekt auf unsere Seite bringt, sind wir mit den Dingen so, wie sie sind!


Beide Schatten und eine maskierte Form erhalten

Um dieses Problem zu lösen, verwenden wir zwei Ebenen zur Darstellung jeder Seite, eine zur Erzeugung von Schatten und die andere zur Darstellung des gezeichneten Inhalts in einem geformten Bereich. In Bezug auf die Hierarchie fügen wir die Schattenebenen als direkte Unterebenen der Ebene der Hintergrundansicht hinzu. Dann fügen wir den Schattenebenen die Inhaltsebenen hinzu. Daher werden die Schattenebenen als "Container" für die Inhaltsebene verdoppelt. Alle unsere geometrischen Transformationen (für den Seitenwechseleffekt) werden auf die Schattenebenen angewendet. Da die Inhaltsebenen relativ zu ihren Containern gerendert werden, müssen wir keine Transformationen auf sie anwenden.

Wenn Sie dies verstanden haben, ist das Schreiben des Codes relativ einfach. Aber wegen all der Änderungen, die wir vornehmen, wird es unangenehm sein, den vorherigen Code zu ändern. Ich empfehle Ihnen, ihn zu ersetzen alles der Code in viewDidAppear: mit den folgenden:

 - (void) viewDidAppear: (BOOL) animiert [super viewDidAppear: animiert]; self.view.backgroundColor = [UIColor whiteColor]; leftPageShadowLayer = [CAShapeLayer-Ebene]; rightPageShadowLayer = [CAShapeLayer-Ebene]; 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 nachRoundingCorners: UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii: CGSizeMake. UIBezierPath * rightPageRoundedCornersPath = [UIBezierPath bezierPathWithRoundedRect: rightPageShadowLayer.bounds vonRoundingCorners: 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-Ebene]; rightPage = [CALayer-Ebene]; 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-Ebene]; CAShapeLayer * rightPageRoundedCornersMask = [CAShapeLayer-Ebene]; 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-Zuordnung] 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-Zuordnung] initWithTarget: Selbstaktion: @selector (fold :)]; [self.view addGestureRecognizer: foldTap]; UITapGestureRecognizer * unfoldTap = [[UITapGestureRecognizer-Zuordnung] initWithTarget: Eigenaktion: @selector (entfalten :)]; unfoldTap.numberOfTouchesRequired = 2; [self.view addGestureRecognizer: unfoldTap]; 

Sie müssen auch die beiden Instanzvariablen hinzufügen, die den beiden Schattenebenen entsprechen. Ändern Sie den Code am Anfang des @Implementierung zu lesender Abschnitt:

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

Basierend auf unserer vorherigen Diskussion sollten Sie den Code unkompliziert finden.

Ich erinnere daran, dass ich zuvor erwähnt hatte, dass die Ebenen, die den gezeichneten Inhalt enthalten, als Unterebenen der Schattenerzeugungsebene enthalten wären. Deshalb müssen wir unser ändern falten: und entfalten: Methoden zur Durchführung der erforderlichen Transformation auf den Schattenebenen.

 - (void) fold: (UITapGestureRecognizer *) gr // Zeichnet die Bitmap "incrementalImage" in unsere Ebenen. 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); // Dieses Rechteck repräsentiert die linke Hälfte des Bildes rightPage.contentsRect = CGRectMake (0.5, 0.0, 0.5, 1.0); // Dieses Rechteck steht für die rechte Hälfte des Bildes 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) entfalten sich: (UITapGestureRecognizer *) gr leftPageShadowLayer.transform = CATransform3DIdentity; rightPageShadowLayer.transform = CATransform3DIdentity; leftPageShadowLayer.transform = makePerspectiveTransform (); // später unkommentiert rightPageShadowLayer.transform = makePerspectiveTransform (); // unkommentieren später [curtainView removeFromSuperview]; 

Erstellen und starten Sie die App, um unsere Seiten mit abgerundeten Ecken und Schatten zu überprüfen!

Wie zuvor bewirkt das Tippen mit einem Finger, dass sich das Buch schrittweise zusammenfaltet, während durch Wiederholen mit zwei Fingern der Effekt entfernt und die App in den normalen Zeichenmodus versetzt wird.


Einbinden auf Prise-Basis

Die Dinge sehen optisch gut aus, aber der Abgriff ist für eine Buch-Metapher nicht wirklich eine realistische Geste. Wenn wir über iPad-Apps nachdenken Papier, Das Falten und Entfalten wird durch eine Prise Geste gesteuert. Lassen Sie uns das jetzt umsetzen!

Implementieren Sie die folgende Methode in ViewController.m:

 - (void) foldWithPinch: (UIPinchGestureRecognizer *) p if (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) scale = scale < 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];  

Eine kurze Erläuterung zum Code in Bezug auf die im Code genannten Bezeichnungen A, B und C:

  1. Wenn die Pinch-Geste erkannt wird (durch ihr gekennzeichnet) Zustand Eigenschaft, die den Wert annimmt UIGestureRecognizerStateBegan) fangen wir an, uns auf die Faltenanimation vorzubereiten. Die Aussage self.view.userInteractionEnabled = NO; stellt sicher, dass die zusätzlichen Berührungen während der Pinch-Geste nicht dazu führen, dass in der Leinwandansicht gezeichnet wird. Der verbleibende Code sollte Ihnen bekannt sein. Wir setzen gerade die Ebenentransformationen zurück.
  2. Das Rahmen Die Eigenschaft der Quetschung bestimmt das Verhältnis des Abstandes zwischen den Fingern in Bezug auf den Beginn der Quetschung. Ich habe mich entschieden, den Wert zu kürzen, den wir für die Berechnung der Skalierungs- und Rotationsumwandlungen unserer Seiten zwischen verwenden 0,48. Die Bedingung Skala ist so, dass eine "umgekehrte Quetschung" (der Benutzer bewegt seine Finger weiter auseinander als der Beginn der Quetschung, entsprechend p.scale> 1,0) hat keine Wirkung. Die Bedingung p.scale> 0,48 Wenn der Abstand zwischen den Fingern ungefähr halb so groß ist wie zu Beginn der Prise, ist unsere Faltanimation abgeschlossen und jede weitere Prise hat keine Wirkung. Ich wähle 0,48 anstelle von 0,50, da ich den Drehwinkel der Rotationstransformation der Ebene berechne. Bei einem Wert von 0,48 ist der Rotationswinkel etwas weniger als 90 Grad, sodass das Buch nicht vollständig gefaltet und somit nicht unsichtbar wird.
  3. Nachdem der Benutzer den Vorgang beendet hat, entfernen wir die Ansicht, die unsere animierten Ebenen (wie zuvor) aus der Leinwandansicht darstellt, und stellen die Interaktivität der Leinwand wieder her.

Fügen Sie den Code hinzu, um am Ende eine Pinch-Erkennung hinzuzufügen viewDidAppear:

 UIPinchGestureRecognizer * pinch = [[UIPinchGestureRecognizer-Zuordnung] initWithTarget: Selbstaktion: @selector (foldWithPinch :)]; [self.view addGestureRecognizer: pinch];

Sie können den gesamten Code, der sich auf die beiden Tipperkenner bezieht, von entfernen ViewController.m weil wir die nicht mehr brauchen werden.

Bauen und ausführen Wenn Sie anstelle des Geräts am Simulator testen, denken Sie daran, dass Sie die Optionstaste gedrückt halten müssen, um zwei Fingerbewegungen zu simulieren, z. B. Kneifen.

Im Prinzip funktioniert unser Falt-Entfalten auf Pinch-Basis, aber Sie werden (vor allem, wenn Sie auf einem physischen Gerät testen) feststellen, dass die Animation langsam ist und hinter dem Pinch zurückbleibt, wodurch die Illusion geschwächt wird, dass der Pinch drückt treibt die Falt-Entfaltungs-Animation an. So was ist los?


Die Animationen richtig machen

Erinnern Sie sich an die implizite Animation der Transformation, die wir "kostenlos" genossen haben, als die Animation durch das Tippen gesteuert wurde? Nun, es stellt sich heraus, dass die gleiche implizite Animation jetzt zum Hindernis geworden ist! Wenn Sie eine Animation durch eine Geste steuern möchten, z. B. eine Ansicht, die mit einer Ziehbewegung (Schwenken) (oder dem Fall mit unserer App hier) über den Bildschirm gezogen wird, möchten wir implizite Animationen abbrechen. Stellen Sie sicher, dass wir verstehen, warum, in Anbetracht der Tatsache, dass ein Benutzer eine Ansicht mit dem Finger über den Bildschirm zieht:

  1. Das Aussicht ist auf Position p0 anfangs (d. h. view.position = p0 wobei p0 = (x0, y0) und a ist CGPoint)
  2. Wenn UIKit als nächstes die Berührung des Benutzers auf dem Bildschirm abtastet, hat er seinen Finger an eine andere Position gezogen, etwa p1.
  3. Implizite Animation setzt ein, wodurch das Animationssystem die Positionsänderung von animiert Aussicht von p0 zu p1 mit der "entspannten" Dauer von 0,25 Sekunden. Die Animation hat jedoch gerade erst begonnen und der Benutzer hat seinen Finger bereits an eine neue Position gezogen, p2. Die Animation muss abgebrochen und eine neue in Richtung p2 begonnen werden.
  4. … und so weiter und so fort!

Da die Schwenkgeste des Benutzers (in unserem hypothetischen Beispiel) effektiv eine kontinuierliche ist, müssen wir nur die Position der Ansicht in Schritt mit der Geste des Benutzers ändern, um die Illusion einer Antwortanimation aufrechtzuerhalten! Genau das gleiche gilt für unsere Seitenfalte mit Notlage. Ich habe nur das schleppende Beispiel erwähnt, weil es einfacher schien zu erklären, was los war.

Es gibt verschiedene Möglichkeiten, implizite Animationen zu deaktivieren. Wir wählen den einfachsten Code aus, indem wir unseren Code in ein packen CATransaction Blockieren und Aufrufen der Klassenmethode [CATransaction setDisableActions: YES]. Also, was ist ein? CATransaction sowieso? Im einfachsten Sinne, CATransaction Eigenschaftsänderungen "bündeln", die animiert werden müssen, und aktualisieren nacheinander ihre Werte auf dem Bildschirm, wobei alle zeitlichen Aspekte berücksichtigt werden. Tatsächlich erledigt es alle harte Arbeit, die für das Rendern unserer Animationen auf dem Bildschirm erforderlich ist. Obwohl wir bisher noch keine explizite Animationstransaktion verwendet haben, ist immer implizit eine Transaktion vorhanden, wenn animationsbezogener Code ausgeführt wird. Jetzt müssen wir die Animation mit einem benutzerdefinierten Code abschließen CATransaction Block.

In dem pinchToFold: Methode, fügen Sie diese Zeilen hinzu:

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

An der Stelle des Kommentars // EINIGE CODE WIRD HIER GEHEN (1),
füge die Zeile hinzu:

 [CATransaction-Commit];

Wenn Sie die App jetzt erstellen und ausführen, werden Sie feststellen, dass das Falten und Entfalten viel fließender und reaktionsschneller ist!

Ein weiteres Animationsproblem, das wir angehen müssen, ist das Entfaltung: Die Methode animiert die Wiederherstellung des Buchs nicht in den abgeflachten Zustand, wenn die Pinch-Geste beendet ist. Sie können sich darüber beschweren, dass wir uns in unserem Code nicht wirklich darum gekümmert haben verwandeln im "dann" -Block unseres if (p.state == UIGestureRecognizerStateEnded) Aussage. Sie können jedoch gerne die Anweisungen einfügen, die die Transformation der Ebene vor der Anweisung zurücksetzen [canvasView removeFromSuperview]; um zu sehen, ob die Layer diese Eigenschaft animieren (Spoiler: sie werden nicht!).

Der Grund, warum die Layer die Eigenschaftsänderung nicht animieren, besteht darin, dass jede Eigenschaftsänderung, die wir in diesem Codeblock ausführen könnten, in demselben Bereich (implizit) zusammengefasst wird. CATransaction als Code zum Entfernen der Layer-Hosting-Ansicht (canvasView) vom Bildschirm aus. Das Entfernen der Ansicht würde sofort erfolgen - es handelt sich schließlich nicht um eine animierte Änderung -, und es würde keine Animation in Unteransichten (oder in ihrer Ebene hinzugefügten Unterebenen) auftreten.

Wieder eine explizite CATransaction Block kommt zu unserer Rettung! EIN CATransaction hat einen Beendigungsblock, der erst ausgeführt wird, wenn sich nach dem Animieren Änderungen an den Eigenschaften ergeben.

Ändern Sie den Code nach dem if (p.state == UIGestureRecognizerStateEnded) Klausel, damit die if-Anweisung wie folgt lautet:

 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]; 

Beachten Sie, dass ich mich dazu entschlossen habe, die Animationsdauer in Bezug auf den Wert umgekehrt zu ändern Rahmen Je größer der Grad der Falte zu dem Zeitpunkt ist, zu dem die Geste endet, desto mehr Zeit sollte die Wiederherstellungsanimation beanspruchen.

Das Entscheidende hier zu verstehen ist das erst nach dem verwandeln Änderungen haben animiert den Code im CompletionBlock ausführen. Das CATransaction Klasse hat andere Eigenschaften, die Sie verwenden können, um eine Animation genau so zu konfigurieren, wie Sie es möchten. Ich schlage vor, Sie schauen sich die Dokumentation an, um mehr zu erfahren.

Bauen und ausführen Schließlich sieht unsere Animation nicht nur gut aus, sondern reagiert auch richtig auf die Benutzerinteraktion!


Fazit

Ich hoffe, dieses Tutorial hat Sie überzeugt, dass Core Animation Layers eine realistische Wahl ist, um mit wenig Aufwand recht anspruchsvolle 3D-Effekte und Animationen zu erzielen. Mit etwas Optimierung sollten Sie in der Lage sein, einige hundert Ebenen gleichzeitig auf dem Bildschirm zu animieren, wenn Sie dies benötigen. Ein großartiger Anwendungsfall für Core Animation ist das Einbinden eines coolen 3D-Übergangs, wenn Sie in Ihrer App von einer Ansicht zur anderen wechseln. Ich denke auch, dass Core Animation eine praktikable Option für das Erstellen einfacher wort- oder kartenbasierter Spiele ist.

Obwohl unser Tutorial aus zwei Teilen bestand, haben wir die Oberfläche von Ebenen und Animationen kaum zerkratzt (aber das ist hoffentlich eine gute Sache!). Es gibt andere interessante Arten CALayer Unterklassen, die wir nicht sehen konnten. Animation ist ein großes Thema für sich. Ich empfehle die WWDC-Vorträge zu diesen Themen, z. B. "Core-Animation in der Praxis" (Sitzungen 424 und 424, WWDC 2010), "Core Animation Essentials" (Sitzung 421, WWDC 2011), "iOS-App-Leistung - Grafiken und Animationen". (Sitzung 238, WWDC 2012) und "Optimieren der Leistung von 2D-Grafiken und Animationen" (Sitzung 506, WWDC 2012) und dann die Dokumentation. Viel Spaß beim Lernen und beim App-Schreiben!