Jak zastosować transformację perspektywy do UIView?

199

Chcę wykonać transformację perspektywy na UIView (np. Widoczne w coverflow)

Czy ktoś wie, czy jest to możliwe?

Badałem użycie CALayeri przeglądałem wszystkie pragmatyczne podcasty programisty Core Animation, ale nadal nie jestem pewien, jak stworzyć taką transformację na iPhonie.

Każda pomoc, wskaźniki lub przykładowe fragmenty kodu byłyby bardzo mile widziane!

Nick Cartwright
źródło
Nie jestem pewien, czy jest to odpowiednie dla ciebie, czy nie, ale kiedy robię animację, stwierdziłem, że m14 jest dla mnie lepszy Tylko w celach informacyjnych :)
b123400
Dzięki! - jakie wartości to podajesz?
Nick Cartwright,
1
Ustawiając m14, faktycznie dziwnie przekrzywisz fragment widoku na osi X. Matematyka jest całkowicie poprawna, ale w ogólnym przypadku spowoduje to pewne zamieszanie.
puszysty
Bardzo, bardzo dobry artykuł o transformacji i perspektywie CALayer 3D, w tym dokładne wyjaśnienie pola m34, można znaleźć w tym doskonałym artykule: animacja-rdzeń-model 3d
Markus

Odpowiedzi:

329

Jak powiedział Ben, będziesz musiał pracować z UIView'swarstwą, używając CATransform3Ddo wykonania layer's rotation. Sztuką dostać perspektywiczny działa, jak opisano tutaj , ma bezpośredni dostęp do jednego z matrycą cellso CATransform3D(M34). Matematyka matematyczna nigdy nie była moją rzeczą, więc nie mogę dokładnie wyjaśnić, dlaczego to działa, ale działa. Musisz ustawić tę wartość na wartość ujemną dla początkowej transformacji, a następnie zastosować do tego transformacje obrotu warstw. Powinieneś być również w stanie wykonać następujące czynności:

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

który odbudowuje transformację warstwy od zera dla każdego obrotu.

Pełny przykład tego (z kodem) można znaleźć tutaj , gdzie zaimplementowałem rotację dotykową i skalowanie na kilku CALayers, na podstawie przykładu Billa Dudneya. Najnowsza wersja programu, znajdująca się na samym dole strony, implementuje tego rodzaju operacje perspektywiczne. Kod powinien być w miarę łatwy do odczytania.

sublayerTransformOdnieść w swojej odpowiedzi to przekształcać, który jest stosowany do podwarstwy twoich UIView's CALayer. Jeśli nie masz podwarstw, nie przejmuj się tym. Używam sublayerTransform w moim przykładzie po prostu dlatego, że są dwie CALayerszawarte w jednej warstwie, którą obracam.

Brad Larson
źródło
1
Dzięki, Brad - jesteś gwiazdą. PS: Przepraszam za spóźnione zaznaczenie doskonałej odpowiedzi! Nacięcie.
Nick Cartwright,
Działa to dla mnie ładnie, z tym wyjątkiem, że wydaje mi się, że tracę połowę mojego obrazu (wzdłuż pionowego podziału) - czasem LHS, a czasem RHS.
iPadDeveloper2011
18
@ iPadDeveloper2011 - Prawdopodobnie próbujesz obrócić widok lub warstwę, gdy na tej samej płaszczyźnie jest inny nieprzejrzysty widok lub warstwa. Połowa twojego widoku lub warstwy, która wystaje poza ekran, byłaby wówczas poniżej tego innego widoku, a zatem byłaby ukryta. Możesz zmienić kolejność hierarchii widoków, aby temu zapobiec, lub użyć właściwości zPosition, aby przenieść widok pierwszego planu wystarczająco wysoko ponad ten, który go odcina.
Brad Larson
26
Do Twojej wiadomości, powodem, dla którego komórka m34 wpływa na transformację perspektywiczną, jest komórka w macierzy, która wpływa na to, w jaki sposób wartość Z w przestrzeni świata odwzorowuje się na wartość W w przestrzeni klipu (która jest używana do rzutowania perspektywicznego). Przeczytaj więcej na temat jednorodnych macierzy transformacji, aby uzyskać więcej informacji.
puszysty
2
@uerceg - Będziesz chciał zadać to jako osobne pytanie, najlepiej ze zdjęciami ilustrującymi, co się dzieje i co chcesz zrobić.
Brad Larson
7

