UICollectionView Sel Ukuran Mandiri dengan Tata Letak Otomatis

207

Saya mencoba membuat ukuran sendiri UICollectionViewCellsbekerja dengan Tata Letak Otomatis, tetapi sepertinya saya tidak bisa membuat sel untuk mengukur sendiri untuk konten. Saya mengalami kesulitan memahami bagaimana ukuran sel diperbarui dari konten yang ada di dalam contentView sel.

Berikut pengaturan yang saya coba:

  • Ubahsuaikan UICollectionViewCelldengan UITextViewdi isinyaView.
  • Menggulir untuk yang UITextViewdinonaktifkan.
  • Batasan horizontal contentView adalah: "H: | [_textView (320)]", yaitu UITextViewdisematkan di sebelah kiri sel dengan lebar eksplisit 320.
  • Batasan vertikal contentView adalah: "V: | -0 - [_ textView]", yaitu yang UITextViewdisematkan di bagian atas sel.
  • The UITextViewmemiliki kendala tinggi diatur ke sebuah konstanta yang UITextViewlaporan akan cocok teks.

Begini tampilannya dengan latar belakang sel yang disetel menjadi merah, dan UITextViewlatar diatur ke Biru: latar belakang sel merah, latar belakang UITextView biru

Saya meletakkan proyek yang telah saya mainkan di GitHub di sini .

rawbee
sumber
Apakah Anda menerapkan metode sizeForItemAtIndexPath?
Daniel Galasko
@DanielGalasko saya tidak. Pemahaman saya adalah bahwa fitur sel sizing diri baru di iOS 8 tidak mengharuskan Anda untuk melakukan itu lagi.
rawbee
tidak yakin dari mana Anda mendapatkan info itu tetapi Anda masih perlu, lihatlah kutipan WWDC asciiwwdc.com/2014/sessions/226
Daniel Galasko
tapi sekali lagi itu tergantung pada kasus penggunaan Anda, pastikan Anda menerapkan Estimasi estimasi
item
1
2019 - Sangat penting untuk melihat ini: stackoverflow.com/a/58108897/294884
Fattie

Jawaban:

309

Diperbarui untuk Swift 5

preferredLayoutAttributesFittingAttributesdiubah namanya menjadi preferredLayoutAttributesFittingdan menggunakan ukuran otomatis


Diperbarui untuk Swift 4

systemLayoutSizeFittingSize diganti namanya menjadi systemLayoutSizeFitting


Diperbarui untuk iOS 9

Setelah melihat solusi GitHub saya istirahat di iOS 9 saya akhirnya punya waktu untuk menyelidiki masalah ini sepenuhnya. Saya sekarang telah memperbarui repo untuk memasukkan beberapa contoh konfigurasi yang berbeda untuk sel-sel ukuran sendiri. Kesimpulan saya adalah bahwa sel-sel ukuran diri besar dalam teori tetapi berantakan dalam praktiknya. Sebuah kata peringatan ketika melanjutkan dengan sel sizing diri.

TL; DR

Lihat proyek GitHub saya


Sel ukuran sendiri hanya didukung dengan tata letak aliran jadi pastikan itulah yang Anda gunakan.

Ada dua hal yang Anda butuhkan untuk mengatur agar sel-sel ukuran sendiri berfungsi.

1. Set estimatedItemSizediUICollectionViewFlowLayout

Tata letak aliran akan menjadi dinamis setelah Anda mengatur estimatedItemSizeproperti.

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. Tambahkan dukungan untuk ukuran pada subkelas sel Anda

Ini hadir dalam 2 rasa; Tata Letak Otomatis atau penggantian kustom preferredLayoutAttributesFittingAttributes.

Buat dan konfigurasikan sel dengan Tata Letak Otomatis

Saya tidak akan masuk ke detail tentang ini karena ada posting SO brilian tentang mengkonfigurasi kendala untuk sel. Hanya berhati-hatilah bahwa Xcode 6 memecahkan banyak hal dengan iOS 7 jadi, jika Anda mendukung iOS 7, Anda harus melakukan hal-hal seperti memastikan autoresizingMask diatur pada konten sel. sel dimuat (yaitu awakeFromNib).

Hal yang perlu Anda ketahui adalah bahwa sel Anda harus dibatasi lebih serius daripada Table View Cell. Misalnya, jika Anda ingin lebar Anda menjadi dinamis maka sel Anda membutuhkan batasan ketinggian. Demikian juga, jika Anda ingin ketinggian menjadi dinamis maka Anda akan memerlukan batasan lebar untuk sel Anda.

