Sepertinya yang Anda minta adalah cara menggunakan UICollectionView untuk menghasilkan layout seperti UITableView. Jika itu yang Anda inginkan, cara yang tepat untuk melakukannya adalah dengan subkelas UICollectionViewLayout kustom (mungkin sesuatu seperti SBTableLayout ).
Di sisi lain, jika Anda benar-benar bertanya apakah ada cara yang bersih untuk melakukan ini dengan UICollectionViewFlowLayout default, maka saya yakin tidak mungkin. Bahkan dengan sel self-sizing iOS8, itu tidak langsung. Masalah mendasar, seperti yang Anda katakan, adalah bahwa mesin tata letak aliran tidak menyediakan cara untuk memperbaiki satu dimensi dan membiarkan dimensi lain merespons. (Selain itu, meskipun Anda bisa, akan ada kerumitan tambahan seputar perlunya dua penerusan tata letak untuk mengukur label multi-baris. Ini mungkin tidak sesuai dengan cara sel yang mengukur sendiri ingin menghitung semua ukuran melalui satu panggilan ke systemLayoutSizeFittingSize.)
Namun, jika Anda masih ingin membuat tata letak seperti tampilan tabel dengan tata letak aliran, dengan sel yang menentukan ukurannya sendiri, dan merespons secara alami lebar tampilan koleksi, tentu saja hal itu dimungkinkan. Masih ada cara yang berantakan. Saya telah melakukannya dengan "sel ukuran", yaitu UICollectionViewCell yang tidak ditampilkan yang disimpan oleh pengontrol hanya untuk menghitung ukuran sel.
Ada dua bagian dalam pendekatan ini. Bagian pertama adalah untuk delegasi tampilan koleksi untuk menghitung ukuran sel yang benar, dengan mengambil lebar tampilan koleksi dan menggunakan sel ukuran untuk menghitung tinggi sel.
Di UICollectionViewDelegateFlowLayout Anda, Anda menerapkan metode seperti ini:
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
// NOTE: here is where we say we want cells to use the width of the collection view
let requiredWidth = collectionView.bounds.size.width
// NOTE: here is where we ask our sizing cell to compute what height it needs
let targetSize = CGSize(width: requiredWidth, height: 0)
/// NOTE: populate the sizing cell's contents so it can compute accurately
self.sizingCell.label.text = items[indexPath.row]
let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
return adequateSize
}
Ini akan menyebabkan tampilan koleksi menyetel lebar sel berdasarkan tampilan koleksi terlampir, tetapi kemudian meminta sel ukuran untuk menghitung tinggi.
Bagian kedua adalah mendapatkan sel ukuran untuk menggunakan batasan AL-nya sendiri untuk menghitung ketinggian. Ini bisa lebih sulit dari yang seharusnya, karena cara UILabel multi-baris secara efektif membutuhkan proses tata letak dua tahap. Pekerjaan dilakukan dengan metode preferredLayoutSizeFittingSize
, yang seperti ini:
/*
Computes the size the cell will need to be to fit within targetSize.
targetSize should be used to pass in a width.
the returned size will have the same width, and the height which is
calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
can fit within that width.
*/
func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {
// save original frame and preferredMaxLayoutWidth
let originalFrame = self.frame
let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth
// assert: targetSize.width has the required width of the cell
// step1: set the cell.frame to use that width
var frame = self.frame
frame.size = targetSize
self.frame = frame
// step2: layout the cell
self.setNeedsLayout()
self.layoutIfNeeded()
self.label.preferredMaxLayoutWidth = self.label.bounds.size.width
// assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width
// step3: compute how tall the cell needs to be
// this causes the cell to compute the height it needs, which it does by asking the
// label what height it needs to wrap within its current bounds (which we just set).
let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
// assert: computedSize has the needed height for the cell
// Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
let newSize = CGSize(width:targetSize.width,height:computedSize.height)
// restore old frame and preferredMaxLayoutWidth
self.frame = originalFrame
self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth
return newSize
}
(Kode ini diadaptasi dari kode contoh Apple dari kode contoh sesi WWDC2014 pada "Tampilan Koleksi Lanjutan".)
Beberapa hal yang perlu diperhatikan. Ini menggunakan layoutIfNeeded () untuk memaksa tata letak seluruh sel, untuk menghitung dan menyetel lebar label. Tapi itu belum cukup. Saya yakin Anda juga perlu mengatur preferredMaxLayoutWidth
agar label akan menggunakan lebar itu dengan Tata Letak Otomatis. Dan hanya dengan begitu Anda dapat menggunakansystemLayoutSizeFittingSize
agar sel menghitung tingginya sambil mempertimbangkan label.
Apakah saya menyukai pendekatan ini? Tidak!! Rasanya terlalu rumit, dan tata letak dua kali. Namun selama kinerja tidak menjadi masalah, saya lebih suka melakukan tata letak dua kali pada waktu proses daripada harus mendefinisikannya dua kali dalam kode, yang tampaknya menjadi satu-satunya alternatif lain.
Harapan saya adalah bahwa pada akhirnya sel yang mengukur sendiri akan bekerja secara berbeda dan ini semua akan menjadi jauh lebih sederhana.
Contoh proyek yang menunjukkannya di tempat kerja.
Tapi mengapa tidak menggunakan sel yang mengukur sendiri?
Secara teori, fasilitas baru iOS8 untuk "sel yang mengukur sendiri" seharusnya membuat hal ini tidak perlu. Jika Anda telah menentukan sel dengan Tata Letak Otomatis (AL), tampilan koleksi harus cukup pintar untuk membiarkannya mengukur sendiri dan menata dirinya sendiri dengan benar. Dalam praktiknya, saya belum melihat contoh apa pun yang membuat ini berfungsi dengan label multi-garis. Saya pikir ini sebagian karena mekanisme sel yang mengukur sendiri masih bermasalah.
Tapi saya berani bertaruh itu sebagian besar karena kerumitan Tata Letak Otomatis dan label yang biasa, yaitu UILabel pada dasarnya memerlukan proses tata letak dua langkah. Tidak jelas bagi saya bagaimana Anda dapat melakukan kedua langkah dengan sel yang mengukur sendiri.
Dan seperti yang saya katakan, ini benar-benar pekerjaan untuk tata letak yang berbeda. Ini adalah bagian dari esensi tata letak aliran yang memposisikan benda-benda yang memiliki ukuran, daripada memperbaiki lebar dan membiarkan mereka memilih tingginya.
Dan bagaimana dengan preferLayoutAttributesFittingAttributes:?
The preferredLayoutAttributesFittingAttributes:
Metode adalah ikan merah, saya pikir. Itu hanya untuk digunakan dengan mekanisme sel ukuran sendiri yang baru. Jadi ini bukan jawabannya selama mekanismenya tidak bisa diandalkan.
Dan ada apa dengan systemlayoutSizeFittingSize :?
Anda benar, dokumen membingungkan.
Dokumen di systemLayoutSizeFittingSize:
dan systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:
keduanya menyarankan agar Anda hanya meneruskan UILayoutFittingCompressedSize
dan UILayoutFittingExpandedSize
sebagai filetargetSize
. Namun, tanda tangan metode itu sendiri, komentar header, dan perilaku fungsi menunjukkan bahwa mereka merespons nilai targetSize
parameter yang tepat.
Faktanya, jika Anda menyetel UICollectionViewFlowLayoutDelegate.estimatedItemSize
, untuk mengaktifkan mekanisme sel self-sizing baru, nilai tersebut tampaknya diteruskan sebagai targetSize. Dan UILabel.systemLayoutSizeFittingSize
tampaknya mengembalikan nilai yang sama persis seperti UILabel.sizeThatFits
. Hal ini mencurigakan, mengingat dalil ke systemLayoutSizeFittingSize
seharusnya menjadi sasaran kasar dan dalil untuksizeThatFits:
seharusnya menjadi ukuran yang membatasi maksimum.
Sumber Daya Lainnya
Meskipun menyedihkan untuk berpikir bahwa persyaratan rutin seperti itu harus membutuhkan "sumber daya penelitian", saya rasa memang demikian. Contoh dan diskusi yang bagus adalah: