Cara menentukan ketinggian UICollectionView dengan FlowLayout

118

Saya punya UICollectionViewdengan UICollectionViewFlowLayout, dan saya ingin menghitung ukuran isinya (untuk pengembalian intrinsicContentSizediperlukan untuk menyesuaikan ketinggiannya melalui AutoLayout).

Masalahnya adalah: Bahkan jika saya memiliki tinggi yang tetap dan sama untuk semua sel, saya tidak tahu berapa banyak "baris" / baris yang saya miliki di UICollectionView. Saya juga tidak dapat menentukan jumlah itu dengan jumlah item di sumber data saya, karena sel yang mewakili item data memiliki lebar yang bervariasi, begitu juga dengan jumlah item yang saya miliki dalam satu baris UICollectionView.

Karena saya tidak dapat menemukan petunjuk tentang topik ini di dokumentasi resmi dan googling tidak membawa saya lebih jauh, bantuan dan ide apa pun akan sangat dihargai.

Ignatius Tremor
sumber

Jawaban:

255

Wah! Untuk beberapa alasan, setelah berjam-jam penelitian, sekarang saya menemukan jawaban yang cukup mudah untuk pertanyaan saya: Saya benar-benar mencari di tempat yang salah, menggali semua dokumentasi yang dapat saya temukan UICollectionView.

Yang sederhana dan solusi mudah terletak pada tata letak yang mendasari: Hanya panggilan collectionViewContentSizepada Anda myCollectionView.collectionViewLayoutproperti dan Anda mendapatkan tinggi dan lebar dari konten sebagai CGSize. Semudah itu.

Ignatius Tremor
sumber
1
whoa, saya berharap UITableView memiliki sesuatu yang serupa
Chris Chen
1
Ignatus, @jbandi, Chris dkk, dapatkah Anda memperluas ini? Ketika saya menguji dengan arah gulir horizontal dengan jarak antar-item yang besar (sehingga item akan ditata secara horizontal), tampilan koleksi mengembalikan ukuran konten intrinsik yang tidak valid (-1, -1), dan collectionViewContentSize mengembalikan apa yang efektif batas tampilan koleksi - tidak memperhitungkan bahwa hanya ada satu baris yang dikelilingi oleh ruang. Singkatnya, itu tidak terlalu intrinsik. Terima kasih!
Chris Conover
8
Apa perbedaan antara collectionViewContentSize dan contentSize dari tampilan gulir?
rounak
Saat Anda memanggil contentSize dari tampilan koleksi di viewDidLoad, ia mengembalikan bingkai tampilan koleksi, collectionViewContentSize mengembalikan ukuran konten yang benar.
Fury
1
Untuk siapa ini dapat membantu: Saya harus melakukan "layoutIfNeeded ()" sebelum ini sehingga saya bisa membuatnya berfungsi.
asanli
37

Jika Anda menggunakan tata letak Otomatis , Anda dapat membuat subkelasUICollectionView

Jika Anda menggunakan kode di bawah ini, Anda tidak perlu menentukan batasan ketinggian apa pun untuk tampilan koleksi karena ini akan bervariasi berdasarkan konten tampilan koleksi.

Diberikan di bawah ini adalah implementasinya:

@interface DynamicCollectionView : UICollectionView

@end

@implementation DynamicCollectionView

- (void) layoutSubviews
{
    [super layoutSubviews];

    if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize]))
    {
        [self invalidateIntrinsicContentSize];
    }
}

- (CGSize)intrinsicContentSize
{
    CGSize intrinsicContentSize = self.contentSize;

    return intrinsicContentSize;
}

@end
pengguna1046037
sumber
12
tidak bekerja untuk saya ... Saya menggunakan CollectionView saya di dalam UITableViewCell dan ingin tampilan tabel bertambah tinggi saat tampilan koleksi bertambah tinggi menggunakan iOS8 UITableViewAutomaticDimension Tetapi tampilan koleksi saya tetap kecil :(
Georg
Mengagumkan. Solusi singkat dan manis untuk UICollectionView saya di dalam UIScrollView!
dotToString
2
Satu hal yang penting adalah menonaktifkan scrolling dan scrollbar dalam tampilan koleksi
Georg
1
Masalah yang sama seperti Georg, tingginya sekitar 50px padahal seharusnya lebih dari 400
xtrinch
3
Ya, saya menyetel collectionview ke "bukan scrollabe, tanpa scollbars" lalu memuatnya kembali, lalu konten tampilan tabel disetel. Collectionview juga merupakan subclass yang mengembalikan ukuran kontennya sebagai intrinsicContentSize. Semoga membantu!
Georg
25

Di viewDidAppearAnda bisa mendapatkannya dengan:

float height = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;

Mungkin pada saat reload data perlu menghitung ketinggian baru dengan data baru maka bisa didapatkan dengan cara: add observer to listening setelah CollectionViewselesai reload data di viewdidload:

[self.myCollectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

Kemudian tambahkan fungsi di bawah untuk mendapatkan ketinggian baru atau lakukan apa saja setelah collectionview selesai memuat ulang:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
    //Whatever you do here when the reloadData finished
    float newHeight = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;    
}

Dan jangan lupa untuk menghapus pengamat:

[self.myCollectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
lee
sumber
11

pengguna1046037 jawaban di Swift ...

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize() {
            invalidateIntrinsicContentSize()
        }
    }

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}
Nick Wood
sumber
Ini berfungsi dengan sempurna tetapi Jika Anda memiliki sesuatu seperti di UILabelatas, konten dari collectionViewmelewati elemen lain di atas, apakah Anda punya solusi lain?
KolaCaine
11