Terapkan preferredLayoutAttributesFittingAttributesdi sel khusus Anda

Ketika fungsi ini disebut, tampilan Anda telah dikonfigurasikan dengan konten (yaitu cellForItemtelah dipanggil). Dengan asumsi kendala Anda telah diatur dengan tepat, Anda dapat memiliki implementasi seperti ini:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

CATATAN Pada iOS 9 perilaku berubah sedikit yang dapat menyebabkan crash pada implementasi Anda jika Anda tidak hati-hati (Lihat lebih lanjut di sini ). Saat Anda menerapkan, preferredLayoutAttributesFittingAttributesAnda perlu memastikan bahwa Anda hanya mengubah bingkai atribut tata letak Anda satu kali. Jika Anda tidak melakukan ini, tata letak akan memanggil implementasi Anda tanpa batas dan akhirnya macet. Salah satu solusinya adalah men-cache ukuran yang dihitung dalam sel Anda dan membatalkan ini kapan saja Anda menggunakan kembali sel atau mengubah kontennya seperti yang telah saya lakukan dengan isHeightCalculatedproperti.

Rasakan tata letak Anda

Pada titik ini Anda harus memiliki sel dinamis 'berfungsi' di collectionView Anda. Saya belum menemukan solusi out-of-the-box yang cukup selama pengujian saya jadi jangan ragu untuk berkomentar jika Anda punya Rasanya masih seperti UITableViewmemenangkan pertempuran untuk IMHO ukuran dinamis.

Peringatan

Perhatikan bahwa jika Anda menggunakan sel prototipe untuk menghitung estimasiItemSize - ini akan pecah jika XIB Anda menggunakan kelas ukuran . Alasannya adalah ketika Anda memuat sel Anda dari XIB, kelas ukurannya akan dikonfigurasikan Undefined. Ini hanya akan rusak di iOS 8 dan lebih tinggi karena di iOS 7 kelas ukuran akan dimuat berdasarkan perangkat (iPad = Reguler-Any, iPhone = Compact-Any). Anda bisa mengatur Estimasi item tanpa memuat XIB, atau Anda dapat memuat sel dari XIB, menambahkannya ke collectionView (ini akan mengatur traitCollection), melakukan tata letak, dan kemudian menghapusnya dari superview. Atau Anda juga bisa membuat sel Anda menimpa traitCollectionpengambil dan mengembalikan sifat yang sesuai. Terserah kamu.

Beri tahu saya jika saya melewatkan sesuatu, semoga saya membantu dan semoga berhasil coding


Daniel Galasko
sumber
2
Saya mencoba menerapkan hal yang sama dengan tata letak aliran, namun ketika saya mengembalikannyaFrame baru dengan ukuran yang diperbarui, tata letak tersebut tampaknya tidak menghitung ulang posisi Anda, yang masih didasarkan pada ketinggian di Perkiraan Ukuran. Apa yang hilang
anna
@anna, apakah Anda memanggil invalidateLayout di tata letak?
Daniel Galasko
1
Ah, temukan perbedaannya, Anda menetapkan tinggi yang diperkirakan cukup tinggi (400) sedangkan saya melakukan minimum (44). Itu membantu. Tapi contentSize tampaknya masih belum diatur dengan benar sampai Anda menggulir semua itu, jadi masih belum bisa digunakan. Btw, saya mengubah UITextView menjadi UILabel, perilaku yang sama.
anna
17
Ini tampaknya rusak secara mendasar pada iOS 9 GM. Menyetel estimatedItemSizemengarah ke macet - sepertinya ada beberapa bug besar dalam cara tata letak otomatis mencoba memproses UICollectionViewCell.
Wes Campaigne
3
Berlari ke masalah yang sama, adakah yang menemukan pekerjaan?
Tony
47

Di iOS10 ada konstanta baru yang disebut UICollectionViewFlowLayout.automaticSize(sebelumnya UICollectionViewFlowLayoutAutomaticSize), jadi alih-alih:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

Anda bisa menggunakan ini:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

Ini memiliki kinerja yang lebih baik terutama ketika sel dalam tampilan koleksi Anda memiliki wid konstan

Mengakses Tata Letak Alur:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
   }
}

Swift 5 Diperbarui:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}
pawel221
sumber
8
Di mana Anda mengatur ini? dalam viewdidload? Bagaimana cara Anda menangani flowlayout?
UKDataGeek
1
Saya berhasil menerapkan ini - tetapi memiliki perilaku aneh yang membuatnya memusatkan sel jika ada sel tunggal. Inilah pertanyaan stack overflow tentang hal itu - membuat saya bingung: stackoverflow.com/questions/43266924/…
UKDataGeek
1
Anda dapat mengakses tata letak aliran melalui collectionViews collectionViewLayoutdan cukup periksa apakah itu bertipeUICollectionViewFlowLayout
d4Rk
4
itu membuat sel saya sangat kecil, tidak berguna
user924
44