Można używać tylko transformacji Core Graphics (tylko kwarc, 2D) bezpośrednio zastosowanych do właściwości transformacji UIView. Aby uzyskać efekty w ukryciu okładki, musisz użyć CATransform3D, które są stosowane w przestrzeni 3D, dzięki czemu możesz uzyskać pożądany widok perspektywiczny. Możesz zastosować CATransform3Ds tylko do warstw, a nie widoków, więc w tym celu musisz przełączyć się na warstwy.

Sprawdź próbkę „CovertFlow” dostarczoną z Xcode. Jest to tylko Mac (tj. Nie dla iPhone'a), ale wiele koncepcji dobrze się przenosi.

Ben Gottlieb
źródło
Szukam tej próbki przepływu okładki w / Developer / Examples, bez powodzenia, gdzie można ją znaleźć?
Alex
W rzeczywistości jest to „CovertFlow” i znajduje się w / Developer / Examples / Quartz / Core Animation / ”
Ben Gottlieb
3

Aby dodać do odpowiedzi Brada i podać konkretny przykład, pożyczam od własnej odpowiedzi w innym poście na podobny temat. W mojej odpowiedzi utworzyłem prosty plac zabaw Swift, z którego można pobierać i bawić się , gdzie demonstruję, jak tworzyć perspektywiczne efekty UIView za pomocą prostej hierarchii warstw, i pokazuję różne wyniki między używaniem transforma sublayerTransform. Sprawdź to.

Oto niektóre zdjęcia z posta:

Opcja .sublayerTransform

.transformuj opcję

HuaTham
źródło
Kod należy opublikować jako tekst. Naprawdę trudno jest odczytać kod na obrazie. Ponadto kodu na obrazie nie można odwoływać się, kopiować ani przeszukiwać.
rmaddy
@rmaddy, mam kod dostępny w linku Bitbucket w mojej odpowiedzi.
HuaTham
Z czasem linki się psują. Zawsze najlepiej wkleić ważny kod jako tekst bezpośrednio w odpowiedzi.
rmaddy
-1

Dokładny efekt karuzeli można uzyskać za pomocą iCarousel SDK.

Możesz uzyskać natychmiastowy efekt Cover Flow na iOS, korzystając ze wspaniałej i bezpłatnej biblioteki iCarousel. Możesz go pobrać ze strony https://github.com/nicklockwood/iCarousel i dość łatwo upuścić do projektu Xcode, dodając nagłówek pomostowy (napisany w Objective-C).

Jeśli nie dodałeś wcześniej kodu Objective-C do projektu Swift, wykonaj następujące kroki:

  • Pobierz iCarousel i rozpakuj go
  • Przejdź do rozpakowanego folderu, otwórz jego podfolder iCarousel, a następnie wybierz iCarousel.h i iCarousel.m i przeciągnij je do nawigacji projektu - to lewe okienko w Xcode. Tuż poniżej Info.plist jest w porządku.
  • Zaznacz „Skopiuj elementy w razie potrzeby”, a następnie kliknij przycisk Zakończ.
  • Xcode wyświetli komunikat „Czy chcesz skonfigurować nagłówek mostkujący Objective-C?” Kliknij „Utwórz nagłówek pomostowy”. W swoim projekcie powinieneś zobaczyć nowy plik o nazwie YourProjectName-Bridging-Header.h.
  • Dodaj ten wiersz do pliku: #import „iCarousel.h”
  • Po dodaniu iCarousel do swojego projektu możesz zacząć z niego korzystać.
  • Upewnij się, że jesteś zgodny zarówno z protokołami iCarouselDelegate, jak i iCarouselDataSource.

Przykładowy kod 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
    }
Tech
źródło