Comment appliquer une transformation de perspective à une UIView?

199

Je cherche à effectuer une transformation de perspective sur un UIView (comme vu dans coverflow)

Est-ce que quelqu'un sait si c'est possible?

J'ai cherché à utiliser CALayeret à parcourir tous les podcasts pragmatiques de Core Animation, mais je ne sais toujours pas comment créer ce type de transformation sur un iPhone.

Toute aide, pointeur ou exemple d'extrait de code serait vraiment apprécié!

Nick Cartwright
la source
Je ne sais pas si cela convient à toi ou pas mais quand je fais de l'animation, j'ai trouvé que m14 est mieux pour moi Juste pour référence :)
b123400
Merci! - quelles valeurs donnez-vous à cela?
Nick Cartwright
1
En réglant m14, vous faîtes en fait bizarrement le tronc de la vue sur l'axe X. Le calcul est parfaitement valide, mais cela rendra les choses confuses dans le cas général.
moelleux
Un très, très bon article sur la transformation et la perspective de CALayer 3D, y compris une explication approfondie du champ m34, peut être trouvé dans cet excellent article: core-animation-3d-model
Markus

Réponses:

329

Comme l'a dit Ben, vous devrez travailler avec le UIView'scalque, en utilisant un CATransform3Dpour effectuer le layer's rotation. L'astuce pour faire fonctionner la perspective, comme décrit ici , est d'accéder directement à l'une des matrices cellsdu CATransform3D(m34). Les mathématiques matricielles n'ont jamais été mon truc, donc je ne peux pas expliquer exactement pourquoi cela fonctionne, mais ça marche. Vous devrez définir cette valeur sur une fraction négative pour votre transformation initiale, puis appliquer vos transformations de rotation de calque à cela. Vous devriez également pouvoir effectuer les opérations suivantes:

Objectif c

UIView *myView = [[self subviews] objectAtIndex:0];
CALayer *layer = myView.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0f * M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;

Swift 5.0

if let myView = self.subviews.first {
    let layer = myView.layer
    var rotationAndPerspectiveTransform = CATransform3DIdentity
    rotationAndPerspectiveTransform.m34 = 1.0 / -500
    rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0 * .pi / 180.0, 0.0, 1.0, 0.0)
    layer.transform = rotationAndPerspectiveTransform
}

qui reconstruit la transformation de calque à partir de zéro pour chaque rotation.

Un exemple complet de ceci (avec code) peut être trouvé ici , où j'ai implémenté une rotation tactile et une mise à l'échelle sur quelques-uns CALayers, sur la base d'un exemple de Bill Dudney. La dernière version du programme, tout en bas de la page, implémente ce type d'opération de perspective. Le code doit être assez simple à lire.

Le sublayerTransformauquel vous faites référence dans votre réponse est une transformation qui est appliquée aux sous-couches de votre UIView's CALayer. Si vous n'avez pas de sous-couches, ne vous en faites pas. J'utilise le sublayerTransform dans mon exemple simplement parce qu'il y en a deux CALayerscontenus dans le même calque que je tourne.

