Wie wende ich eine Perspektiventransformation auf eine UIView an?

199

Ich möchte eine perspektivische Transformation in einem UIView durchführen (wie im Coverflow zu sehen).

Weiß jemand neu, ob dies möglich ist?

Ich habe untersucht, CALayerwie ich alle pragmatischen Programmierer-Core-Animation-Podcasts verwendet und durchlaufen habe, aber ich weiß immer noch nicht, wie ich diese Art von Transformation auf einem iPhone erstellen kann.

Jede Hilfe, Hinweise oder Beispielcode-Schnipsel wäre wirklich dankbar!

Nick Cartwright
quelle
Ich bin nicht sicher, ob dies für dich geeignet ist oder nicht, aber als ich eine Animation mache, fand ich, dass m14 besser für mich ist. Nur als Referenz :)
b123400
Vielen Dank! - Welche Werte geben Sie an?
Nick Cartwright
1
Wenn Sie m14 einstellen, verzerren Sie den Sichtstumpf auf der X-Achse merkwürdig. Die Mathematik ist vollkommen gültig, aber es wird die Dinge im allgemeinen Fall etwas verwirrend machen.
flauschige
Ein sehr, sehr guter Artikel über CALayer 3D-Transformation und -Perspektive, einschließlich einer gründlichen Erklärung des m34-Feldes, ist in diesem ausgezeichneten Artikel zu finden: Core-Animation-3D-Modell
Markus

Antworten:

329

Wie Ben sagte, müssen Sie mit der UIView'sEbene arbeiten und a verwenden CATransform3D, um die auszuführen layer's rotation. Der Trick, um die Perspektive zum Laufen zu bringen, wie hier beschrieben , besteht darin, direkt auf eine der Matrix cellsvon CATransform3D(m34) zuzugreifen. Matrixmathematik war noch nie mein Ding, daher kann ich nicht genau erklären, warum dies funktioniert, aber es funktioniert. Sie müssen diesen Wert für Ihre anfängliche Transformation auf einen negativen Bruch setzen und dann Ihre Ebenenrotationstransformationen darauf anwenden. Sie sollten auch in der Lage sein, Folgendes zu tun:

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

Dadurch wird die Ebenentransformation für jede Drehung von Grund auf neu erstellt.

Ein vollständiges Beispiel hierfür (mit Code) finden Sie hier , wo ich eine berührungsbasierte Rotation und Skalierung für einige implementiert habe CALayers, basierend auf einem Beispiel von Bill Dudney. Die neueste Version des Programms ganz unten auf der Seite implementiert diese Art der perspektivischen Operation. Der Code sollte relativ einfach zu lesen sein.

Das sublayerTransform, auf das Sie sich in Ihrer Antwort beziehen, ist eine Transformation, die auf die Unterschichten von Ihnen angewendet wird UIView's CALayer. Wenn Sie keine Unterschichten haben, machen Sie sich darüber keine Sorgen. Ich verwende die sublayerTransform in meinem Beispiel einfach, weil CALayersin einer Ebene, die ich drehe, zwei enthalten sind .

Brad Larson
quelle
1
Danke Brad - du bist ein Star. PS: Entschuldigen Sie das späte Ankreuzen Ihrer hervorragenden Antwort! Nick.
Nick Cartwright
Dies funktioniert gut für mich, außer dass ich die Hälfte meines Bildes (entlang einer vertikalen Teilung) zu verlieren scheine - manchmal LHS, manchmal RHS.
iPadDeveloper2011
18
@ iPadDeveloper2011 - Wahrscheinlich versuchen Sie, eine Ansicht oder Ebene zu drehen, wenn sich eine andere undurchsichtige Ansicht oder Ebene in derselben Ebene befindet. Die Hälfte Ihrer Ansicht oder Ebene, die vom Bildschirm wegragt, befindet sich dann unter dieser anderen Ansicht und wird daher ausgeblendet. Sie können entweder Ihre Ansichtshierarchie neu anordnen, um dies zu verhindern, oder die zPosition-Eigenschaft verwenden, um Ihre Vordergrundansicht hoch genug über die zu verschieben, die sie abschneidet.
Brad Larson
26
Zu Ihrer Information, der Grund, warum die m34-Zelle die perspektivische Transformation beeinflusst, ist die Zelle in der Matrix, die beeinflusst, wie der Z-Wert des Weltraums dem W-Wert des Clipraums zugeordnet wird (der für die perspektivische Projektion verwendet wird). Weitere Informationen finden Sie unter homogene Transformationsmatrizen.
flauschige
2
@uerceg - Sie möchten dies als separate Frage stellen, vorzugsweise mit Bildern, die veranschaulichen, was passiert und was Sie passieren möchten.
Brad Larson
7

