¿Cómo aplico una transformación de perspectiva a una UIView?

199

Estoy buscando realizar una transformación de perspectiva en una UIView (como se ve en coverflow)

¿Alguien sabe si esto es posible?

He investigado el uso CALayery he revisado todos los podcasts de Core Animation del programador pragmático, pero todavía no tengo claro cómo crear este tipo de transformación en un iPhone.

Cualquier ayuda, punteros o fragmentos de código de ejemplo serán realmente apreciados.

Nick Cartwright
fuente
No estoy seguro de si esto es adecuado para u o no, pero cuando hago animación, he encontrado m14 es mejor para mí Sólo como referencia :)
b123400
¡Gracias! - ¿Qué valores le das a esto?
Nick Cartwright
1
Al configurar m14, en realidad está sesgando el eje de vista en el eje X de manera extraña. La matemática es perfectamente válida, pero hará las cosas un poco confusas en el caso general.
esponjoso
Un artículo muy, muy bueno sobre la transformación y perspectiva 3D de CALayer, que incluye una explicación detallada del campo m34, se puede encontrar en este excelente artículo: core-animation-3d-model
Markus

Respuestas:

329

Como dijo Ben, deberá trabajar con la UIView'scapa, utilizando a CATransform3Dpara realizar la layer's rotation. El truco para que la perspectiva funcione, como se describe aquí , es acceder directamente a una de las matrices cellsde CATransform3D(m34). Las matemáticas de matriz nunca han sido lo mío, así que no puedo explicar exactamente por qué esto funciona, pero lo hace. Tendrá que establecer este valor en una fracción negativa para su transformación inicial, luego aplique las transformaciones de rotación de capa a eso. También debe poder hacer lo siguiente:

C objetivo

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
}

que reconstruye la transformación de capa desde cero para cada rotación.

Puede encontrar un ejemplo completo de esto (con código) aquí , donde he implementado la rotación táctil y la escala en un par de CALayers, según un ejemplo de Bill Dudney. La última versión del programa, en la parte inferior de la página, implementa este tipo de operación en perspectiva. El código debe ser razonablemente simple de leer.

La sublayerTransformreferencia a la que hace referencia en su respuesta es una transformación que se aplica a las subcapas de su UIView's CALayer. Si no tienes subcapas, no te preocupes por eso. Utilizo sublayerTransform en mi ejemplo simplemente porque hay dos CALayerscontenidos dentro de la capa que estoy rotando.

Brad Larson
fuente
1
Gracias Brad, eres una estrella. PD: ¡Perdón por el retraso de su excelente respuesta! Mella.
Nick Cartwright
Esto funciona muy bien para mí, excepto que parece perder la mitad de mi imagen (a lo largo de una división vertical), a veces LHS, a veces RHS.
iPadDeveloper2011
18
@ iPadDeveloper2011: las probabilidades son que está intentando rotar una vista o capa cuando hay otra vista opaca o capa en el mismo plano. La mitad de su vista o capa que se proyecta fuera de la pantalla estaría debajo de esta otra vista y, por lo tanto, estaría oculta. Puede reordenar su jerarquía de vistas para evitar esto o usar la propiedad zPosition para mover su vista en primer plano lo suficientemente arriba de la que la está cortando.
Brad Larson
26
Para su información, la razón por la cual la celda m34 afecta la transformación de perspectiva es que esa celda en la matriz afecta cómo el valor Z del espacio mundial se asigna al valor W del espacio de clip (que se usa para la proyección en perspectiva). Lea sobre matrices de transformación homogéneas para obtener más información.
esponjoso
2
@uerceg: querrá hacer eso como una pregunta separada, preferiblemente con imágenes que ilustren lo que está sucediendo y lo que quiere que suceda.
Brad Larson
7

Solo puede usar transformaciones de Core Graphics (Quartz, solo 2D) aplicadas directamente a la propiedad de transformación de UIView. Para obtener los efectos en el flujo de cobertura, deberá usar CATransform3D, que se aplica en el espacio tridimensional y, por lo tanto, puede darle la vista en perspectiva que desea. Solo puede aplicar CATransform3Ds a capas, no a vistas, por lo que tendrá que cambiar a capas para esto.

Consulte la muestra "CovertFlow" que viene con Xcode. Es solo para Mac (es decir, no para iPhone), pero muchos de los conceptos se transfieren bien.

Ben Gottlieb
fuente
Estoy buscando esta muestra de Coverflow en / Developer / Ejemplos, sin éxito, ¿dónde puede encontrarla?
Alex
En realidad, es "CovertFlow", y está en / Developer / Ejemplos / Quartz / Core Animation / "
Ben Gottlieb
3

Para agregar a la respuesta de Brad y proporcionar un ejemplo concreto, tomo prestado de mi propia respuesta en otra publicación sobre un tema similar. En mi respuesta, creé un patio de juegos Swift simple que puedes descargar y jugar , donde demuestro cómo crear efectos de perspectiva de una UIView con una jerarquía simple de capas, y muestro diferentes resultados entre usar transformy sublayerTransform. Echale un vistazo.

Estas son algunas de las imágenes de la publicación:

Opción .sublayerTransform

opción de transformación

HuaTham
fuente
El código debe publicarse como texto. Es realmente difícil leer el código en una imagen. Además, el código en una imagen no puede ser referenciado, copiado o buscado.
rmaddy
@rmaddy, tengo un código disponible en el enlace de Bitbucket en mi respuesta.
HuaTham
Los enlaces van mal con el tiempo. Siempre es mejor pegar el código importante como texto directamente en la respuesta.
rmaddy
-1

Puede obtener un efecto de carrusel preciso con iCarousel SDK.

Puede obtener un efecto Cover Flow instantáneo en iOS utilizando la maravillosa y gratuita biblioteca iCarousel. Puede descargarlo desde https://github.com/nicklockwood/iCarousel y soltarlo en su proyecto Xcode con bastante facilidad agregando un encabezado de puente (está escrito en Objective-C).

Si no ha agregado el código Objective-C a un proyecto Swift antes, siga estos pasos:

  • Descarga iCarousel y descomprímelo
  • Vaya a la carpeta que descomprimió, abra su subcarpeta iCarousel, luego seleccione iCarousel.h e iCarousel.m y arrástrelos a la navegación del proyecto: ese es el panel izquierdo en Xcode. Justo debajo de Info.plist está bien.
  • Marque "Copiar elementos si es necesario" y luego haga clic en Finalizar.
  • Xcode le indicará el mensaje "¿Desea configurar un encabezado de puente de Objective-C?" Haga clic en "Crear encabezado de puente" Debería ver un nuevo archivo en su proyecto, llamado YourProjectName-Bridging-Header.h.
  • Agregue esta línea al archivo: #import "iCarousel.h"
  • Una vez que haya agregado iCarousel a su proyecto, puede comenzar a usarlo.
  • Asegúrese de cumplir con los protocolos iCarouselDelegate e iCarouselDataSource.

Código de muestra 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
    }
Tecnología
fuente