Come posso applicare una trasformazione prospettica a un UIView?


199

Sto cercando di eseguire una trasformazione prospettica su un UIView (come visto nel coverflow)

Qualcuno sa se questo è possibile?

Ho studiato usando CALayere ho esaminato tutti i podcast pragmatici del programmatore Core Animation, ma non sono ancora più chiaro su come creare questo tipo di trasformazione su un iPhone.

Qualsiasi aiuto, puntatore o esempio di frammenti di codice sarebbe davvero apprezzato!


Non sono sicuro che sia adatto a te o no, ma quando
realizzo l'

Grazie! - quali valori dai questo?
Nick Cartwright,

1
Impostando m14 in realtà stai distorcendo il frustum della vista sull'asse X in modo strano. La matematica è perfettamente valida ma renderà le cose un po 'confuse nel caso generale.
soffice

Un ottimo articolo sulla trasformazione e prospettiva 3D di CALayer, che comprende una spiegazione approfondita del campo m34, è disponibile in questo eccellente articolo: core-animation-3d-model
Markus,

Risposte:


329

Come ha detto Ben, dovrai lavorare con il UIView'slivello, usando a CATransform3Dper eseguire il layer's rotation. Il trucco per far funzionare la prospettiva, come descritto qui , è accedere direttamente a una delle matrici cellsdi CATransform3D(m34). La matematica Matrix non è mai stata la mia cosa, quindi non posso spiegare esattamente perché questo funziona, ma lo fa. Dovrai impostare questo valore su una frazione negativa per la tua trasformazione iniziale, quindi applicare le trasformazioni di rotazione del livello a quella. Dovresti anche essere in grado di fare quanto segue:

Objective-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
}

che ricostruisce la trasformazione del livello da zero per ogni rotazione.

Un esempio completo di questo (con codice) può essere trovato qui , dove ho implementato la rotazione basata sul tocco e il ridimensionamento su un paio di CALayers, sulla base di un esempio di Bill Dudney. La versione più recente del programma, in fondo alla pagina, implementa questo tipo di operazione prospettica. Il codice dovrebbe essere ragionevolmente semplice da leggere.

Il sublayerTransformriferimento a cui fai riferimento nella tua risposta è una trasformazione che viene applicata ai tuoi sublayer UIView's CALayer. Se non hai sublayer, non preoccuparti. Uso il sublayerTransform nel mio esempio semplicemente perché ci sono due CALayerscontenuti all'interno di uno strato che sto ruotando.


1
Grazie Brad - sei una stella. PS: scusami per il ticchettio ritardato della tua eccellente risposta! Nick.
Nick Cartwright,

Questo funziona bene per me, tranne che mi sembra di perdere metà dell'immagine (lungo una divisione verticale) - a volte LHS, a volte RHS.
iPadDeveloper2011

18
@ iPadDeveloper2011 - Le probabilità sono che stai provando a ruotare una vista o un livello quando c'è un'altra vista o livello opaco sullo stesso piano. La metà della vista o del livello che proietta lontano dallo schermo sarebbe quindi al di sotto di questa altra vista e quindi nascosta. Puoi riordinare la gerarchia della vista per impedirlo o utilizzare la proprietà zPosition per spostare la vista in primo piano abbastanza in alto rispetto a quella che la interrompe.
Brad Larson

26
Cordiali saluti, il motivo per cui la cellula m34 influenza la trasformazione della prospettiva è che è la cella nella matrice che influenza il modo in cui il valore Z dello spazio mondo si associa al valore W dello spazio clip (che viene utilizzato per la proiezione prospettica). Leggi le matrici di trasformazione omogenee per ulteriori informazioni.
soffice

2
@uerceg - Lo vorrai porre come una domanda separata, preferibilmente con immagini che illustrano cosa sta succedendo e cosa vuoi che accada.
Brad Larson

7

Puoi utilizzare solo trasformazioni Core Graphics (solo Quartz, 2D) direttamente applicate alla proprietà di trasformazione di una UIView. Per ottenere gli effetti nel coverflow, dovrai usare CATransform3D, che viene applicato nello spazio 3D, e quindi può darti la vista prospettica che desideri. Puoi applicare CATransform3Ds solo ai livelli, non alle viste, quindi dovrai passare ai livelli per questo.

Guarda l'esempio "CovertFlow" fornito con Xcode. È solo per Mac (cioè non per iPhone), ma molti concetti si trasferiscono bene.


Sto cercando questo esempio di coverflow in / Sviluppatore / Esempi, senza successo, dove puoi trovarlo?
Alex,

In realtà è "CovertFlow", ed è in / Sviluppatore / Esempi / Quartz / Core Animation / "
Ben Gottlieb il

3

Per aggiungere alla risposta di Brad e per fornire un esempio concreto, prendo in prestito dalla mia risposta in un altro post su un argomento simile. Nella mia risposta, ho creato un semplice parco giochi Swift con il quale è possibile scaricare e giocare , in cui dimostrerò come creare effetti prospettici di una UIView con una semplice gerarchia di livelli e mostrerò risultati diversi tra l'utilizzo di transforme sublayerTransform. Controlla.

Ecco alcune delle immagini dal post:

Opzione .sublayerTransform

Opzione .transform


Il codice deve essere pubblicato come testo. È davvero difficile leggere il codice in un'immagine. Inoltre, il codice in una foto non può essere referenziato, copiato o cercato.
rmaddy,

@rmaddy, ho il codice disponibile nel link Bitbucket nella mia risposta.
HuaTham,

I collegamenti vanno male nel tempo. È sempre meglio incollare il codice importante come testo direttamente nella risposta.
rmaddy,

-1

Puoi ottenere un preciso effetto Carousel usando iCarousel SDK.

Puoi ottenere un effetto Cover Flow istantaneo su iOS usando la meravigliosa e gratuita libreria iCarousel. Puoi scaricarlo da https://github.com/nicklockwood/iCarousel e rilasciarlo nel tuo progetto Xcode abbastanza facilmente aggiungendo un'intestazione a ponte (è scritto in Objective-C).

Se non hai ancora aggiunto il codice Objective-C a un progetto Swift, segui questi passaggi:

  • Scarica iCarousel e decomprimilo
  • Vai nella cartella decompressa, apri la sua sottocartella iCarousel, quindi seleziona iCarousel.h e iCarousel.m e trascinali nella navigazione del progetto: questo è il riquadro sinistro in Xcode. Proprio sotto Info.plist va bene.
  • Seleziona "Copia elementi se necessario", quindi fai clic su Fine.
  • Xcode ti chiederà il messaggio "Desideri configurare un'intestazione di bridge Objective-C?" Fai clic su "Crea intestazione ponte" Dovresti vedere un nuovo file nel tuo progetto, chiamato YourProjectName-Bridging-Header.h.
  • Aggiungi questa riga al file: #import "iCarousel.h"
  • Dopo aver aggiunto iCarousel al tuo progetto, puoi iniziare a usarlo.
  • Assicurati di essere conforme ai protocolli iCarouselDelegate e iCarouselDataSource.

Codice di esempio 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
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.