Sie können nur Core Graphics-Transformationen (nur Quarz, 2D) verwenden, die direkt auf die Transformationseigenschaft eines UIView angewendet werden. Um die Effekte im Coverflow zu erzielen, müssen Sie CATransform3D verwenden, die im 3D-Bereich angewendet werden, damit Sie die gewünschte perspektivische Ansicht erhalten. Sie können CATransform3Ds nur auf Ebenen anwenden, nicht auf Ansichten. Daher müssen Sie dazu zu Ebenen wechseln.

Schauen Sie sich das mit Xcode gelieferte Beispiel "CovertFlow" an. Es ist nur für Mac (dh nicht für iPhone), aber viele der Konzepte lassen sich gut übertragen.

Ben Gottlieb
quelle
Ich suche dieses Coverflow-Beispiel in / Developer / Examples, ohne Erfolg. Wo finden Sie es?
Alex
Es ist eigentlich "CovertFlow" und es ist in / Developer / Examples / Quartz / Core Animation / "
Ben Gottlieb
3

Um Brads Antwort zu ergänzen und ein konkretes Beispiel zu liefern, leihe ich mir meine eigene Antwort in einem anderen Beitrag zu einem ähnlichen Thema aus. In meiner Antwort habe ich einen einfachen Swift-Spielplatz erstellt, den Sie herunterladen und spielen können. Dort zeige ich, wie perspektivische Effekte einer UIView mit einer einfachen Hierarchie von Ebenen erstellt werden, und ich zeige unterschiedliche Ergebnisse zwischen der Verwendung von transformund sublayerTransform. Hör zu.

Hier sind einige Bilder aus dem Beitrag:

Option .sublayerTransform

.transform Option

HuaTham
quelle
Code sollte als Text veröffentlicht werden. Es ist wirklich schwierig, Code in einem Bild zu lesen. Außerdem kann der Code in einem Bild nicht referenziert, kopiert oder durchsucht werden.
rmaddy
@rmaddy, ich habe Code in meiner Antwort im Bitbucket-Link verfügbar.
HuaTham
Links werden mit der Zeit schlecht. Es ist immer am besten, den wichtigen Code als Text direkt in die Antwort einzufügen.
rmaddy
-1

Mit dem iCarousel SDK können Sie einen genauen Karusselleffekt erzielen.

Mit der wunderbaren und kostenlosen iCarousel-Bibliothek können Sie unter iOS einen sofortigen Cover Flow-Effekt erzielen. Sie können es von https://github.com/nicklockwood/iCarousel herunterladen und ganz einfach in Ihr Xcode-Projekt einfügen, indem Sie einen Bridging-Header hinzufügen (geschrieben in Objective-C).

Wenn Sie einem Swift-Projekt noch keinen Objective-C-Code hinzugefügt haben, gehen Sie folgendermaßen vor:

  • Laden Sie iCarousel herunter und entpacken Sie es
  • Gehen Sie in den Ordner, den Sie entpackt haben, öffnen Sie den iCarousel-Unterordner, wählen Sie iCarousel.h und iCarousel.m aus und ziehen Sie sie in Ihre Projektnavigation - das ist der linke Bereich in Xcode. Direkt unter Info.plist ist in Ordnung.
  • Aktivieren Sie "Elemente bei Bedarf kopieren" und klicken Sie auf "Fertig stellen".
  • Xcode fordert Sie mit der Meldung "Möchten Sie einen Objective-C-Bridging-Header konfigurieren?" Auf. Klicken Sie auf "Bridging-Header erstellen". In Ihrem Projekt sollte eine neue Datei mit dem Namen YourProjectName-Bridging-Header.h angezeigt werden.
  • Fügen Sie diese Zeile zur Datei hinzu: #import "iCarousel.h"
  • Sobald Sie Ihrem Projekt iCarousel hinzugefügt haben, können Sie es verwenden.
  • Stellen Sie sicher, dass Sie sowohl den Protokollen iCarouselDelegate als auch iCarouselDataSource entsprechen.

Swift 3-Beispielcode:

    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
    }
Technik
quelle