Beberapa perubahan kunci pada jawaban Daniel Galasko memperbaiki semua masalah saya. Sayangnya, saya tidak memiliki reputasi yang cukup untuk berkomentar secara langsung (belum).

Pada langkah 1, saat menggunakan Tata Letak Otomatis, cukup tambahkan satu induk UIView ke dalam sel. SEGALA SESUATU di dalam sel harus menjadi subview dari induknya. Itu menjawab semua masalah saya. Sementara Xcode menambahkan ini untuk UITableViewCells secara otomatis, itu tidak (tetapi seharusnya) untuk UICollectionViewCells. Menurut dokumen :

Untuk mengonfigurasi tampilan sel Anda, tambahkan tampilan yang diperlukan untuk menyajikan konten item data sebagai subview ke tampilan di properti contentView. Jangan langsung menambahkan subview ke sel itu sendiri.

Kemudian lewati langkah 3 sepenuhnya. Itu tidak diperlukan.

Matt Koala
sumber
1
Menarik; ini khusus untuk Xibs tetapi tetap terima kasih, akan mencoba dan mengacaukan proyek Github dan melihat apakah saya dapat mereproduksi. Mungkin membagi jawaban menjadi Xib vs terprogram
Daniel Galasko
Sangat membantu . Terima kasih!
ProblemSlover
2
iOS 9, Xcode 7. Sel-sel protoyped di storyboard, atur subkelas kustom. Sudah mencoba membuat properti yang disebut contentView, Xcode mengeluh konflik dengan properti yang ada. Sudah mencoba menambahkan subview ke self.contentViewdan menetapkan batasan untuk itu, lalu aplikasi mogok.
Nicolas Miari 615
@ NicolasMiari Saya tidak tahu tentang bagaimana melakukan ini secara pemrograman, solusi saya sebenarnya hanya dengan menambahkan uiview tunggal di XIB di mana semuanya berjalan di dalamnya. Anda tidak perlu membuat properti atau apa pun.
Matt Koala
Saya menghadapi masalah yang sama dengan @NicolasMiari. Ketika saya menetapkan taksiran ContentSize untuk sel yang di-prototipe di storyboard dengan batasan autolayout di iOS 9 / Xcode 7 aplikasi mogok dengan pengecualian akses yang buruk dan tidak ada stacktrace yang membantu
wasabi
34

Di iOS 10+ ini adalah proses 2 langkah yang sangat sederhana.

  1. Pastikan bahwa semua konten sel Anda ditempatkan dalam UIView tunggal (atau di dalam turunan UIView seperti UIStackView yang menyederhanakan banyak pembayaran otomatis). Seperti halnya UITableViewCells yang mengubah ukuran secara dinamis, hierarki tampilan keseluruhan harus memiliki kendala yang dikonfigurasikan, dari wadah terluar ke tampilan terdalam. Itu termasuk kendala antara UICollectionViewCell dan childview langsung

  2. Instruksikan flowlayout UICollectionView Anda untuk mengukur secara otomatis

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
Marmoy
sumber
Saya ingin tahu, bagaimana membungkus isi sel Anda dalam UIView membuat Tata Letak Otomatis berfungsi lebih baik? Saya pikir itu akan tampil lebih baik dengan hierarki yang lebih dangkal?
James Heald
1
Saya tidak menyebutkan kinerja, hanya kesederhanaan. Sel ukuran sendiri hanya akan berfungsi jika Anda memiliki batasan yang membentang di antara kiri dan kanan, dan antara atas dan bawah. Pencapaian yang paling mudah adalah ketika semua tampilan dibungkus dalam satu wadah. Jika wadah itu adalah UIStackView, lebih mudah jika itu adalah turunan lain dari UIView.
Marmoy
Bisakah Anda memberi tahu saya cara mengatur konstanta tinggi (seperti mengatur tinggi 25, dan lebar akan diubah secara otomatis) untuk UICollectionViewFlowLayoutAutomaticSize.
Svetoslav Atanasov
Menggunakan Swift 4.1, Xcode memberi tahu saya UICollectionViewFlowLayout.automaticSizetelah diubah namanya menjadi UICollectionViewFlowLayoutAutomaticSize.
LinusGeffarth
14
  1. Tambahkan flowLayout di viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. Juga, tetapkan UIView sebagai mainContainer untuk sel Anda dan tambahkan semua tampilan yang diperlukan di dalamnya.

  3. Lihat tutorial yang mengagumkan dan mengejutkan ini untuk referensi lebih lanjut: UICollectionView dengan sel autosisasi menggunakan autolayout di iOS 9 & 10