Kode Swift 3 untuk jawaban pengguna1046037

import UIKit

class DynamicCollectionView: UICollectionView {

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
            self.invalidateIntrinsicContentSize()
        }

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}
Mohammad Sadiq Shaikh
sumber
Saya lebih segar, dan saya tidak yakin tentang di mana dan bagaimana menggunakan ini di ViewController saya, Bisakah Anda mengedit dengan jawabannya .....?
Abirami Bala
1
cukup setel kelas ini ke tampilan koleksi Anda di Storyboard
Mohammad Sadiq Shaikh
7

Cepat 4

class DynamicCollectionView: UICollectionView {
  override func layoutSubviews() {
    super.layoutSubviews()
    if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
      self.invalidateIntrinsicContentSize()
    }
  }

  override var intrinsicContentSize: CGSize {
    return collectionViewLayout.collectionViewContentSize
  }
}
Wilson
sumber
1
Saya menemukan bahwa ini tidak berfungsi di iPhone 6/7 plus dan XR, XS-MAX
Rahul K Rajan
7

Swift 4.2.0

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}
Cesare
sumber
4

Sudah terlambat untuk menjawab pertanyaan ini tetapi saya baru saja membahas masalah ini.
@ lee jawaban di atas banyak membantu saya untuk mendapatkan jawaban saya. Tetapi jawaban itu terbatas pada tujuan-c dan saya bekerja dengan cepat .
@lee Dengan mengacu pada jawaban Anda, izinkan saya untuk membiarkan pengguna cepat menyelesaikan masalah ini dengan mudah. Untuk itu, ikuti langkah-langkah di bawah ini:

Deklarasikan CGFloatvariabel di bagian deklarasi:

var height : CGFloat!

Di viewDidAppearAnda bisa mendapatkannya dengan:

height = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

Mungkin ketika Anda reloaddata kemudian perlu menghitung ketinggian baru dengan data baru maka Anda bisa mendapatkannya dengan cara: addObserveruntuk mendengarkan ketika Anda CollectionViewselesai memuat ulang data di viewWillAppear:

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(true)
        ....
        ....
        self.shapeCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.Old, context: nil)
    }

Kemudian tambahkan fungsi di bawah untuk mendapatkan ketinggian baru atau melakukan apapun setelah collectionviewselesai memuat ulang:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let newHeight : CGFloat = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

        var frame : CGRect! = self.myCollectionView.frame
        frame.size.height = newHeight

        self.myCollectionView.frame = frame
    }

Dan jangan lupa untuk menghapus pengamat:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    ....
    ....
    self.myCollectionView.removeObserver(self, forKeyPath: "contentSize")
}

Saya harap ini akan membantu Anda menyelesaikan masalah Anda dengan cepat.

Er. Vihar
sumber
@ShikhaSharma: Anda dapat menambahkan kode removeObserver dalam metode "ViewDidDisappear", yang akan menghapus pengamat hanya setelah tampilan mengonfirmasi bahwa itu menghilang. Silakan periksa jawaban yang diperbarui.
Er. Vihar
Saya mendapatkan kesalahan ini: Sebuah -observeValueForKeyPath: ofObject: change: context: pesan telah diterima tetapi tidak ditangani.
Shikha Sharma
@ShikhaSharma: Maaf, saya salah menyebutkan metode ini. Cobalah untuk meletakkan kode removeobserver di viewWillDisappear.
Er. Vihar
1

Jawaban ini didasarkan pada @Er. Jawaban Vihar. Anda dapat dengan mudah menerapkan solusi dengan menggunakan RxSwift

     collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in
        print("sizer \(size)")
    }).disposed(by: rx.disposeBag)
HUNG TRAN DUY
sumber
0

Versi cepat dari @ user1046037:

public class DynamicCollectionView: UICollectionView {

    override public func layoutSubviews() {
        super.layoutSubviews()
        if !bounds.size.equalTo(intrinsicContentSize) {
            invalidateIntrinsicContentSize()
        }
    }

    override public var intrinsicContentSize: CGSize {
        let intrinsicContentSize: CGSize = contentSize
        return intrinsicContentSize
    }
}
Joshua Hart
sumber
0

Swift 4.2, Xcode 10.1, iOS 12.1:

Untuk beberapa alasan, collectionView.contentSize.heighttampak lebih kecil dari ketinggian yang diselesaikan dari tampilan koleksi saya. Pertama, saya menggunakan batasan tata letak otomatis yang relatif terhadap 1/2 dari tinggi tampilan super. Untuk mengatasinya, saya mengubah batasan menjadi relatif terhadap "area aman" tampilan.

Ini memungkinkan saya untuk menyetel tinggi sel untuk mengisi tampilan koleksi saya menggunakan collectionView.contentSize.height:

private func setCellSize() {
    let height: CGFloat = (collectionView.contentSize.height) / CGFloat(numberOfRows)
    let width: CGFloat = view.frame.width - CGFloat(horizontalCellMargin * 2)

    let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = CGSize(width: width, height: height)
}

Sebelum

batasan ketinggian relatif terhadap superview hasil simulator yang buruk

Setelah

kendala ketinggian relatif terhadap area aman hasil simulator yang bagus

Andrew Kirna
sumber
0

dengan asumsi collectionView Anda dinamai collectionView, Anda bisa mengukur tingginya sebagai berikut.

let height = collectionView.collectionViewLayout.collectionViewContentSize.height
Pramodya Abeysinghe
sumber
0

Selain itu, Anda sebaiknya menelepon reloadDatasebelum mendapatkan ketinggian:

theCollectionView.collectionViewLayout.collectionViewContentSize;
guozqzzu
sumber