Bagaimana cara saya menerapkan transformasi perspektif ke UIView?

199

Saya ingin melakukan transformasi perspektif pada UIView (seperti terlihat pada coverflow)

Adakah yang baru tahu jika ini mungkin?

Saya telah diselidiki menggunakan CALayerdan telah menjalankan semua programmer podcast Core Animation yang pragmatis, tapi saya masih belum jelas tentang cara membuat transformasi semacam ini pada iPhone.

Bantuan, petunjuk atau contoh cuplikan kode akan sangat dihargai!

Nick Cartwright
sumber
Saya tidak yakin apakah ini cocok untuk Anda atau tidak, tetapi ketika saya membuat animasi, saya menemukan m14 lebih baik bagi saya. Hanya untuk referensi :)
b123400
Terima kasih! - Nilai apa yang Anda berikan ini?
Nick Cartwright
1
Dengan menyetel m14 Anda sebenarnya memiringkan pandangan frustum pada sumbu X secara aneh. Matematika itu benar-benar valid tetapi akan membuat hal-hal agak membingungkan dalam kasus umum.
lembut
Artikel yang sangat, sangat bagus tentang transformasi dan perspektif CALayer 3D, termasuk penjelasan menyeluruh tentang bidang m34, dapat ditemukan dalam artikel yang luar biasa ini: core-animation-3d-model
Markus

Jawaban:

329

Seperti kata Ben, Anda harus bekerja dengan UIView'slayer, menggunakan a CATransform3Duntuk melakukan layer's rotation. Trik untuk mendapatkan perspektif kerja, seperti yang dijelaskan di sini , adalah untuk langsung akses salah satu matriks cellsdari CATransform3D(m34). Matriks matematika tidak pernah menjadi tujuan saya, jadi saya tidak dapat menjelaskan dengan tepat mengapa ini berhasil, tetapi ternyata demikian. Anda harus mengatur nilai ini ke fraksi negatif untuk transformasi awal Anda, lalu menerapkan transformasi rotasi layer Anda ke sana. Anda juga harus dapat melakukan hal berikut:

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

yang membangun kembali transformasi lapisan dari awal untuk setiap rotasi.

Contoh lengkap dari ini (dengan kode) dapat ditemukan di sini , di mana saya telah menerapkan rotasi berbasis sentuhan dan penskalaan pada beberapa CALayers, berdasarkan contoh oleh Bill Dudney. Versi terbaru dari program, di bagian paling bawah halaman, mengimplementasikan operasi perspektif semacam ini. Kode harus cukup sederhana untuk dibaca.

Yang sublayerTransformAnda rujuk dalam respons Anda adalah transformasi yang diterapkan ke sublapisan Anda UIView's CALayer. Jika Anda tidak memiliki sublayer, jangan khawatir tentang hal itu. Saya menggunakan sublayerTransform dalam contoh saya hanya karena ada dua yang CALayersterkandung dalam satu lapisan yang saya putar.

Brad Larson
sumber
1
Terima kasih Brad - Anda seorang bintang. PS: Maaf atas keterlambatan jawaban Anda yang luar biasa! Nick.
Nick Cartwright
Ini berfungsi baik untuk saya, kecuali saya tampaknya kehilangan setengah gambar saya (sepanjang divisi vertikal) - kadang-kadang LHS, kadang-kadang RHS.
iPadDeveloper2011
18
@ iPadDeveloper2011 - Mustahil, Anda mencoba memutar tampilan atau lapisan saat ada tampilan atau lapisan buram lain di bidang yang sama. Setengah dari tampilan Anda atau lapisan yang memproyeksikan jauh dari layar kemudian akan berada di bawah tampilan lain ini, dan dengan demikian disembunyikan. Anda dapat menyusun ulang hierarki tampilan untuk mencegah hal ini atau menggunakan properti zPosition untuk memindahkan tampilan latar depan Anda cukup tinggi di atas yang memotongnya.
Brad Larson
26
FYI, alasan sel m34 memengaruhi transformasi perspektif adalah karena sel dalam matriks yang memengaruhi bagaimana nilai Z ruang-dunia memetakan ke nilai ruang-klip W (yang digunakan untuk proyeksi perspektif). Baca pada matriks transformasi homogen untuk informasi lebih lanjut.
lembut
2
@uerceg - Anda ingin menanyakannya sebagai pertanyaan terpisah, lebih disukai dengan gambar yang menggambarkan apa yang terjadi dan apa yang Anda inginkan terjadi.
Brad Larson
7