Brad Larson
la source
1
Merci Brad - tu es une star. PS: Désolé pour le retard de votre excellente réponse! Pseudo.
Nick Cartwright
Cela fonctionne bien pour moi, sauf que je semble perdre la moitié de mon image (le long d'une division verticale) - parfois LHS, parfois RHS.
iPadDeveloper2011
18
@ iPadDeveloper2011 - Il y a de fortes chances que vous essayiez de faire pivoter une vue ou un calque lorsqu'il existe une autre vue ou couche opaque sur le même plan. La moitié de votre vue ou couche qui se projette loin de l'écran serait alors en dessous de cette autre vue et serait donc masquée. Vous pouvez soit réorganiser votre hiérarchie de vues pour éviter cela, soit utiliser la propriété zPosition pour déplacer votre vue de premier plan suffisamment haut au-dessus de celle qui la coupe.
Brad Larson
26
Pour info, la raison pour laquelle la cellule m34 affecte la transformation de perspective est que c'est la cellule de la matrice qui affecte la correspondance entre la valeur Z de l'espace-monde et la valeur W de l'espace-clip (qui est utilisée pour la projection en perspective). Lisez sur les matrices de transformation homogènes pour plus d'informations.
moelleux
2
@uerceg - Vous allez vouloir poser cette question séparément, de préférence avec des images qui illustrent ce qui se passe et ce que vous voulez faire.
Brad Larson
7

Vous ne pouvez utiliser que les transformations Core Graphics (Quartz, 2D uniquement) directement appliquées à la propriété de transformation d'UIView. Pour obtenir les effets dans le coverflow, vous devrez utiliser CATransform3D, qui sont appliqués dans l'espace 3D, et peuvent donc vous donner la vue en perspective que vous souhaitez. Vous ne pouvez appliquer CATransform3D qu'aux calques, pas aux vues, vous devrez donc passer aux calques pour cela.

Découvrez l'exemple "CovertFlow" fourni avec Xcode. C'est uniquement pour mac (c'est-à-dire pas pour iPhone), mais beaucoup de concepts se transfèrent bien.

Ben Gottlieb
la source
Je cherche cet exemple de coverflow dans / Developer / Examples, sans succès, où pouvez-vous le trouver?
Alex
C'est en fait "CovertFlow", et c'est dans / Developer / Examples / Quartz / Core Animation / "
Ben Gottlieb
3

Pour compléter la réponse de Brad et fournir un exemple concret, j'emprunte à ma propre réponse dans un autre article sur un sujet similaire. Dans ma réponse, j'ai créé un simple terrain de jeu Swift que vous pouvez télécharger et jouer avec , où je montre comment créer des effets de perspective d'une UIView avec une hiérarchie simple de couches, et je montre des résultats différents entre l'utilisation transformetsublayerTransform . Vérifiez-le.

Voici quelques images du post:

Option .sublayerTransform

Option .transform

HuaTham
la source
Le code doit être affiché sous forme de texte. Il est vraiment difficile de lire du code dans une image. De plus, le code dans une image ne peut pas être référencé, copié ou recherché.
rmaddy
@rmaddy, j'ai du code disponible dans le lien Bitbucket dans ma réponse.
HuaTham
Les liens se détériorent avec le temps. Il est toujours préférable de coller le code important sous forme de texte directement dans la réponse.
rmaddy
-1

Vous pouvez obtenir un effet carrousel précis à l'aide du SDK iCarousel.

Vous pouvez obtenir un effet de flux de couverture instantané sur iOS en utilisant la merveilleuse et gratuite bibliothèque iCarousel. Vous pouvez le télécharger sur https://github.com/nicklockwood/iCarousel et le déposer dans votre projet Xcode assez facilement en ajoutant un en-tête de pontage (il est écrit en Objective-C).

Si vous n'avez pas ajouté de code Objective-C à un projet Swift auparavant, procédez comme suit:

  • Téléchargez iCarousel et dézippez-le
  • Allez dans le dossier que vous avez décompressé, ouvrez son sous-dossier iCarousel, puis sélectionnez iCarousel.h et iCarousel.m et faites-les glisser dans la navigation de votre projet - c'est le volet gauche dans Xcode. Juste en dessous, Info.plist va bien.
  • Cochez "Copier les éléments si nécessaire", puis cliquez sur Terminer.
  • Xcode vous demandera le message "Voulez-vous configurer un en-tête de pontage Objective-C?" Cliquez sur "Créer un en-tête de pontage" Vous devriez voir un nouveau fichier dans votre projet, nommé YourProjectName-Bridging-Header.h.
  • Ajoutez cette ligne au fichier: #import "iCarousel.h"
  • Une fois que vous avez ajouté iCarousel à votre projet, vous pouvez commencer à l'utiliser.
  • Assurez-vous de vous conformer aux protocoles iCarouselDelegate et iCarouselDataSource.

Exemple de code Swift 3:

    override func viewDidLoad() {
      super.viewDidLoad()
      let carousel = iCarousel(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
      carousel.dataSource = self
      carousel.type = .coverFlow
      view.addSubview(carousel) 
    }

   func numberOfItems(in carousel: iCarousel) -> Int {
        return 10
    }

    func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
        let imageView: UIImageView

        if view != nil {
            imageView = view as! UIImageView
        } else {
            imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 128, height: 128))
        }

        imageView.image = UIImage(named: "example")

        return imageView
    }
Technologie
la source