Créer une animation de pliage de page 3D effet de pli de page et d'épine

Cette mini-série en deux parties vous apprendra à créer un effet de pliage de page impressionnant avec Core Animation. Dans cet article, vous allez d'abord apprendre à créer une vue de carnet de croquis, puis à appliquer une animation de base de pliage d'épine sur cette vue. Continuer à lire!


Démo finale du projet


Présentation du tutoriel

Travailler avec UIView La classe est au cœur du développement du SDK iOS. Les vues ont à la fois un aspect visuel (c'est-à-dire ce que vous voyez à l'écran) et généralement un aspect de contrôle (interaction de l'utilisateur via des touches et des gestes). L’aspect visuel est en fait géré par une classe appartenant à Core Animation Framework, appelée CALayer (qui à son tour est implémenté via OpenGL, seulement nous ne nous soucions pas de descendre à ce niveau d'abstraction ici). Les objets de calque sont des instances de CALayer classe ou l'une de ses sous-classes. Sans approfondir la théorie (après tout, ce tutoriel est supposé être pratique!), Nous devons garder à l’esprit les points suivants:

  • Chaque vue dans iOS est soutenue par une couche, qui est responsable de son contenu visuel. Par programme, on y accède en tant que propriété de couche de la vue.
  • Il existe une hiérarchie de couches parallèles correspondant à la hiérarchie des vues à l'écran. Cela signifie que si (par exemple) une étiquette est une sous-vue d'un bouton, le calque de l'étiquette est la sous-couche du calque du bouton. Strictement parlant, ce parallélisme n’est valable que tant que nous n’ajouterons pas nos propres sous-couches au mélange..
  • Plusieurs propriétés que nous définissons dans les vues (notamment les vues liées à l'apparence) sont en réalité des propriétés du calque sous-jacent. Cependant, la couche expose certaines propriétés qui ne sont pas disponibles au niveau de la vue. En ce sens, les calques sont plus puissants que les vues.
  • Comparées aux vues, les couches sont des objets plus "légers". Par conséquent, si pour certains aspects de notre application, nous avons besoin d'une visualisation sans interactivité, les couches sont probablement l'option la plus performante..
  • UNE CALayerLe contenu de consiste en un bitmap. Bien que la classe de base soit assez utile, elle contient également plusieurs sous-classes importantes dans Core Animation. Notamment, il y a CAShapeLayer ce qui nous permet de représenter des formes à l'aide de chemins vectoriels.

Alors, que pouvons-nous atteindre avec des couches que nous ne pouvons pas facilement faire avec des vues directement? La 3D, par exemple, est ce sur quoi nous allons nous concentrer. CALayerLes capacités de ce logiciel mettent à votre portée des effets et des animations 3D assez sophistiqués, sans avoir à descendre au niveau OpenGL. C’est l’objectif de ce didacticiel: démontrer un effet 3D intéressant qui donne un petit aperçu de ce que nous pouvons réaliser avec CALayers.


Notre objectif

Nous avons une application de dessin qui permet à un utilisateur de dessiner sur l'écran avec son doigt (pour lequel je vais réutiliser le code d'un de mes précédents tutoriels). Si l'utilisateur effectue ensuite un geste de pincement sur la zone de dessin, il se replie le long d'une ligne verticale longeant le centre de la zone de dessin (un peu comme le dos d'un livre). Le livre plié jette même une ombre sur le fond.

La partie dessin de l'application que j'emprunte n'a pas vraiment d'importance, et nous pourrions très bien utiliser n'importe quelle image pour démontrer l'effet de repliement. Cependant, l'effet constitue une très belle métaphore visuelle dans le contexte d'une application de dessin où l'action de pincement expose un livre à plusieurs feuilles (contenant nos dessins précédents) que nous pouvons parcourir. Cette métaphore est notamment visible dans l'application Paper. Bien que pour les besoins du tutoriel, notre implémentation soit plus simple et moins sophistiquée, mais ce n’est pas si loin… et bien sûr, vous pouvez prendre ce que vous avez appris dans ce tutoriel et le rendre encore meilleur!


Géométrie de couche en bref

Rappelez-vous que dans le système de coordonnées iOS, l'origine se situe dans le coin supérieur gauche de l'écran, l'axe des abscisses augmentant vers la droite et l'axe des ordonnées vers le bas. Le cadre d'une vue décrit son rectangle dans le système de coordonnées de son aperçu. Les couches peuvent également être interrogées pour leur cadre, mais il existe un autre moyen (préféré) de décrire l'emplacement et la taille d'une couche. Nous allons les motiver avec un exemple simple: imaginez deux couches, A et B, sous la forme de morceaux de papier rectangulaires. Vous souhaitez faire de la couche B une sous-couche de A, vous devez donc fixer B au sommet de A en utilisant une épingle, en maintenant les côtés droits parallèles. La broche passe par deux points, l'un en A et l'autre en B. Connaître l'emplacement de ces deux points nous donne un moyen efficace de décrire la position de B par rapport à A. Nous nommerons le point que la goupille perce dans A le " le point d'ancrage "et le point en B" position ". Regardez la figure suivante, pour laquelle nous ferons le calcul:

La figure semble avoir beaucoup à faire, mais ne vous inquiétez pas, nous l'examinerons petit à petit:

  • Le graphique supérieur montre la relation hiérarchique: la couche violette (A, de notre discussion précédente) est transformée en une sous-couche de la couche bleue (B). Le cercle vert avec le plus est l'endroit où A est épinglé dans B. Le position (dans le système de coordonnées de A) est donné comme 32.5, 62.5.
  • Portez maintenant votre attention sur le graphique inférieur. le point d'ancrage est spécifié différemment. Ses relatif taille du calque B, telle que le coin supérieur gauche dans 0.0, 0.0 et le coin inférieur droit dans 1.0, 1.0. Puisque notre épingle est le quart de la distance sur la largeur de B et la moitié de son descente, le point d'ancrage est 0.25, 0.5.
  • Connaissant la taille de B (50 x 45), nous pouvons maintenant calculer les coordonnées du coin supérieur gauche. Par rapport au coin supérieur gauche de B, le point d'ancrage est 0,25 x 50 = 12,5 points dans la direction x et 0,50 x 45 = 22,5 points dans la direction y. Soustrayez-les des coordonnées de la position pour obtenir les coordonnées de l'origine de B dans le système de A: 32.5 - 12.5, 62.5 - 22.5 = 20, 40. Clairement, le cadre de B est 20, 40, 50, 45.

Le calcul est assez simple, alors assurez-vous de bien le comprendre. Cela vous donnera une bonne idée de la relation entre la position, le point d’ancrage et le cadre..

Le point d'ancrage est assez important car lorsque nous effectuons des transformations 3D sur le calque, ces transformations sont effectuées par rapport au point d'ancrage. Nous en reparlerons ensuite (et ensuite vous verrez du code, c'est promis!).


Transformations de calque

Je suis sûr que vous connaissez le concept de transformation tel que la mise à l'échelle, la traduction et la rotation. Dans l'application Photos sous iOS 6, si vous pincez avec deux doigts pour effectuer un zoom avant ou arrière sur une photo de votre album, vous effectuez une transformation de la taille. Si vous faites un mouvement de rotation avec vos deux doigts, la photo pivote et si vous la faites glisser tout en gardant les côtés parallèles, vous obtenez une translation. Animation de base et CALayer atout UIView en vous permettant d'effectuer des transformations en 3D au lieu de simplement 2D. Bien sûr, en 2013, nos écrans iDevice sont toujours en 2D. Par conséquent, les transformations 3D utilisent une astuce géométrique pour tromper nos yeux et les amener à interpréter une image plate comme un objet 3D (le processus n’est pas différent de la représentation d’un objet 3D crayon, vraiment). Pour traiter la 3ème dimension, nous devons utiliser un axe z que nous imaginons percer l'écran de notre appareil et perpendiculairement à celui-ci..

Le point d'ancrage est important car le résultat précis de la même transformation appliquée diffère généralement en fonction de celui-ci. Ceci est particulièrement important - et plus facile à comprendre - avec une transformation de rotation suivant le même angle appliqué par rapport à deux points d'ancrage différents dans le rectangle de la figure ci-dessous (le point rouge et le point bleu). Notez que la rotation est dans le plan de l'image (ou autour de l'axe des z, si vous préférez penser de cette façon).


L'animation de page pliée

Alors, comment pouvons-nous mettre en œuvre l'effet de pliage que nous recherchons? Bien que les couches soient vraiment cool, vous ne pouvez pas les plier au milieu! La solution consiste, comme vous l'avez sûrement compris, à utiliser deux calques, un pour chaque page de chaque côté du pli. Sur la base de ce que nous avons discuté précédemment, définissons à l'avance les propriétés géométriques de ces deux couches:

  • Nous avons choisi un point le long de la "colonne vertébrale du pli" comme point d'ancrage de nos deux couches, car c'est là que se produit notre pli (c'est-à-dire la transformation en rotation). La rotation se déroule autour d’une ligne verticale (c’est-à-dire l’axe des ordonnées) - veillez à bien visualiser cela. C'est bien, pourriez-vous dire, mais pourquoi ai-je choisi le milieu de la colonne vertébrale (au lieu de dire un point en bas ou en haut)? En fait, dans ce cas particulier, cela ne fait aucune différence en ce qui concerne la rotation. Mais nous souhaitons également effectuer une transformation d’échelle (en réduisant légèrement les couches à mesure qu’elles se plient) et en maintenant le point d’ancrage au centre, le livre reste ainsi bien centré lors du pliage. En effet, pour la mise à l'échelle, le point coïncidant avec le point d'ancrage reste fixe.
  • Le point d'ancrage de la première couche est 1.0, 0.5 et pour la deuxième couche est 0.0, 0.5, dans leurs espaces de coordonnées respectifs. Assurez-vous de confirmer cela d'après la figure avant de continuer!
  • Le point situé en dessous du point d’ancrage dans la supercouche (c’est-à-dire la "position") est le point milieu. Ses coordonnées sont donc largeur / 2, hauteur / 2. Rappelez-vous que la propriété de position est en coordonnées standard et non normalisée.
  • La taille de chacune des couches est largeur / 2, hauteur.

la mise en oeuvre

Nous en savons maintenant assez pour écrire du code!

Créez un nouveau projet Xcode avec le modèle "Application vide" et appelez-le. LayerFunTut. Faites-en une application iPad et activez le comptage automatique des références (ARC), mais désactivez les options pour les tests de données de base et les tests unitaires. Sauvegarde le.

Dans la page Cible> Résumé qui apparaît, faites défiler jusqu'à "Orientations d'interface prises en charge" et choisissez les deux orientations de paysage..

Faites défiler la liste jusqu'à "Structures et bibliothèques liées", cliquez sur "+" et ajoutez le cadre principal QuartzCore, indispensable pour Core Animation et CALayers..

Nous allons commencer par incorporer notre application de dessin dans le projet. Créez une nouvelle classe Objective-C appelée CanvasView, en faisant une sous-classe de UIView. Collez le code suivant dans CanvasView.h:

 // // CanvasView.h // #import  @interface CanvasView: UIView @property (nonatomic, strong) UIImage * incrementalImage; @fin

Et puis dans CanvasView.m:

 // // CanvasView.m // #import "CanvasView.h" @implementation CanvasView UIBezierPath * path; Points CGPoint [5]; uint ctr;  - (id) initWithCoder: (NSCoder *) aDecoder if (self = [super initWithCoder: aDecoder]) self.backgroundColor = [UIColor clearColor]; [self setMultipleTouchEnabled: NO]; chemin = [UIBezierPath bezierPath]; [chemin setLineWidth: 6.0];  retourner soi-même;  - (id) initWithFrame: (CGRect) frame self = [super initWithFrame: frame]; if (self) self.backgroundColor = [UIColor clearColor]; [self setMultipleTouchEnabled: NO]; chemin = [UIBezierPath bezierPath]; [chemin setLineWidth: 6.0];  retourner soi-même;  - (void) drawRect: (CGRect) rect [self.incrementalImage drawInRect: rect]; [[UIColor blueColor] setStroke]; [accident vasculaire cérébral];  - (void) touchBegan: (NSSet *) touche withEvent: (UIEvent *) événement ctr = 0; UITouch * touch = [touche anyObject]; pts [0] = [touch locationInView: self];  - (void) toucheMoved: (NSSet *) touche withEvent: (UIEvent *) événement UITouch * touch = [touche anyObject]; CGPoint p = [touch locationInView: self]; ctr ++; pts [ctr] = p; si (ctr == 4) pts [3] = CGPointMake ((pts [2] .x + pts [4] .x) /2.0, (pts [2] .y + pts [4] .y) /2.0 ) [path moveToPoint: pts [0]]; [chemin addCurveToPoint: pts [3] controlPoint1: pts [1] controlPoint2: pts [2]]; [self setNeedsDisplay]; pts [0] = pts [3]; pts [1] = pts [4]; ctr = 1;  - (void) touchesEnded: (NSSet *) touche withEvent: (UIEvent *) event [self drawBitmap]; [self setNeedsDisplay]; [chemin removeAllPoints]; ctr = 0;  - (void) touchesCancelled: (NSSet *) touche withEvent: (UIEvent *) event [auto touchesEnded: touches withEvent: event];  - (void) drawBitmap UIGraphicsBeginImageContextWithOptions (self.bounds.size, NO, 0.0); if (! self.incrementalImage) UIBezierPath * rectpath = [UIBezierPath bezierPathWithRect: self.bounds]; [[UIColor clearColor] setFill]; [remplissage rectpath];  [self.incrementalImage drawAtPoint: CGPointZero]; [[UIColor blueColor] setStroke]; [accident vasculaire cérébral]; self.incrementalImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext ();  @fin

Comme mentionné précédemment, il ne s'agit que du code d'un autre tutoriel que j'ai écrit (avec quelques modifications mineures). Assurez-vous de vérifier si vous n'êtes pas sûr du fonctionnement du code. Pour les besoins de ce tutoriel, l’important est que CanvasView permet à l'utilisateur de dessiner des traits lisses sur l'écran. Nous avons déclaré une propriété appelée incrementalImage qui stocke une version bitmap du dessin de l'utilisateur. C'est l'image avec laquelle nous allons "plier" CALayers.

Il est temps d’écrire le code du contrôleur de vue et de mettre en œuvre les idées que nous avons élaborées précédemment. Une chose dont nous n’avons pas parlé est de savoir comment nous obtenons l’image dessinée dans notre CALayer de sorte que la moitié de l'image soit dessinée dans la page de gauche et l'autre moitié dans la page de droite. Heureusement, ce ne sont que quelques lignes de code, dont je parlerai plus tard.

Créez une nouvelle classe Objective-C appelée ViewController, en faire une sous-classe de UIViewController, et ne cochez aucune des options qui apparaissent.

Collez le code suivant dans ViewController.m

 // // ViewController.m // #import "ViewController.h" #import "CanvasView.h" #import "QuartzCore / QuartzCore.h" #define D2R (x) (x * (M_PI / 180.0)) // macro convertir des degrés en radians @interface ViewController () @end @implementation ViewController CALayer * leftPage; CALayer * rightPage; UIView * curtainView;  - (void) loadView self.view = [[CanvasView alloc]] initWithFrame: [[UIScreen mainScreen] applicationFrame]];  - (void) viewDidLoad [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor];  - (void) viewDidAppear: (BOOL) animated [super viewDidAppear: animated]; self.view.backgroundColor = [UIColor whiteColor]; CGSize size = self.view.bounds.size; leftPage = [couche calque]; rightPage = [couche calque]; leftPage.anchorPoint = (CGPoint) 1.0, 0.5; rightPage.anchorPoint = (CGPoint) 0.0, 0.5; leftPage.position = (CGPoint) size.width / 2.0, size.height / 2.0; rightPage.position = (CGPoint) size.width / 2.0, size.height / 2.0; leftPage.bounds = (CGRect) 0, 0, size.width / 2.0, size.height; rightPage.bounds = (CGRect) 0, 0, size.width / 2.0, size.height; leftPage.backgroundColor = [Couleur blanche UIColor] .CGColor; rightPage.backgroundColor = [UIColor whiteColor] .CGColor; leftPage.borderWidth = 2.0; // bordures ajoutées pour le moment, nous pouvons ainsi distinguer visuellement les pages gauche et droite rightPage.borderWidth = 2.0; leftPage.borderColor = [UIColor darkGrayColor] .CGColor; rightPage.borderColor = [UIColor darkGrayColor] .CGColor; //leftPage.transform = makePerspectiveTransform (); // décommenter plus tard //rightPage.transform = makePerspectiveTransform (); // ne commente pas plus tard curtainView = [[UIView alloc]] initWithFrame: self.view.bounds]; curtainView.backgroundColor = [UIColor scrollViewTexturedBackgroundColor]; [curtainView.layer addSublayer: leftPage]; [curtainView.layer addSublayer: rightPage]; UITapGestureRecognizer * foldTap = [[UITapGestureRecognizer alloc] initWithTarget: action autonome: @selector (fold :)]; [auto.view addGestureRecognizer: foldTap]; UITapGestureRecognizer * unfoldTap = [[UITapGestureRecognizer alloc] initWithTarget: action autonome: @selector (unfold :)]; unfoldTap.numberOfTouchesRequired = 2; [auto.view addGestureRecognizer: unfoldTap];  - (void) fold: (UITapGestureRecognizer *) gr // dessin du bitmap "incrementalImage" dans nos calques 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); // ce rectangle représente la moitié gauche de l'image rightPage.contentsRect = CGRectMake (0.5, 0.0, 0.5, 1.0); // ce rectangle représente la moitié droite de l'image leftPage.transform = CATransform3DScale (leftPage.transform, 0.95, 0.95, 0.95); rightPage.transform = CATransform3DScale (rightPage.transform, 0.95, 0.95, 0.95); leftPage.transform = CATransform3DRotate (leftPage.transform, D2R (7.5), 0,0, 1,0, 0,0); rightPage.transform = CATransform3DRotate (rightPage.transform, D2R (-7,5), 0,0, 1,0, 0,0); [auto.view addSubview: curtainView];  - (void) unfold: (UITapGestureRecognizer *) gr leftPage.transform = CATransform3DIdentity; rightPage.transform = CATransform3DIdentity; // leftPage.transform = makePerspectiveTransform (); // commente plus tard // rightPage.transform = makePerspectiveTransform (); // commenter plus tard [curtainView removeFromSuperview];  // UNCOMMENT LATER: / * CATransform3D makePerspectiveTransform () CATransform3D transform = CATransform3DIdentity; transform.m34 = 1,0 / -2000; transformation de retour;  */ @fin

En ignorant le code commenté pour le moment, vous pouvez voir que la configuration de la couche est exactement comme nous l'avions prévu ci-dessus.

Discutons brièvement de ce code:

  • Nous décidons de passer outre le -viewDidAppear: méthode au lieu de -viewDidLoad (auquel vous êtes peut-être plus habitué), car lorsque cette dernière méthode est appelée, les limites de la vue sont toujours pour le mode portrait - mais notre application fonctionne en mode paysage. Par le temps viewDidAppear: est appelé, les limites ont été définies correctement et nous avons donc placé notre code à cet endroit (nous avons temporairement ajouté des bordures épaisses afin de pouvoir distinguer les couches gauche et droite lorsque nous leur appliquons des transformations).
  • Nous avons ajouté un outil de reconnaissance des gestes qui enregistre un tapotement. Pour chaque tapotement, les pages deviennent légèrement plus petites (95% de leur taille précédente) et les font tourner de 7,5 degrés. Les signes sont différents car l’une des pages tourne dans le sens des aiguilles d’une montre alors que l’autre tourne dans le sens contraire des aiguilles d’une montre. Il faudrait entrer dans les calculs pour voir quel signe correspond à quelle direction, mais comme il n'y a que deux options, il est plus facile d'écrire le code et de vérifier! Par ailleurs, les fonctions de transformation acceptent les angles en radians, nous utilisons donc la macro D2R () convertir des radians en degrés. Une observation importante est que les fonctions qui prennent une transformation dans leur argument (comme CATransform3DScale et CATransform3DRotate) "chaîne ensemble" une transformation avec une autre (la valeur actuelle de la propriété de transformation de couche). D'autres fonctions, telles que CATransform3DMakeRotation, CATransform3DMakeScale, CATransform3DIdentity il suffit de construire la matrice de transformation appropriée. CATransform3DIdentity est la "transformation d'identité" d'une couche lorsque vous en créez une. Il est analogue au nombre "1" dans une multiplication, car appliquer une transformation d'identité à un calque laisse sa transformation inchangée, un peu comme multiplier un nombre par un..
  • En ce qui concerne le dessin, nous définissons la propriété de contenu de nos calques comme étant l'image. Il est très important de noter que nous définissons le rectangle de contenu (normalisé entre 0 et 1 le long de chaque dimension) de telle sorte que chaque page n'affiche que la moitié de l'image qui lui correspond. Ce système de coordonnées normalisé est identique à celui que nous avons discuté précédemment en parlant du point d'ancrage. Vous devriez donc pouvoir calculer les valeurs que nous avons utilisées pour chaque moitié de l'image..
  • le rideau voir object agit simplement comme un conteneur pour les couches de page (plus précisément, elles sont transformées en sous-couches de la couche sous-jacente de curtainView). Rappelez-vous que nous avons déjà calculé l'emplacement et la géométrie des calques, et que ceux-ci sont relatifs au calque de curtainView. Taper une fois ajoute cette vue au-dessus de notre vue canevas et applique la transformation au calque. Un double tapotement le supprime, pour révéler une nouvelle fois le canevas, ainsi que pour rétablir la transformation des calques en transformée d'identité..
  • Notez l'utilisation de CGImage ici - et aussi CGColor plus tôt - au lieu de UIImage et UIColor. Ceci est dû au fait CALayer fonctionne à un niveau inférieur à UIKit et fonctionne avec des types de données "opaques" (ce qui signifie approximativement, ne vous interrogez pas sur leur implémentation sous-jacente!) qui sont définis dans le cadre Core Graphics. Des classes Objective-C comme UIColor et UIImage peuvent être considérés comme des wrappers orientés objet autour de leurs versions CG plus primitives. Par souci de commodité, de nombreux objets UIKit exposent leur type de CG sous-jacent en tant que propriété..

dans le AppDelegate.m fichier, remplacez tout le code par le suivant (la seule chose que nous avons ajoutée est d’inclure le fichier d’en-tête ViewController et de créer une ViewController exemple le contrôleur de vue racine):

 // // AppDelegate.m // #import "AppDelegate.h" #import "ViewController.h" @implementation AppDelegate - (BOOL) application: (UIApplication *) application didFinishLaunchingWithOptions: (NSDictionary *) launchOptions (self.window). [UIWindow alloc] initWithFrame: [[UIScreen mainScreen]]]; self.window.rootViewController = [[ViewController alloc] init]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; retourner OUI;  @fin

Générez le projet et exécutez-le sur le simulateur ou sur votre appareil. Gribouillez un peu sur la toile, puis appuyez sur l'écran avec un seul doigt pour déclencher l'action de reconnaissance des gestes (appuyer avec deux doigts provoque la fin de l'effet 3D et la réapparition de la toile de dessin)..

ne pas assez l'effet que nous recherchons! Que se passe-t-il?


Obtenir notre point de vue

Tout d'abord, notez que les pages deviennent plus petites à chaque pression, le problème ne réside donc pas dans la transformation de mise à l'échelle, mais uniquement dans la rotation. Le problème est que, même si la rotation se produit dans un espace 3D (mathématique), le résultat est projeté sur nos écrans plats de la même manière qu’un objet 3D projette son ombre sur un mur. Pour transmettre de la profondeur, nous devons utiliser une sorte de repère. Le repère le plus important est celui de la perspective: un objet plus proche de nos yeux apparaît plus grand qu’un objet plus éloigné. Les ombres sont une autre bonne idée, et nous y reviendrons dans un instant. Alors, comment pouvons-nous intégrer la perspective dans notre transformation?

Parlons un peu des transformations en premier. Qu'est-ce qu'ils sont vraiment? Mathématiquement, vous devez savoir que si nous représentons les points de notre forme sous forme de vecteurs mathématiques, les transformations géométriques telles que l'échelle, la rotation et la translation sont représentées sous forme de transformations matricielles. Cela signifie que si nous prenons une matrice représentant une transformation et que nous multiplions par un vecteur représentant un point de notre forme, le résultat de la multiplication (également un vecteur) représente le lieu où ce point se termine après avoir été transformé. Nous ne pouvons pas en dire beaucoup plus sans plonger dans la théorie (ce qui vaut la peine d'être étudié, si vous ne l'êtes pas déjà, surtout si vous avez l'intention d'incorporer des effets 3D sympas dans vos applications!).

Qu'en est-il du code? Auparavant, nous définissions la géométrie de la couche en définissant sa point d'ancrage, position, et bornes. Ce que nous voyons à l’écran est la géométrie de la couche après qu’elle a été transformée par sa transformer propriété. Notez les appels de fonction qui ressemblent à layer.transform = //… . Voilà où nous établissons la transformation, ce qui en interne est juste un struct représentant une matrice 4 x 4 de valeurs en virgule flottante. Notez également que les fonctions CATransform3DScale et CATransform3DRotate prenez la transformation actuelle de la couche en tant que paramètre. C'est parce que nous pouvons composer plusieurs transformations ensemble (ce qui signifie simplement que nous multiplions leurs matrices ensemble), le résultat final étant comme si vous aviez effectué ces transformations une par une. Notez que nous ne parlons que du résultat final de la transformation, pas de la manière dont Core Animation anime le calque.!

Pour en revenir au problème de la perspective, nous devons savoir que notre matrice de transformation présente une valeur que nous pouvons modifier pour obtenir l’effet de perspective recherché. Cette valeur est un membre de la structure de transformation, appelée m34 (les nombres indiquent sa position dans la matrice). Pour obtenir l’effet souhaité, nous devons le définir sur un petit nombre négatif..

Décommentez les deux sections commentées de la ViewController.m fichier (le CATransform3D makePerspectiveTransform () fonction et les lignes leftPage.transform = makePerspectiveTransform (); rightPage.transform = makePerspectiveTransform (); et construire à nouveau. Cette fois, l'effet 3D semble plus crédible.

Notez également que lorsque nous changeons la propriété de transformation d'un CALayer, le contrat vient avec une animation "gratuite". C’est ce que nous voulons ici - par opposition à la couche subissant sa transformation brutalement - mais parfois ce n’est pas.

Bien sûr, la perspective ne va pas plus loin que lorsque notre exemple devient plus sophistiqué, nous allons aussi utiliser des ombres! Nous pourrions aussi vouloir arrondir les coins de notre "livre" et masquer nos couches de page avec un CAShapeLayer peut aider avec ça. De plus, nous aimerions utiliser un geste de pincement pour contrôler le pliage / dépliage afin qu'il soit plus interactif. Tout cela sera couvert dans la deuxième partie de cette mini-série de tutoriels.

Je vous encourage à expérimenter le code, en vous référant à la documentation de l'API, et à essayer de mettre en œuvre l'effet souhaité de manière indépendante (vous pourriez même le faire mieux!).

Amusez-vous avec le tutoriel et merci d'avoir lu!

Lire la partie 2