dianakarenms
sumber
11

EDIT 11/19/19: Untuk iOS 13, cukup gunakan UICollectionViewCompositionalLayout dengan perkiraan ketinggian. Jangan buang waktu Anda berurusan dengan API yang rusak ini.

Setelah berjuang dengan ini selama beberapa waktu, saya perhatikan bahwa pengubahan ukuran tidak berfungsi untuk UITextViews jika Anda tidak menonaktifkan pengguliran:

let textView = UITextView()
textView.scrollEnabled = false
noobular
sumber
ketika saya mencoba untuk menggulir, sel tampilan koleksi tidak mulus. Apakah Anda punya solusi untuk ini?
Sathish Kumar Gurunathan
6

contentView misteri jangkar:

Dalam satu kasus aneh ini

    contentView.translatesAutoresizingMaskIntoConstraints = false

tidak akan bekerja Menambahkan empat jangkar eksplisit ke contentView dan berhasil.

class AnnoyingCell: UICollectionViewCell {
    
    @IBOutlet var word: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame); common() }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }
    
    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

dan seperti biasa

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

di YourLayout: UICollectionViewFlowLayout

Siapa tahu? Bisa membantu seseorang.

Kredit

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

tersandung ke ujung sana - tidak pernah melihatnya di tempat lain di semua artikel 1000-an tentang ini.

Fattie
sumber
3

Saya melakukan tampilan koleksi tinggi sel yang dinamis. Inilah repo hub git .

Dan, gali mengapa preferLayoutAttributesFittingAttributes disebut lebih dari sekali. Sebenarnya, itu akan dipanggil minimal 3 kali.

Gambar log konsol: masukkan deskripsi gambar di sini

PreferredLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

LayoutAttributes.frame.size.height adalah status saat ini 57.5 .

2nd preferLayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

Tinggi bingkai sel berubah menjadi 534,5 seperti yang kami harapkan. Tapi, tampilan koleksi masih nol tinggi.

3rdLayoutAttributesFittingAttributes lebih disukai :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

Anda dapat melihat tinggi tampilan koleksi berubah dari 0 menjadi 477 .

Perilaku ini mirip dengan menangani gulir:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

Pada awalnya, saya pikir metode ini hanya memanggil satu kali. Jadi saya kode sebagai berikut:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

Garis ini:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

akan menyebabkan system call infinite loop dan App crash.

Setiap ukuran diubah, itu akan memvalidasi semua sel preferLayoutAttributesFittingAttributes lagi dan lagi sampai setiap posisi sel (yaitu frame) tidak ada lagi perubahan.

Yang Young
sumber
2

Selain jawaban di atas,

Pastikan Anda menyetel properti estimasiItemSize dari UICollectionViewFlowLayout ke beberapa ukuran dan jangan mengimplementasikan sizeForItem: di metode delegate atIndexPath .

Itu dia.

Murat Yasar
sumber
1

Jika Anda menerapkan metode UICollectionViewDelegateFlowLayout:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

Saat Anda menelepon collectionview performBatchUpdates:completion:, tinggi ukuran akan digunakan sizeForItemAtIndexPathalih-alih preferredLayoutAttributesFittingAttributes.

Proses rendering performBatchUpdates:completionakan melalui metode preferredLayoutAttributesFittingAttributestetapi mengabaikan perubahan Anda.

Yang Young
sumber
Saya telah melihat kesalahan perilaku ini, tetapi hanya ketika ukuran perkiraannya nol. Apakah Anda yakin Anda menetapkan perkiraan ukuran?
dgatwood
1

Bagi siapa pun itu mungkin membantu,

Saya mengalami kecelakaan yang tidak menyenangkan itu jika estimatedItemSizeditetapkan. Bahkan jika saya mengembalikan 0 in numberOfItemsInSection. Oleh karena itu, sel-sel itu sendiri dan tata-letak otomatisnya bukanlah penyebab kerusakan ... CollectionView hanya mogok, bahkan ketika kosong, hanya karenaestimatedItemSize diatur untuk mengatur sendiri.

Dalam kasus saya, saya mengatur ulang proyek saya, dari pengontrol yang berisi collectionView ke collectionViewController, dan itu berhasil.

Sosok pergi.