Anda hanya dapat menggunakan transformasi Core Graphics (Quartz, 2D saja) yang diterapkan langsung ke properti transformasi UIView. Untuk mendapatkan efek dalam aliran balik, Anda harus menggunakan CATransform3D, yang diterapkan dalam ruang 3-D, sehingga dapat memberikan Anda tampilan perspektif yang Anda inginkan. Anda hanya dapat menerapkan CATransform3Ds ke lapisan, bukan tampilan, jadi Anda harus beralih ke lapisan untuk ini.

Lihat sampel "CovertFlow" yang disertakan dengan Xcode. Ini mac-only (yaitu bukan untuk iPhone), tetapi banyak konsep yang ditransfer dengan baik.

Ben Gottlieb
sumber
Saya mencari sampel aliran ini di / Pengembang / Contoh, tanpa hasil, di mana Anda dapat menemukannya?
Alex
Ini sebenarnya "CovertFlow", dan itu ada di / Pengembang / Contoh / Kuarsa / Inti Animasi / "
Ben Gottlieb
3

Untuk menambah jawaban Brad dan untuk memberikan contoh nyata, saya meminjam dari jawaban saya sendiri di posting lain pada topik yang sama. Dalam jawaban saya, saya membuat taman bermain Swift sederhana yang dapat Anda unduh dan mainkan , di mana saya mendemonstrasikan cara membuat efek perspektif dari UIView dengan hierarki lapisan sederhana, dan saya menunjukkan hasil yang berbeda antara menggunakan transformdan sublayerTransform. Saksikan berikut ini.

Berikut adalah beberapa gambar dari pos:

Opsi .sublayerTransform

Opsi .transformasikan

HuaTham
sumber
Kode harus diposting sebagai teks. Sangat sulit untuk membaca kode dalam gambar. Ditambah kode dalam gambar tidak dapat direferensikan, disalin, atau dicari.
rmaddy
@rmaddy, saya memiliki kode yang tersedia di tautan Bitbucket dalam jawaban saya.
HuaTham
Tautan menjadi buruk seiring waktu. Itu selalu terbaik untuk menempelkan kode penting sebagai teks langsung ke jawabannya.
rmaddy
-1

Anda bisa mendapatkan efek Carousel yang akurat menggunakan iCarousel SDK.

Anda bisa mendapatkan efek Cover Flow instan di iOS dengan menggunakan perpustakaan iCarousel yang luar biasa dan gratis. Anda dapat mengunduhnya dari https://github.com/nicklockwood/iCarousel dan memasukkannya ke proyek Xcode Anda dengan cukup mudah dengan menambahkan header penghubung (ditulis dalam Objective-C).

Jika Anda belum menambahkan kode Objective-C ke proyek Swift sebelumnya, ikuti langkah-langkah ini:

  • Unduh iCarousel dan unzip
  • Buka folder yang Anda buka ritsleting, buka subfolder iCarousel, lalu pilih iCarousel.h dan iCarousel.m dan seret ke dalam navigasi proyek Anda - itulah panel kiri di Xcode. Tepat di bawah Info.plist baik-baik saja.
  • Centang "Salin item jika perlu" lalu klik Selesai.
  • Xcode akan meminta Anda dengan pesan "Apakah Anda ingin mengkonfigurasi header bridging Objective-C?" Klik "Buat Bridging Header" Anda akan melihat file baru di proyek Anda, bernama YourProjectName-Bridging-Header.h.
  • Tambahkan baris ini ke file: #import "iCarousel.h"
  • Setelah Anda menambahkan iCarousel ke proyek Anda, Anda dapat mulai menggunakannya.
  • Pastikan Anda mematuhi protokol iCarouselDelegate dan iCarouselDataSource.

Kode Sampel 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
sumber