Jean Le Moignan
sumber
1
Coba panggil collectionView.collectionViewLayout.invalidateLayout()setelah collectionView.reloadData().
chengsam
untuk ios10 dan di bawah ini tambahkan kode ini `timpa func viewWillLayoutSubviews () {super.viewWillLayoutSubviews () jika # tersedia (iOS 11.0, *) {} else {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdee Uma
1

Bagi siapa saja yang mencoba segalanya tanpa keberuntungan, ini adalah satu-satunya yang membuatnya bekerja untuk saya. Untuk label multiline di dalam sel, coba tambahkan garis ajaib ini:

label.preferredMaxLayoutWidth = 200

Info lebih lanjut: di sini

Bersulang!

HelloimDarius
sumber
0

Metode contoh di atas tidak dikompilasi. Ini adalah versi yang sudah dikoreksi (tetapi belum diuji apakah berfungsi atau tidak.)

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}
pengguna3246173
sumber
0

Perbarui informasi lebih lanjut:

  • Jika Anda menggunakan flowLayout.estimatedItemSize, sarankan gunakan versi iOS8.3 nanti. Sebelum iOS8.3, itu akan crash [super layoutAttributesForElementsInRect:rect];. Pesan kesalahannya adalah

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • Kedua, dalam versi iOS8.x, flowLayout.estimatedItemSizeakan menyebabkan pengaturan bagian inset yang berbeda tidak berfungsi. fungsi yaitu: (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:.

Yang Young
sumber
0

Solusinya terdiri dari 4 langkah penting:

  1. Mengaktifkan ukuran sel dinamis

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. Aktifkan tata letak otomatis dan sematkan contentViewke tepi sel karena contentViewmencegah swa-ukuran, mengingat tata letak otomatis tidak diaktifkan.
class MultiLineCell: UICollectionViewCell{

    private var textViewHeightContraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .cyan
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentViewWidthAnchor = contentView.widthAnchor.constraint(equalToConstant: 0)

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    } 
    ...
}
  1. Tetapkan contentView.widthAnchor.constraint dari collectionView(:cellForItemAt:)untuk membatasi lebar contentView ke lebar collectionView.

Itu terjadi sebagai berikut; kami akan mengatur sel maxWidthvariabel dengan lebar CollectionView ini di collectionView(:cellForItemAt:)dan di maxWidth's didSetmetode kami akan menetapkan widthAnchor.constant ke MaxWidth.

class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            contentViewWidthAnchor.constant = maxWidth
            contentViewWidthAnchor.isActive = true
        }
    }

    ....
}

Karena Anda ingin mengaktifkan UITextView dengan ukuran sendiri, ini memiliki langkah tambahan untuk;

4. Hitung dan atur ketinggianAnchor.constant dari UITextView.

Jadi, setiap kali lebar contentView diatur kami akan menyesuaikan ketinggian UITextView bersama di didSetdari maxWidth.

Di dalam UICollectionViewCell:

var maxWidth: CGFloat? {
    didSet {
        guard let maxWidth = maxWidth else {
            return
        }
        contentViewWidthAnchor.constant = maxWidth
        contentViewWidthAnchor.isActive = true

        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

Langkah-langkah ini akan memberi Anda hasil yang diinginkan. Inilah intisari runnable yang lengkap

Terima kasih kepada Vadim Bulavin untuk posting blognya yang jelas dan mencerahkan - Collection View Cells Self-Sizing: Tutorial Langkah demi Langkah

Harley
sumber
-2

Saya mencoba menggunakan estimatedItemSizetetapi ada banyak bug ketika memasukkan dan menghapus sel jika estimatedItemSizetidak persis sama dengan tinggi sel. saya berhenti mengatur estimatedItemSizedan mengimplementasikan sel dinamis dengan menggunakan sel prototipe. begini caranya:

buat protokol ini:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

mengimplementasikan protokol ini di kebiasaan Anda UICollectionViewCell:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

sekarang buat pengontrol Anda sesuai UICollectionViewDelegateFlowLayout, dan di dalamnya, dapatkan bidang ini:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

itu akan digunakan sebagai sel prototipe untuk mengikat data dan kemudian menentukan bagaimana data itu mempengaruhi dimensi yang Anda inginkan menjadi dinamis

akhirnya, UICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)harus diimplementasikan:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

dan hanya itu. Mengembalikan ukuran sel dengan collectionView(:layout:sizeForItemAt:)cara ini mencegah saya dari keharusan menggunakan estimatedItemSize, dan menyisipkan dan menghapus sel berfungsi dengan baik.

sepatu
sumber